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 { h, FunctionalComponent } from "preact";
|
||||||
|
import { BackendStateProvider } from "../context/backend.js";
|
||||||
import { PageStateProvider } from "../context/pageState.js";
|
import { PageStateProvider } from "../context/pageState.js";
|
||||||
import { TranslationProvider } from "../context/translation.js";
|
import { TranslationProvider } from "../context/translation.js";
|
||||||
import { Routing } from "../pages/Routing.js";
|
import { Routing } from "../pages/Routing.js";
|
||||||
@ -24,7 +25,9 @@ const App: FunctionalComponent = () => {
|
|||||||
return (
|
return (
|
||||||
<TranslationProvider>
|
<TranslationProvider>
|
||||||
<PageStateProvider>
|
<PageStateProvider>
|
||||||
<Routing />
|
<BackendStateProvider>
|
||||||
|
<Routing />
|
||||||
|
</BackendStateProvider>
|
||||||
</PageStateProvider>
|
</PageStateProvider>
|
||||||
</TranslationProvider>
|
</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 = {
|
const initial: Type = {
|
||||||
pageState: {
|
pageState: {
|
||||||
isLoggedIn: false,
|
|
||||||
isRawPayto: false,
|
isRawPayto: false,
|
||||||
withdrawalInProgress: false,
|
withdrawalInProgress: false,
|
||||||
},
|
},
|
||||||
@ -59,7 +58,6 @@ export const PageStateProvider = ({
|
|||||||
*/
|
*/
|
||||||
function usePageState(
|
function usePageState(
|
||||||
state: PageStateType = {
|
state: PageStateType = {
|
||||||
isLoggedIn: false,
|
|
||||||
isRawPayto: false,
|
isRawPayto: false,
|
||||||
withdrawalInProgress: false,
|
withdrawalInProgress: false,
|
||||||
},
|
},
|
||||||
@ -98,7 +96,6 @@ function usePageState(
|
|||||||
* Track page state.
|
* Track page state.
|
||||||
*/
|
*/
|
||||||
export interface PageStateType {
|
export interface PageStateType {
|
||||||
isLoggedIn: boolean;
|
|
||||||
isRawPayto: boolean;
|
isRawPayto: boolean;
|
||||||
withdrawalInProgress: boolean;
|
withdrawalInProgress: boolean;
|
||||||
error?: {
|
error?: {
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { i18n, setupI18n } from "@gnu-taler/taler-util";
|
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 { useContext, useEffect } from "preact/hooks";
|
||||||
import { hooks } from "@gnu-taler/web-util/lib/index.browser";
|
import { hooks } from "@gnu-taler/web-util/lib/index.browser";
|
||||||
import { strings } from "../i18n/strings.js";
|
import { strings } from "../i18n/strings.js";
|
||||||
@ -60,7 +60,7 @@ const Context = createContext<Type>(initial);
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
initial?: string;
|
initial?: string;
|
||||||
children: any;
|
children: ComponentChildren;
|
||||||
forceLang?: string;
|
forceLang?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,33 +1,55 @@
|
|||||||
import { hooks } from "@gnu-taler/web-util/lib/index.browser";
|
import { hooks } from "@gnu-taler/web-util/lib/index.browser";
|
||||||
import { StateUpdater } from "preact/hooks";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Has the information to reach and
|
* Has the information to reach and
|
||||||
* authenticate at the bank's backend.
|
* authenticate at the bank's backend.
|
||||||
*/
|
*/
|
||||||
export interface BackendStateType {
|
export type BackendState = LoggedIn | LoggedOut
|
||||||
url?: string;
|
|
||||||
username?: string;
|
export interface BackendInfo {
|
||||||
password?: string;
|
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
|
* Return getters and setters for
|
||||||
* login credentials and backend's
|
* login credentials and backend's
|
||||||
* base URL.
|
* base URL.
|
||||||
*/
|
*/
|
||||||
type BackendStateTypeOpt = BackendStateType | undefined;
|
export function useBackendState(): BackendStateHandler {
|
||||||
export function useBackendState(
|
const [value, update] = hooks.useLocalStorage("backend-state", JSON.stringify(defaultState));
|
||||||
state?: BackendStateType,
|
// const parsed = value !== undefined ? JSON.parse(value) : value;
|
||||||
): [BackendStateTypeOpt, StateUpdater<BackendStateTypeOpt>] {
|
let parsed
|
||||||
const ret = hooks.useLocalStorage("backend-state", JSON.stringify(state));
|
try {
|
||||||
const retObj: BackendStateTypeOpt = ret[0] ? JSON.parse(ret[0]) : ret[0];
|
parsed = JSON.parse(value!)
|
||||||
const retSetter: StateUpdater<BackendStateTypeOpt> = function (val) {
|
} catch {
|
||||||
const newVal =
|
parsed = undefined
|
||||||
val instanceof Function
|
}
|
||||||
? JSON.stringify(val(retObj))
|
const state: BackendState = !parsed?.status ? defaultState : parsed
|
||||||
: JSON.stringify(val);
|
|
||||||
ret[1](newVal);
|
return {
|
||||||
};
|
state,
|
||||||
return [retObj, retSetter];
|
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 { Amounts, HttpStatusCode } 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 { h, Fragment, VNode } from "preact";
|
import { ComponentChildren, Fragment, h, VNode } from "preact";
|
||||||
import { StateUpdater, useEffect, useState } from "preact/hooks";
|
import { StateUpdater, useEffect } from "preact/hooks";
|
||||||
import useSWR, { SWRConfig, useSWRConfig } from "swr";
|
import useSWR, { SWRConfig, useSWRConfig } from "swr";
|
||||||
|
import { useBackendContext } from "../../context/backend.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 { useBackendState } from "../../hooks/backend.js";
|
import { BackendInfo } from "../../hooks/backend.js";
|
||||||
import { bankUiSettings } from "../../settings.js";
|
import { bankUiSettings } from "../../settings.js";
|
||||||
import { getIbanFromPayto } from "../../utils.js";
|
import { getIbanFromPayto, prepareHeaders } from "../../utils.js";
|
||||||
import { BankFrame } from "./BankFrame.js";
|
import { BankFrame } from "./BankFrame.js";
|
||||||
import { LoginForm } from "./LoginForm.js";
|
import { LoginForm } from "./LoginForm.js";
|
||||||
import { PaymentOptions } from "./PaymentOptions.js";
|
import { PaymentOptions } from "./PaymentOptions.js";
|
||||||
@ -31,11 +32,10 @@ import { TalerWithdrawalQRCode } from "./TalerWithdrawalQRCode.js";
|
|||||||
import { Transactions } from "./Transactions.js";
|
import { Transactions } from "./Transactions.js";
|
||||||
|
|
||||||
export function AccountPage(): VNode {
|
export function AccountPage(): VNode {
|
||||||
const [backendState, backendStateSetter] = useBackendState();
|
const backend = useBackendContext();
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
const { pageState, pageStateSetter } = usePageContext();
|
|
||||||
|
|
||||||
if (!pageState.isLoggedIn) {
|
if (backend.state.status === "loggedOut") {
|
||||||
return (
|
return (
|
||||||
<BankFrame>
|
<BankFrame>
|
||||||
<h1 class="nav">{i18n.str`Welcome to ${bankUiSettings.bankName}!`}</h1>
|
<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 (
|
return (
|
||||||
<SWRWithCredentials
|
<SWRWithCredentials info={backend.state}>
|
||||||
username={backendState.username}
|
<Account accountLabel={backend.state.username} />
|
||||||
password={backendState.password}
|
|
||||||
backendUrl={backendState.url}
|
|
||||||
>
|
|
||||||
<Account
|
|
||||||
accountLabel={backendState.username}
|
|
||||||
backendState={backendState}
|
|
||||||
/>
|
|
||||||
</SWRWithCredentials>
|
</SWRWithCredentials>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -73,16 +54,20 @@ export function AccountPage(): VNode {
|
|||||||
/**
|
/**
|
||||||
* Factor out login credentials.
|
* Factor out login credentials.
|
||||||
*/
|
*/
|
||||||
function SWRWithCredentials(props: any): VNode {
|
function SWRWithCredentials({
|
||||||
const { username, password, backendUrl } = props;
|
children,
|
||||||
const headers = new Headers();
|
info,
|
||||||
headers.append("Authorization", `Basic ${btoa(`${username}:${password}`)}`);
|
}: {
|
||||||
console.log("Likely backend base URL", backendUrl);
|
children: ComponentChildren;
|
||||||
|
info: BackendInfo;
|
||||||
|
}): VNode {
|
||||||
|
const { username, password, url: backendUrl } = info;
|
||||||
|
const headers = prepareHeaders(username, password);
|
||||||
return (
|
return (
|
||||||
<SWRConfig
|
<SWRConfig
|
||||||
value={{
|
value={{
|
||||||
fetcher: (url: string) => {
|
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() };
|
if (!r.ok) throw { status: r.status, json: r.json() };
|
||||||
|
|
||||||
return r.json();
|
return r.json();
|
||||||
@ -90,7 +75,7 @@ function SWRWithCredentials(props: any): VNode {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.children}
|
{children as any}
|
||||||
</SWRConfig>
|
</SWRConfig>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -100,9 +85,9 @@ function SWRWithCredentials(props: any): VNode {
|
|||||||
* is mostly needed to provide the user's credentials to POST
|
* is mostly needed to provide the user's credentials to POST
|
||||||
* to the bank.
|
* to the bank.
|
||||||
*/
|
*/
|
||||||
function Account(Props: any): VNode {
|
function Account({ accountLabel }: { accountLabel: string }): VNode {
|
||||||
const { cache } = useSWRConfig();
|
const { cache } = useSWRConfig();
|
||||||
const { accountLabel, backendState } = Props;
|
|
||||||
// Getting the bank account balance:
|
// Getting the bank account balance:
|
||||||
const endpoint = `access-api/accounts/${accountLabel}`;
|
const endpoint = `access-api/accounts/${accountLabel}`;
|
||||||
const { data, error, mutate } = useSWR(endpoint, {
|
const { data, error, mutate } = useSWR(endpoint, {
|
||||||
@ -112,14 +97,9 @@ function Account(Props: any): VNode {
|
|||||||
// revalidateOnFocus: false,
|
// revalidateOnFocus: false,
|
||||||
// revalidateOnReconnect: false,
|
// revalidateOnReconnect: false,
|
||||||
});
|
});
|
||||||
|
const backend = useBackendContext();
|
||||||
const { pageState, pageStateSetter: setPageState } = usePageContext();
|
const { pageState, pageStateSetter: setPageState } = usePageContext();
|
||||||
const {
|
const { withdrawalId, talerWithdrawUri, timestamp } = pageState;
|
||||||
withdrawalInProgress,
|
|
||||||
withdrawalId,
|
|
||||||
isLoggedIn,
|
|
||||||
talerWithdrawUri,
|
|
||||||
timestamp,
|
|
||||||
} = pageState;
|
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
mutate();
|
mutate();
|
||||||
@ -129,10 +109,11 @@ function Account(Props: any): VNode {
|
|||||||
* This part shows a list of transactions: with 5 elements by
|
* This part shows a list of transactions: with 5 elements by
|
||||||
* default and offers a "load more" button.
|
* default and offers a "load more" button.
|
||||||
*/
|
*/
|
||||||
const [txPageNumber, setTxPageNumber] = useTransactionPageNumber();
|
// const [txPageNumber, setTxPageNumber] = useTransactionPageNumber();
|
||||||
const txsPages = [];
|
// const txsPages = [];
|
||||||
for (let i = 0; i <= txPageNumber; i++)
|
// for (let i = 0; i <= txPageNumber; i++) {
|
||||||
txsPages.push(<Transactions accountLabel={accountLabel} pageNumber={i} />);
|
// txsPages.push(<Transactions accountLabel={accountLabel} pageNumber={i} />);
|
||||||
|
// }
|
||||||
|
|
||||||
if (typeof error !== "undefined") {
|
if (typeof error !== "undefined") {
|
||||||
console.log("account error", error, endpoint);
|
console.log("account error", error, endpoint);
|
||||||
@ -143,10 +124,10 @@ function Account(Props: any): VNode {
|
|||||||
*/
|
*/
|
||||||
switch (error.status) {
|
switch (error.status) {
|
||||||
case 404: {
|
case 404: {
|
||||||
|
backend.clear();
|
||||||
setPageState((prevState: PageStateType) => ({
|
setPageState((prevState: PageStateType) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
|
|
||||||
isLoggedIn: false,
|
|
||||||
error: {
|
error: {
|
||||||
title: i18n.str`Username or account label '${accountLabel}' not found. Won't login.`,
|
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.Unauthorized:
|
||||||
case HttpStatusCode.Forbidden: {
|
case HttpStatusCode.Forbidden: {
|
||||||
|
backend.clear();
|
||||||
setPageState((prevState: PageStateType) => ({
|
setPageState((prevState: PageStateType) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
|
|
||||||
isLoggedIn: false,
|
|
||||||
error: {
|
error: {
|
||||||
title: i18n.str`Wrong credentials given.`,
|
title: i18n.str`Wrong credentials given.`,
|
||||||
},
|
},
|
||||||
@ -181,10 +161,9 @@ function Account(Props: any): VNode {
|
|||||||
return <p>Wrong credentials...</p>;
|
return <p>Wrong credentials...</p>;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
backend.clear();
|
||||||
setPageState((prevState: PageStateType) => ({
|
setPageState((prevState: PageStateType) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
|
|
||||||
isLoggedIn: false,
|
|
||||||
error: {
|
error: {
|
||||||
title: i18n.str`Account information could not be retrieved.`,
|
title: i18n.str`Account information could not be retrieved.`,
|
||||||
debug: JSON.stringify(error),
|
debug: JSON.stringify(error),
|
||||||
@ -211,13 +190,11 @@ function Account(Props: any): VNode {
|
|||||||
* the outcome.
|
* the outcome.
|
||||||
*/
|
*/
|
||||||
console.log(`maybe new withdrawal ${talerWithdrawUri}`);
|
console.log(`maybe new withdrawal ${talerWithdrawUri}`);
|
||||||
if (talerWithdrawUri) {
|
if (talerWithdrawUri && withdrawalId) {
|
||||||
console.log("Bank created a new Taler withdrawal");
|
console.log("Bank created a new Taler withdrawal");
|
||||||
return (
|
return (
|
||||||
<BankFrame>
|
<BankFrame>
|
||||||
<TalerWithdrawalQRCode
|
<TalerWithdrawalQRCode
|
||||||
accountLabel={accountLabel}
|
|
||||||
backendState={backendState}
|
|
||||||
withdrawalId={withdrawalId}
|
withdrawalId={withdrawalId}
|
||||||
talerWithdrawUri={talerWithdrawUri}
|
talerWithdrawUri={talerWithdrawUri}
|
||||||
/>
|
/>
|
||||||
@ -266,7 +243,7 @@ function Account(Props: any): VNode {
|
|||||||
<h2>{i18n.str`Latest transactions:`}</h2>
|
<h2>{i18n.str`Latest transactions:`}</h2>
|
||||||
<Transactions
|
<Transactions
|
||||||
balanceValue={balanceValue}
|
balanceValue={balanceValue}
|
||||||
pageNumber="0"
|
pageNumber={0}
|
||||||
accountLabel={accountLabel}
|
accountLabel={accountLabel}
|
||||||
/>
|
/>
|
||||||
</article>
|
</article>
|
||||||
|
@ -14,15 +14,21 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
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 talerLogo from "../../assets/logo-white.svg";
|
||||||
import { LangSelectorLikePy as LangSelector } from "../../components/menu/LangSelector.js";
|
import { LangSelectorLikePy as LangSelector } from "../../components/menu/LangSelector.js";
|
||||||
|
import { useBackendContext } from "../../context/backend.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 { bankUiSettings } from "../../settings.js";
|
import { bankUiSettings } from "../../settings.js";
|
||||||
|
|
||||||
export function BankFrame(Props: any): VNode {
|
export function BankFrame({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: ComponentChildren;
|
||||||
|
}): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
|
const backend = useBackendContext();
|
||||||
const { pageState, pageStateSetter } = usePageContext();
|
const { pageState, pageStateSetter } = usePageContext();
|
||||||
console.log("BankFrame state", pageState);
|
console.log("BankFrame state", pageState);
|
||||||
const logOut = (
|
const logOut = (
|
||||||
@ -33,9 +39,9 @@ export function BankFrame(Props: any): VNode {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
pageStateSetter((prevState: PageStateType) => {
|
pageStateSetter((prevState: PageStateType) => {
|
||||||
const { talerWithdrawUri, withdrawalId, ...rest } = prevState;
|
const { talerWithdrawUri, withdrawalId, ...rest } = prevState;
|
||||||
|
backend.clear();
|
||||||
return {
|
return {
|
||||||
...rest,
|
...rest,
|
||||||
isLoggedIn: false,
|
|
||||||
withdrawalInProgress: false,
|
withdrawalInProgress: false,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
info: undefined,
|
info: undefined,
|
||||||
@ -98,10 +104,10 @@ export function BankFrame(Props: any): VNode {
|
|||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<section id="main" class="content">
|
<section id="main" class="content">
|
||||||
<ErrorBanner pageState={[pageState, pageStateSetter]} />
|
<ErrorBanner />
|
||||||
<StatusBanner pageState={[pageState, pageStateSetter]} />
|
<StatusBanner />
|
||||||
{pageState.isLoggedIn ? logOut : null}
|
{backend.state.status === "loggedIn" ? logOut : null}
|
||||||
{Props.children}
|
{children}
|
||||||
</section>
|
</section>
|
||||||
<section id="footer" class="footer">
|
<section id="footer" class="footer">
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
@ -127,9 +133,9 @@ function maybeDemoContent(content: VNode): VNode {
|
|||||||
return <Fragment />;
|
return <Fragment />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ErrorBanner(Props: any): VNode | null {
|
function ErrorBanner(): VNode | null {
|
||||||
const [pageState, pageStateSetter] = Props.pageState;
|
const { pageState, pageStateSetter } = usePageContext();
|
||||||
// const { i18n } = useTranslationContext();
|
|
||||||
if (!pageState.error) return null;
|
if (!pageState.error) return null;
|
||||||
|
|
||||||
const rval = (
|
const rval = (
|
||||||
@ -144,7 +150,7 @@ function ErrorBanner(Props: any): VNode | null {
|
|||||||
class="pure-button"
|
class="pure-button"
|
||||||
value="Clear"
|
value="Clear"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
pageStateSetter((prev: any) => ({ ...prev, error: undefined }));
|
pageStateSetter((prev) => ({ ...prev, error: undefined }));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -156,8 +162,8 @@ function ErrorBanner(Props: any): VNode | null {
|
|||||||
return rval;
|
return rval;
|
||||||
}
|
}
|
||||||
|
|
||||||
function StatusBanner(Props: any): VNode | null {
|
function StatusBanner(): VNode | null {
|
||||||
const [pageState, pageStateSetter] = Props.pageState;
|
const { pageState, pageStateSetter } = usePageContext();
|
||||||
if (!pageState.info) return null;
|
if (!pageState.info) return null;
|
||||||
|
|
||||||
const rval = (
|
const rval = (
|
||||||
@ -172,7 +178,7 @@ function StatusBanner(Props: any): VNode | null {
|
|||||||
class="pure-button"
|
class="pure-button"
|
||||||
value="Clear"
|
value="Clear"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
pageStateSetter((prev: any) => ({ ...prev, info: undefined }));
|
pageStateSetter((prev) => ({ ...prev, info: undefined }));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,10 +16,10 @@
|
|||||||
|
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { route } from "preact-router";
|
import { route } from "preact-router";
|
||||||
import { StateUpdater, useEffect, useRef, useState } from "preact/hooks";
|
import { useEffect, useRef, useState } from "preact/hooks";
|
||||||
import { PageStateType, usePageContext } from "../../context/pageState.js";
|
import { useBackendContext } from "../../context/backend.js";
|
||||||
import { useTranslationContext } from "../../context/translation.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 { bankUiSettings } from "../../settings.js";
|
||||||
import { getBankBackendBaseUrl, undefinedIfEmpty } from "../../utils.js";
|
import { getBankBackendBaseUrl, undefinedIfEmpty } from "../../utils.js";
|
||||||
import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
|
import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
|
||||||
@ -28,8 +28,7 @@ import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
|
|||||||
* Collect and submit login data.
|
* Collect and submit login data.
|
||||||
*/
|
*/
|
||||||
export function LoginForm(): VNode {
|
export function LoginForm(): VNode {
|
||||||
const [backendState, backendStateSetter] = useBackendState();
|
const backend = useBackendContext();
|
||||||
const { pageState, pageStateSetter } = usePageContext();
|
|
||||||
const [username, setUsername] = useState<string | undefined>();
|
const [username, setUsername] = useState<string | undefined>();
|
||||||
const [password, setPassword] = useState<string | undefined>();
|
const [password, setPassword] = useState<string | undefined>();
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
@ -93,11 +92,7 @@ export function LoginForm(): VNode {
|
|||||||
disabled={!!errors}
|
disabled={!!errors}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!username || !password) return;
|
if (!username || !password) return;
|
||||||
loginCall(
|
loginCall({ username, password }, backend);
|
||||||
{ username, password },
|
|
||||||
backendStateSetter,
|
|
||||||
pageStateSetter,
|
|
||||||
);
|
|
||||||
setUsername(undefined);
|
setUsername(undefined);
|
||||||
setPassword(undefined);
|
setPassword(undefined);
|
||||||
}}
|
}}
|
||||||
@ -129,21 +124,16 @@ async function loginCall(
|
|||||||
* FIXME: figure out if the two following
|
* FIXME: figure out if the two following
|
||||||
* functions can be retrieved from the state.
|
* functions can be retrieved from the state.
|
||||||
*/
|
*/
|
||||||
backendStateSetter: StateUpdater<BackendStateType | undefined>,
|
backend: BackendStateHandler,
|
||||||
pageStateSetter: StateUpdater<PageStateType>,
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
/**
|
/**
|
||||||
* Optimistically setting the state as 'logged in', and
|
* Optimistically setting the state as 'logged in', and
|
||||||
* let the Account component request the balance to check
|
* let the Account component request the balance to check
|
||||||
* whether the credentials are valid. */
|
* whether the credentials are valid. */
|
||||||
pageStateSetter((prevState) => ({ ...prevState, isLoggedIn: true }));
|
|
||||||
let baseUrl = getBankBackendBaseUrl();
|
|
||||||
if (!baseUrl.endsWith("/")) baseUrl += "/";
|
|
||||||
|
|
||||||
backendStateSetter((prevState) => ({
|
backend.save({
|
||||||
...prevState,
|
url: getBankBackendBaseUrl(),
|
||||||
url: baseUrl,
|
|
||||||
username: req.username,
|
username: req.username,
|
||||||
password: req.password,
|
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 { hooks } from "@gnu-taler/web-util/lib/index.browser";
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { StateUpdater, useEffect, useRef, useState } from "preact/hooks";
|
import { StateUpdater, useEffect, useRef, useState } from "preact/hooks";
|
||||||
|
import { useBackendContext } from "../../context/backend.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 { BackendState } from "../../hooks/backend.js";
|
||||||
import { prepareHeaders, undefinedIfEmpty } from "../../utils.js";
|
import { prepareHeaders, undefinedIfEmpty } from "../../utils.js";
|
||||||
import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
|
import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
|
||||||
|
|
||||||
@ -31,7 +32,7 @@ export function PaytoWireTransferForm({
|
|||||||
focus?: boolean;
|
focus?: boolean;
|
||||||
currency?: string;
|
currency?: string;
|
||||||
}): VNode {
|
}): VNode {
|
||||||
const [backendState, backendStateSetter] = useBackendState();
|
const backend = useBackendContext();
|
||||||
const { pageState, pageStateSetter } = usePageContext(); // NOTE: used for go-back button?
|
const { pageState, pageStateSetter } = usePageContext(); // NOTE: used for go-back button?
|
||||||
|
|
||||||
const [submitData, submitDataSetter] = useWireTransferRequestType();
|
const [submitData, submitDataSetter] = useWireTransferRequestType();
|
||||||
@ -81,7 +82,7 @@ export function PaytoWireTransferForm({
|
|||||||
required
|
required
|
||||||
pattern={ibanRegex}
|
pattern={ibanRegex}
|
||||||
onInput={(e): void => {
|
onInput={(e): void => {
|
||||||
submitDataSetter((submitData: any) => ({
|
submitDataSetter((submitData) => ({
|
||||||
...submitData,
|
...submitData,
|
||||||
iban: e.currentTarget.value,
|
iban: e.currentTarget.value,
|
||||||
}));
|
}));
|
||||||
@ -102,7 +103,7 @@ export function PaytoWireTransferForm({
|
|||||||
value={submitData?.subject ?? ""}
|
value={submitData?.subject ?? ""}
|
||||||
required
|
required
|
||||||
onInput={(e): void => {
|
onInput={(e): void => {
|
||||||
submitDataSetter((submitData: any) => ({
|
submitDataSetter((submitData) => ({
|
||||||
...submitData,
|
...submitData,
|
||||||
subject: e.currentTarget.value,
|
subject: e.currentTarget.value,
|
||||||
}));
|
}));
|
||||||
@ -133,7 +134,7 @@ export function PaytoWireTransferForm({
|
|||||||
required
|
required
|
||||||
value={submitData?.amount ?? ""}
|
value={submitData?.amount ?? ""}
|
||||||
onInput={(e): void => {
|
onInput={(e): void => {
|
||||||
submitDataSetter((submitData: any) => ({
|
submitDataSetter((submitData) => ({
|
||||||
...submitData,
|
...submitData,
|
||||||
amount: e.currentTarget.value,
|
amount: e.currentTarget.value,
|
||||||
}));
|
}));
|
||||||
@ -179,7 +180,7 @@ export function PaytoWireTransferForm({
|
|||||||
};
|
};
|
||||||
return await createTransactionCall(
|
return await createTransactionCall(
|
||||||
transactionData,
|
transactionData,
|
||||||
backendState,
|
backend.state,
|
||||||
pageStateSetter,
|
pageStateSetter,
|
||||||
() =>
|
() =>
|
||||||
submitDataSetter((p) => ({
|
submitDataSetter((p) => ({
|
||||||
@ -209,7 +210,7 @@ export function PaytoWireTransferForm({
|
|||||||
href="/account"
|
href="/account"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
console.log("switch to raw payto form");
|
console.log("switch to raw payto form");
|
||||||
pageStateSetter((prevState: any) => ({
|
pageStateSetter((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
isRawPayto: true,
|
isRawPayto: true,
|
||||||
}));
|
}));
|
||||||
@ -283,7 +284,7 @@ export function PaytoWireTransferForm({
|
|||||||
|
|
||||||
return await createTransactionCall(
|
return await createTransactionCall(
|
||||||
transactionData,
|
transactionData,
|
||||||
backendState,
|
backend.state,
|
||||||
pageStateSetter,
|
pageStateSetter,
|
||||||
() => rawPaytoInputSetter(undefined),
|
() => rawPaytoInputSetter(undefined),
|
||||||
);
|
);
|
||||||
@ -295,7 +296,7 @@ export function PaytoWireTransferForm({
|
|||||||
href="/account"
|
href="/account"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
console.log("switch to wire-transfer-form");
|
console.log("switch to wire-transfer-form");
|
||||||
pageStateSetter((prevState: any) => ({
|
pageStateSetter((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
isRawPayto: false,
|
isRawPayto: false,
|
||||||
}));
|
}));
|
||||||
@ -345,7 +346,7 @@ function useWireTransferRequestType(
|
|||||||
*/
|
*/
|
||||||
async function createTransactionCall(
|
async function createTransactionCall(
|
||||||
req: TransactionRequestType,
|
req: TransactionRequestType,
|
||||||
backendState: BackendStateType | undefined,
|
backendState: BackendState,
|
||||||
pageStateSetter: StateUpdater<PageStateType>,
|
pageStateSetter: StateUpdater<PageStateType>,
|
||||||
/**
|
/**
|
||||||
* Optional since the raw payto form doesn't have
|
* Optional since the raw payto form doesn't have
|
||||||
@ -353,13 +354,30 @@ async function createTransactionCall(
|
|||||||
*/
|
*/
|
||||||
cleanUpForm: () => void,
|
cleanUpForm: () => void,
|
||||||
): Promise<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 {
|
try {
|
||||||
res = await postToBackend(
|
const { username, password } = backendState;
|
||||||
`access-api/accounts/${getUsername(backendState)}/transactions`,
|
const headers = prepareHeaders(username, password);
|
||||||
backendState,
|
const url = new URL(
|
||||||
JSON.stringify(req),
|
`access-api/accounts/${backendState.username}/transactions`,
|
||||||
|
backendState.url,
|
||||||
);
|
);
|
||||||
|
res = await fetch(url.href, {
|
||||||
|
method: "POST",
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify(req),
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Could not POST transaction request to the bank", error);
|
console.log("Could not POST transaction request to the bank", error);
|
||||||
pageStateSetter((prevState) => ({
|
pageStateSetter((prevState) => ({
|
||||||
@ -402,41 +420,3 @@ async function createTransactionCall(
|
|||||||
// be discarded.
|
// be discarded.
|
||||||
cleanUpForm();
|
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 { 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 { route } from "preact-router";
|
||||||
import { StateUpdater } from "preact/hooks";
|
import { StateUpdater } from "preact/hooks";
|
||||||
import useSWR, { SWRConfig } from "swr";
|
import useSWR, { SWRConfig } from "swr";
|
||||||
@ -35,8 +35,13 @@ export function PublicHistoriesPage(): VNode {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SWRWithoutCredentials(Props: any): VNode {
|
function SWRWithoutCredentials({
|
||||||
const { baseUrl } = Props;
|
baseUrl,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: ComponentChildren;
|
||||||
|
baseUrl: string;
|
||||||
|
}): VNode {
|
||||||
console.log("Base URL", baseUrl);
|
console.log("Base URL", baseUrl);
|
||||||
return (
|
return (
|
||||||
<SWRConfig
|
<SWRConfig
|
||||||
@ -49,7 +54,7 @@ function SWRWithoutCredentials(Props: any): VNode {
|
|||||||
}),
|
}),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Props.children}
|
{children as any}
|
||||||
</SWRConfig>
|
</SWRConfig>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -93,7 +98,7 @@ function PublicHistories(): VNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!data) return <p>Waiting public accounts list...</p>;
|
if (!data) return <p>Waiting public accounts list...</p>;
|
||||||
const txs: any = {};
|
const txs: Record<string, h.JSX.Element> = {};
|
||||||
const accountsBar = [];
|
const accountsBar = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,9 +16,10 @@
|
|||||||
import { Fragment, h, VNode } from "preact";
|
import { Fragment, h, VNode } from "preact";
|
||||||
import { route } from "preact-router";
|
import { route } from "preact-router";
|
||||||
import { StateUpdater, useState } from "preact/hooks";
|
import { StateUpdater, useState } from "preact/hooks";
|
||||||
|
import { useBackendContext } from "../../context/backend.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 { BackendStateHandler } from "../../hooks/backend.js";
|
||||||
import { bankUiSettings } from "../../settings.js";
|
import { bankUiSettings } from "../../settings.js";
|
||||||
import { getBankBackendBaseUrl, undefinedIfEmpty } from "../../utils.js";
|
import { getBankBackendBaseUrl, undefinedIfEmpty } from "../../utils.js";
|
||||||
import { BankFrame } from "./BankFrame.js";
|
import { BankFrame } from "./BankFrame.js";
|
||||||
@ -44,7 +45,7 @@ export function RegistrationPage(): VNode {
|
|||||||
* Collect and submit registration data.
|
* Collect and submit registration data.
|
||||||
*/
|
*/
|
||||||
function RegistrationForm(): VNode {
|
function RegistrationForm(): VNode {
|
||||||
const [backendState, backendStateSetter] = useBackendState();
|
const backend = useBackendContext();
|
||||||
const { pageState, pageStateSetter } = usePageContext();
|
const { pageState, pageStateSetter } = usePageContext();
|
||||||
const [username, setUsername] = useState<string | undefined>();
|
const [username, setUsername] = useState<string | undefined>();
|
||||||
const [password, setPassword] = useState<string | undefined>();
|
const [password, setPassword] = useState<string | undefined>();
|
||||||
@ -132,7 +133,7 @@ function RegistrationForm(): VNode {
|
|||||||
if (!username || !password) return;
|
if (!username || !password) return;
|
||||||
registrationCall(
|
registrationCall(
|
||||||
{ username, password },
|
{ username, password },
|
||||||
backendStateSetter, // will store BE URL, if OK.
|
backend, // will store BE URL, if OK.
|
||||||
pageStateSetter,
|
pageStateSetter,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -177,23 +178,17 @@ async function registrationCall(
|
|||||||
* functions can be retrieved somewhat from
|
* functions can be retrieved somewhat from
|
||||||
* the state.
|
* the state.
|
||||||
*/
|
*/
|
||||||
backendStateSetter: StateUpdater<BackendStateType | undefined>,
|
backend: BackendStateHandler,
|
||||||
pageStateSetter: StateUpdater<PageStateType>,
|
pageStateSetter: StateUpdater<PageStateType>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let baseUrl = getBankBackendBaseUrl();
|
const url = 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 headers = new Headers();
|
const headers = new Headers();
|
||||||
headers.append("Content-Type", "application/json");
|
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;
|
let res: Response;
|
||||||
try {
|
try {
|
||||||
res = await fetch(url.href, {
|
res = await fetch(registerEndpoint.href, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
username: req.username,
|
username: req.username,
|
||||||
@ -203,7 +198,7 @@ async function registrationCall(
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(
|
console.log(
|
||||||
`Could not POST new registration to the bank (${url.href})`,
|
`Could not POST new registration to the bank (${registerEndpoint.href})`,
|
||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
pageStateSetter((prevState) => ({
|
pageStateSetter((prevState) => ({
|
||||||
@ -239,16 +234,11 @@ async function registrationCall(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// registration was ok
|
// registration was ok
|
||||||
pageStateSetter((prevState) => ({
|
backend.save({
|
||||||
...prevState,
|
url,
|
||||||
isLoggedIn: true,
|
|
||||||
}));
|
|
||||||
backendStateSetter((prevState) => ({
|
|
||||||
...prevState,
|
|
||||||
url: baseUrl,
|
|
||||||
username: req.username,
|
username: req.username,
|
||||||
password: req.password,
|
password: req.password,
|
||||||
}));
|
});
|
||||||
route("/account");
|
route("/account");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
import { Fragment, h, VNode } from "preact";
|
import { Fragment, h, VNode } from "preact";
|
||||||
import { StateUpdater } from "preact/hooks";
|
import { StateUpdater } from "preact/hooks";
|
||||||
|
import { useBackendContext } from "../../context/backend.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 } from "../../hooks/backend.js";
|
import { BackendState } from "../../hooks/backend.js";
|
||||||
import { prepareHeaders } from "../../utils.js";
|
import { prepareHeaders } from "../../utils.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Additional authentication required to complete the operation.
|
* Additional authentication required to complete the operation.
|
||||||
* Not providing a back button, only abort.
|
* Not providing a back button, only abort.
|
||||||
*/
|
*/
|
||||||
export function TalerWithdrawalConfirmationQuestion(Props: any): VNode {
|
export function TalerWithdrawalConfirmationQuestion(): VNode {
|
||||||
const { pageState, pageStateSetter } = usePageContext();
|
const { pageState, pageStateSetter } = usePageContext();
|
||||||
const { backendState } = Props;
|
const backend = useBackendContext();
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
const captchaNumbers = {
|
const captchaNumbers = {
|
||||||
a: Math.floor(Math.random() * 10),
|
a: Math.floor(Math.random() * 10),
|
||||||
@ -57,7 +58,7 @@ export function TalerWithdrawalConfirmationQuestion(Props: any): VNode {
|
|||||||
(captchaNumbers.a + captchaNumbers.b).toString()
|
(captchaNumbers.a + captchaNumbers.b).toString()
|
||||||
) {
|
) {
|
||||||
confirmWithdrawalCall(
|
confirmWithdrawalCall(
|
||||||
backendState,
|
backend.state,
|
||||||
pageState.withdrawalId,
|
pageState.withdrawalId,
|
||||||
pageStateSetter,
|
pageStateSetter,
|
||||||
);
|
);
|
||||||
@ -79,7 +80,7 @@ export function TalerWithdrawalConfirmationQuestion(Props: any): VNode {
|
|||||||
class="pure-button pure-button-secondary btn-cancel"
|
class="pure-button pure-button-secondary btn-cancel"
|
||||||
onClick={async () =>
|
onClick={async () =>
|
||||||
await abortWithdrawalCall(
|
await abortWithdrawalCall(
|
||||||
backendState,
|
backend.state,
|
||||||
pageState.withdrawalId,
|
pageState.withdrawalId,
|
||||||
pageStateSetter,
|
pageStateSetter,
|
||||||
)
|
)
|
||||||
@ -116,11 +117,11 @@ export function TalerWithdrawalConfirmationQuestion(Props: any): VNode {
|
|||||||
* 'page state' and let the related components refresh.
|
* 'page state' and let the related components refresh.
|
||||||
*/
|
*/
|
||||||
async function confirmWithdrawalCall(
|
async function confirmWithdrawalCall(
|
||||||
backendState: BackendStateType | undefined,
|
backendState: BackendState,
|
||||||
withdrawalId: string | undefined,
|
withdrawalId: string | undefined,
|
||||||
pageStateSetter: StateUpdater<PageStateType>,
|
pageStateSetter: StateUpdater<PageStateType>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (typeof backendState === "undefined") {
|
if (backendState.status === "loggedOut") {
|
||||||
console.log("No credentials found.");
|
console.log("No credentials found.");
|
||||||
pageStateSetter((prevState) => ({
|
pageStateSetter((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
@ -211,11 +212,11 @@ async function confirmWithdrawalCall(
|
|||||||
* Abort a withdrawal operation via the Access API's /abort.
|
* Abort a withdrawal operation via the Access API's /abort.
|
||||||
*/
|
*/
|
||||||
async function abortWithdrawalCall(
|
async function abortWithdrawalCall(
|
||||||
backendState: BackendStateType | undefined,
|
backendState: BackendState,
|
||||||
withdrawalId: string | undefined,
|
withdrawalId: string | undefined,
|
||||||
pageStateSetter: StateUpdater<PageStateType>,
|
pageStateSetter: StateUpdater<PageStateType>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (typeof backendState === "undefined") {
|
if (backendState.status === "loggedOut") {
|
||||||
console.log("No credentials found.");
|
console.log("No credentials found.");
|
||||||
pageStateSetter((prevState) => ({
|
pageStateSetter((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
@ -237,7 +238,7 @@ async function abortWithdrawalCall(
|
|||||||
}));
|
}));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let res: any;
|
let res: Response;
|
||||||
try {
|
try {
|
||||||
const { username, password } = backendState;
|
const { username, password } = backendState;
|
||||||
const headers = prepareHeaders(username, password);
|
const headers = prepareHeaders(username, password);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Fragment, h, VNode } from "preact";
|
import { Fragment, h, VNode } from "preact";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
import { useBackendContext } from "../../context/backend.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 { QrCodeSection } from "./QrCodeSection.js";
|
import { QrCodeSection } from "./QrCodeSection.js";
|
||||||
@ -10,10 +11,15 @@ import { TalerWithdrawalConfirmationQuestion } from "./TalerWithdrawalConfirmati
|
|||||||
* permit the passing of exchange and reserve details to
|
* permit the passing of exchange and reserve details to
|
||||||
* the bank. Poll the backend until such operation is done.
|
* 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:
|
// turns true when the wallet POSTed the reserve details:
|
||||||
const { pageState, pageStateSetter } = usePageContext();
|
const { pageState, pageStateSetter } = usePageContext();
|
||||||
const { withdrawalId, talerWithdrawUri, backendState } = Props;
|
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
const abortButton = (
|
const abortButton = (
|
||||||
<a
|
<a
|
||||||
@ -93,5 +99,5 @@ export function TalerWithdrawalQRCode(Props: any): VNode {
|
|||||||
* Wallet POSTed the withdrawal details! Ask the
|
* Wallet POSTed the withdrawal details! Ask the
|
||||||
* user to authorize the operation (here CAPTCHA).
|
* user to authorize the operation (here CAPTCHA).
|
||||||
*/
|
*/
|
||||||
return <TalerWithdrawalConfirmationQuestion backendState={backendState} />;
|
return <TalerWithdrawalConfirmationQuestion />;
|
||||||
}
|
}
|
||||||
|
@ -10,14 +10,20 @@ export function Transactions({
|
|||||||
pageNumber,
|
pageNumber,
|
||||||
accountLabel,
|
accountLabel,
|
||||||
balanceValue,
|
balanceValue,
|
||||||
}: any): VNode {
|
}: {
|
||||||
|
pageNumber: number;
|
||||||
|
accountLabel: string;
|
||||||
|
balanceValue?: string;
|
||||||
|
}): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
const { data, error, mutate } = useSWR(
|
const { data, error, mutate } = useSWR(
|
||||||
`access-api/accounts/${accountLabel}/transactions?page=${pageNumber}`,
|
`access-api/accounts/${accountLabel}/transactions?page=${pageNumber}`,
|
||||||
);
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
mutate();
|
if (balanceValue) {
|
||||||
}, [balanceValue]);
|
mutate();
|
||||||
|
}
|
||||||
|
}, [balanceValue ?? ""]);
|
||||||
if (typeof error !== "undefined") {
|
if (typeof error !== "undefined") {
|
||||||
console.log("transactions not found error", error);
|
console.log("transactions not found error", error);
|
||||||
switch (error.status) {
|
switch (error.status) {
|
||||||
|
@ -16,9 +16,10 @@
|
|||||||
|
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { StateUpdater, useEffect, useRef } from "preact/hooks";
|
import { StateUpdater, useEffect, useRef } from "preact/hooks";
|
||||||
|
import { useBackendContext } from "../../context/backend.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 { BackendState } from "../../hooks/backend.js";
|
||||||
import { prepareHeaders, validateAmount } from "../../utils.js";
|
import { prepareHeaders, validateAmount } from "../../utils.js";
|
||||||
|
|
||||||
export function WalletWithdrawForm({
|
export function WalletWithdrawForm({
|
||||||
@ -28,10 +29,10 @@ export function WalletWithdrawForm({
|
|||||||
currency?: string;
|
currency?: string;
|
||||||
focus?: boolean;
|
focus?: boolean;
|
||||||
}): VNode {
|
}): VNode {
|
||||||
const [backendState, backendStateSetter] = useBackendState();
|
const backend = useBackendContext();
|
||||||
const { pageState, pageStateSetter } = usePageContext();
|
const { pageState, pageStateSetter } = usePageContext();
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
let submitAmount = "5.00";
|
let submitAmount: string | undefined = "5.00";
|
||||||
|
|
||||||
const ref = useRef<HTMLInputElement>(null);
|
const ref = useRef<HTMLInputElement>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -83,7 +84,7 @@ export function WalletWithdrawForm({
|
|||||||
if (!submitAmount && currency) return;
|
if (!submitAmount && currency) return;
|
||||||
createWithdrawalCall(
|
createWithdrawalCall(
|
||||||
`${currency}:${submitAmount}`,
|
`${currency}:${submitAmount}`,
|
||||||
backendState,
|
backend.state,
|
||||||
pageStateSetter,
|
pageStateSetter,
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
@ -105,10 +106,10 @@ export function WalletWithdrawForm({
|
|||||||
* the user about the operation's outcome. (2) use POST helper. */
|
* the user about the operation's outcome. (2) use POST helper. */
|
||||||
async function createWithdrawalCall(
|
async function createWithdrawalCall(
|
||||||
amount: string,
|
amount: string,
|
||||||
backendState: BackendStateType | undefined,
|
backendState: BackendState,
|
||||||
pageStateSetter: StateUpdater<PageStateType>,
|
pageStateSetter: StateUpdater<PageStateType>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (typeof backendState === "undefined") {
|
if (backendState?.status === "loggedOut") {
|
||||||
console.log("Page has a problem: no credentials found in the state.");
|
console.log("Page has a problem: no credentials found in the state.");
|
||||||
pageStateSetter((prevState) => ({
|
pageStateSetter((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
@ -120,7 +121,7 @@ async function createWithdrawalCall(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let res: any;
|
let res: Response;
|
||||||
try {
|
try {
|
||||||
const { username, password } = backendState;
|
const { username, password } = backendState;
|
||||||
const headers = prepareHeaders(username, password);
|
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,
|
* Validate (the number part of) an amount. If needed,
|
||||||
* replace comma with a dot. Returns 'false' whenever
|
* replace comma with a dot. Returns 'false' whenever
|
||||||
* the input is invalid, the valid amount otherwise.
|
* 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]+)?$";
|
const amountRegex = "^[0-9]+(.[0-9]+)?$";
|
||||||
if (!maybeAmount) {
|
if (!maybeAmount) {
|
||||||
console.log(`Entered amount (${maybeAmount}) mismatched <input> pattern.`);
|
console.log(`Entered amount (${maybeAmount}) mismatched <input> pattern.`);
|
||||||
@ -15,7 +17,7 @@ export function validateAmount(maybeAmount: string): any {
|
|||||||
const re = RegExp(amountRegex);
|
const re = RegExp(amountRegex);
|
||||||
if (!re.test(maybeAmount)) {
|
if (!re.test(maybeAmount)) {
|
||||||
console.log(`Not using invalid amount '${maybeAmount}'.`);
|
console.log(`Not using invalid amount '${maybeAmount}'.`);
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return maybeAmount;
|
return maybeAmount;
|
||||||
@ -33,18 +35,19 @@ export function getIbanFromPayto(url: string): string {
|
|||||||
return iban;
|
return iban;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const maybeRootPath = "https://bank.demo.taler.net/demobanks/default/";
|
||||||
|
|
||||||
export function getBankBackendBaseUrl(): string {
|
export function getBankBackendBaseUrl(): string {
|
||||||
const overrideUrl = localStorage.getItem("bank-base-url");
|
const overrideUrl = localStorage.getItem("bank-base-url");
|
||||||
if (overrideUrl) {
|
if (overrideUrl) {
|
||||||
console.log(
|
console.log(
|
||||||
`using bank base URL ${overrideUrl} (override via bank-base-url localStorage)`,
|
`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/";
|
return canonicalizeBaseUrl(overrideUrl ? overrideUrl : maybeRootPath)
|
||||||
if (!maybeRootPath.endsWith("/")) return `${maybeRootPath}/`;
|
|
||||||
console.log(`using bank base URL (${maybeRootPath})`);
|
|
||||||
return maybeRootPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function undefinedIfEmpty<T extends object>(obj: T): T | undefined {
|
export function undefinedIfEmpty<T extends object>(obj: T): T | undefined {
|
||||||
|
Loading…
Reference in New Issue
Block a user