no-fix: moving out public histories page
This commit is contained in:
parent
0fa3b8ddb9
commit
5d5b63416b
@ -2,11 +2,8 @@ import { createHashHistory } from "history";
|
||||
import { h, VNode } from "preact";
|
||||
import Router, { route, Route } from "preact-router";
|
||||
import { useEffect } from "preact/hooks";
|
||||
import {
|
||||
AccountPage,
|
||||
PublicHistoriesPage,
|
||||
RegistrationPage,
|
||||
} from "./home/index.js";
|
||||
import { AccountPage, RegistrationPage } from "./home/index.js";
|
||||
import { PublicHistoriesPage } from "./home/PublicHistoriesPage.js";
|
||||
|
||||
export function Routing(): VNode {
|
||||
const history = createHashHistory();
|
||||
|
201
packages/demobank-ui/src/pages/home/BankFrame.tsx
Normal file
201
packages/demobank-ui/src/pages/home/BankFrame.tsx
Normal file
@ -0,0 +1,201 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { Fragment, h, VNode } from "preact";
|
||||
import { PageStateType, usePageContext } from "../../context/pageState.js";
|
||||
import { useTranslationContext } from "../../context/translation.js";
|
||||
import { bankUiSettings } from "../../settings.js";
|
||||
import { LangSelectorLikePy as LangSelector } from "../../components/menu/LangSelector.js";
|
||||
import { StateUpdater } from "preact/hooks";
|
||||
import talerLogo from "../../assets/logo-white.svg";
|
||||
|
||||
export function BankFrame(Props: any): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
const { pageState, pageStateSetter } = usePageContext();
|
||||
console.log("BankFrame state", pageState);
|
||||
const logOut = (
|
||||
<div class="logout">
|
||||
<a
|
||||
href="#"
|
||||
class="pure-button logout-button"
|
||||
onClick={() => {
|
||||
pageStateSetter((prevState: PageStateType) => {
|
||||
const { talerWithdrawUri, withdrawalId, ...rest } = prevState;
|
||||
return {
|
||||
...rest,
|
||||
isLoggedIn: false,
|
||||
withdrawalInProgress: false,
|
||||
error: undefined,
|
||||
info: undefined,
|
||||
isRawPayto: false,
|
||||
};
|
||||
});
|
||||
}}
|
||||
>{i18n.str`Logout`}</a>
|
||||
</div>
|
||||
);
|
||||
|
||||
const demo_sites = [];
|
||||
for (const i in bankUiSettings.demoSites)
|
||||
demo_sites.push(
|
||||
<a href={bankUiSettings.demoSites[i][1]}>
|
||||
{bankUiSettings.demoSites[i][0]}
|
||||
</a>,
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<header
|
||||
class="demobar"
|
||||
style="display: flex; flex-direction: row; justify-content: space-between;"
|
||||
>
|
||||
<a href="#main" class="skip">{i18n.str`Skip to main content`}</a>
|
||||
<div style="max-width: 50em; margin-left: 2em;">
|
||||
<h1>
|
||||
<span class="it">
|
||||
<a href="/">{bankUiSettings.bankName}</a>
|
||||
</span>
|
||||
</h1>
|
||||
{maybeDemoContent(
|
||||
<p>
|
||||
<i18n.Translate>
|
||||
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{" "}
|
||||
<a
|
||||
href="/public-accounts"
|
||||
onClick={goPublicAccounts(pageStateSetter)}
|
||||
>
|
||||
Public Accounts
|
||||
</a>
|
||||
.
|
||||
</i18n.Translate>
|
||||
</p>,
|
||||
)}
|
||||
</div>
|
||||
<a href="https://taler.net/">
|
||||
<img
|
||||
src={talerLogo}
|
||||
alt={i18n.str`Taler logo`}
|
||||
height="100"
|
||||
width="224"
|
||||
style="margin: 2em 2em"
|
||||
/>
|
||||
</a>
|
||||
</header>
|
||||
<div style="display:flex; flex-direction: column;" class="navcontainer">
|
||||
<nav class="demolist">
|
||||
{maybeDemoContent(<Fragment>{demo_sites}</Fragment>)}
|
||||
<div class="right">
|
||||
<LangSelector />
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<section id="main" class="content">
|
||||
<ErrorBanner pageState={[pageState, pageStateSetter]} />
|
||||
<StatusBanner pageState={[pageState, pageStateSetter]} />
|
||||
{pageState.isLoggedIn ? logOut : null}
|
||||
{Props.children}
|
||||
</section>
|
||||
<section id="footer" class="footer">
|
||||
<div class="footer">
|
||||
<hr />
|
||||
<div>
|
||||
<p>
|
||||
You can learn more about GNU Taler on our{" "}
|
||||
<a href="https://taler.net">main website</a>.
|
||||
</p>
|
||||
</div>
|
||||
<div style="flex-grow:1" />
|
||||
<p>Copyright © 2014—2022 Taler Systems SA</p>
|
||||
</div>
|
||||
</section>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
function maybeDemoContent(content: VNode): VNode {
|
||||
if (bankUiSettings.showDemoNav) {
|
||||
return content;
|
||||
}
|
||||
return <Fragment />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bring the state to show the public accounts page.
|
||||
*/
|
||||
function goPublicAccounts(pageStateSetter: StateUpdater<PageStateType>) {
|
||||
return () =>
|
||||
pageStateSetter((prevState) => ({
|
||||
...prevState,
|
||||
showPublicHistories: true,
|
||||
}));
|
||||
}
|
||||
|
||||
function ErrorBanner(Props: any): VNode | null {
|
||||
const [pageState, pageStateSetter] = Props.pageState;
|
||||
// const { i18n } = useTranslationContext();
|
||||
if (!pageState.error) return null;
|
||||
|
||||
const rval = (
|
||||
<div class="informational informational-fail" style={{ marginTop: 8 }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<p>
|
||||
<b>{pageState.error.title}</b>
|
||||
</p>
|
||||
<div>
|
||||
<input
|
||||
type="button"
|
||||
class="pure-button"
|
||||
value="Clear"
|
||||
onClick={async () => {
|
||||
pageStateSetter((prev: any) => ({ ...prev, error: undefined }));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p>{pageState.error.description}</p>
|
||||
</div>
|
||||
);
|
||||
delete pageState.error;
|
||||
return rval;
|
||||
}
|
||||
|
||||
function StatusBanner(Props: any): VNode | null {
|
||||
const [pageState, pageStateSetter] = Props.pageState;
|
||||
if (!pageState.info) return null;
|
||||
|
||||
const rval = (
|
||||
<div class="informational informational-ok" style={{ marginTop: 8 }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<p>
|
||||
<b>{pageState.info}</b>
|
||||
</p>
|
||||
<div>
|
||||
<input
|
||||
type="button"
|
||||
class="pure-button"
|
||||
value="Clear"
|
||||
onClick={async () => {
|
||||
pageStateSetter((prev: any) => ({ ...prev, info: undefined }));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return rval;
|
||||
}
|
187
packages/demobank-ui/src/pages/home/PublicHistoriesPage.tsx
Normal file
187
packages/demobank-ui/src/pages/home/PublicHistoriesPage.tsx
Normal file
@ -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 <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { hooks } from "@gnu-taler/web-util/lib/index.browser";
|
||||
import { Fragment, h, VNode } from "preact";
|
||||
import { StateUpdater } from "preact/hooks";
|
||||
import useSWR, { SWRConfig } from "swr";
|
||||
import { PageStateType, usePageContext } from "../../context/pageState.js";
|
||||
import { useTranslationContext } from "../../context/translation.js";
|
||||
import { getBankBackendBaseUrl } from "../../utils.js";
|
||||
import { BankFrame } from "./BankFrame.js";
|
||||
import { Transactions } from "./Transactions.js";
|
||||
|
||||
export function PublicHistoriesPage(): VNode {
|
||||
const { pageState, pageStateSetter } = usePageContext();
|
||||
// const { i18n } = useTranslationContext();
|
||||
return (
|
||||
<SWRWithoutCredentials baseUrl={getBankBackendBaseUrl()}>
|
||||
<BankFrame>
|
||||
<PublicHistories pageStateSetter={pageStateSetter}>
|
||||
<br />
|
||||
<a
|
||||
class="pure-button"
|
||||
onClick={() => {
|
||||
pageStateSetter((prevState: PageStateType) => ({
|
||||
...prevState,
|
||||
showPublicHistories: false,
|
||||
}));
|
||||
}}
|
||||
>
|
||||
Go back
|
||||
</a>
|
||||
</PublicHistories>
|
||||
</BankFrame>
|
||||
</SWRWithoutCredentials>
|
||||
);
|
||||
}
|
||||
|
||||
function SWRWithoutCredentials(Props: any): VNode {
|
||||
const { baseUrl } = Props;
|
||||
console.log("Base URL", baseUrl);
|
||||
return (
|
||||
<SWRConfig
|
||||
value={{
|
||||
fetcher: (url: string) =>
|
||||
fetch(baseUrl + url || "").then((r) => {
|
||||
if (!r.ok) throw { status: r.status, json: r.json() };
|
||||
|
||||
return r.json();
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{Props.children}
|
||||
</SWRConfig>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show histories of public accounts.
|
||||
*/
|
||||
function PublicHistories(Props: any): VNode {
|
||||
const [showAccount, setShowAccount] = useShowPublicAccount();
|
||||
const { data, error } = useSWR("access-api/public-accounts");
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
if (typeof error !== "undefined") {
|
||||
console.log("account error", error);
|
||||
switch (error.status) {
|
||||
case 404:
|
||||
console.log("public accounts: 404", error);
|
||||
Props.pageStateSetter((prevState: PageStateType) => ({
|
||||
...prevState,
|
||||
|
||||
showPublicHistories: false,
|
||||
error: {
|
||||
title: i18n.str`List of public accounts was not found.`,
|
||||
debug: JSON.stringify(error),
|
||||
},
|
||||
}));
|
||||
break;
|
||||
default:
|
||||
console.log("public accounts: non-404 error", error);
|
||||
Props.pageStateSetter((prevState: PageStateType) => ({
|
||||
...prevState,
|
||||
|
||||
showPublicHistories: false,
|
||||
error: {
|
||||
title: i18n.str`List of public accounts could not be retrieved.`,
|
||||
debug: JSON.stringify(error),
|
||||
},
|
||||
}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!data) return <p>Waiting public accounts list...</p>;
|
||||
const txs: any = {};
|
||||
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);
|
||||
console.log(`Public history tab: ${showAccount}`);
|
||||
|
||||
// Ask story of all the public accounts.
|
||||
for (const account of data.publicAccounts) {
|
||||
console.log("Asking transactions for", account.accountLabel);
|
||||
const isSelected = account.accountLabel == showAccount;
|
||||
accountsBar.push(
|
||||
<li
|
||||
class={
|
||||
isSelected
|
||||
? "pure-menu-selected pure-menu-item"
|
||||
: "pure-menu-item pure-menu"
|
||||
}
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
class="pure-menu-link"
|
||||
onClick={() => setShowAccount(account.accountLabel)}
|
||||
>
|
||||
{account.accountLabel}
|
||||
</a>
|
||||
</li>,
|
||||
);
|
||||
txs[account.accountLabel] = (
|
||||
<Transactions accountLabel={account.accountLabel} pageNumber={0} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<h1 class="nav">{i18n.str`History of public accounts`}</h1>
|
||||
<section id="main">
|
||||
<article>
|
||||
<div class="pure-menu pure-menu-horizontal" name="accountMenu">
|
||||
<ul class="pure-menu-list">{accountsBar}</ul>
|
||||
{typeof showAccount !== "undefined" ? (
|
||||
txs[showAccount]
|
||||
) : (
|
||||
<p>No public transactions found.</p>
|
||||
)}
|
||||
{Props.children}
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 <input> fields.
|
||||
*/
|
||||
function useShowPublicAccount(
|
||||
state?: string,
|
||||
): [string | undefined, StateUpdater<string | undefined>] {
|
||||
const ret = hooks.useLocalStorage(
|
||||
"show-public-account",
|
||||
JSON.stringify(state),
|
||||
);
|
||||
const retObj: string | undefined = ret[0] ? JSON.parse(ret[0]) : ret[0];
|
||||
const retSetter: StateUpdater<string | undefined> = function (val) {
|
||||
const newVal =
|
||||
val instanceof Function
|
||||
? JSON.stringify(val(retObj))
|
||||
: JSON.stringify(val);
|
||||
ret[1](newVal);
|
||||
};
|
||||
return [retObj, retSetter];
|
||||
}
|
82
packages/demobank-ui/src/pages/home/Transactions.tsx
Normal file
82
packages/demobank-ui/src/pages/home/Transactions.tsx
Normal file
@ -0,0 +1,82 @@
|
||||
import { h, VNode } from "preact";
|
||||
import { useEffect } from "preact/hooks";
|
||||
import useSWR from "swr";
|
||||
import { useTranslationContext } from "../../context/translation.js";
|
||||
|
||||
/**
|
||||
* Show one page of transactions.
|
||||
*/
|
||||
export function Transactions({
|
||||
pageNumber,
|
||||
accountLabel,
|
||||
balanceValue,
|
||||
}: any): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
const { data, error, mutate } = useSWR(
|
||||
`access-api/accounts/${accountLabel}/transactions?page=${pageNumber}`,
|
||||
);
|
||||
useEffect(() => {
|
||||
mutate();
|
||||
}, [balanceValue]);
|
||||
if (typeof error !== "undefined") {
|
||||
console.log("transactions not found error", error);
|
||||
switch (error.status) {
|
||||
case 404: {
|
||||
return <p>Transactions page {pageNumber} was not found.</p>;
|
||||
}
|
||||
case 401: {
|
||||
return <p>Wrong credentials given.</p>;
|
||||
}
|
||||
default: {
|
||||
return <p>Transaction page {pageNumber} could not be retrieved.</p>;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!data) {
|
||||
console.log(`History data of ${accountLabel} not arrived`);
|
||||
return <p>Transactions page loading...</p>;
|
||||
}
|
||||
console.log(`History data of ${accountLabel}`, data);
|
||||
return (
|
||||
<div class="results">
|
||||
<table class="pure-table pure-table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{i18n.str`Date`}</th>
|
||||
<th>{i18n.str`Amount`}</th>
|
||||
<th>{i18n.str`Counterpart`}</th>
|
||||
<th>{i18n.str`Subject`}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.transactions.map((item: any, idx: number) => {
|
||||
const sign = item.direction == "DBIT" ? "-" : "";
|
||||
const counterpart =
|
||||
item.direction == "DBIT" ? item.creditorIban : item.debtorIban;
|
||||
// Pattern:
|
||||
//
|
||||
// DD/MM YYYY subject -5 EUR
|
||||
// DD/MM YYYY subject 5 EUR
|
||||
const dateRegex = /^([0-9]{4})-([0-9]{2})-([0-9]{1,2})/;
|
||||
const dateParse = dateRegex.exec(item.date);
|
||||
const date =
|
||||
dateParse !== null
|
||||
? `${dateParse[3]}/${dateParse[2]} ${dateParse[1]}`
|
||||
: "date not found";
|
||||
return (
|
||||
<tr key={idx}>
|
||||
<td>{date}</td>
|
||||
<td>
|
||||
{sign}
|
||||
{item.amount} {item.currency}
|
||||
</td>
|
||||
<td>{counterpart}</td>
|
||||
<td>{item.subject}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -20,11 +20,8 @@ import useSWR, { SWRConfig, useSWRConfig } from "swr";
|
||||
|
||||
import { Amounts, HttpStatusCode, parsePaytoUri } from "@gnu-taler/taler-util";
|
||||
import { hooks } from "@gnu-taler/web-util/lib/index.browser";
|
||||
import { createHashHistory } from "history";
|
||||
import Router, { Route, route } from "preact-router";
|
||||
import { route } from "preact-router";
|
||||
import { StateUpdater, useEffect, useRef, useState } from "preact/hooks";
|
||||
import talerLogo from "../../assets/logo-white.svg";
|
||||
import { LangSelectorLikePy as LangSelector } from "../../components/menu/LangSelector.js";
|
||||
import { PageStateType, usePageContext } from "../../context/pageState.js";
|
||||
import { useTranslationContext } from "../../context/translation.js";
|
||||
import { BackendStateType, useBackendState } from "../../hooks/backend.js";
|
||||
@ -35,6 +32,8 @@ import {
|
||||
getIbanFromPayto,
|
||||
validateAmount,
|
||||
} from "../../utils.js";
|
||||
import { BankFrame } from "./BankFrame.js";
|
||||
import { Transactions } from "./Transactions.js";
|
||||
|
||||
/**
|
||||
* FIXME:
|
||||
@ -57,24 +56,6 @@ import {
|
||||
* Helpers. *
|
||||
***********/
|
||||
|
||||
function maybeDemoContent(content: VNode): VNode {
|
||||
if (bankUiSettings.showDemoNav) {
|
||||
return content;
|
||||
}
|
||||
return <Fragment />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bring the state to show the public accounts page.
|
||||
*/
|
||||
function goPublicAccounts(pageStateSetter: StateUpdater<PageStateType>) {
|
||||
return () =>
|
||||
pageStateSetter((prevState) => ({
|
||||
...prevState,
|
||||
showPublicHistories: true,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get username from the backend state, and throw
|
||||
* exception if not found.
|
||||
@ -145,29 +126,6 @@ function prepareHeaders(username?: string, password?: string): Headers {
|
||||
* State managers. *
|
||||
******************/
|
||||
|
||||
/**
|
||||
* 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 <input> fields.
|
||||
*/
|
||||
function useShowPublicAccount(
|
||||
state?: string,
|
||||
): [string | undefined, StateUpdater<string | undefined>] {
|
||||
const ret = hooks.useLocalStorage(
|
||||
"show-public-account",
|
||||
JSON.stringify(state),
|
||||
);
|
||||
const retObj: string | undefined = ret[0] ? JSON.parse(ret[0]) : ret[0];
|
||||
const retSetter: StateUpdater<string | undefined> = function (val) {
|
||||
const newVal =
|
||||
val instanceof Function
|
||||
? JSON.stringify(val(retObj))
|
||||
: JSON.stringify(val);
|
||||
ret[1](newVal);
|
||||
};
|
||||
return [retObj, retSetter];
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the raw Payto value entered by the user in the state.
|
||||
*/
|
||||
@ -689,165 +647,6 @@ async function registrationCall(
|
||||
* Functional components. *
|
||||
*************************/
|
||||
|
||||
function ErrorBanner(Props: any): VNode | null {
|
||||
const [pageState, pageStateSetter] = Props.pageState;
|
||||
// const { i18n } = useTranslationContext();
|
||||
if (!pageState.error) return null;
|
||||
|
||||
const rval = (
|
||||
<div class="informational informational-fail" style={{ marginTop: 8 }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<p>
|
||||
<b>{pageState.error.title}</b>
|
||||
</p>
|
||||
<div>
|
||||
<input
|
||||
type="button"
|
||||
class="pure-button"
|
||||
value="Clear"
|
||||
onClick={async () => {
|
||||
pageStateSetter((prev: any) => ({ ...prev, error: undefined }));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p>{pageState.error.description}</p>
|
||||
</div>
|
||||
);
|
||||
delete pageState.error;
|
||||
return rval;
|
||||
}
|
||||
|
||||
function StatusBanner(Props: any): VNode | null {
|
||||
const [pageState, pageStateSetter] = Props.pageState;
|
||||
if (!pageState.info) return null;
|
||||
|
||||
const rval = (
|
||||
<div class="informational informational-ok" style={{ marginTop: 8 }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<p>
|
||||
<b>{pageState.info}</b>
|
||||
</p>
|
||||
<div>
|
||||
<input
|
||||
type="button"
|
||||
class="pure-button"
|
||||
value="Clear"
|
||||
onClick={async () => {
|
||||
pageStateSetter((prev: any) => ({ ...prev, info: undefined }));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return rval;
|
||||
}
|
||||
|
||||
function BankFrame(Props: any): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
const { pageState, pageStateSetter } = usePageContext();
|
||||
console.log("BankFrame state", pageState);
|
||||
const logOut = (
|
||||
<div class="logout">
|
||||
<a
|
||||
href="#"
|
||||
class="pure-button logout-button"
|
||||
onClick={() => {
|
||||
pageStateSetter((prevState: PageStateType) => {
|
||||
const { talerWithdrawUri, withdrawalId, ...rest } = prevState;
|
||||
return {
|
||||
...rest,
|
||||
isLoggedIn: false,
|
||||
withdrawalInProgress: false,
|
||||
error: undefined,
|
||||
info: undefined,
|
||||
isRawPayto: false,
|
||||
};
|
||||
});
|
||||
}}
|
||||
>{i18n.str`Logout`}</a>
|
||||
</div>
|
||||
);
|
||||
|
||||
const demo_sites = [];
|
||||
for (const i in bankUiSettings.demoSites)
|
||||
demo_sites.push(
|
||||
<a href={bankUiSettings.demoSites[i][1]}>
|
||||
{bankUiSettings.demoSites[i][0]}
|
||||
</a>,
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<header
|
||||
class="demobar"
|
||||
style="display: flex; flex-direction: row; justify-content: space-between;"
|
||||
>
|
||||
<a href="#main" class="skip">{i18n.str`Skip to main content`}</a>
|
||||
<div style="max-width: 50em; margin-left: 2em;">
|
||||
<h1>
|
||||
<span class="it">
|
||||
<a href="/">{bankUiSettings.bankName}</a>
|
||||
</span>
|
||||
</h1>
|
||||
{maybeDemoContent(
|
||||
<p>
|
||||
<i18n.Translate>
|
||||
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{" "}
|
||||
<a
|
||||
href="/public-accounts"
|
||||
onClick={goPublicAccounts(pageStateSetter)}
|
||||
>
|
||||
Public Accounts
|
||||
</a>
|
||||
.
|
||||
</i18n.Translate>
|
||||
</p>,
|
||||
)}
|
||||
</div>
|
||||
<a href="https://taler.net/">
|
||||
<img
|
||||
src={talerLogo}
|
||||
alt={i18n.str`Taler logo`}
|
||||
height="100"
|
||||
width="224"
|
||||
style="margin: 2em 2em"
|
||||
/>
|
||||
</a>
|
||||
</header>
|
||||
<div style="display:flex; flex-direction: column;" class="navcontainer">
|
||||
<nav class="demolist">
|
||||
{maybeDemoContent(<Fragment>{demo_sites}</Fragment>)}
|
||||
<div class="right">
|
||||
<LangSelector />
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<section id="main" class="content">
|
||||
<ErrorBanner pageState={[pageState, pageStateSetter]} />
|
||||
<StatusBanner pageState={[pageState, pageStateSetter]} />
|
||||
{pageState.isLoggedIn ? logOut : null}
|
||||
{Props.children}
|
||||
</section>
|
||||
<section id="footer" class="footer">
|
||||
<div class="footer">
|
||||
<hr />
|
||||
<div>
|
||||
<p>
|
||||
You can learn more about GNU Taler on our{" "}
|
||||
<a href="https://taler.net">main website</a>.
|
||||
</p>
|
||||
</div>
|
||||
<div style="flex-grow:1" />
|
||||
<p>Copyright © 2014—2022 Taler Systems SA</p>
|
||||
</div>
|
||||
</section>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
function ShowInputErrorLabel({
|
||||
isDirty,
|
||||
message,
|
||||
@ -1685,81 +1484,6 @@ function RegistrationForm(): VNode {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show one page of transactions.
|
||||
*/
|
||||
function Transactions(Props: any): VNode {
|
||||
const { pageNumber, accountLabel, balanceValue } = Props;
|
||||
const { i18n } = useTranslationContext();
|
||||
const { data, error, mutate } = useSWR(
|
||||
`access-api/accounts/${accountLabel}/transactions?page=${pageNumber}`,
|
||||
);
|
||||
useEffect(() => {
|
||||
mutate();
|
||||
}, [balanceValue]);
|
||||
if (typeof error !== "undefined") {
|
||||
console.log("transactions not found error", error);
|
||||
switch (error.status) {
|
||||
case 404: {
|
||||
return <p>Transactions page {pageNumber} was not found.</p>;
|
||||
}
|
||||
case 401: {
|
||||
return <p>Wrong credentials given.</p>;
|
||||
}
|
||||
default: {
|
||||
return <p>Transaction page {pageNumber} could not be retrieved.</p>;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!data) {
|
||||
console.log(`History data of ${accountLabel} not arrived`);
|
||||
return <p>Transactions page loading...</p>;
|
||||
}
|
||||
console.log(`History data of ${accountLabel}`, data);
|
||||
return (
|
||||
<div class="results">
|
||||
<table class="pure-table pure-table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{i18n.str`Date`}</th>
|
||||
<th>{i18n.str`Amount`}</th>
|
||||
<th>{i18n.str`Counterpart`}</th>
|
||||
<th>{i18n.str`Subject`}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.transactions.map((item: any, idx: number) => {
|
||||
const sign = item.direction == "DBIT" ? "-" : "";
|
||||
const counterpart =
|
||||
item.direction == "DBIT" ? item.creditorIban : item.debtorIban;
|
||||
// Pattern:
|
||||
//
|
||||
// DD/MM YYYY subject -5 EUR
|
||||
// DD/MM YYYY subject 5 EUR
|
||||
const dateRegex = /^([0-9]{4})-([0-9]{2})-([0-9]{1,2})/;
|
||||
const dateParse = dateRegex.exec(item.date);
|
||||
const date =
|
||||
dateParse !== null
|
||||
? `${dateParse[3]}/${dateParse[2]} ${dateParse[1]}`
|
||||
: "date not found";
|
||||
return (
|
||||
<tr key={idx}>
|
||||
<td>{date}</td>
|
||||
<td>
|
||||
{sign}
|
||||
{item.amount} {item.currency}
|
||||
</td>
|
||||
<td>{counterpart}</td>
|
||||
<td>{item.subject}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show only the account's balance. NOTE: the backend state
|
||||
* is mostly needed to provide the user's credentials to POST
|
||||
@ -1965,145 +1689,6 @@ function SWRWithCredentials(props: any): VNode {
|
||||
);
|
||||
}
|
||||
|
||||
function SWRWithoutCredentials(Props: any): VNode {
|
||||
const { baseUrl } = Props;
|
||||
console.log("Base URL", baseUrl);
|
||||
return (
|
||||
<SWRConfig
|
||||
value={{
|
||||
fetcher: (url: string) =>
|
||||
fetch(baseUrl + url || "").then((r) => {
|
||||
if (!r.ok) throw { status: r.status, json: r.json() };
|
||||
|
||||
return r.json();
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{Props.children}
|
||||
</SWRConfig>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show histories of public accounts.
|
||||
*/
|
||||
function PublicHistories(Props: any): VNode {
|
||||
const [showAccount, setShowAccount] = useShowPublicAccount();
|
||||
const { data, error } = useSWR("access-api/public-accounts");
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
if (typeof error !== "undefined") {
|
||||
console.log("account error", error);
|
||||
switch (error.status) {
|
||||
case 404:
|
||||
console.log("public accounts: 404", error);
|
||||
Props.pageStateSetter((prevState: PageStateType) => ({
|
||||
...prevState,
|
||||
|
||||
showPublicHistories: false,
|
||||
error: {
|
||||
title: i18n.str`List of public accounts was not found.`,
|
||||
debug: JSON.stringify(error),
|
||||
},
|
||||
}));
|
||||
break;
|
||||
default:
|
||||
console.log("public accounts: non-404 error", error);
|
||||
Props.pageStateSetter((prevState: PageStateType) => ({
|
||||
...prevState,
|
||||
|
||||
showPublicHistories: false,
|
||||
error: {
|
||||
title: i18n.str`List of public accounts could not be retrieved.`,
|
||||
debug: JSON.stringify(error),
|
||||
},
|
||||
}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!data) return <p>Waiting public accounts list...</p>;
|
||||
const txs: any = {};
|
||||
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);
|
||||
console.log(`Public history tab: ${showAccount}`);
|
||||
|
||||
// Ask story of all the public accounts.
|
||||
for (const account of data.publicAccounts) {
|
||||
console.log("Asking transactions for", account.accountLabel);
|
||||
const isSelected = account.accountLabel == showAccount;
|
||||
accountsBar.push(
|
||||
<li
|
||||
class={
|
||||
isSelected
|
||||
? "pure-menu-selected pure-menu-item"
|
||||
: "pure-menu-item pure-menu"
|
||||
}
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
class="pure-menu-link"
|
||||
onClick={() => setShowAccount(account.accountLabel)}
|
||||
>
|
||||
{account.accountLabel}
|
||||
</a>
|
||||
</li>,
|
||||
);
|
||||
txs[account.accountLabel] = (
|
||||
<Transactions accountLabel={account.accountLabel} pageNumber={0} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<h1 class="nav">{i18n.str`History of public accounts`}</h1>
|
||||
<section id="main">
|
||||
<article>
|
||||
<div class="pure-menu pure-menu-horizontal" name="accountMenu">
|
||||
<ul class="pure-menu-list">{accountsBar}</ul>
|
||||
{typeof showAccount !== "undefined" ? (
|
||||
txs[showAccount]
|
||||
) : (
|
||||
<p>No public transactions found.</p>
|
||||
)}
|
||||
{Props.children}
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export function PublicHistoriesPage(): VNode {
|
||||
const { pageState, pageStateSetter } = usePageContext();
|
||||
// const { i18n } = useTranslationContext();
|
||||
return (
|
||||
<SWRWithoutCredentials baseUrl={getBankBackendBaseUrl()}>
|
||||
<BankFrame>
|
||||
<PublicHistories pageStateSetter={pageStateSetter}>
|
||||
<br />
|
||||
<a
|
||||
class="pure-button"
|
||||
onClick={() => {
|
||||
pageStateSetter((prevState: PageStateType) => ({
|
||||
...prevState,
|
||||
showPublicHistories: false,
|
||||
}));
|
||||
}}
|
||||
>
|
||||
Go back
|
||||
</a>
|
||||
</PublicHistories>
|
||||
</BankFrame>
|
||||
</SWRWithoutCredentials>
|
||||
);
|
||||
}
|
||||
|
||||
export function RegistrationPage(): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
if (!bankUiSettings.allowRegistrations) {
|
||||
|
Loading…
Reference in New Issue
Block a user