diff options
-rw-r--r-- | packages/demobank-ui/src/declaration.d.ts | 9 | ||||
-rw-r--r-- | packages/demobank-ui/src/hooks/backend.ts | 34 | ||||
-rw-r--r-- | packages/demobank-ui/src/pages/home/index.tsx | 156 | ||||
-rw-r--r-- | packages/demobank-ui/src/utils.ts | 49 | ||||
-rw-r--r-- | packages/demobank-ui/tsconfig.json | 18 |
5 files changed, 123 insertions, 143 deletions
diff --git a/packages/demobank-ui/src/declaration.d.ts b/packages/demobank-ui/src/declaration.d.ts index 084686bd2..70272cc54 100644 --- a/packages/demobank-ui/src/declaration.d.ts +++ b/packages/demobank-ui/src/declaration.d.ts @@ -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. diff --git a/packages/demobank-ui/src/hooks/backend.ts b/packages/demobank-ui/src/hooks/backend.ts new file mode 100644 index 000000000..fa4211f13 --- /dev/null +++ b/packages/demobank-ui/src/hooks/backend.ts @@ -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]; +} diff --git a/packages/demobank-ui/src/pages/home/index.tsx b/packages/demobank-ui/src/pages/home/index.tsx index a64c4abe3..49fe9e929 100644 --- a/packages/demobank-ui/src/pages/home/index.tsx +++ b/packages/demobank-ui/src/pages/home/index.tsx @@ -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. * ***********/ @@ -92,56 +76,10 @@ 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. * ******************/ @@ -316,27 +240,6 @@ function useCredentialsRequestType( } /** - * 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. * * These functions aim at sanitizing the input received @@ -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> ); } diff --git a/packages/demobank-ui/src/utils.ts b/packages/demobank-ui/src/utils.ts new file mode 100644 index 000000000..d812f2ec9 --- /dev/null +++ b/packages/demobank-ui/src/utils.ts @@ -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; +} diff --git a/packages/demobank-ui/tsconfig.json b/packages/demobank-ui/tsconfig.json index d9d56ad4f..61be44bf1 100644 --- a/packages/demobank-ui/tsconfig.json +++ b/packages/demobank-ui/tsconfig.json @@ -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/**/*" + ] +}
\ No newline at end of file |