no-fix: remove 'any' and login status is taken from backend
This commit is contained in:
parent
9112655ef5
commit
d2554bedf3
@ -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>
|
||||
);
|
||||
|
52
packages/demobank-ui/src/context/backend.ts
Normal file
52
packages/demobank-ui/src/context/backend.ts
Normal file
@ -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,
|
||||
});
|
||||
};
|
@ -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?: {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
@ -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 = [];
|
||||
|
||||
/**
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 />;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user