summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2022-12-07 15:44:16 -0300
committerSebastian <sebasjm@gmail.com>2022-12-07 16:08:19 -0300
commitd2554bedf3984ba4eb3a52b5649daa9c7c686c39 (patch)
tree237e020c1ef78634a5a288df4420f541bf706a1b
parent9112655ef5b68245f934ad25d2c8b9fa19a0f7bd (diff)
downloadwallet-core-d2554bedf3984ba4eb3a52b5649daa9c7c686c39.tar.gz
wallet-core-d2554bedf3984ba4eb3a52b5649daa9c7c686c39.tar.bz2
wallet-core-d2554bedf3984ba4eb3a52b5649daa9c7c686c39.zip
no-fix: remove 'any' and login status is taken from backend
-rw-r--r--packages/demobank-ui/src/components/app.tsx5
-rw-r--r--packages/demobank-ui/src/context/backend.ts52
-rw-r--r--packages/demobank-ui/src/context/pageState.ts3
-rw-r--r--packages/demobank-ui/src/context/translation.ts4
-rw-r--r--packages/demobank-ui/src/hooks/backend.ts60
-rw-r--r--packages/demobank-ui/src/pages/home/AccountPage.tsx91
-rw-r--r--packages/demobank-ui/src/pages/home/BankFrame.tsx34
-rw-r--r--packages/demobank-ui/src/pages/home/LoginForm.tsx28
-rw-r--r--packages/demobank-ui/src/pages/home/PaytoWireTransferForm.tsx86
-rw-r--r--packages/demobank-ui/src/pages/home/PublicHistoriesPage.tsx15
-rw-r--r--packages/demobank-ui/src/pages/home/RegistrationPage.tsx34
-rw-r--r--packages/demobank-ui/src/pages/home/TalerWithdrawalConfirmationQuestion.tsx21
-rw-r--r--packages/demobank-ui/src/pages/home/TalerWithdrawalQRCode.tsx12
-rw-r--r--packages/demobank-ui/src/pages/home/Transactions.tsx12
-rw-r--r--packages/demobank-ui/src/pages/home/WalletWithdrawForm.tsx15
-rw-r--r--packages/demobank-ui/src/utils.ts17
16 files changed, 264 insertions, 225 deletions
diff --git a/packages/demobank-ui/src/components/app.tsx b/packages/demobank-ui/src/components/app.tsx
index f3bc3f571..b6b88f910 100644
--- a/packages/demobank-ui/src/components/app.tsx
+++ b/packages/demobank-ui/src/components/app.tsx
@@ -1,4 +1,5 @@
import { h, FunctionalComponent } from "preact";
+import { BackendStateProvider } from "../context/backend.js";
import { PageStateProvider } from "../context/pageState.js";
import { TranslationProvider } from "../context/translation.js";
import { Routing } from "../pages/Routing.js";
@@ -24,7 +25,9 @@ const App: FunctionalComponent = () => {
return (
<TranslationProvider>
<PageStateProvider>
- <Routing />
+ <BackendStateProvider>
+ <Routing />
+ </BackendStateProvider>
</PageStateProvider>
</TranslationProvider>
);
diff --git a/packages/demobank-ui/src/context/backend.ts b/packages/demobank-ui/src/context/backend.ts
new file mode 100644
index 000000000..b9b7f8527
--- /dev/null
+++ b/packages/demobank-ui/src/context/backend.ts
@@ -0,0 +1,52 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 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 { ComponentChildren, createContext, h, VNode } from "preact";
+import { useContext } from "preact/hooks";
+import { BackendStateHandler, defaultState, useBackendState } from "../hooks/backend.js";
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+export type Type = BackendStateHandler;
+
+const initial: Type = {
+ state: defaultState,
+ clear() {
+ null
+ },
+ save(info) {
+ null
+ },
+};
+const Context = createContext<Type>(initial);
+
+export const useBackendContext = (): Type => useContext(Context);
+
+export const BackendStateProvider = ({
+ children,
+}: {
+ children: ComponentChildren;
+}): VNode => {
+ const value = useBackendState();
+
+ return h(Context.Provider, {
+ value,
+ children,
+ });
+}; \ No newline at end of file
diff --git a/packages/demobank-ui/src/context/pageState.ts b/packages/demobank-ui/src/context/pageState.ts
index 4ef21b8f0..b954ad20e 100644
--- a/packages/demobank-ui/src/context/pageState.ts
+++ b/packages/demobank-ui/src/context/pageState.ts
@@ -29,7 +29,6 @@ export type Type = {
};
const initial: Type = {
pageState: {
- isLoggedIn: false,
isRawPayto: false,
withdrawalInProgress: false,
},
@@ -59,7 +58,6 @@ export const PageStateProvider = ({
*/
function usePageState(
state: PageStateType = {
- isLoggedIn: false,
isRawPayto: false,
withdrawalInProgress: false,
},
@@ -98,7 +96,6 @@ function usePageState(
* Track page state.
*/
export interface PageStateType {
- isLoggedIn: boolean;
isRawPayto: boolean;
withdrawalInProgress: boolean;
error?: {
diff --git a/packages/demobank-ui/src/context/translation.ts b/packages/demobank-ui/src/context/translation.ts
index 478bdbde0..0a7e9429d 100644
--- a/packages/demobank-ui/src/context/translation.ts
+++ b/packages/demobank-ui/src/context/translation.ts
@@ -20,7 +20,7 @@
*/
import { i18n, setupI18n } from "@gnu-taler/taler-util";
-import { createContext, h, VNode } from "preact";
+import { ComponentChildren, createContext, h, VNode } from "preact";
import { useContext, useEffect } from "preact/hooks";
import { hooks } from "@gnu-taler/web-util/lib/index.browser";
import { strings } from "../i18n/strings.js";
@@ -60,7 +60,7 @@ const Context = createContext<Type>(initial);
interface Props {
initial?: string;
- children: any;
+ children: ComponentChildren;
forceLang?: string;
}
diff --git a/packages/demobank-ui/src/hooks/backend.ts b/packages/demobank-ui/src/hooks/backend.ts
index 3b00edee3..967d5ee85 100644
--- a/packages/demobank-ui/src/hooks/backend.ts
+++ b/packages/demobank-ui/src/hooks/backend.ts
@@ -1,33 +1,55 @@
import { hooks } from "@gnu-taler/web-util/lib/index.browser";
-import { StateUpdater } from "preact/hooks";
/**
* Has the information to reach and
* authenticate at the bank's backend.
*/
-export interface BackendStateType {
- url?: string;
- username?: string;
- password?: string;
+export type BackendState = LoggedIn | LoggedOut
+
+export interface BackendInfo {
+ url: string;
+ username: string;
+ password: string;
+}
+
+interface LoggedIn extends BackendInfo {
+ status: "loggedIn"
+}
+interface LoggedOut {
+ status: "loggedOut"
}
+export const defaultState: BackendState = { status: "loggedOut" }
+
+export interface BackendStateHandler {
+ state: BackendState,
+ clear(): void;
+ save(info: BackendInfo): void;
+}
/**
* Return getters and setters for
* login credentials and backend's
* base URL.
*/
-type BackendStateTypeOpt = BackendStateType | undefined;
-export function useBackendState(
- state?: BackendStateType,
-): [BackendStateTypeOpt, StateUpdater<BackendStateTypeOpt>] {
- const ret = hooks.useLocalStorage("backend-state", JSON.stringify(state));
- const retObj: BackendStateTypeOpt = ret[0] ? JSON.parse(ret[0]) : ret[0];
- const retSetter: StateUpdater<BackendStateTypeOpt> = function (val) {
- const newVal =
- val instanceof Function
- ? JSON.stringify(val(retObj))
- : JSON.stringify(val);
- ret[1](newVal);
- };
- return [retObj, retSetter];
+export function useBackendState(): BackendStateHandler {
+ const [value, update] = hooks.useLocalStorage("backend-state", JSON.stringify(defaultState));
+ // const parsed = value !== undefined ? JSON.parse(value) : value;
+ let parsed
+ try {
+ parsed = JSON.parse(value!)
+ } catch {
+ parsed = undefined
+ }
+ const state: BackendState = !parsed?.status ? defaultState : parsed
+
+ return {
+ state,
+ clear() {
+ update(JSON.stringify(defaultState))
+ },
+ save(info) {
+ const nextState: BackendState = { status: "loggedIn", ...info }
+ update(JSON.stringify(nextState))
+ },
+ }
}
diff --git a/packages/demobank-ui/src/pages/home/AccountPage.tsx b/packages/demobank-ui/src/pages/home/AccountPage.tsx
index 2bc05c332..16ff601ec 100644
--- a/packages/demobank-ui/src/pages/home/AccountPage.tsx
+++ b/packages/demobank-ui/src/pages/home/AccountPage.tsx
@@ -16,14 +16,15 @@
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 { ComponentChildren, Fragment, h, VNode } from "preact";
+import { StateUpdater, useEffect } from "preact/hooks";
import useSWR, { SWRConfig, useSWRConfig } from "swr";
+import { useBackendContext } from "../../context/backend.js";
import { PageStateType, usePageContext } from "../../context/pageState.js";
import { useTranslationContext } from "../../context/translation.js";
-import { useBackendState } from "../../hooks/backend.js";
+import { BackendInfo } from "../../hooks/backend.js";
import { bankUiSettings } from "../../settings.js";
-import { getIbanFromPayto } from "../../utils.js";
+import { getIbanFromPayto, prepareHeaders } from "../../utils.js";
import { BankFrame } from "./BankFrame.js";
import { LoginForm } from "./LoginForm.js";
import { PaymentOptions } from "./PaymentOptions.js";
@@ -31,11 +32,10 @@ import { TalerWithdrawalQRCode } from "./TalerWithdrawalQRCode.js";
import { Transactions } from "./Transactions.js";
export function AccountPage(): VNode {
- const [backendState, backendStateSetter] = useBackendState();
+ const backend = useBackendContext();
const { i18n } = useTranslationContext();
- const { pageState, pageStateSetter } = usePageContext();
- if (!pageState.isLoggedIn) {
+ if (backend.state.status === "loggedOut") {
return (
<BankFrame>
<h1 class="nav">{i18n.str`Welcome to ${bankUiSettings.bankName}!`}</h1>
@@ -44,28 +44,9 @@ export function AccountPage(): VNode {
);
}
- 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 <p>Error: waiting for details...</p>;
- }
- console.log("Showing the profile page..");
return (
- <SWRWithCredentials
- username={backendState.username}
- password={backendState.password}
- backendUrl={backendState.url}
- >
- <Account
- accountLabel={backendState.username}
- backendState={backendState}
- />
+ <SWRWithCredentials info={backend.state}>
+ <Account accountLabel={backend.state.username} />
</SWRWithCredentials>
);
}
@@ -73,16 +54,20 @@ export function AccountPage(): VNode {
/**
* 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);
+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(backendUrl + url || "", { headers }).then((r) => {
+ return fetch(new URL(url, backendUrl).href, { headers }).then((r) => {
if (!r.ok) throw { status: r.status, json: r.json() };
return r.json();
@@ -90,7 +75,7 @@ function SWRWithCredentials(props: any): VNode {
},
}}
>
- {props.children}
+ {children as any}
</SWRConfig>
);
}
@@ -100,9 +85,9 @@ function SWRWithCredentials(props: any): VNode {
* is mostly needed to provide the user's credentials to POST
* to the bank.
*/
-function Account(Props: any): VNode {
+function Account({ accountLabel }: { accountLabel: string }): 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, {
@@ -112,14 +97,9 @@ function Account(Props: any): VNode {
// revalidateOnFocus: false,
// revalidateOnReconnect: false,
});
+ const backend = useBackendContext();
const { pageState, pageStateSetter: setPageState } = usePageContext();
- const {
- withdrawalInProgress,
- withdrawalId,
- isLoggedIn,
- talerWithdrawUri,
- timestamp,
- } = pageState;
+ const { withdrawalId, talerWithdrawUri, timestamp } = pageState;
const { i18n } = useTranslationContext();
useEffect(() => {
mutate();
@@ -129,10 +109,11 @@ function Account(Props: any): VNode {
* 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} />);
+ // const [txPageNumber, setTxPageNumber] = useTransactionPageNumber();
+ // const txsPages = [];
+ // for (let i = 0; i <= txPageNumber; i++) {
+ // txsPages.push(<Transactions accountLabel={accountLabel} pageNumber={i} />);
+ // }
if (typeof error !== "undefined") {
console.log("account error", error, endpoint);
@@ -143,10 +124,10 @@ function Account(Props: any): VNode {
*/
switch (error.status) {
case 404: {
+ backend.clear();
setPageState((prevState: PageStateType) => ({
...prevState,
- isLoggedIn: false,
error: {
title: i18n.str`Username or account label '${accountLabel}' not found. Won't login.`,
},
@@ -170,10 +151,9 @@ function Account(Props: any): VNode {
}
case HttpStatusCode.Unauthorized:
case HttpStatusCode.Forbidden: {
+ backend.clear();
setPageState((prevState: PageStateType) => ({
...prevState,
-
- isLoggedIn: false,
error: {
title: i18n.str`Wrong credentials given.`,
},
@@ -181,10 +161,9 @@ function Account(Props: any): VNode {
return <p>Wrong credentials...</p>;
}
default: {
+ backend.clear();
setPageState((prevState: PageStateType) => ({
...prevState,
-
- isLoggedIn: false,
error: {
title: i18n.str`Account information could not be retrieved.`,
debug: JSON.stringify(error),
@@ -211,13 +190,11 @@ function Account(Props: any): VNode {
* the outcome.
*/
console.log(`maybe new withdrawal ${talerWithdrawUri}`);
- if (talerWithdrawUri) {
+ if (talerWithdrawUri && withdrawalId) {
console.log("Bank created a new Taler withdrawal");
return (
<BankFrame>
<TalerWithdrawalQRCode
- accountLabel={accountLabel}
- backendState={backendState}
withdrawalId={withdrawalId}
talerWithdrawUri={talerWithdrawUri}
/>
@@ -266,7 +243,7 @@ function Account(Props: any): VNode {
<h2>{i18n.str`Latest transactions:`}</h2>
<Transactions
balanceValue={balanceValue}
- pageNumber="0"
+ pageNumber={0}
accountLabel={accountLabel}
/>
</article>
diff --git a/packages/demobank-ui/src/pages/home/BankFrame.tsx b/packages/demobank-ui/src/pages/home/BankFrame.tsx
index 3b099e34b..f6b8fbd96 100644
--- a/packages/demobank-ui/src/pages/home/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/home/BankFrame.tsx
@@ -14,15 +14,21 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { Fragment, h, VNode } from "preact";
+import { ComponentChildren, Fragment, h, VNode } from "preact";
import talerLogo from "../../assets/logo-white.svg";
import { LangSelectorLikePy as LangSelector } from "../../components/menu/LangSelector.js";
+import { useBackendContext } from "../../context/backend.js";
import { PageStateType, usePageContext } from "../../context/pageState.js";
import { useTranslationContext } from "../../context/translation.js";
import { bankUiSettings } from "../../settings.js";
-export function BankFrame(Props: any): VNode {
+export function BankFrame({
+ children,
+}: {
+ children: ComponentChildren;
+}): VNode {
const { i18n } = useTranslationContext();
+ const backend = useBackendContext();
const { pageState, pageStateSetter } = usePageContext();
console.log("BankFrame state", pageState);
const logOut = (
@@ -33,9 +39,9 @@ export function BankFrame(Props: any): VNode {
onClick={() => {
pageStateSetter((prevState: PageStateType) => {
const { talerWithdrawUri, withdrawalId, ...rest } = prevState;
+ backend.clear();
return {
...rest,
- isLoggedIn: false,
withdrawalInProgress: false,
error: undefined,
info: undefined,
@@ -98,10 +104,10 @@ export function BankFrame(Props: any): VNode {
</nav>
</div>
<section id="main" class="content">
- <ErrorBanner pageState={[pageState, pageStateSetter]} />
- <StatusBanner pageState={[pageState, pageStateSetter]} />
- {pageState.isLoggedIn ? logOut : null}
- {Props.children}
+ <ErrorBanner />
+ <StatusBanner />
+ {backend.state.status === "loggedIn" ? logOut : null}
+ {children}
</section>
<section id="footer" class="footer">
<div class="footer">
@@ -127,9 +133,9 @@ function maybeDemoContent(content: VNode): VNode {
return <Fragment />;
}
-function ErrorBanner(Props: any): VNode | null {
- const [pageState, pageStateSetter] = Props.pageState;
- // const { i18n } = useTranslationContext();
+function ErrorBanner(): VNode | null {
+ const { pageState, pageStateSetter } = usePageContext();
+
if (!pageState.error) return null;
const rval = (
@@ -144,7 +150,7 @@ function ErrorBanner(Props: any): VNode | null {
class="pure-button"
value="Clear"
onClick={async () => {
- pageStateSetter((prev: any) => ({ ...prev, error: undefined }));
+ pageStateSetter((prev) => ({ ...prev, error: undefined }));
}}
/>
</div>
@@ -156,8 +162,8 @@ function ErrorBanner(Props: any): VNode | null {
return rval;
}
-function StatusBanner(Props: any): VNode | null {
- const [pageState, pageStateSetter] = Props.pageState;
+function StatusBanner(): VNode | null {
+ const { pageState, pageStateSetter } = usePageContext();
if (!pageState.info) return null;
const rval = (
@@ -172,7 +178,7 @@ function StatusBanner(Props: any): VNode | null {
class="pure-button"
value="Clear"
onClick={async () => {
- pageStateSetter((prev: any) => ({ ...prev, info: undefined }));
+ pageStateSetter((prev) => ({ ...prev, info: undefined }));
}}
/>
</div>
diff --git a/packages/demobank-ui/src/pages/home/LoginForm.tsx b/packages/demobank-ui/src/pages/home/LoginForm.tsx
index f60c9f600..f31f91190 100644
--- a/packages/demobank-ui/src/pages/home/LoginForm.tsx
+++ b/packages/demobank-ui/src/pages/home/LoginForm.tsx
@@ -16,10 +16,10 @@
import { h, VNode } from "preact";
import { route } from "preact-router";
-import { StateUpdater, useEffect, useRef, useState } from "preact/hooks";
-import { PageStateType, usePageContext } from "../../context/pageState.js";
+import { useEffect, useRef, useState } from "preact/hooks";
+import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "../../context/translation.js";
-import { BackendStateType, useBackendState } from "../../hooks/backend.js";
+import { BackendStateHandler } from "../../hooks/backend.js";
import { bankUiSettings } from "../../settings.js";
import { getBankBackendBaseUrl, undefinedIfEmpty } from "../../utils.js";
import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
@@ -28,8 +28,7 @@ import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
* Collect and submit login data.
*/
export function LoginForm(): VNode {
- const [backendState, backendStateSetter] = useBackendState();
- const { pageState, pageStateSetter } = usePageContext();
+ const backend = useBackendContext();
const [username, setUsername] = useState<string | undefined>();
const [password, setPassword] = useState<string | undefined>();
const { i18n } = useTranslationContext();
@@ -93,11 +92,7 @@ export function LoginForm(): VNode {
disabled={!!errors}
onClick={() => {
if (!username || !password) return;
- loginCall(
- { username, password },
- backendStateSetter,
- pageStateSetter,
- );
+ loginCall({ username, password }, backend);
setUsername(undefined);
setPassword(undefined);
}}
@@ -129,21 +124,16 @@ async function loginCall(
* FIXME: figure out if the two following
* functions can be retrieved from the state.
*/
- backendStateSetter: StateUpdater<BackendStateType | undefined>,
- pageStateSetter: StateUpdater<PageStateType>,
+ backend: BackendStateHandler,
): Promise<void> {
/**
* Optimistically setting the state as 'logged in', and
* let the Account component request the balance to check
* whether the credentials are valid. */
- pageStateSetter((prevState) => ({ ...prevState, isLoggedIn: true }));
- let baseUrl = getBankBackendBaseUrl();
- if (!baseUrl.endsWith("/")) baseUrl += "/";
- backendStateSetter((prevState) => ({
- ...prevState,
- url: baseUrl,
+ backend.save({
+ url: getBankBackendBaseUrl(),
username: req.username,
password: req.password,
- }));
+ });
}
diff --git a/packages/demobank-ui/src/pages/home/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/home/PaytoWireTransferForm.tsx
index 45e7cf5ca..e4fe386ff 100644
--- a/packages/demobank-ui/src/pages/home/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/home/PaytoWireTransferForm.tsx
@@ -18,9 +18,10 @@ import { Amounts, parsePaytoUri } from "@gnu-taler/taler-util";
import { hooks } from "@gnu-taler/web-util/lib/index.browser";
import { h, VNode } from "preact";
import { StateUpdater, useEffect, useRef, useState } from "preact/hooks";
+import { useBackendContext } from "../../context/backend.js";
import { PageStateType, usePageContext } from "../../context/pageState.js";
import { useTranslationContext } from "../../context/translation.js";
-import { BackendStateType, useBackendState } from "../../hooks/backend.js";
+import { BackendState } from "../../hooks/backend.js";
import { prepareHeaders, undefinedIfEmpty } from "../../utils.js";
import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
@@ -31,7 +32,7 @@ export function PaytoWireTransferForm({
focus?: boolean;
currency?: string;
}): VNode {
- const [backendState, backendStateSetter] = useBackendState();
+ const backend = useBackendContext();
const { pageState, pageStateSetter } = usePageContext(); // NOTE: used for go-back button?
const [submitData, submitDataSetter] = useWireTransferRequestType();
@@ -81,7 +82,7 @@ export function PaytoWireTransferForm({
required
pattern={ibanRegex}
onInput={(e): void => {
- submitDataSetter((submitData: any) => ({
+ submitDataSetter((submitData) => ({
...submitData,
iban: e.currentTarget.value,
}));
@@ -102,7 +103,7 @@ export function PaytoWireTransferForm({
value={submitData?.subject ?? ""}
required
onInput={(e): void => {
- submitDataSetter((submitData: any) => ({
+ submitDataSetter((submitData) => ({
...submitData,
subject: e.currentTarget.value,
}));
@@ -133,7 +134,7 @@ export function PaytoWireTransferForm({
required
value={submitData?.amount ?? ""}
onInput={(e): void => {
- submitDataSetter((submitData: any) => ({
+ submitDataSetter((submitData) => ({
...submitData,
amount: e.currentTarget.value,
}));
@@ -179,7 +180,7 @@ export function PaytoWireTransferForm({
};
return await createTransactionCall(
transactionData,
- backendState,
+ backend.state,
pageStateSetter,
() =>
submitDataSetter((p) => ({
@@ -209,7 +210,7 @@ export function PaytoWireTransferForm({
href="/account"
onClick={() => {
console.log("switch to raw payto form");
- pageStateSetter((prevState: any) => ({
+ pageStateSetter((prevState) => ({
...prevState,
isRawPayto: true,
}));
@@ -283,7 +284,7 @@ export function PaytoWireTransferForm({
return await createTransactionCall(
transactionData,
- backendState,
+ backend.state,
pageStateSetter,
() => rawPaytoInputSetter(undefined),
);
@@ -295,7 +296,7 @@ export function PaytoWireTransferForm({
href="/account"
onClick={() => {
console.log("switch to wire-transfer-form");
- pageStateSetter((prevState: any) => ({
+ pageStateSetter((prevState) => ({
...prevState,
isRawPayto: false,
}));
@@ -345,7 +346,7 @@ function useWireTransferRequestType(
*/
async function createTransactionCall(
req: TransactionRequestType,
- backendState: BackendStateType | undefined,
+ backendState: BackendState,
pageStateSetter: StateUpdater<PageStateType>,
/**
* Optional since the raw payto form doesn't have
@@ -353,13 +354,30 @@ async function createTransactionCall(
*/
cleanUpForm: () => void,
): Promise<void> {
- let res: any;
+ if (backendState.status === "loggedOut") {
+ console.log("No credentials found.");
+ pageStateSetter((prevState) => ({
+ ...prevState,
+
+ error: {
+ title: "No credentials found.",
+ },
+ }));
+ return;
+ }
+ let res: Response;
try {
- res = await postToBackend(
- `access-api/accounts/${getUsername(backendState)}/transactions`,
- backendState,
- JSON.stringify(req),
+ const { username, password } = backendState;
+ const headers = prepareHeaders(username, password);
+ const url = new URL(
+ `access-api/accounts/${backendState.username}/transactions`,
+ backendState.url,
);
+ res = await fetch(url.href, {
+ method: "POST",
+ headers,
+ body: JSON.stringify(req),
+ });
} catch (error) {
console.log("Could not POST transaction request to the bank", error);
pageStateSetter((prevState) => ({
@@ -402,41 +420,3 @@ async function createTransactionCall(
// be discarded.
cleanUpForm();
}
-
-/**
- * Get username from the backend state, and throw
- * exception if not found.
- */
-function getUsername(backendState: BackendStateType | undefined): string {
- if (typeof backendState === "undefined")
- throw Error("Username can't be found in a undefined backend state.");
-
- if (!backendState.username) {
- throw Error("No username, must login first.");
- }
- return backendState.username;
-}
-
-/**
- * Helps extracting the credentials from the state
- * and wraps the actual call to 'fetch'. Should be
- * enclosed in a try-catch block by the caller.
- */
-async function postToBackend(
- uri: string,
- backendState: BackendStateType | undefined,
- body: string,
-): Promise<any> {
- if (typeof backendState === "undefined")
- throw Error("Credentials can't be found in a undefined backend state.");
-
- const { username, password } = backendState;
- const headers = prepareHeaders(username, password);
- // Backend URL must have been stored _with_ a final slash.
- const url = new URL(uri, backendState.url);
- return await fetch(url.href, {
- method: "POST",
- headers,
- body,
- });
-}
diff --git a/packages/demobank-ui/src/pages/home/PublicHistoriesPage.tsx b/packages/demobank-ui/src/pages/home/PublicHistoriesPage.tsx
index 215dc7321..a8028f3bf 100644
--- a/packages/demobank-ui/src/pages/home/PublicHistoriesPage.tsx
+++ b/packages/demobank-ui/src/pages/home/PublicHistoriesPage.tsx
@@ -15,7 +15,7 @@
*/
import { hooks } from "@gnu-taler/web-util/lib/index.browser";
-import { Fragment, h, VNode } from "preact";
+import { ComponentChildren, Fragment, h, VNode } from "preact";
import { route } from "preact-router";
import { StateUpdater } from "preact/hooks";
import useSWR, { SWRConfig } from "swr";
@@ -35,8 +35,13 @@ export function PublicHistoriesPage(): VNode {
);
}
-function SWRWithoutCredentials(Props: any): VNode {
- const { baseUrl } = Props;
+function SWRWithoutCredentials({
+ baseUrl,
+ children,
+}: {
+ children: ComponentChildren;
+ baseUrl: string;
+}): VNode {
console.log("Base URL", baseUrl);
return (
<SWRConfig
@@ -49,7 +54,7 @@ function SWRWithoutCredentials(Props: any): VNode {
}),
}}
>
- {Props.children}
+ {children as any}
</SWRConfig>
);
}
@@ -93,7 +98,7 @@ function PublicHistories(): VNode {
}
}
if (!data) return <p>Waiting public accounts list...</p>;
- const txs: any = {};
+ const txs: Record<string, h.JSX.Element> = {};
const accountsBar = [];
/**
diff --git a/packages/demobank-ui/src/pages/home/RegistrationPage.tsx b/packages/demobank-ui/src/pages/home/RegistrationPage.tsx
index 9a120cb4f..08e9bd480 100644
--- a/packages/demobank-ui/src/pages/home/RegistrationPage.tsx
+++ b/packages/demobank-ui/src/pages/home/RegistrationPage.tsx
@@ -16,9 +16,10 @@
import { Fragment, h, VNode } from "preact";
import { route } from "preact-router";
import { StateUpdater, useState } from "preact/hooks";
+import { useBackendContext } from "../../context/backend.js";
import { PageStateType, usePageContext } from "../../context/pageState.js";
import { useTranslationContext } from "../../context/translation.js";
-import { BackendStateType, useBackendState } from "../../hooks/backend.js";
+import { BackendStateHandler } from "../../hooks/backend.js";
import { bankUiSettings } from "../../settings.js";
import { getBankBackendBaseUrl, undefinedIfEmpty } from "../../utils.js";
import { BankFrame } from "./BankFrame.js";
@@ -44,7 +45,7 @@ export function RegistrationPage(): VNode {
* Collect and submit registration data.
*/
function RegistrationForm(): VNode {
- const [backendState, backendStateSetter] = useBackendState();
+ const backend = useBackendContext();
const { pageState, pageStateSetter } = usePageContext();
const [username, setUsername] = useState<string | undefined>();
const [password, setPassword] = useState<string | undefined>();
@@ -132,7 +133,7 @@ function RegistrationForm(): VNode {
if (!username || !password) return;
registrationCall(
{ username, password },
- backendStateSetter, // will store BE URL, if OK.
+ backend, // will store BE URL, if OK.
pageStateSetter,
);
@@ -177,23 +178,17 @@ async function registrationCall(
* functions can be retrieved somewhat from
* the state.
*/
- backendStateSetter: StateUpdater<BackendStateType | undefined>,
+ backend: BackendStateHandler,
pageStateSetter: StateUpdater<PageStateType>,
): Promise<void> {
- let baseUrl = getBankBackendBaseUrl();
- /**
- * If the base URL doesn't end with slash and the path
- * is not empty, then the concatenation made by URL()
- * drops the last path element.
- */
- if (!baseUrl.endsWith("/")) baseUrl += "/";
+ const url = getBankBackendBaseUrl();
const headers = new Headers();
headers.append("Content-Type", "application/json");
- const url = new URL("access-api/testing/register", baseUrl);
+ const registerEndpoint = new URL("access-api/testing/register", url);
let res: Response;
try {
- res = await fetch(url.href, {
+ res = await fetch(registerEndpoint.href, {
method: "POST",
body: JSON.stringify({
username: req.username,
@@ -203,7 +198,7 @@ async function registrationCall(
});
} catch (error) {
console.log(
- `Could not POST new registration to the bank (${url.href})`,
+ `Could not POST new registration to the bank (${registerEndpoint.href})`,
error,
);
pageStateSetter((prevState) => ({
@@ -239,16 +234,11 @@ async function registrationCall(
}
} else {
// registration was ok
- pageStateSetter((prevState) => ({
- ...prevState,
- isLoggedIn: true,
- }));
- backendStateSetter((prevState) => ({
- ...prevState,
- url: baseUrl,
+ backend.save({
+ url,
username: req.username,
password: req.password,
- }));
+ });
route("/account");
}
}
diff --git a/packages/demobank-ui/src/pages/home/TalerWithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/home/TalerWithdrawalConfirmationQuestion.tsx
index e3d8957b8..4fd46878b 100644
--- a/packages/demobank-ui/src/pages/home/TalerWithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/home/TalerWithdrawalConfirmationQuestion.tsx
@@ -1,17 +1,18 @@
import { Fragment, h, VNode } from "preact";
import { StateUpdater } from "preact/hooks";
+import { useBackendContext } from "../../context/backend.js";
import { PageStateType, usePageContext } from "../../context/pageState.js";
import { useTranslationContext } from "../../context/translation.js";
-import { BackendStateType } from "../../hooks/backend.js";
+import { BackendState } from "../../hooks/backend.js";
import { prepareHeaders } from "../../utils.js";
/**
* Additional authentication required to complete the operation.
* Not providing a back button, only abort.
*/
-export function TalerWithdrawalConfirmationQuestion(Props: any): VNode {
+export function TalerWithdrawalConfirmationQuestion(): VNode {
const { pageState, pageStateSetter } = usePageContext();
- const { backendState } = Props;
+ const backend = useBackendContext();
const { i18n } = useTranslationContext();
const captchaNumbers = {
a: Math.floor(Math.random() * 10),
@@ -57,7 +58,7 @@ export function TalerWithdrawalConfirmationQuestion(Props: any): VNode {
(captchaNumbers.a + captchaNumbers.b).toString()
) {
confirmWithdrawalCall(
- backendState,
+ backend.state,
pageState.withdrawalId,
pageStateSetter,
);
@@ -79,7 +80,7 @@ export function TalerWithdrawalConfirmationQuestion(Props: any): VNode {
class="pure-button pure-button-secondary btn-cancel"
onClick={async () =>
await abortWithdrawalCall(
- backendState,
+ backend.state,
pageState.withdrawalId,
pageStateSetter,
)
@@ -116,11 +117,11 @@ export function TalerWithdrawalConfirmationQuestion(Props: any): VNode {
* 'page state' and let the related components refresh.
*/
async function confirmWithdrawalCall(
- backendState: BackendStateType | undefined,
+ backendState: BackendState,
withdrawalId: string | undefined,
pageStateSetter: StateUpdater<PageStateType>,
): Promise<void> {
- if (typeof backendState === "undefined") {
+ if (backendState.status === "loggedOut") {
console.log("No credentials found.");
pageStateSetter((prevState) => ({
...prevState,
@@ -211,11 +212,11 @@ async function confirmWithdrawalCall(
* Abort a withdrawal operation via the Access API's /abort.
*/
async function abortWithdrawalCall(
- backendState: BackendStateType | undefined,
+ backendState: BackendState,
withdrawalId: string | undefined,
pageStateSetter: StateUpdater<PageStateType>,
): Promise<void> {
- if (typeof backendState === "undefined") {
+ if (backendState.status === "loggedOut") {
console.log("No credentials found.");
pageStateSetter((prevState) => ({
...prevState,
@@ -237,7 +238,7 @@ async function abortWithdrawalCall(
}));
return;
}
- let res: any;
+ let res: Response;
try {
const { username, password } = backendState;
const headers = prepareHeaders(username, password);
diff --git a/packages/demobank-ui/src/pages/home/TalerWithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/home/TalerWithdrawalQRCode.tsx
index da4ccc45e..848a9c45c 100644
--- a/packages/demobank-ui/src/pages/home/TalerWithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/home/TalerWithdrawalQRCode.tsx
@@ -1,5 +1,6 @@
import { Fragment, h, VNode } from "preact";
import useSWR from "swr";
+import { useBackendContext } from "../../context/backend.js";
import { PageStateType, usePageContext } from "../../context/pageState.js";
import { useTranslationContext } from "../../context/translation.js";
import { QrCodeSection } from "./QrCodeSection.js";
@@ -10,10 +11,15 @@ import { TalerWithdrawalConfirmationQuestion } from "./TalerWithdrawalConfirmati
* permit the passing of exchange and reserve details to
* the bank. Poll the backend until such operation is done.
*/
-export function TalerWithdrawalQRCode(Props: any): VNode {
+export function TalerWithdrawalQRCode({
+ withdrawalId,
+ talerWithdrawUri,
+}: {
+ withdrawalId: string;
+ talerWithdrawUri: string;
+}): VNode {
// turns true when the wallet POSTed the reserve details:
const { pageState, pageStateSetter } = usePageContext();
- const { withdrawalId, talerWithdrawUri, backendState } = Props;
const { i18n } = useTranslationContext();
const abortButton = (
<a
@@ -93,5 +99,5 @@ export function TalerWithdrawalQRCode(Props: any): VNode {
* Wallet POSTed the withdrawal details! Ask the
* user to authorize the operation (here CAPTCHA).
*/
- return <TalerWithdrawalConfirmationQuestion backendState={backendState} />;
+ return <TalerWithdrawalConfirmationQuestion />;
}
diff --git a/packages/demobank-ui/src/pages/home/Transactions.tsx b/packages/demobank-ui/src/pages/home/Transactions.tsx
index eb344403f..c0bb86024 100644
--- a/packages/demobank-ui/src/pages/home/Transactions.tsx
+++ b/packages/demobank-ui/src/pages/home/Transactions.tsx
@@ -10,14 +10,20 @@ export function Transactions({
pageNumber,
accountLabel,
balanceValue,
-}: any): VNode {
+}: {
+ pageNumber: number;
+ accountLabel: string;
+ balanceValue?: string;
+}): VNode {
const { i18n } = useTranslationContext();
const { data, error, mutate } = useSWR(
`access-api/accounts/${accountLabel}/transactions?page=${pageNumber}`,
);
useEffect(() => {
- mutate();
- }, [balanceValue]);
+ if (balanceValue) {
+ mutate();
+ }
+ }, [balanceValue ?? ""]);
if (typeof error !== "undefined") {
console.log("transactions not found error", error);
switch (error.status) {
diff --git a/packages/demobank-ui/src/pages/home/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/home/WalletWithdrawForm.tsx
index 842f14a5f..ee43d2006 100644
--- a/packages/demobank-ui/src/pages/home/WalletWithdrawForm.tsx
+++ b/packages/demobank-ui/src/pages/home/WalletWithdrawForm.tsx
@@ -16,9 +16,10 @@
import { h, VNode } from "preact";
import { StateUpdater, useEffect, useRef } from "preact/hooks";
+import { useBackendContext } from "../../context/backend.js";
import { PageStateType, usePageContext } from "../../context/pageState.js";
import { useTranslationContext } from "../../context/translation.js";
-import { BackendStateType, useBackendState } from "../../hooks/backend.js";
+import { BackendState } from "../../hooks/backend.js";
import { prepareHeaders, validateAmount } from "../../utils.js";
export function WalletWithdrawForm({
@@ -28,10 +29,10 @@ export function WalletWithdrawForm({
currency?: string;
focus?: boolean;
}): VNode {
- const [backendState, backendStateSetter] = useBackendState();
+ const backend = useBackendContext();
const { pageState, pageStateSetter } = usePageContext();
const { i18n } = useTranslationContext();
- let submitAmount = "5.00";
+ let submitAmount: string | undefined = "5.00";
const ref = useRef<HTMLInputElement>(null);
useEffect(() => {
@@ -83,7 +84,7 @@ export function WalletWithdrawForm({
if (!submitAmount && currency) return;
createWithdrawalCall(
`${currency}:${submitAmount}`,
- backendState,
+ backend.state,
pageStateSetter,
);
}}
@@ -105,10 +106,10 @@ export function WalletWithdrawForm({
* the user about the operation's outcome. (2) use POST helper. */
async function createWithdrawalCall(
amount: string,
- backendState: BackendStateType | undefined,
+ backendState: BackendState,
pageStateSetter: StateUpdater<PageStateType>,
): Promise<void> {
- if (typeof backendState === "undefined") {
+ if (backendState?.status === "loggedOut") {
console.log("Page has a problem: no credentials found in the state.");
pageStateSetter((prevState) => ({
...prevState,
@@ -120,7 +121,7 @@ async function createWithdrawalCall(
return;
}
- let res: any;
+ let res: Response;
try {
const { username, password } = backendState;
const headers = prepareHeaders(username, password);
diff --git a/packages/demobank-ui/src/utils.ts b/packages/demobank-ui/src/utils.ts
index 23cade0e8..d74f4d129 100644
--- a/packages/demobank-ui/src/utils.ts
+++ b/packages/demobank-ui/src/utils.ts
@@ -1,9 +1,11 @@
+import { canonicalizeBaseUrl } from "@gnu-taler/taler-util";
+
/**
* Validate (the number part of) an amount. If needed,
* replace comma with a dot. Returns 'false' whenever
* the input is invalid, the valid amount otherwise.
*/
-export function validateAmount(maybeAmount: string): any {
+export function validateAmount(maybeAmount: string | undefined): string | undefined {
const amountRegex = "^[0-9]+(.[0-9]+)?$";
if (!maybeAmount) {
console.log(`Entered amount (${maybeAmount}) mismatched <input> pattern.`);
@@ -15,7 +17,7 @@ export function validateAmount(maybeAmount: string): any {
const re = RegExp(amountRegex);
if (!re.test(maybeAmount)) {
console.log(`Not using invalid amount '${maybeAmount}'.`);
- return false;
+ return;
}
}
return maybeAmount;
@@ -33,18 +35,19 @@ export function getIbanFromPayto(url: string): string {
return iban;
}
+const maybeRootPath = "https://bank.demo.taler.net/demobanks/default/";
+
export function getBankBackendBaseUrl(): string {
const overrideUrl = localStorage.getItem("bank-base-url");
if (overrideUrl) {
console.log(
`using bank base URL ${overrideUrl} (override via bank-base-url localStorage)`,
);
- return overrideUrl;
+ } else {
+ console.log(`using bank base URL (${maybeRootPath})`);
}
- const maybeRootPath = "https://bank.demo.taler.net/demobanks/default/";
- if (!maybeRootPath.endsWith("/")) return `${maybeRootPath}/`;
- console.log(`using bank base URL (${maybeRootPath})`);
- return maybeRootPath;
+ return canonicalizeBaseUrl(overrideUrl ? overrideUrl : maybeRootPath)
+
}
export function undefinedIfEmpty<T extends object>(obj: T): T | undefined {