no-fix: remove 'any' and login status is taken from backend

This commit is contained in:
Sebastian 2022-12-07 15:44:16 -03:00
parent 9112655ef5
commit d2554bedf3
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
16 changed files with 264 additions and 225 deletions

View File

@ -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>
);

View 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,
});
};

View File

@ -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?: {

View File

@ -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;
}

View File

@ -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))
},
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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,
}));
});
}

View File

@ -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,
});
}

View File

@ -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 = [];
/**

View File

@ -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");
}
}

View File

@ -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);

View File

@ -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 />;
}

View File

@ -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) {

View File

@ -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);

View File

@ -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 {