diff options
Diffstat (limited to 'packages/demobank-ui/src')
-rw-r--r-- | packages/demobank-ui/src/components/app.tsx | 5 | ||||
-rw-r--r-- | packages/demobank-ui/src/context/pageState.ts | 121 | ||||
-rw-r--r-- | packages/demobank-ui/src/context/translation.ts | 4 | ||||
-rw-r--r-- | packages/demobank-ui/src/declaration.d.ts | 49 | ||||
-rw-r--r-- | packages/demobank-ui/src/hooks/index.ts | 10 | ||||
-rw-r--r-- | packages/demobank-ui/src/hooks/useLang.ts | 30 | ||||
-rw-r--r-- | packages/demobank-ui/src/hooks/useLocalStorage.ts | 80 | ||||
-rw-r--r-- | packages/demobank-ui/src/pages/home/index.tsx | 384 | ||||
-rw-r--r-- | packages/demobank-ui/src/settings.ts | 27 |
9 files changed, 301 insertions, 409 deletions
diff --git a/packages/demobank-ui/src/components/app.tsx b/packages/demobank-ui/src/components/app.tsx index 49b218205..91410a485 100644 --- a/packages/demobank-ui/src/components/app.tsx +++ b/packages/demobank-ui/src/components/app.tsx @@ -1,11 +1,14 @@ import { h, FunctionalComponent } from "preact"; +import { PageStateProvider } from "../context/pageState.js"; import { TranslationProvider } from "../context/translation.js"; import { BankHome } from "../pages/home/index.js"; const App: FunctionalComponent = () => { return ( <TranslationProvider> - <BankHome /> + <PageStateProvider> + <BankHome /> + </PageStateProvider> </TranslationProvider> ); }; diff --git a/packages/demobank-ui/src/context/pageState.ts b/packages/demobank-ui/src/context/pageState.ts new file mode 100644 index 000000000..3d7ccd85b --- /dev/null +++ b/packages/demobank-ui/src/context/pageState.ts @@ -0,0 +1,121 @@ +/* + 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 { hooks } from "@gnu-taler/web-util/lib/index.browser"; +import { ComponentChildren, createContext, h, VNode } from "preact"; +import { StateUpdater, useContext } from "preact/hooks"; + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +export type Type = { + pageState: PageStateType; + pageStateSetter: StateUpdater<PageStateType>; +}; +const initial: Type = { + pageState: { + isLoggedIn: false, + isRawPayto: false, + showPublicHistories: false, + withdrawalInProgress: false, + }, + pageStateSetter: () => { + null; + }, +}; +const Context = createContext<Type>(initial); + +export const usePageContext = (): Type => useContext(Context); + +export const PageStateProvider = ({ + children, +}: { + children: ComponentChildren; +}): VNode => { + const [pageState, pageStateSetter] = usePageState(); + + return h(Context.Provider, { + value: { pageState, pageStateSetter }, + children, + }); +}; + +/** + * Wrapper providing defaults. + */ +function usePageState( + state: PageStateType = { + isLoggedIn: false, + isRawPayto: false, + showPublicHistories: false, + withdrawalInProgress: false, + }, +): [PageStateType, StateUpdater<PageStateType>] { + const ret = hooks.useNotNullLocalStorage("page-state", JSON.stringify(state)); + const retObj: PageStateType = JSON.parse(ret[0]); + + const retSetter: StateUpdater<PageStateType> = function (val) { + const newVal = + val instanceof Function + ? JSON.stringify(val(retObj)) + : JSON.stringify(val); + + ret[1](newVal); + }; + + //when moving from one page to another + //clean up the info and error bar + function removeLatestInfo(val: any): ReturnType<typeof retSetter> { + const updater = typeof val === "function" ? val : (c: any) => val; + return retSetter((current: any) => { + const cleanedCurrent: PageStateType = { + ...current, + info: undefined, + errors: undefined, + timestamp: new Date().getTime(), + }; + return updater(cleanedCurrent); + }); + } + + return [retObj, removeLatestInfo]; +} + +/** + * Track page state. + */ +export interface PageStateType { + isLoggedIn: boolean; + isRawPayto: boolean; + showPublicHistories: boolean; + withdrawalInProgress: boolean; + error?: { + description?: string; + title: string; + debug?: string; + }; + + info?: string; + talerWithdrawUri?: string; + /** + * Not strictly a presentational value, could + * be moved in a future "withdrawal state" object. + */ + withdrawalId?: string; + timestamp?: number; +} diff --git a/packages/demobank-ui/src/context/translation.ts b/packages/demobank-ui/src/context/translation.ts index a50f81b86..478bdbde0 100644 --- a/packages/demobank-ui/src/context/translation.ts +++ b/packages/demobank-ui/src/context/translation.ts @@ -22,7 +22,7 @@ import { i18n, setupI18n } from "@gnu-taler/taler-util"; import { createContext, h, VNode } from "preact"; import { useContext, useEffect } from "preact/hooks"; -import { useLang } from "../hooks/useLang.js"; +import { hooks } from "@gnu-taler/web-util/lib/index.browser"; import { strings } from "../i18n/strings.js"; interface Type { @@ -70,7 +70,7 @@ export const TranslationProvider = ({ children, forceLang, }: Props): VNode => { - const [lang, changeLanguage, isSaved] = useLang(initial); + const [lang, changeLanguage, isSaved] = hooks.useLang(initial); useEffect(() => { if (forceLang) { changeLanguage(forceLang); diff --git a/packages/demobank-ui/src/declaration.d.ts b/packages/demobank-ui/src/declaration.d.ts index 00b3d41d5..084686bd2 100644 --- a/packages/demobank-ui/src/declaration.d.ts +++ b/packages/demobank-ui/src/declaration.d.ts @@ -18,3 +18,52 @@ declare module "jed" { const x: any; export = x; } + +/********************************************** + * 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. + * + * If the amount appears twice: both as a Payto parameter and + * in the JSON dedicate field, the one on the Payto URI takes + * precedence. + */ +interface TransactionRequestType { + paytoUri: string; + amount?: string; // with currency. +} + +/** + * Request body of /register. + */ +interface CredentialsRequestType { + username?: string; + password?: string; + repeatPassword?: string; +} + +/** + * Request body of /register. + */ +// interface LoginRequestType { +// username: string; +// password: string; +// } + +interface WireTransferRequestType { + iban?: string; + subject?: string; + amount?: string; +} diff --git a/packages/demobank-ui/src/hooks/index.ts b/packages/demobank-ui/src/hooks/index.ts index b4191d182..c6e3fe8c1 100644 --- a/packages/demobank-ui/src/hooks/index.ts +++ b/packages/demobank-ui/src/hooks/index.ts @@ -20,7 +20,7 @@ */ import { StateUpdater } from "preact/hooks"; -import { useLocalStorage, useNotNullLocalStorage } from "./useLocalStorage.js"; +import { hooks } from "@gnu-taler/web-util/lib/index.browser"; export type ValueOrFunction<T> = T | ((p: T) => T); const calculateRootPath = () => { @@ -34,11 +34,11 @@ const calculateRootPath = () => { export function useBackendURL( url?: string, ): [string, boolean, StateUpdater<string>, () => void] { - const [value, setter] = useNotNullLocalStorage( + const [value, setter] = hooks.useNotNullLocalStorage( "backend-url", url || calculateRootPath(), ); - const [triedToLog, setTriedToLog] = useLocalStorage("tried-login"); + const [triedToLog, setTriedToLog] = hooks.useLocalStorage("tried-login"); const checkedSetter = (v: ValueOrFunction<string>) => { setTriedToLog("yes"); @@ -55,13 +55,13 @@ export function useBackendDefaultToken(): [ string | undefined, StateUpdater<string | undefined>, ] { - return useLocalStorage("backend-token"); + return hooks.useLocalStorage("backend-token"); } export function useBackendInstanceToken( id: string, ): [string | undefined, StateUpdater<string | undefined>] { - const [token, setToken] = useLocalStorage(`backend-token-${id}`); + const [token, setToken] = hooks.useLocalStorage(`backend-token-${id}`); const [defaultToken, defaultSetToken] = useBackendDefaultToken(); // instance named 'default' use the default token diff --git a/packages/demobank-ui/src/hooks/useLang.ts b/packages/demobank-ui/src/hooks/useLang.ts deleted file mode 100644 index 5b02c5255..000000000 --- a/packages/demobank-ui/src/hooks/useLang.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - This file is part of GNU Anastasis - (C) 2021-2022 Anastasis SARL - - GNU Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Anastasis 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -import { useNotNullLocalStorage } from "./useLocalStorage.js"; - -function getBrowserLang(): string | undefined { - if (window.navigator.languages) return window.navigator.languages[0]; - if (window.navigator.language) return window.navigator.language; - return undefined; -} - -export function useLang( - initial?: string, -): [string, (s: string) => void, boolean] { - const defaultLang = (getBrowserLang() || initial || "en").substring(0, 2); - return useNotNullLocalStorage("lang-preference", defaultLang); -} diff --git a/packages/demobank-ui/src/hooks/useLocalStorage.ts b/packages/demobank-ui/src/hooks/useLocalStorage.ts deleted file mode 100644 index ed5b491f2..000000000 --- a/packages/demobank-ui/src/hooks/useLocalStorage.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* - This file is part of GNU Anastasis - (C) 2021-2022 Anastasis SARL - - GNU Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Anastasis 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { StateUpdater, useState } from "preact/hooks"; - -export function useLocalStorage( - key: string, - initialValue?: string, -): [string | undefined, StateUpdater<string | undefined>] { - const [storedValue, setStoredValue] = useState<string | undefined>( - (): string | undefined => { - return typeof window !== "undefined" - ? window.localStorage.getItem(key) || initialValue - : initialValue; - }, - ); - - const setValue = ( - value?: string | ((val?: string) => string | undefined), - ): void => { - setStoredValue((p) => { - const toStore = value instanceof Function ? value(p) : value; - if (typeof window !== "undefined") { - if (!toStore) { - window.localStorage.removeItem(key); - } else { - window.localStorage.setItem(key, toStore); - } - } - return toStore; - }); - }; - - return [storedValue, setValue]; -} - -//TODO: merge with the above function -export function useNotNullLocalStorage( - key: string, - initialValue: string, -): [string, StateUpdater<string>, boolean] { - const [storedValue, setStoredValue] = useState<string>((): string => { - return typeof window !== "undefined" - ? window.localStorage.getItem(key) || initialValue - : initialValue; - }); - - const setValue = (value: string | ((val: string) => string)): void => { - const valueToStore = value instanceof Function ? value(storedValue) : value; - setStoredValue(valueToStore); - if (typeof window !== "undefined") { - if (!valueToStore) { - window.localStorage.removeItem(key); - } else { - window.localStorage.setItem(key, valueToStore); - } - } - }; - - const isSaved = window.localStorage.getItem(key) !== null; - return [storedValue, setValue, isSaved]; -} diff --git a/packages/demobank-ui/src/pages/home/index.tsx b/packages/demobank-ui/src/pages/home/index.tsx index 8b2ffefac..a64c4abe3 100644 --- a/packages/demobank-ui/src/pages/home/index.tsx +++ b/packages/demobank-ui/src/pages/home/index.tsx @@ -27,44 +27,14 @@ import { } from "preact/hooks"; import talerLogo from "../../assets/logo-white.svg"; import { LangSelectorLikePy as LangSelector } from "../../components/menu/LangSelector.js"; -import { - useLocalStorage, - useNotNullLocalStorage, -} from "../../hooks/useLocalStorage.js"; -// import { Translate, useTranslator } from "../../i18n/index.js"; import { useTranslationContext } from "../../context/translation.js"; import { Amounts, HttpStatusCode, parsePaytoUri } from "@gnu-taler/taler-util"; import { createHashHistory } from "history"; import Router, { Route, route } from "preact-router"; import { QrCodeSection } from "./QrCodeSection.js"; - -interface BankUiSettings { - allowRegistrations: boolean; - showDemoNav: boolean; - bankName: string; - demoSites: [string, string][]; -} - -/** - * Global settings for the demobank UI. - */ -const defaultSettings: BankUiSettings = { - allowRegistrations: true, - bankName: "Taler Bank", - showDemoNav: true, - demoSites: [ - ["Landing", "https://demo.taler.net/"], - ["Bank", "https://bank.demo.taler.net/"], - ["Essay Shop", "https://shop.demo.taler.net/"], - ["Donations", "https://donations.demo.taler.net/"], - ["Survey", "https://survey.demo.taler.net/"], - ], -}; - -const bankUiSettings: BankUiSettings = - "talerDemobankSettings" in globalThis - ? (globalThis as any).talerDemobankSettings - : defaultSettings; +import { hooks } from "@gnu-taler/web-util/lib/index.browser"; +import { bankUiSettings } from "../../settings.js"; +import { PageStateType, usePageContext } from "../../context/pageState.js"; /** * FIXME: @@ -90,94 +60,6 @@ const bankUiSettings: BankUiSettings = /************ * Contexts * ***********/ -const CurrencyContext = createContext<any>(null); -type PageContextType = [PageStateType, StateUpdater<PageStateType>]; -const PageContextDefault: PageContextType = [ - { - isLoggedIn: false, - isRawPayto: false, - showPublicHistories: false, - - withdrawalInProgress: false, - }, - () => { - null; - }, -]; -const PageContext = createContext<PageContextType>(PageContextDefault); - -/********************************************** - * 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. - * - * If the amount appears twice: both as a Payto parameter and - * in the JSON dedicate field, the one on the Payto URI takes - * precedence. - */ -interface TransactionRequestType { - paytoUri: string; - amount?: string; // with currency. -} - -/** - * Request body of /register. - */ -interface CredentialsRequestType { - username?: string; - password?: string; - repeatPassword?: string; -} - -/** - * Request body of /register. - */ -// interface LoginRequestType { -// username: string; -// password: string; -// } - -interface WireTransferRequestType { - iban?: string; - subject?: string; - amount?: string; -} - -/** - * Track page state. - */ -interface PageStateType { - isLoggedIn: boolean; - isRawPayto: boolean; - showPublicHistories: boolean; - withdrawalInProgress: boolean; - error?: { - description?: string; - title: string; - debug?: string; - }; - - info?: string; - talerWithdrawUri?: string; - /** - * Not strictly a presentational value, could - * be moved in a future "withdrawal state" object. - */ - withdrawalId?: string; - timestamp?: number; -} /** * Bank account specific information. @@ -294,7 +176,7 @@ async function postToBackend( } function useTransactionPageNumber(): [number, StateUpdater<number>] { - const ret = useNotNullLocalStorage("transaction-page", "0"); + const ret = hooks.useNotNullLocalStorage("transaction-page", "0"); const retObj = JSON.parse(ret[0]); const retSetter: StateUpdater<number> = function (val) { const newVal = @@ -347,7 +229,10 @@ const getBankBackendBaseUrl = (): string => { function useShowPublicAccount( state?: string, ): [string | undefined, StateUpdater<string | undefined>] { - const ret = useLocalStorage("show-public-account", JSON.stringify(state)); + const ret = hooks.useLocalStorage( + "show-public-account", + JSON.stringify(state), + ); const retObj: string | undefined = ret[0] ? JSON.parse(ret[0]) : ret[0]; const retSetter: StateUpdater<string | undefined> = function (val) { const newVal = @@ -367,7 +252,7 @@ type RawPaytoInputTypeOpt = RawPaytoInputType | undefined; function useRawPaytoInputType( state?: RawPaytoInputType, ): [RawPaytoInputTypeOpt, StateUpdater<RawPaytoInputTypeOpt>] { - const ret = useLocalStorage("raw-payto-input-state", state); + const ret = hooks.useLocalStorage("raw-payto-input-state", state); const retObj: RawPaytoInputTypeOpt = ret[0]; const retSetter: StateUpdater<RawPaytoInputTypeOpt> = function (val) { const newVal = val instanceof Function ? val(retObj) : val; @@ -387,7 +272,7 @@ type WireTransferRequestTypeOpt = WireTransferRequestType | undefined; function useWireTransferRequestType( state?: WireTransferRequestType, ): [WireTransferRequestTypeOpt, StateUpdater<WireTransferRequestTypeOpt>] { - const ret = useLocalStorage( + const ret = hooks.useLocalStorage( "wire-transfer-request-state", JSON.stringify(state), ); @@ -413,7 +298,7 @@ type CredentialsRequestTypeOpt = CredentialsRequestType | undefined; function useCredentialsRequestType( state?: CredentialsRequestType, ): [CredentialsRequestTypeOpt, StateUpdater<CredentialsRequestTypeOpt>] { - const ret = useLocalStorage( + const ret = hooks.useLocalStorage( "credentials-request-state", JSON.stringify(state), ); @@ -439,7 +324,7 @@ type BackendStateTypeOpt = BackendStateType | undefined; function useBackendState( state?: BackendStateType, ): [BackendStateTypeOpt, StateUpdater<BackendStateTypeOpt>] { - const ret = useLocalStorage("backend-state", JSON.stringify(state)); + 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 = @@ -452,67 +337,6 @@ function useBackendState( } /** - * Keep mere business information, like account balance or - * transactions history. - */ -// type AccountStateTypeOpt = AccountStateType | undefined; -// function useAccountState( -// state?: AccountStateType, -// ): [AccountStateTypeOpt, StateUpdater<AccountStateTypeOpt>] { -// const ret = useLocalStorage("account-state", JSON.stringify(state)); -// const retObj: AccountStateTypeOpt = ret[0] ? JSON.parse(ret[0]) : ret[0]; -// const retSetter: StateUpdater<AccountStateTypeOpt> = function (val) { -// const newVal = -// val instanceof Function -// ? JSON.stringify(val(retObj)) -// : JSON.stringify(val); -// ret[1](newVal); -// }; -// return [retObj, retSetter]; -// } - -/** - * Wrapper providing defaults. - */ -function usePageState( - state: PageStateType = { - isLoggedIn: false, - isRawPayto: false, - showPublicHistories: false, - withdrawalInProgress: false, - }, -): [PageStateType, StateUpdater<PageStateType>] { - const ret = useNotNullLocalStorage("page-state", JSON.stringify(state)); - const retObj: PageStateType = JSON.parse(ret[0]); - - const retSetter: StateUpdater<PageStateType> = function (val) { - const newVal = - val instanceof Function - ? JSON.stringify(val(retObj)) - : JSON.stringify(val); - - ret[1](newVal); - }; - - //when moving from one page to another - //clean up the info and error bar - function removeLatestInfo(val: any): ReturnType<typeof retSetter> { - const updater = typeof val === "function" ? val : (c: any) => val; - return retSetter((current: any) => { - const cleanedCurrent: PageStateType = { - ...current, - info: undefined, - errors: undefined, - timestamp: new Date().getTime(), - }; - return updater(cleanedCurrent); - }); - } - - return [retObj, removeLatestInfo]; -} - -/** * Request preparators. * * These functions aim at sanitizing the input received @@ -1045,7 +869,7 @@ function StatusBanner(Props: any): VNode | null { function BankFrame(Props: any): VNode { const { i18n } = useTranslationContext(); - const [pageState, pageStateSetter] = useContext(PageContext); + const { pageState, pageStateSetter } = usePageContext(); console.log("BankFrame state", pageState); const logOut = ( <div class="logout"> @@ -1164,19 +988,16 @@ function ShowInputErrorLabel({ } function PaytoWireTransfer(Props: any): VNode { - const currency = useContext(CurrencyContext); - const [pageState, pageStateSetter] = useContext(PageContext); // NOTE: used for go-back button? + const { pageState, pageStateSetter } = usePageContext(); // NOTE: used for go-back button? + const [submitData, submitDataSetter] = useWireTransferRequestType(); - // const [rawPaytoInput, rawPaytoInputSetter] = useRawPaytoInputType(); + const [rawPaytoInput, rawPaytoInputSetter] = useState<string | undefined>( undefined, ); const { i18n } = useTranslationContext(); - const { focus, backendState } = Props; - const amountRegex = "^[0-9]+(.[0-9]+)?$"; + const { focus, backendState, currency } = Props; const ibanRegex = "^[A-Z][A-Z][0-9]+$"; - const receiverInput = ""; - const subjectInput = ""; let transactionData: TransactionRequestType; const ref = useRef<HTMLInputElement>(null); useEffect(() => { @@ -1213,7 +1034,7 @@ function PaytoWireTransfer(Props: any): VNode { if (!pageState.isRawPayto) return ( <div> - <div class="pure-form" name="wire-transfer-form"> + <form class="pure-form" name="wire-transfer-form"> <p> <label for="iban">{i18n.str`Receiver IBAN:`}</label> <input @@ -1261,13 +1082,22 @@ function PaytoWireTransfer(Props: any): VNode { <br /> <label for="amount">{i18n.str`Amount:`}</label> <input + type="text" + readonly + class="currency-indicator" + size={currency.length} + maxLength={currency.length} + tabIndex={-1} + value={currency} + /> + + <input type="number" name="amount" id="amount" placeholder="amount" required value={submitData?.amount ?? ""} - pattern={amountRegex} onInput={(e): void => { submitDataSetter((submitData: any) => ({ ...submitData, @@ -1275,16 +1105,6 @@ function PaytoWireTransfer(Props: any): VNode { })); }} /> - - <input - type="text" - readonly - class="currency-indicator" - size={currency.length} - maxLength={currency.length} - tabIndex={-1} - value={currency} - /> <ShowInputErrorLabel message={errorsWire?.amount} isDirty={submitData?.amount !== undefined} @@ -1349,7 +1169,7 @@ function PaytoWireTransfer(Props: any): VNode { }} /> </p> - </div> + </form> <p> <a href="/account" @@ -1460,7 +1280,7 @@ function PaytoWireTransfer(Props: any): VNode { * Not providing a back button, only abort. */ function TalerWithdrawalConfirmationQuestion(Props: any): VNode { - const [pageState, pageStateSetter] = useContext(PageContext); + const { pageState, pageStateSetter } = usePageContext(); const { backendState } = Props; const { i18n } = useTranslationContext(); const captchaNumbers = { @@ -1474,7 +1294,7 @@ function TalerWithdrawalConfirmationQuestion(Props: any): VNode { <h1 class="nav">{i18n.str`Confirm Withdrawal`}</h1> <article> <div class="challenge-div"> - <form class="challenge-form"> + <form class="challenge-form" noValidate> <div class="pure-form" id="captcha" name="capcha-form"> <h2>{i18n.str`Authorize withdrawal by solving challenge`}</h2> <p> @@ -1562,8 +1382,8 @@ function TalerWithdrawalConfirmationQuestion(Props: any): VNode { */ function TalerWithdrawalQRCode(Props: any): VNode { // turns true when the wallet POSTed the reserve details: - const [pageState, pageStateSetter] = useContext(PageContext); - const { withdrawalId, talerWithdrawUri, accountLabel, backendState } = Props; + const { pageState, pageStateSetter } = usePageContext(); + const { withdrawalId, talerWithdrawUri, backendState } = Props; const { i18n } = useTranslationContext(); const abortButton = ( <a @@ -1647,29 +1467,35 @@ function TalerWithdrawalQRCode(Props: any): VNode { } function WalletWithdraw(Props: any): VNode { - const { backendState, pageStateSetter, focus } = Props; - const currency = useContext(CurrencyContext); + const { backendState, pageStateSetter, focus, currency } = Props; const { i18n } = useTranslationContext(); let submitAmount = "5.00"; - const amountRegex = "^[0-9]+(.[0-9]+)?$"; const ref = useRef<HTMLInputElement>(null); useEffect(() => { if (focus) ref.current?.focus(); }, [focus]); return ( - <div id="reserve-form" class="pure-form" name="tform"> + <form id="reserve-form" class="pure-form" name="tform"> <p> <label for="withdraw-amount">{i18n.str`Amount to withdraw:`}</label> <input + type="text" + readonly + class="currency-indicator" + size={currency.length} + maxLength={currency.length} + tabIndex={-1} + value={currency} + /> + + <input type="number" ref={ref} id="withdraw-amount" name="withdraw-amount" value={submitAmount} - pattern={amountRegex} - class="amount" onChange={(e): void => { // FIXME: validate using 'parseAmount()', // deactivate submit button as long as @@ -1677,16 +1503,6 @@ function WalletWithdraw(Props: any): VNode { submitAmount = e.currentTarget.value; }} /> - - <input - type="text" - readonly - class="currency-indicator" - size={currency.length} - maxLength={currency.length} - tabIndex={-1} - value={currency} - /> </p> <p> <div> @@ -1712,7 +1528,7 @@ function WalletWithdraw(Props: any): VNode { /> </div> </p> - </div> + </form> ); } @@ -1721,8 +1537,7 @@ function WalletWithdraw(Props: any): VNode { * then specify the details trigger the action. */ function PaymentOptions(Props: any): VNode { - const { backendState, pageStateSetter, focus } = Props; - const currency = useContext(CurrencyContext); + const { backendState, pageStateSetter, currency } = Props; const { i18n } = useTranslationContext(); const [tab, setTab] = useState<"charge-wallet" | "wire-transfer">( @@ -1756,6 +1571,7 @@ function PaymentOptions(Props: any): VNode { <WalletWithdraw backendState={backendState} focus + currency={currency} pageStateSetter={pageStateSetter} /> </div> @@ -1766,6 +1582,7 @@ function PaymentOptions(Props: any): VNode { <PaytoWireTransfer backendState={backendState} focus + currency={currency} pageStateSetter={pageStateSetter} /> </div> @@ -1819,7 +1636,7 @@ function LoginForm(Props: any): VNode { return ( <div class="login-div"> - <form action="javascript:void(0);" class="login-form"> + <form action="javascript:void(0);" class="login-form" noValidate> <div class="pure-form"> <h2>{i18n.str`Please login!`}</h2> <p class="unameFieldLabel loginFieldLabel formFieldLabel"> @@ -1903,7 +1720,7 @@ function LoginForm(Props: any): VNode { */ function RegistrationForm(Props: any): VNode { // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [pageState, pageStateSetter] = useContext(PageContext); + const { pageState, pageStateSetter } = usePageContext(); const [submitData, submitDataSetter] = useCredentialsRequestType(); const { i18n } = useTranslationContext(); @@ -1924,7 +1741,7 @@ function RegistrationForm(Props: any): VNode { <h1 class="nav">{i18n.str`Welcome to ${bankUiSettings.bankName}!`}</h1> <article> <div class="register-div"> - <form action="javascript:void(0);" class="register-form"> + <form action="javascript:void(0);" class="register-form" noValidate> <div class="pure-form"> <h2>{i18n.str`Please register!`}</h2> <p class="unameFieldLabel registerFieldLabel formFieldLabel"> @@ -2140,7 +1957,7 @@ function Account(Props: any): VNode { // revalidateOnFocus: false, // revalidateOnReconnect: false, }); - const [pageState, setPageState] = useContext(PageContext); + const { pageState, pageStateSetter: setPageState } = usePageContext(); const { withdrawalInProgress, withdrawalId, @@ -2275,14 +2092,11 @@ function Account(Props: any): VNode { <section id="payments"> <div class="payments"> <h2>{i18n.str`Payments`}</h2> - {/* FIXME: turn into button! */} - <CurrencyContext.Provider value={balance.currency}> - {Props.children} - <PaymentOptions - backendState={backendState} - pageStateSetter={setPageState} - /> - </CurrencyContext.Provider> + <PaymentOptions + currency={balance.currency} + backendState={backendState} + pageStateSetter={setPageState} + /> </div> </section> <section id="main"> @@ -2439,71 +2253,61 @@ function PublicHistories(Props: any): VNode { } function PublicHistoriesPage(): VNode { - // const [backendState, backendStateSetter] = useBackendState(); - const [pageState, pageStateSetter] = usePageState(); + const { pageState, pageStateSetter } = usePageContext(); // const { i18n } = useTranslationContext(); return ( <SWRWithoutCredentials baseUrl={getBankBackendBaseUrl()}> - <PageContext.Provider value={[pageState, pageStateSetter]}> - <BankFrame> - <PublicHistories pageStateSetter={pageStateSetter}> - <br /> - <a - class="pure-button" - onClick={() => { - pageStateSetter((prevState: PageStateType) => ({ - ...prevState, - showPublicHistories: false, - })); - }} - > - Go back - </a> - </PublicHistories> - </BankFrame> - </PageContext.Provider> + <BankFrame> + <PublicHistories pageStateSetter={pageStateSetter}> + <br /> + <a + class="pure-button" + onClick={() => { + pageStateSetter((prevState: PageStateType) => ({ + ...prevState, + showPublicHistories: false, + })); + }} + > + Go back + </a> + </PublicHistories> + </BankFrame> </SWRWithoutCredentials> ); } function RegistrationPage(): VNode { const [backendState, backendStateSetter] = useBackendState(); - const [pageState, pageStateSetter] = usePageState(); const { i18n } = useTranslationContext(); if (!bankUiSettings.allowRegistrations) { return ( - <PageContext.Provider value={[pageState, pageStateSetter]}> - <BankFrame> - <p>{i18n.str`Currently, the bank is not accepting new registrations!`}</p> - </BankFrame> - </PageContext.Provider> + <BankFrame> + <p>{i18n.str`Currently, the bank is not accepting new registrations!`}</p> + </BankFrame> ); } return ( - <PageContext.Provider value={[pageState, pageStateSetter]}> - <BankFrame> - <RegistrationForm backendStateSetter={backendStateSetter} /> - </BankFrame> - </PageContext.Provider> + <BankFrame> + <RegistrationForm backendStateSetter={backendStateSetter} /> + </BankFrame> ); } function AccountPage(): VNode { const [backendState, backendStateSetter] = useBackendState(); - const [pageState, pageStateSetter] = usePageState(); const { i18n } = useTranslationContext(); + const { pageState, pageStateSetter } = usePageContext(); if (!pageState.isLoggedIn) { return ( - <PageContext.Provider value={[pageState, pageStateSetter]}> - <BankFrame> - <h1 class="nav">{i18n.str`Welcome to ${bankUiSettings.bankName}!`}</h1> - <LoginForm - pageStateSetter={pageStateSetter} - backendStateSetter={backendStateSetter} - /> - </BankFrame> - </PageContext.Provider> + <BankFrame> + <h1 class="nav">{i18n.str`Welcome to ${bankUiSettings.bankName}!`}</h1> + <LoginForm + pageStateSetter={pageStateSetter} + backendStateSetter={backendStateSetter} + /> + </BankFrame> ); } @@ -2525,12 +2329,10 @@ function AccountPage(): VNode { password={backendState.password} backendUrl={backendState.url} > - <PageContext.Provider value={[pageState, pageStateSetter]}> - <Account - accountLabel={backendState.username} - backendState={backendState} - /> - </PageContext.Provider> + <Account + accountLabel={backendState.username} + backendState={backendState} + /> </SWRWithCredentials> ); } diff --git a/packages/demobank-ui/src/settings.ts b/packages/demobank-ui/src/settings.ts new file mode 100644 index 000000000..a63ce347e --- /dev/null +++ b/packages/demobank-ui/src/settings.ts @@ -0,0 +1,27 @@ +export interface BankUiSettings { + allowRegistrations: boolean; + showDemoNav: boolean; + bankName: string; + demoSites: [string, string][]; +} + +/** + * Global settings for the demobank UI. + */ +const defaultSettings: BankUiSettings = { + allowRegistrations: true, + bankName: "Taler Bank", + showDemoNav: true, + demoSites: [ + ["Landing", "https://demo.taler.net/"], + ["Bank", "https://bank.demo.taler.net/"], + ["Essay Shop", "https://shop.demo.taler.net/"], + ["Donations", "https://donations.demo.taler.net/"], + ["Survey", "https://survey.demo.taler.net/"], + ], +}; + +export const bankUiSettings: BankUiSettings = + "talerDemobankSettings" in globalThis + ? (globalThis as any).talerDemobankSettings + : defaultSettings; |