no-fix: move out useBackend hook
This commit is contained in:
parent
bb0955d89c
commit
54c8d0db31
9
packages/demobank-ui/src/declaration.d.ts
vendored
9
packages/demobank-ui/src/declaration.d.ts
vendored
@ -23,15 +23,6 @@ declare module "jed" {
|
||||
* Type definitions for states and API calls. *
|
||||
*********************************************/
|
||||
|
||||
/**
|
||||
* Has the information to reach and
|
||||
* authenticate at the bank's backend.
|
||||
*/
|
||||
interface BackendStateType {
|
||||
url?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request body of POST /transactions.
|
||||
|
34
packages/demobank-ui/src/hooks/backend.ts
Normal file
34
packages/demobank-ui/src/hooks/backend.ts
Normal file
@ -0,0 +1,34 @@
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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];
|
||||
}
|
@ -15,26 +15,26 @@
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { createContext, Fragment, h, VNode } from "preact";
|
||||
import { h, Fragment, VNode } from "preact";
|
||||
import useSWR, { SWRConfig, useSWRConfig } from "swr";
|
||||
|
||||
import {
|
||||
StateUpdater,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "preact/hooks";
|
||||
import talerLogo from "../../assets/logo-white.svg";
|
||||
import { LangSelectorLikePy as LangSelector } from "../../components/menu/LangSelector.js";
|
||||
import { useTranslationContext } from "../../context/translation.js";
|
||||
import { Amounts, HttpStatusCode, parsePaytoUri } from "@gnu-taler/taler-util";
|
||||
import { hooks } from "@gnu-taler/web-util/lib/index.browser";
|
||||
import { createHashHistory } from "history";
|
||||
import Router, { Route, route } from "preact-router";
|
||||
import { QrCodeSection } from "./QrCodeSection.js";
|
||||
import { hooks } from "@gnu-taler/web-util/lib/index.browser";
|
||||
import { bankUiSettings } from "../../settings.js";
|
||||
import { StateUpdater, useEffect, useRef, useState } from "preact/hooks";
|
||||
import talerLogo from "../../assets/logo-white.svg";
|
||||
import { LangSelectorLikePy as LangSelector } from "../../components/menu/LangSelector.js";
|
||||
import { PageStateType, usePageContext } from "../../context/pageState.js";
|
||||
import { useTranslationContext } from "../../context/translation.js";
|
||||
import { BackendStateType, useBackendState } from "../../hooks/backend.js";
|
||||
import { bankUiSettings } from "../../settings.js";
|
||||
import { QrCodeSection } from "./QrCodeSection.js";
|
||||
import {
|
||||
getBankBackendBaseUrl,
|
||||
getIbanFromPayto,
|
||||
validateAmount,
|
||||
} from "../../utils.js";
|
||||
|
||||
/**
|
||||
* FIXME:
|
||||
@ -53,22 +53,6 @@ import { PageStateType, usePageContext } from "../../context/pageState.js";
|
||||
* - Many strings need to be i18n-wrapped.
|
||||
*/
|
||||
|
||||
/***********
|
||||
* Globals *
|
||||
**********/
|
||||
|
||||
/************
|
||||
* Contexts *
|
||||
***********/
|
||||
|
||||
/**
|
||||
* Bank account specific information.
|
||||
*/
|
||||
// interface AccountStateType {
|
||||
// balance: string;
|
||||
// /* FIXME: Need history here. */
|
||||
// }
|
||||
|
||||
/************
|
||||
* Helpers. *
|
||||
***********/
|
||||
@ -91,57 +75,11 @@ function goPublicAccounts(pageStateSetter: StateUpdater<PageStateType>) {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
function validateAmount(maybeAmount: string): any {
|
||||
const amountRegex = "^[0-9]+(.[0-9]+)?$";
|
||||
if (!maybeAmount) {
|
||||
console.log(`Entered amount (${maybeAmount}) mismatched <input> pattern.`);
|
||||
return;
|
||||
}
|
||||
if (typeof maybeAmount !== "undefined" || maybeAmount !== "") {
|
||||
console.log(`Maybe valid amount: ${maybeAmount}`);
|
||||
// tolerating comma instead of point.
|
||||
const re = RegExp(amountRegex);
|
||||
if (!re.test(maybeAmount)) {
|
||||
console.log(`Not using invalid amount '${maybeAmount}'.`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return maybeAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract IBAN from a Payto URI.
|
||||
*/
|
||||
function getIbanFromPayto(url: string): string {
|
||||
const pathSplit = new URL(url).pathname.split("/");
|
||||
let lastIndex = pathSplit.length - 1;
|
||||
// Happens if the path ends with "/".
|
||||
if (pathSplit[lastIndex] === "") lastIndex--;
|
||||
const iban = pathSplit[lastIndex];
|
||||
return iban;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract value and currency from a $currency:x.y amount.
|
||||
*/
|
||||
// function parseAmount(val: string): Amount {
|
||||
// Amounts.parse(val)
|
||||
// const format = /^[A-Z]+:[0-9]+(\.[0-9]+)?$/;
|
||||
// if (!format.test(val)) throw Error(`Backend gave invalid amount: ${val}.`);
|
||||
// const amountSplit = val.split(":");
|
||||
// return { value: amountSplit[1], currency: amountSplit[0] };
|
||||
// }
|
||||
|
||||
/**
|
||||
* Get username from the backend state, and throw
|
||||
* exception if not found.
|
||||
*/
|
||||
function getUsername(backendState: BackendStateTypeOpt): string {
|
||||
function getUsername(backendState: BackendStateType | undefined): string {
|
||||
if (typeof backendState === "undefined")
|
||||
throw Error("Username can't be found in a undefined backend state.");
|
||||
|
||||
@ -158,7 +96,7 @@ function getUsername(backendState: BackendStateTypeOpt): string {
|
||||
*/
|
||||
async function postToBackend(
|
||||
uri: string,
|
||||
backendState: BackendStateTypeOpt,
|
||||
backendState: BackendStateType | undefined,
|
||||
body: string,
|
||||
): Promise<any> {
|
||||
if (typeof backendState === "undefined")
|
||||
@ -203,20 +141,6 @@ function prepareHeaders(username?: string, password?: string): Headers {
|
||||
return headers;
|
||||
}
|
||||
|
||||
const 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;
|
||||
}
|
||||
const maybeRootPath = "https://bank.demo.taler.net/demobanks/default/";
|
||||
if (!maybeRootPath.endsWith("/")) return `${maybeRootPath}/`;
|
||||
console.log(`using bank base URL (${maybeRootPath})`);
|
||||
return maybeRootPath;
|
||||
};
|
||||
|
||||
/*******************
|
||||
* State managers. *
|
||||
******************/
|
||||
@ -315,27 +239,6 @@ function useCredentialsRequestType(
|
||||
return [retObj, retSetter];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return getters and setters for
|
||||
* login credentials and backend's
|
||||
* base URL.
|
||||
*/
|
||||
type BackendStateTypeOpt = BackendStateType | undefined;
|
||||
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];
|
||||
}
|
||||
|
||||
/**
|
||||
* Request preparators.
|
||||
*
|
||||
@ -356,7 +259,7 @@ function useBackendState(
|
||||
* Abort a withdrawal operation via the Access API's /abort.
|
||||
*/
|
||||
async function abortWithdrawalCall(
|
||||
backendState: BackendStateTypeOpt,
|
||||
backendState: BackendStateType | undefined,
|
||||
withdrawalId: string | undefined,
|
||||
pageStateSetter: StateUpdater<PageStateType>,
|
||||
): Promise<void> {
|
||||
@ -455,7 +358,7 @@ async function abortWithdrawalCall(
|
||||
* 'page state' and let the related components refresh.
|
||||
*/
|
||||
async function confirmWithdrawalCall(
|
||||
backendState: BackendStateTypeOpt,
|
||||
backendState: BackendStateType | undefined,
|
||||
withdrawalId: string | undefined,
|
||||
pageStateSetter: StateUpdater<PageStateType>,
|
||||
): Promise<void> {
|
||||
@ -554,7 +457,7 @@ async function confirmWithdrawalCall(
|
||||
*/
|
||||
async function createTransactionCall(
|
||||
req: TransactionRequestType,
|
||||
backendState: BackendStateTypeOpt,
|
||||
backendState: BackendStateType | undefined,
|
||||
pageStateSetter: StateUpdater<PageStateType>,
|
||||
/**
|
||||
* Optional since the raw payto form doesn't have
|
||||
@ -623,9 +526,9 @@ async function createTransactionCall(
|
||||
* the user about the operation's outcome. (2) use POST helper. */
|
||||
async function createWithdrawalCall(
|
||||
amount: string,
|
||||
backendState: BackendStateTypeOpt,
|
||||
backendState: BackendStateType | undefined,
|
||||
pageStateSetter: StateUpdater<PageStateType>,
|
||||
) {
|
||||
): Promise<void> {
|
||||
if (typeof backendState === "undefined") {
|
||||
console.log("Page has a problem: no credentials found in the state.");
|
||||
pageStateSetter((prevState) => ({
|
||||
@ -699,9 +602,9 @@ async function loginCall(
|
||||
* FIXME: figure out if the two following
|
||||
* functions can be retrieved from the state.
|
||||
*/
|
||||
backendStateSetter: StateUpdater<BackendStateTypeOpt>,
|
||||
backendStateSetter: StateUpdater<BackendStateType | undefined>,
|
||||
pageStateSetter: StateUpdater<PageStateType>,
|
||||
) {
|
||||
): Promise<void> {
|
||||
/**
|
||||
* Optimistically setting the state as 'logged in', and
|
||||
* let the Account component request the balance to check
|
||||
@ -732,9 +635,9 @@ async function registrationCall(
|
||||
* functions can be retrieved somewhat from
|
||||
* the state.
|
||||
*/
|
||||
backendStateSetter: StateUpdater<BackendStateTypeOpt>,
|
||||
backendStateSetter: StateUpdater<BackendStateType | undefined>,
|
||||
pageStateSetter: StateUpdater<PageStateType>,
|
||||
) {
|
||||
): Promise<void> {
|
||||
let baseUrl = getBankBackendBaseUrl();
|
||||
/**
|
||||
* If the base URL doesn't end with slash and the path
|
||||
@ -1718,8 +1621,10 @@ function LoginForm(Props: any): VNode {
|
||||
/**
|
||||
* Collect and submit registration data.
|
||||
*/
|
||||
function RegistrationForm(Props: any): VNode {
|
||||
function RegistrationForm(): VNode {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
|
||||
const [backendState, backendStateSetter] = useBackendState();
|
||||
const { pageState, pageStateSetter } = usePageContext();
|
||||
const [submitData, submitDataSetter] = useCredentialsRequestType();
|
||||
const { i18n } = useTranslationContext();
|
||||
@ -1823,7 +1728,7 @@ function RegistrationForm(Props: any): VNode {
|
||||
if (!submitData) return;
|
||||
registrationCall(
|
||||
{ ...submitData },
|
||||
Props.backendStateSetter, // will store BE URL, if OK.
|
||||
backendStateSetter, // will store BE URL, if OK.
|
||||
pageStateSetter,
|
||||
);
|
||||
console.log("Clearing the input data");
|
||||
@ -2278,7 +2183,6 @@ function PublicHistoriesPage(): VNode {
|
||||
}
|
||||
|
||||
function RegistrationPage(): VNode {
|
||||
const [backendState, backendStateSetter] = useBackendState();
|
||||
const { i18n } = useTranslationContext();
|
||||
if (!bankUiSettings.allowRegistrations) {
|
||||
return (
|
||||
@ -2289,7 +2193,7 @@ function RegistrationPage(): VNode {
|
||||
}
|
||||
return (
|
||||
<BankFrame>
|
||||
<RegistrationForm backendStateSetter={backendStateSetter} />
|
||||
<RegistrationForm />
|
||||
</BankFrame>
|
||||
);
|
||||
}
|
||||
|
49
packages/demobank-ui/src/utils.ts
Normal file
49
packages/demobank-ui/src/utils.ts
Normal file
@ -0,0 +1,49 @@
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
const amountRegex = "^[0-9]+(.[0-9]+)?$";
|
||||
if (!maybeAmount) {
|
||||
console.log(`Entered amount (${maybeAmount}) mismatched <input> pattern.`);
|
||||
return;
|
||||
}
|
||||
if (typeof maybeAmount !== "undefined" || maybeAmount !== "") {
|
||||
console.log(`Maybe valid amount: ${maybeAmount}`);
|
||||
// tolerating comma instead of point.
|
||||
const re = RegExp(amountRegex);
|
||||
if (!re.test(maybeAmount)) {
|
||||
console.log(`Not using invalid amount '${maybeAmount}'.`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return maybeAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract IBAN from a Payto URI.
|
||||
*/
|
||||
export function getIbanFromPayto(url: string): string {
|
||||
const pathSplit = new URL(url).pathname.split("/");
|
||||
let lastIndex = pathSplit.length - 1;
|
||||
// Happens if the path ends with "/".
|
||||
if (pathSplit[lastIndex] === "") lastIndex--;
|
||||
const iban = pathSplit[lastIndex];
|
||||
return iban;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
const maybeRootPath = "https://bank.demo.taler.net/demobanks/default/";
|
||||
if (!maybeRootPath.endsWith("/")) return `${maybeRootPath}/`;
|
||||
console.log(`using bank base URL (${maybeRootPath})`);
|
||||
return maybeRootPath;
|
||||
}
|
@ -3,16 +3,20 @@
|
||||
/* Basic Options */
|
||||
"target": "ES5",
|
||||
"module": "ES6",
|
||||
"lib": ["DOM", "ES2016"],
|
||||
"lib": [
|
||||
"DOM",
|
||||
"ES2016"
|
||||
],
|
||||
"allowJs": true /* Allow javascript files to be compiled. */,
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
"jsx": "react-jsx" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
|
||||
"jsxImportSource": "preact",
|
||||
"jsxFactory": "h",
|
||||
"jsxFragmentFactory": "Fragment",
|
||||
"noEmit": true /* Do not emit outputs. */,
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
|
||||
@ -21,7 +25,6 @@
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
"moduleResolution": "Node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
|
||||
"esModuleInterop": true /* */,
|
||||
@ -32,19 +35,18 @@
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
"allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
|
||||
/* Advanced Options */
|
||||
"skipLibCheck": true /* Skip type checking of declaration files. */
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user