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 { h, VNode } from "preact";
|
||||||
import Router, { route, Route } from "preact-router";
|
import Router, { route, Route } from "preact-router";
|
||||||
import { useEffect } from "preact/hooks";
|
import { useEffect } from "preact/hooks";
|
||||||
import {
|
import { AccountPage, RegistrationPage } from "./home/index.js";
|
||||||
AccountPage,
|
import { PublicHistoriesPage } from "./home/PublicHistoriesPage.js";
|
||||||
PublicHistoriesPage,
|
|
||||||
RegistrationPage,
|
|
||||||
} from "./home/index.js";
|
|
||||||
|
|
||||||
export function Routing(): VNode {
|
export function Routing(): VNode {
|
||||||
const history = createHashHistory();
|
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 { Amounts, HttpStatusCode, parsePaytoUri } from "@gnu-taler/taler-util";
|
||||||
import { hooks } from "@gnu-taler/web-util/lib/index.browser";
|
import { hooks } from "@gnu-taler/web-util/lib/index.browser";
|
||||||
import { createHashHistory } from "history";
|
import { route } from "preact-router";
|
||||||
import Router, { Route, route } from "preact-router";
|
|
||||||
import { StateUpdater, useEffect, useRef, useState } from "preact/hooks";
|
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 { PageStateType, usePageContext } from "../../context/pageState.js";
|
||||||
import { useTranslationContext } from "../../context/translation.js";
|
import { useTranslationContext } from "../../context/translation.js";
|
||||||
import { BackendStateType, useBackendState } from "../../hooks/backend.js";
|
import { BackendStateType, useBackendState } from "../../hooks/backend.js";
|
||||||
@ -35,6 +32,8 @@ import {
|
|||||||
getIbanFromPayto,
|
getIbanFromPayto,
|
||||||
validateAmount,
|
validateAmount,
|
||||||
} from "../../utils.js";
|
} from "../../utils.js";
|
||||||
|
import { BankFrame } from "./BankFrame.js";
|
||||||
|
import { Transactions } from "./Transactions.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FIXME:
|
* FIXME:
|
||||||
@ -57,24 +56,6 @@ import {
|
|||||||
* Helpers. *
|
* 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
|
* Get username from the backend state, and throw
|
||||||
* exception if not found.
|
* exception if not found.
|
||||||
@ -145,29 +126,6 @@ function prepareHeaders(username?: string, password?: string): Headers {
|
|||||||
* State managers. *
|
* 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.
|
* Stores the raw Payto value entered by the user in the state.
|
||||||
*/
|
*/
|
||||||
@ -689,165 +647,6 @@ async function registrationCall(
|
|||||||
* Functional components. *
|
* 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({
|
function ShowInputErrorLabel({
|
||||||
isDirty,
|
isDirty,
|
||||||
message,
|
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
|
* Show only the account's balance. NOTE: the backend state
|
||||||
* is mostly needed to provide the user's credentials to POST
|
* 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 {
|
export function RegistrationPage(): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
if (!bankUiSettings.allowRegistrations) {
|
if (!bankUiSettings.allowRegistrations) {
|
||||||
|
Loading…
Reference in New Issue
Block a user