diff options
-rw-r--r-- | packages/bank-ui/postcss.config.js | 15 | ||||
-rw-r--r-- | packages/bank-ui/src/app.tsx | 3 | ||||
-rw-r--r-- | packages/bank-ui/src/context/config.ts | 320 | ||||
-rw-r--r-- | packages/bank-ui/src/context/navigation.ts | 92 | ||||
-rw-r--r-- | packages/bank-ui/src/route.ts | 139 | ||||
-rw-r--r-- | packages/bank-ui/tailwind.config.js | 16 |
6 files changed, 30 insertions, 555 deletions
diff --git a/packages/bank-ui/postcss.config.js b/packages/bank-ui/postcss.config.js index 2e7af2b7f..c9a60a43c 100644 --- a/packages/bank-ui/postcss.config.js +++ b/packages/bank-ui/postcss.config.js @@ -1,3 +1,18 @@ +/* + This file is part of GNU Taler + (C) 2022-2024 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/> + */ export default { plugins: { tailwindcss: {}, diff --git a/packages/bank-ui/src/app.tsx b/packages/bank-ui/src/app.tsx index a2aa6ec37..29dabddd6 100644 --- a/packages/bank-ui/src/app.tsx +++ b/packages/bank-ui/src/app.tsx @@ -34,10 +34,7 @@ import { h } from "preact"; import { useEffect, useState } from "preact/hooks"; import { SWRConfig } from "swr"; import { Routing } from "./Routing.js"; -// import { BankCoreApiProvider } from "./context/config.js"; -// import { BrowserHashNavigationProvider } from "./context/navigation.js"; import { SettingsProvider } from "./context/settings.js"; -// import { TalerWalletIntegrationBrowserProvider } from "./context/wallet-integration.js"; import { strings } from "./i18n/strings.js"; import { BankFrame } from "./pages/BankFrame.js"; import { BankUiSettings, fetchSettings } from "./settings.js"; diff --git a/packages/bank-ui/src/context/config.ts b/packages/bank-ui/src/context/config.ts deleted file mode 100644 index 342a65c4f..000000000 --- a/packages/bank-ui/src/context/config.ts +++ /dev/null @@ -1,320 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022-2024 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 { - LibtoolVersion, - ObservableHttpClientLibrary, - TalerAuthenticationHttpClient, - TalerBankConversionCacheEviction, - TalerBankConversionHttpClient, - TalerCoreBankCacheEviction, - TalerCoreBankHttpClient, - TalerCorebankApi, - TalerError, - assertUnreachable, - CacheEvictor, - ObservabilityEvent, -} from "@gnu-taler/taler-util"; -import { - BrowserFetchHttpLib, - ErrorLoading, - useTranslationContext, -} from "@gnu-taler/web-util/browser"; -import { - ComponentChildren, - FunctionComponent, - VNode, - createContext, - h, -} from "preact"; -import { useContext, useEffect, useState } from "preact/hooks"; -import { - revalidateAccountDetails, - revalidatePublicAccounts, - revalidateTransactions, -} from "../hooks/account.js"; -import { - revalidateBusinessAccounts, - revalidateCashouts, - revalidateConversionInfo, -} from "../hooks/regional.js"; - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -export type Type = { - url: URL; - config: TalerCorebankApi.Config; - bank: TalerCoreBankHttpClient; - conversion: TalerBankConversionHttpClient; - authenticator: (user: string) => TalerAuthenticationHttpClient; - hints: VersionHint[]; - onBackendActivity: (fn: Listener) => Unsuscriber; - cancelRequest: (eventId: string) => void; -}; - -// FIXME: below -// @ts-expect-error default value to undefined, should it be another thing? -const Context = createContext<Type>(undefined); - -export const useBankCoreApiContext = (): Type => useContext(Context); - -export enum VersionHint { - /** - * when this flag is on, server is running an old version with cashout before implementing 2fa API - */ - CASHOUT_BEFORE_2FA, -} - -const observers = new Array<(e: ObservabilityEvent) => void>(); -type Listener = (e: ObservabilityEvent) => void; -type Unsuscriber = () => void; - -const activity = Object.freeze({ - notify: (data: ObservabilityEvent) => - observers.forEach((observer) => observer(data)), - subscribe: (func: Listener): Unsuscriber => { - observers.push(func); - return () => { - observers.forEach((observer, index) => { - if (observer === func) { - observers.splice(index, 1); - } - }); - }; - }, -}); - -export type ConfigResult = - | undefined - | { type: "ok"; config: TalerCorebankApi.Config; hints: VersionHint[] } - | { type: "incompatible"; result: TalerCorebankApi.Config; supported: string } - | { type: "error"; error: TalerError }; - -export const BankCoreApiProvider = ({ - baseUrl, - children, - frameOnError, -}: { - baseUrl: string; - children: ComponentChildren; - frameOnError: FunctionComponent<{ children: ComponentChildren }>; -}): VNode => { - const [checked, setChecked] = useState<ConfigResult>(); - const { i18n } = useTranslationContext(); - - const { bankClient, conversionClient, authClient, cancelRequest } = - buildApiClient(new URL(baseUrl)); - - useEffect(() => { - bankClient - .getConfig() - .then((resp) => { - if (resp.type === "fail") { - setChecked({ type: "error", error: TalerError.fromUncheckedDetail(resp.detail) }); - } else if (bankClient.isCompatible(resp.body.version)) { - setChecked({ type: "ok", config: resp.body, hints: [] }); - } else { - // this API supports version 3.0.3 - const compare = LibtoolVersion.compare("3:0:3", resp.body.version); - if (compare?.compatible ?? false) { - setChecked({ - type: "ok", - config: resp.body, - hints: [VersionHint.CASHOUT_BEFORE_2FA], - }); - } else { - setChecked({ - type: "incompatible", - result: resp.body, - supported: bankClient.PROTOCOL_VERSION, - }); - } - } - }) - .catch((error: unknown) => { - if (error instanceof TalerError) { - setChecked({ type: "error", error }); - } - }); - }, []); - - if (checked === undefined) { - return h(frameOnError, { children: h("div", {}, "loading...") }); - } - if (checked.type === "error") { - return h(frameOnError, { - children: h(ErrorLoading, { error: checked.error, showDetail: true }), - }); - } - if (checked.type === "incompatible") { - return h(frameOnError, { - children: h( - "div", - {}, - i18n.str`The bank backend is not supported. Supported version "${checked.supported}", server version "${checked.result.version}"`, - ), - }); - } - const value: Type = { - url: new URL(bankClient.baseUrl), - config: checked.config, - bank: bankClient, - onBackendActivity: activity.subscribe, - conversion: conversionClient, - authenticator: authClient, - cancelRequest, - hints: checked.hints, - }; - return h(Context.Provider, { - value, - children, - }); -}; - -/** - * build http client with cache breaker due to SWR - * @param url - * @returns - */ -function buildApiClient(url: URL) { - const httpFetch = new BrowserFetchHttpLib({ - enableThrottling: true, - requireTls: false, - }); - const httpLib = new ObservableHttpClientLibrary(httpFetch, { - observe(ev) { - activity.notify(ev); - }, - }); - - function cancelRequest(id: string) { - httpLib.cancelRequest(id); - } - - const bankClient = new TalerCoreBankHttpClient( - url.href, - httpLib, - evictBankSwrCache, - ); - const conversionClient = new TalerBankConversionHttpClient( - bankClient.getConversionInfoAPI().href, - httpLib, - evictConversionSwrCache, - ); - const authClient = (user: string) => - new TalerAuthenticationHttpClient( - bankClient.getAuthenticationAPI(user).href, - httpLib, - ); - - return { bankClient, conversionClient, authClient, cancelRequest }; -} - -export const BankCoreApiProviderTesting = ({ - children, - state, - url, -}: { - children: ComponentChildren; - state: TalerCorebankApi.Config; - url: string; -}): VNode => { - const value: Type = { - url: new URL(url), - config: state, - // @ts-expect-error this API is not being used, not really needed - bank: undefined, - hints: [], - }; - - return h(Context.Provider, { - value, - children, - }); -}; - -const evictBankSwrCache: CacheEvictor<TalerCoreBankCacheEviction> = { - async notifySuccess(op) { - switch (op) { - case TalerCoreBankCacheEviction.DELETE_ACCOUNT: { - await Promise.all([ - revalidatePublicAccounts(), - revalidateBusinessAccounts(), - ]); - return; - } - case TalerCoreBankCacheEviction.CREATE_ACCOUNT: { - // admin balance change on new account - await Promise.all([ - revalidateAccountDetails(), - revalidateTransactions(), - revalidatePublicAccounts(), - revalidateBusinessAccounts(), - ]); - return; - } - case TalerCoreBankCacheEviction.UPDATE_ACCOUNT: { - await Promise.all([revalidateAccountDetails()]); - return; - } - case TalerCoreBankCacheEviction.CREATE_TRANSACTION: { - await Promise.all([ - revalidateAccountDetails(), - revalidateTransactions(), - ]); - return; - } - case TalerCoreBankCacheEviction.CONFIRM_WITHDRAWAL: { - await Promise.all([ - revalidateAccountDetails(), - revalidateTransactions(), - ]); - return; - } - case TalerCoreBankCacheEviction.CREATE_CASHOUT: { - await Promise.all([ - revalidateAccountDetails(), - revalidateCashouts(), - revalidateTransactions(), - ]); - return; - } - case TalerCoreBankCacheEviction.UPDATE_PASSWORD: - case TalerCoreBankCacheEviction.ABORT_WITHDRAWAL: - case TalerCoreBankCacheEviction.CREATE_WITHDRAWAL: - return; - default: - assertUnreachable(op); - } - }, -}; - -const evictConversionSwrCache: CacheEvictor<TalerBankConversionCacheEviction> = - { - async notifySuccess(op) { - switch (op) { - case TalerBankConversionCacheEviction.UPDATE_RATE: { - await revalidateConversionInfo(); - return; - } - default: - assertUnreachable(op); - } - }, - }; diff --git a/packages/bank-ui/src/context/navigation.ts b/packages/bank-ui/src/context/navigation.ts deleted file mode 100644 index 9552bf899..000000000 --- a/packages/bank-ui/src/context/navigation.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022-2024 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, useEffect, useState } from "preact/hooks"; -import { AppLocation } from "../route.js"; - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -export type Type = { - path: string; - params: Record<string, string>; - navigateTo: (path: AppLocation) => void; - // addNavigationListener: (listener: (path: string, params: Record<string, string>) => void) => (() => void); -}; - -// @ts-expect-error should not be used without provider -const Context = createContext<Type>(undefined); - -export const useNavigationContext = (): Type => useContext(Context); - -function getPathAndParamsFromWindow() { - const path = - typeof window !== "undefined" ? window.location.hash.substring(1) : "/"; - const params: Record<string, string> = {}; - if (typeof window !== "undefined") { - for (const [key, value] of new URLSearchParams(window.location.search)) { - params[key] = value; - } - } - return { path, params }; -} - -const { path: initialPath, params: initialParams } = - getPathAndParamsFromWindow(); - -// there is a possibility that if the browser does a redirection -// (which doesn't go through navigatTo function) and that executed -// too early (before addEventListener runs) it won't be taking -// into account -const PopStateEventType = "popstate"; - -export const BrowserHashNavigationProvider = ({ - children, -}: { - children: ComponentChildren; -}): VNode => { - const [{ path, params }, setState] = useState({ - path: initialPath, - params: initialParams, - }); - if (typeof window === "undefined") { - throw Error( - "Can't use BrowserHashNavigationProvider if there is no window object", - ); - } - function navigateTo(path: string) { - const { params } = getPathAndParamsFromWindow(); - setState({ path, params }); - window.location.href = path; - } - - useEffect(() => { - function eventListener() { - setState(getPathAndParamsFromWindow()); - } - window.addEventListener(PopStateEventType, eventListener); - return () => { - window.removeEventListener(PopStateEventType, eventListener); - }; - }, []); - return h(Context.Provider, { - value: { path, params, navigateTo }, - children, - }); -}; diff --git a/packages/bank-ui/src/route.ts b/packages/bank-ui/src/route.ts deleted file mode 100644 index 11f13d140..000000000 --- a/packages/bank-ui/src/route.ts +++ /dev/null @@ -1,139 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022-2024 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 { useNavigationContext } from "./context/navigation.js"; - -declare const __location: unique symbol; -/** - * special string that defined a location in the application - * - * this help to prevent wrong path - */ -export type AppLocation = string & { - [__location]: true; -}; -export type EmptyObject = Record<string, never>; - -export function urlPattern< - T extends Record<string, string | undefined> = EmptyObject, ->(pattern: RegExp, reverse: (p: T) => string): RouteDefinition<T> { - const url = reverse as (p: T) => AppLocation; - return { - pattern: new RegExp(pattern), - url, - }; -} - -/** - * defines a location in the app - * - * pattern: how a string will trigger this location - * url(): how a state serialize to a location - */ - -export type ObjectOf<T> = Record<string, T> | EmptyObject; - -export type RouteDefinition< - T extends ObjectOf<string | undefined> = EmptyObject, -> = { - pattern: RegExp; - url: (p: T) => AppLocation; -}; - -const nullRountDef = { - pattern: new RegExp(/.*/), - url: () => "" as AppLocation, -}; -export function buildNullRoutDefinition< - T extends ObjectOf<string>, ->(): RouteDefinition<T> { - return nullRountDef; -} - -/** - * Search path in the pageList - * get the values from the path found - * add params from searchParams - * - * @param path - * @param params - */ -function findMatch<T extends ObjectOf<RouteDefinition>>( - pagesMap: T, - pageList: Array<keyof T>, - path: string, - params: Record<string, string>, -): Location<T> | undefined { - for (let idx = 0; idx < pageList.length; idx++) { - const name = pageList[idx]; - const found = pagesMap[name].pattern.exec(path); - if (found !== null) { - const values = {} as Record<string, unknown>; - - Object.entries(params).forEach(([key, value]) => { - values[key] = value; - }); - - if (found.groups !== undefined) { - Object.entries(found.groups).forEach(([key, value]) => { - values[key] = value; - }); - } - - // @ts-expect-error values is a map string which is equivalent to the RouteParamsType - return { name, parent: pagesMap, values }; - } - } - return undefined; -} - -/** - * get the type of the params of a location - * - */ -type RouteParamsType< - RouteType, - Key extends keyof RouteType, -> = RouteType[Key] extends RouteDefinition<infer ParamType> ? ParamType : never; - -/** - * Helps to create a map of a type with the key - */ -type MapKeyValue<Type> = { - [Key in keyof Type]: Key extends string - ? { - parent: Type; - name: Key; - values: RouteParamsType<Type, Key>; - } - : never; -}; - -/** - * create a enumeration of value of a mapped type - */ -type EnumerationOf<T> = T[keyof T]; - -type Location<T> = EnumerationOf<MapKeyValue<T>>; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function useCurrentLocation<T extends ObjectOf<RouteDefinition<any>>>( - pagesMap: T, -): Location<T> | undefined { - const pageList = Object.keys(pagesMap as object) as Array<keyof T>; - const { path, params } = useNavigationContext(); - - return findMatch(pagesMap, pageList, path, params); -} diff --git a/packages/bank-ui/tailwind.config.js b/packages/bank-ui/tailwind.config.js index ec51dfbb8..d384690e2 100644 --- a/packages/bank-ui/tailwind.config.js +++ b/packages/bank-ui/tailwind.config.js @@ -1,4 +1,18 @@ -/** @type {import('tailwindcss').Config} */ +/* + This file is part of GNU Taler + (C) 2022-2024 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/> + */ export default { content: { relative: true, |