diff options
Diffstat (limited to 'packages')
52 files changed, 744 insertions, 676 deletions
diff --git a/packages/aml-backoffice-ui/copyleft-header.js b/packages/aml-backoffice-ui/copyleft-header.js index 2635717c5..7fa276bea 100644 --- a/packages/aml-backoffice-ui/copyleft-header.js +++ b/packages/aml-backoffice-ui/copyleft-header.js @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/App.tsx b/packages/aml-backoffice-ui/src/App.tsx index 55f03322d..d55de776b 100644 --- a/packages/aml-backoffice-ui/src/App.tsx +++ b/packages/aml-backoffice-ui/src/App.tsx @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 @@ -13,26 +13,46 @@ 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 { TranslationProvider } from "@gnu-taler/web-util/browser"; -import { h, VNode } from "preact"; +import { canonicalizeBaseUrl } from "@gnu-taler/taler-util"; +import { + BrowserHashNavigationProvider, + ExchangeApiProvider, + Loading, + TranslationProvider, +} from "@gnu-taler/web-util/browser"; +import { VNode, h } from "preact"; +import { useEffect, useState } from "preact/hooks"; import { SWRConfig } from "swr"; -import { ExchangeApiProvider } from "./context/config.js"; -import { ExchangeAmlFrame } from "./Dashboard.js"; -import { getInitialBackendBaseURL } from "./hooks/useBackend.js"; -import { Pages } from "./pages.js"; -import { HashPathProvider, Router } from "./route.js"; +import { ExchangeAmlFrame } from "./ExchangeAmlFrame.js"; +import { Routing } from "./Routing.js"; +import { SettingsProvider } from "./context/settings.js"; +import { strings } from "./i18n/strings.js"; import "./scss/main.css"; +import { UiSettings, fetchSettings } from "./settings.js"; const WITH_LOCAL_STORAGE_CACHE = false; -const pageList = Object.values(Pages); - export function App(): VNode { - const baseUrl = getInitialBackendBaseURL(); + const [settings, setSettings] = useState<UiSettings>(); + useEffect(() => { + fetchSettings(setSettings); + }, []); + if (!settings) return <Loading />; + + const baseUrl = getInitialBackendBaseURL(settings.backendBaseURL); return ( - <TranslationProvider source={{}}> - <ExchangeApiProvider baseUrl={baseUrl} frameOnError={ExchangeAmlFrame}> - <HashPathProvider> + <SettingsProvider value={settings}> + <TranslationProvider + source={strings} + completeness={{ + es: strings["es"].completeness, + de: strings["de"].completeness, + }} + > + <ExchangeApiProvider + baseUrl={new URL("/", baseUrl)} + frameOnError={ExchangeAmlFrame} + > <SWRConfig value={{ provider: WITH_LOCAL_STORAGE_CACHE @@ -60,19 +80,13 @@ export function App(): VNode { keepPreviousData: true, }} > - <ExchangeAmlFrame> - <Router - pageList={pageList} - onNotFound={() => { - window.location.href = Pages.cases.url; - return <div>not found</div>; - }} - /> - </ExchangeAmlFrame> + <BrowserHashNavigationProvider> + <Routing /> + </BrowserHashNavigationProvider> </SWRConfig> - </HashPathProvider> - </ExchangeApiProvider> - </TranslationProvider> + </ExchangeApiProvider> + </TranslationProvider> + </SettingsProvider> ); } @@ -85,3 +99,34 @@ function localStorageProvider(): Map<unknown, unknown> { }); return map; } + +function getInitialBackendBaseURL( + backendFromSettings: string | undefined, +): string { + const overrideUrl = + typeof localStorage !== "undefined" + ? localStorage.getItem("exchange-base-url") + : undefined; + let result: string; + + if (!overrideUrl) { + // normal path + if (!backendFromSettings) { + console.error( + "ERROR: backendBaseURL was overridden by a setting file and missing. Setting value to 'window.origin'", + ); + result = window.origin; + } else { + result = backendFromSettings; + } + } else { + // testing/development path + result = overrideUrl; + } + try { + return canonicalizeBaseUrl(result); + } catch (e) { + // fall back + return canonicalizeBaseUrl(window.origin); + } +} diff --git a/packages/aml-backoffice-ui/src/Dashboard.tsx b/packages/aml-backoffice-ui/src/ExchangeAmlFrame.tsx index 78980265d..66eb3df36 100644 --- a/packages/aml-backoffice-ui/src/Dashboard.tsx +++ b/packages/aml-backoffice-ui/src/ExchangeAmlFrame.tsx @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 @@ -20,19 +20,20 @@ import { ToastBanner, notifyError, notifyException, + useNavigationContext, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { ComponentChildren, VNode, h } from "preact"; import { useEffect, useErrorBoundary } from "preact/hooks"; -import { useOfficer } from "./hooks/useOfficer.js"; +import { privatePages } from "./Routing.js"; +import { useSettingsContext } from "./context/settings.js"; +import { OfficerState } from "./hooks/officer.js"; import { - getAllBooleanSettings, - getLabelForSetting, - useSettings, -} from "./hooks/useSettings.js"; -import { Pages } from "./pages.js"; -import { PageEntry, useChangeLocation } from "./route.js"; -import { uiSettings } from "./settings.js"; + getAllBooleanPreferences, + getLabelForPreferences, + usePreferences, +} from "./hooks/preferences.js"; +import { HomeIcon } from "./pages/Cases.js"; /** * mapping route to view @@ -107,7 +108,9 @@ const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined; export function ExchangeAmlFrame({ children, + officer, }: { + officer?: OfficerState, children?: ComponentChildren; }): VNode { const { i18n } = useTranslationContext(); @@ -129,8 +132,8 @@ export function ExchangeAmlFrame({ } }, [error]); - const officer = useOfficer(); - const [settings, updateSettings] = useSettings(); + const [preferences, updatePreferences] = usePreferences(); + const settings = useSettingsContext() return ( <div @@ -140,9 +143,9 @@ export function ExchangeAmlFrame({ <div class="bg-indigo-600 pb-32"> <Header title="Exchange" - iconLinkURL={uiSettings.backendBaseURL ?? "#"} + iconLinkURL={settings.backendBaseURL ?? "#"} onLogout={ - officer.state !== "ready" + officer?.state !== "ready" ? undefined : () => { officer.lock(); @@ -156,8 +159,8 @@ export function ExchangeAmlFrame({ <i18n.Translate>Preferences</i18n.Translate> </div> <ul role="list" class="space-y-1"> - {getAllBooleanSettings().map((set) => { - const isOn: boolean = !!settings[set]; + {getAllBooleanPreferences().map((set) => { + const isOn: boolean = !!preferences[set]; return ( <li key={set} class="mt-2 pl-2"> <div class="flex items-center justify-between"> @@ -166,7 +169,7 @@ export function ExchangeAmlFrame({ class="text-sm text-black font-medium leading-6 " id="availability-label" > - {getLabelForSetting(set, i18n)} + {getLabelForPreferences(set, i18n)} </span> </span> <button @@ -178,7 +181,7 @@ export function ExchangeAmlFrame({ aria-labelledby="availability-label" aria-describedby="availability-description" onClick={() => { - updateSettings(set, !isOn); + updatePreferences(set, !isOn); }} > <span @@ -203,7 +206,7 @@ export function ExchangeAmlFrame({ </div> <div class="-mt-32 flex grow "> - {officer.state !== "ready" ? undefined : <Navigation />} + {officer?.state !== "ready" ? undefined : <Navigation />} <div class="flex mx-auto my-4"> <main class="rounded-lg bg-white px-5 py-6 shadow">{children}</main> </div> @@ -219,24 +222,28 @@ export function ExchangeAmlFrame({ } function Navigation(): VNode { - const pageList: Array<PageEntry> = [Pages.officer, Pages.cases]; - const location = useChangeLocation(); + const { i18n } = useTranslationContext(); + const pageList = [ + { route: privatePages.account, Icon: HomeIcon, label: i18n.str`Account` }, + { route: privatePages.cases, Icon: HomeIcon, label: i18n.str`Cases` }, + ]; + const { path } = useNavigationContext(); return ( <div class="hidden sm:block min-w-min bg-indigo-600 divide-y rounded-r-lg divide-cyan-800 overflow-y-auto overflow-x-clip"> <nav class="flex flex-1 flex-col mx-4 mt-4 mb-2"> <ul role="list" class="flex flex-1 flex-col gap-y-7"> <li> <ul role="list" class="-mx-2 space-y-1"> - {pageList.map((p) => { + {pageList.map((p, idx) => { return ( - <li key={p.url}> + <li key={idx}> <a - href={p.url} - data-selected={location == p.url} + href={p.route.url({})} + data-selected={path == p.route.url({})} class="data-[selected=true]:bg-indigo-700 pr-4 data-[selected=true]:text-white text-indigo-200 hover:text-white hover:bg-indigo-700 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold" > {p.Icon && <p.Icon />} - <span class="hidden md:inline">{p.name}</span> + <span class="hidden md:inline">{p.label}</span> </a> </li> ); diff --git a/packages/aml-backoffice-ui/src/Routing.tsx b/packages/aml-backoffice-ui/src/Routing.tsx new file mode 100644 index 000000000..1e32e4f4c --- /dev/null +++ b/packages/aml-backoffice-ui/src/Routing.tsx @@ -0,0 +1,151 @@ +/* + 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 { + urlPattern, + useCurrentLocation, + useNavigationContext, + useTranslationContext, +} from "@gnu-taler/web-util/browser"; +import { Fragment, VNode, h } from "preact"; + +import { assertUnreachable } from "@gnu-taler/taler-util"; +import { useEffect } from "preact/hooks"; +import { ExchangeAmlFrame } from "./ExchangeAmlFrame.js"; +import { useOfficer } from "./hooks/officer.js"; +import { Cases } from "./pages/Cases.js"; +import { Officer } from "./pages/Officer.js"; +import { CaseDetails } from "./pages/CaseDetails.js"; +import { CaseUpdate, SelectForm } from "./pages/CaseUpdate.js"; +import { HandleAccountNotReady } from "./pages/HandleAccountNotReady.js"; + +export function Routing(): VNode { + const session = useOfficer(); + + if (session.state === "ready") { + return ( + <ExchangeAmlFrame officer={session}> + <PrivateRouting /> + </ExchangeAmlFrame> + ); + } + return ( + <ExchangeAmlFrame> + <PublicRounting /> + </ExchangeAmlFrame> + ); +} + +const publicPages = { + config: urlPattern(/\/config/, () => "#/config"), + login: urlPattern(/\/login/, () => "#/login"), +}; + +function PublicRounting(): VNode { + const { i18n } = useTranslationContext(); + const location = useCurrentLocation(publicPages); + // const { navigateTo } = useNavigationContext(); + // const { config, lib } = useExchangeApiContext(); + // const [notification, notify, handleError] = useLocalNotification(); + const session = useOfficer(); + + if (location === undefined) { + if (session.state !== "ready") { + return <HandleAccountNotReady officer={session}/>; + } else { + return <div /> + } + } + + switch (location.name) { + case "config": { + return ( + <Fragment> + <div class="sm:mx-auto sm:w-full sm:max-w-sm"> + <h2 class="text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">{i18n.str`Welcome to exchange config!`}</h2> + </div> + </Fragment> + ); + } + case "login": { + return ( + <Fragment> + <div class="sm:mx-auto sm:w-full sm:max-w-sm"> + <h2 class="text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">{i18n.str`Welcome to exchange config!`}</h2> + </div> + </Fragment> + ); + } + default: + assertUnreachable(location); + } +} + +export const privatePages = { + account: urlPattern(/\/account/, () => "#/account"), + cases: urlPattern(/\/cases/, () => "#/cases"), + caseDetails: urlPattern<{ cid: string }>( + /\/case\/(?<cid>[a-zA-Z0-9]+)/, + ({ cid }) => `#/case/${cid}`, + ), + caseUpdate: urlPattern<{ cid: string; type: string }>( + /\/case\/(?<cid>[a-zA-Z0-9]+)\/new\/(?<type>[a-zA-Z0-9]+)/, + ({ cid, type }) => `#/case/${cid}/new/${type}`, + ), + caseNew: urlPattern<{ cid: string }>( + /\/case\/(?<cid>[a-zA-Z0-9]+)\/new/, + ({ cid }) => `#/case/${cid}/new`, + ), +}; + +function PrivateRouting(): VNode { + const { navigateTo } = useNavigationContext(); + const location = useCurrentLocation(privatePages); + useEffect(() => { + if (location === undefined) { + navigateTo(privatePages.account.url({})); + } + }, [location]); + + if (location === undefined) { + return <Fragment />; + } + + switch (location.name) { + case "account": { + return <Officer />; + } + case "caseDetails": { + return <CaseDetails account={location.values.cid} />; + } + case "caseUpdate": { + return ( + <CaseUpdate + account={location.values.cid} + type={location.values.type} + /> + ); + } + case "caseNew": { + return <SelectForm account={location.values.cid} />; + } + case "cases": { + return <Cases />; + } + default: + assertUnreachable(location); + } +} diff --git a/packages/aml-backoffice-ui/src/context/config.ts b/packages/aml-backoffice-ui/src/context/config.ts deleted file mode 100644 index d2bc58578..000000000 --- a/packages/aml-backoffice-ui/src/context/config.ts +++ /dev/null @@ -1,100 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022 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 { TalerExchangeApi, TalerExchangeHttpClient, TalerError } from "@gnu-taler/taler-util"; -import { BrowserFetchHttpLib, useTranslationContext } from "@gnu-taler/web-util/browser"; -import { ComponentChildren, createContext, FunctionComponent, h, VNode } from "preact"; -import { useContext, useEffect, useState } from "preact/hooks"; -import { ErrorLoading } from "@gnu-taler/web-util/browser"; - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -export type Type = { - url: URL, - config: TalerExchangeApi.ExchangeVersionResponse, - api: TalerExchangeHttpClient, -}; - -const Context = createContext<Type>(undefined!); - -export const useExchangeApiContext = (): Type => useContext(Context); -export const useMaybeExchangeApiContext = (): Type | undefined => useContext(Context); - -export function ExchangeApiContextTesting({ config, children }: { config: TalerExchangeApi.ExchangeVersionResponse, children?: ComponentChildren; }): VNode { - return h(Context.Provider, { - value: { url: new URL("http://testing"), config, api: null! }, - children - } - ) -} - -export type ConfigResult = undefined - | { type: "ok", config: TalerExchangeApi.ExchangeVersionResponse } - | { type: "incompatible", result: TalerExchangeApi.ExchangeVersionResponse, supported: string } - | { type: "error", error: TalerError } - -export const ExchangeApiProvider = ({ - baseUrl, - children, - frameOnError, -}: { - baseUrl: string, - children: ComponentChildren; - frameOnError: FunctionComponent<{ children: ComponentChildren }>, -}): VNode => { - const [checked, setChecked] = useState<ConfigResult>() - const { i18n } = useTranslationContext(); - const url = new URL(baseUrl) - const api = new TalerExchangeHttpClient(url.href, new BrowserFetchHttpLib()) - useEffect(() => { - api.getConfig() - .then((resp) => { - if (resp.type === "fail") { - setChecked({ type: "error", error: TalerError.fromUncheckedDetail(resp.detail) }); - } else if (api.isCompatible(resp.body.version)) { - setChecked({ type: "ok", config: resp.body }); - } else { - setChecked({ type: "incompatible", result: resp.body, supported: api.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, config: checked.config, api - } - return h(Context.Provider, { - value, - children, - }); -}; - diff --git a/packages/aml-backoffice-ui/src/context/settings.ts b/packages/aml-backoffice-ui/src/context/settings.ts new file mode 100644 index 000000000..6c61a7b4a --- /dev/null +++ b/packages/aml-backoffice-ui/src/context/settings.ts @@ -0,0 +1,44 @@ +/* + 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 } from "preact/hooks"; +import { UiSettings } from "../settings.js"; + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +export type Type = UiSettings; + +const initial: UiSettings = {}; +const Context = createContext<Type>(initial); + +export const useSettingsContext = (): Type => useContext(Context); + +export const SettingsProvider = ({ + children, + value, +}: { + value: UiSettings; + children: ComponentChildren; +}): VNode => { + return h(Context.Provider, { + value, + children, + }); +}; diff --git a/packages/aml-backoffice-ui/src/declaration.d.ts b/packages/aml-backoffice-ui/src/declaration.d.ts index 663271ec7..7868e41bd 100644 --- a/packages/aml-backoffice-ui/src/declaration.d.ts +++ b/packages/aml-backoffice-ui/src/declaration.d.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/forms.ts b/packages/aml-backoffice-ui/src/forms.ts index e1fb283d6..3ecec2bb0 100644 --- a/packages/aml-backoffice-ui/src/forms.ts +++ b/packages/aml-backoffice-ui/src/forms.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/forms/902_11e.ts b/packages/aml-backoffice-ui/src/forms/902_11e.ts index b1cdda6ba..ee4323f77 100644 --- a/packages/aml-backoffice-ui/src/forms/902_11e.ts +++ b/packages/aml-backoffice-ui/src/forms/902_11e.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/forms/902_12e.ts b/packages/aml-backoffice-ui/src/forms/902_12e.ts index d5a420177..0c14f6ee7 100644 --- a/packages/aml-backoffice-ui/src/forms/902_12e.ts +++ b/packages/aml-backoffice-ui/src/forms/902_12e.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/forms/902_13e.ts b/packages/aml-backoffice-ui/src/forms/902_13e.ts index 9e05e061a..a4851002e 100644 --- a/packages/aml-backoffice-ui/src/forms/902_13e.ts +++ b/packages/aml-backoffice-ui/src/forms/902_13e.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/forms/902_15e.ts b/packages/aml-backoffice-ui/src/forms/902_15e.ts index 72acf8d06..915b7dbf7 100644 --- a/packages/aml-backoffice-ui/src/forms/902_15e.ts +++ b/packages/aml-backoffice-ui/src/forms/902_15e.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/forms/902_1e.ts b/packages/aml-backoffice-ui/src/forms/902_1e.ts index c6d33b9ca..1e7c54f25 100644 --- a/packages/aml-backoffice-ui/src/forms/902_1e.ts +++ b/packages/aml-backoffice-ui/src/forms/902_1e.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/forms/902_4e.ts b/packages/aml-backoffice-ui/src/forms/902_4e.ts index 15446bb27..46803333b 100644 --- a/packages/aml-backoffice-ui/src/forms/902_4e.ts +++ b/packages/aml-backoffice-ui/src/forms/902_4e.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/forms/902_5e.ts b/packages/aml-backoffice-ui/src/forms/902_5e.ts index 40dd4d7ad..efe47b213 100644 --- a/packages/aml-backoffice-ui/src/forms/902_5e.ts +++ b/packages/aml-backoffice-ui/src/forms/902_5e.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/forms/902_9e.ts b/packages/aml-backoffice-ui/src/forms/902_9e.ts index 539e086f5..62fca5647 100644 --- a/packages/aml-backoffice-ui/src/forms/902_9e.ts +++ b/packages/aml-backoffice-ui/src/forms/902_9e.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/forms/declaration.ts b/packages/aml-backoffice-ui/src/forms/declaration.ts index d944a7a53..fb7b8f334 100644 --- a/packages/aml-backoffice-ui/src/forms/declaration.ts +++ b/packages/aml-backoffice-ui/src/forms/declaration.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/forms/icons.tsx b/packages/aml-backoffice-ui/src/forms/icons.tsx index 824f75c8f..8bd369c4f 100644 --- a/packages/aml-backoffice-ui/src/forms/icons.tsx +++ b/packages/aml-backoffice-ui/src/forms/icons.tsx @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/forms/index.ts b/packages/aml-backoffice-ui/src/forms/index.ts index 0a6c1df40..6c5f5d767 100644 --- a/packages/aml-backoffice-ui/src/forms/index.ts +++ b/packages/aml-backoffice-ui/src/forms/index.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/forms/simplest.ts b/packages/aml-backoffice-ui/src/forms/simplest.ts index 77ed4ebbb..bd512546d 100644 --- a/packages/aml-backoffice-ui/src/forms/simplest.ts +++ b/packages/aml-backoffice-ui/src/forms/simplest.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/hooks/form.ts b/packages/aml-backoffice-ui/src/hooks/form.ts new file mode 100644 index 000000000..fae11c05c --- /dev/null +++ b/packages/aml-backoffice-ui/src/hooks/form.ts @@ -0,0 +1,124 @@ +/* + 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 { AmountJson, TranslatedString } from "@gnu-taler/taler-util"; +import { useState } from "preact/hooks"; + +export type UIField = { + value: string | undefined; + onUpdate: (s: string) => void; + error: TranslatedString | undefined; +}; + +type FormHandler<T> = { + [k in keyof T]?: T[k] extends string + ? UIField + : T[k] extends AmountJson + ? UIField + : FormHandler<T[k]>; +}; + +export type FormValues<T> = { + [k in keyof T]: T[k] extends string + ? string | undefined + : T[k] extends AmountJson + ? string | undefined + : FormValues<T[k]>; +}; + +export type RecursivePartial<T> = { + [k in keyof T]?: T[k] extends string + ? string + : T[k] extends AmountJson + ? AmountJson + : RecursivePartial<T[k]>; +}; + +export type FormErrors<T> = { + [k in keyof T]?: T[k] extends string + ? TranslatedString + : T[k] extends AmountJson + ? TranslatedString + : FormErrors<T[k]>; +}; + +export type FormStatus<T> = + | { + status: "ok"; + result: T; + errors: undefined; + } + | { + status: "fail"; + result: RecursivePartial<T>; + errors: FormErrors<T>; + }; + +function constructFormHandler<T>( + form: FormValues<T>, + updateForm: (d: FormValues<T>) => void, + errors: FormErrors<T> | undefined, +): FormHandler<T> { + + const keys = Object.keys(form) as Array<keyof T>; + + const handler = keys.reduce((prev, fieldName) => { + const currentValue: unknown = form[fieldName]; + const currentError: unknown = errors ? errors[fieldName] : undefined; + function updater(newValue: unknown) { + updateForm({ ...form, [fieldName]: newValue }); + } + if (typeof currentValue === "object") { + // @ts-expect-error FIXME better typing + const group = constructFormHandler(currentValue, updater, currentError); + // @ts-expect-error FIXME better typing + prev[fieldName] = group; + return prev; + } + const field: UIField = { + // @ts-expect-error FIXME better typing + error: currentError, + // @ts-expect-error FIXME better typing + value: currentValue, + onUpdate: updater, + }; + // @ts-expect-error FIXME better typing + prev[fieldName] = field; + return prev; + }, {} as FormHandler<T>); + + return handler; +} + +/** + * FIXME: Consider sending this to web-utils + * + * + * @param defaultValue + * @param check + * @returns + */ +export function useFormState<T>( + defaultValue: FormValues<T>, + check: (f: FormValues<T>) => FormStatus<T>, +): [FormHandler<T>, FormStatus<T>] { + const [form, updateForm] = useState<FormValues<T>>(defaultValue); + + const status = check(form); + const handler = constructFormHandler(form, updateForm, status.errors); + + return [handler, status]; +} diff --git a/packages/aml-backoffice-ui/src/hooks/useOfficer.ts b/packages/aml-backoffice-ui/src/hooks/officer.ts index b49c9db26..3ac4c857c 100644 --- a/packages/aml-backoffice-ui/src/hooks/useOfficer.ts +++ b/packages/aml-backoffice-ui/src/hooks/officer.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 @@ -26,14 +26,11 @@ import { createNewOfficerAccount, decodeCrock, encodeCrock, - unlockOfficerAccount + unlockOfficerAccount, } from "@gnu-taler/taler-util"; -import { - buildStorageKey, - useLocalStorage -} from "@gnu-taler/web-util/browser"; +import { buildStorageKey, useExchangeApiContext, useLocalStorage } from "@gnu-taler/web-util/browser"; import { useMemo } from "preact/hooks"; -import { useMaybeExchangeApiContext } from "../context/config.js"; +import { usePreferences } from "./preferences.js"; export interface Officer { account: LockedAccount; @@ -43,9 +40,9 @@ export interface Officer { const codecForLockedAccount = codecForString() as Codec<LockedAccount>; type OfficerAccountString = { - id: string, + id: string; strKey: string; -} +}; export const codecForOfficerAccount = (): Codec<OfficerAccountString> => buildCodecForObject<OfficerAccountString>() @@ -78,46 +75,55 @@ interface OfficerReady { } const OFFICER_KEY = buildStorageKey("officer", codecForOfficer()); -const DEV_ACCOUNT_KEY = buildStorageKey("account-dev", codecForOfficerAccount()); +const DEV_ACCOUNT_KEY = buildStorageKey( + "account-dev", + codecForOfficerAccount(), +); export function useOfficer(): OfficerState { - const exchangeContext = useMaybeExchangeApiContext(); - // dev account, is save when reloaded. + const exchangeContext = useExchangeApiContext(); + const [pref] = usePreferences(); + pref.keepSessionAfterReload; + // dev account, is kept on reloaded. const accountStorage = useLocalStorage(DEV_ACCOUNT_KEY); const account = useMemo(() => { - if (!accountStorage.value) return undefined + if (!accountStorage.value) return undefined; return { id: accountStorage.value.id as OfficerId, - signingKey: decodeCrock(accountStorage.value.strKey) as SigningKey - } - }, [accountStorage.value?.id, accountStorage.value?.strKey]) + signingKey: decodeCrock(accountStorage.value.strKey) as SigningKey, + }; + }, [accountStorage.value?.id, accountStorage.value?.strKey]); const officerStorage = useLocalStorage(OFFICER_KEY); const officer = useMemo(() => { - if (!officerStorage.value) return undefined - return officerStorage.value - }, [officerStorage.value?.account, officerStorage.value?.when.t_ms]) + if (!officerStorage.value) return undefined; + return officerStorage.value; + }, [officerStorage.value?.account, officerStorage.value?.when.t_ms]); if (officer === undefined) { return { state: "not-found", create: async (pwd: string) => { - if (!exchangeContext) return; - const req = await fetch(new URL("seed", exchangeContext.api.baseUrl).href) - const b = await req.blob() - const ar = await b.arrayBuffer() - const uintar = new Uint8Array(ar) - - const { id, safe, signingKey } = await createNewOfficerAccount(pwd, uintar); + const req = await fetch( + new URL("seed", exchangeContext.lib.exchange.baseUrl).href, + ); + const b = await req.blob(); + const ar = await b.arrayBuffer(); + const uintar = new Uint8Array(ar); + + const { id, safe, signingKey } = await createNewOfficerAccount( + pwd, + uintar, + ); officerStorage.update({ account: safe, when: AbsoluteTime.now(), }); // accountStorage.update({ id, signingKey }); - const strKey = encodeCrock(signingKey) - accountStorage.update({ id, strKey }) + const strKey = encodeCrock(signingKey); + accountStorage.update({ id, strKey }); }, }; } @@ -131,7 +137,10 @@ export function useOfficer(): OfficerState { tryUnlock: async (pwd: string) => { const ac = await unlockOfficerAccount(officer.account, pwd); // accountStorage.update(ac); - accountStorage.update({ id: ac.id, strKey: encodeCrock(ac.signingKey) }) + accountStorage.update({ + id: ac.id, + strKey: encodeCrock(ac.signingKey), + }); }, }; } diff --git a/packages/aml-backoffice-ui/src/hooks/useSettings.ts b/packages/aml-backoffice-ui/src/hooks/preferences.ts index 55cdafb23..12e85d249 100644 --- a/packages/aml-backoffice-ui/src/hooks/useSettings.ts +++ b/packages/aml-backoffice-ui/src/hooks/preferences.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 @@ -20,52 +20,66 @@ import { buildCodecForObject, codecForBoolean } from "@gnu-taler/taler-util"; -import { buildStorageKey, useLocalStorage, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { + buildStorageKey, + useLocalStorage, + useTranslationContext, +} from "@gnu-taler/web-util/browser"; -interface Settings { +interface Preferences { allowInsecurePassword: boolean; keepSessionAfterReload: boolean; } -export function getAllBooleanSettings(): Array<keyof Settings> { - return ["allowInsecurePassword", "keepSessionAfterReload"] -} - -export function getLabelForSetting(k: keyof Settings, i18n: ReturnType<typeof useTranslationContext>["i18n"]): TranslatedString { - switch (k) { - case "allowInsecurePassword": return i18n.str`Allow Insecure password` - case "keepSessionAfterReload": return i18n.str`Keep session after reload` - } -} - -export const codecForSettings = (): Codec<Settings> => - buildCodecForObject<Settings>() +export const codecForPreferences = (): Codec<Preferences> => + buildCodecForObject<Preferences>() .property("allowInsecurePassword", (codecForBoolean())) .property("keepSessionAfterReload", (codecForBoolean())) - .build("Settings"); + .build("Preferences"); -const defaultSettings: Settings = { +const defaultPreferences: Preferences = { allowInsecurePassword: false, keepSessionAfterReload: false, }; -const EXCHANGE_SETTINGS_KEY = buildStorageKey( - "exchange-settings", - codecForSettings(), +const PREFERENCES_KEY = buildStorageKey( + "exchange-preferences", + codecForPreferences(), ); - -export function useSettings(): [ - Readonly<Settings>, - <T extends keyof Settings>(key: T, value: Settings[T]) => void, +/** + * User preferences. + * + * @returns tuple of [state, update()] + */ +export function usePreferences(): [ + Readonly<Preferences>, + <T extends keyof Preferences>(key: T, value: Preferences[T]) => void, ] { const { value, update } = useLocalStorage( - EXCHANGE_SETTINGS_KEY, - defaultSettings, + PREFERENCES_KEY, + defaultPreferences, ); - function updateField<T extends keyof Settings>(k: T, v: Settings[T]) { + function updateField<T extends keyof Preferences>(k: T, v: Preferences[T]) { const newValue = { ...value, [k]: v }; update(newValue); } return [value, updateField]; } + +export function getAllBooleanPreferences(): Array<keyof Preferences> { + return [ + "allowInsecurePassword", + "keepSessionAfterReload", + ]; +} + +export function getLabelForPreferences( + k: keyof Preferences, + i18n: ReturnType<typeof useTranslationContext>["i18n"], +): TranslatedString { + switch (k) { + case "allowInsecurePassword": return i18n.str`Allow Insecure password` + case "keepSessionAfterReload": return i18n.str`Keep session after reload` + } +} diff --git a/packages/aml-backoffice-ui/src/hooks/useBackend.ts b/packages/aml-backoffice-ui/src/hooks/useBackend.ts deleted file mode 100644 index 310f7fe59..000000000 --- a/packages/aml-backoffice-ui/src/hooks/useBackend.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022 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 { canonicalizeBaseUrl } from "@gnu-taler/taler-util"; -import { uiSettings } from "../settings.js"; - - -export function getInitialBackendBaseURL(): string { - const overrideUrl = - typeof localStorage !== "undefined" - ? localStorage.getItem("exchange-base-url") - : undefined; - - let result: string; - - if (!overrideUrl) { - //normal path - if (!uiSettings.backendBaseURL) { - console.error( - "ERROR: backendBaseURL was overridden by a setting file and missing. Setting value to 'window.origin'", - ); - result = typeof window !== "undefined" ? window.origin : "localhost" - } else { - result = uiSettings.backendBaseURL; - } - } else { - // testing/development path - result = overrideUrl - } - try { - return canonicalizeBaseUrl(result) - } catch (e) { - //fall back - return canonicalizeBaseUrl(window.origin) - } -} diff --git a/packages/aml-backoffice-ui/src/hooks/useCaseDetails.ts b/packages/aml-backoffice-ui/src/hooks/useCaseDetails.ts index de1c5af17..78574ada4 100644 --- a/packages/aml-backoffice-ui/src/hooks/useCaseDetails.ts +++ b/packages/aml-backoffice-ui/src/hooks/useCaseDetails.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 @@ -16,15 +16,15 @@ import { OfficerAccount, PaytoString, TalerExchangeResultByMethod, TalerHttpError } from "@gnu-taler/taler-util"; // FIX default import https://github.com/microsoft/TypeScript/issues/49189 import _useSWR, { SWRHook } from "swr"; -import { useExchangeApiContext } from "../context/config.js"; -import { useOfficer } from "./useOfficer.js"; +import { useOfficer } from "./officer.js"; +import { useExchangeApiContext } from "@gnu-taler/web-util/browser"; const useSWR = _useSWR as unknown as SWRHook; export function useCaseDetails(paytoHash: string) { const officer = useOfficer(); const session = officer.state === "ready" ? officer.account : undefined; - const { api } = useExchangeApiContext(); + const { lib: {exchange: api} } = useExchangeApiContext(); async function fetcher([officer, account]: [OfficerAccount, PaytoString]) { return await api.getDecisionDetails(officer, account) diff --git a/packages/aml-backoffice-ui/src/hooks/useCases.ts b/packages/aml-backoffice-ui/src/hooks/useCases.ts index 7c8bb5bc1..59d1c9001 100644 --- a/packages/aml-backoffice-ui/src/hooks/useCases.ts +++ b/packages/aml-backoffice-ui/src/hooks/useCases.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 @@ -16,11 +16,16 @@ import { useState } from "preact/hooks"; // FIX default import https://github.com/microsoft/TypeScript/issues/49189 -import { OfficerAccount, OperationOk, TalerExchangeResultByMethod, TalerHttpError } from "@gnu-taler/taler-util"; +import { + OfficerAccount, + OperationOk, + TalerExchangeResultByMethod, + TalerHttpError, +} from "@gnu-taler/taler-util"; import _useSWR, { SWRHook } from "swr"; -import { useExchangeApiContext } from "../context/config.js"; import { AmlExchangeBackend } from "../utils/types.js"; -import { useOfficer } from "./useOfficer.js"; +import { useOfficer } from "./officer.js"; +import { useExchangeApiContext } from "@gnu-taler/web-util/browser"; const useSWR = _useSWR as unknown as SWRHook; export const PAGINATED_LIST_SIZE = 10; @@ -37,17 +42,28 @@ export const PAGINATED_LIST_REQUEST = PAGINATED_LIST_SIZE + 1; export function useCases(state: AmlExchangeBackend.AmlState) { const officer = useOfficer(); const session = officer.state === "ready" ? officer.account : undefined; - const { api } = useExchangeApiContext(); + const { + lib: { exchange: api }, + } = useExchangeApiContext(); const [offset, setOffset] = useState<string>(); - async function fetcher([officer, state, offset]: [OfficerAccount, AmlExchangeBackend.AmlState, string | undefined]) { + async function fetcher([officer, state, offset]: [ + OfficerAccount, + AmlExchangeBackend.AmlState, + string | undefined, + ]) { return await api.getDecisionsByState(officer, state, { - order: "asc", offset, limit: PAGINATED_LIST_REQUEST - }) + order: "asc", + offset, + limit: PAGINATED_LIST_REQUEST, + }); } - const { data, error } = useSWR<TalerExchangeResultByMethod<"getDecisionsByState">, TalerHttpError>( + const { data, error } = useSWR< + TalerExchangeResultByMethod<"getDecisionsByState">, + TalerHttpError + >( !session ? undefined : [session, state, offset, "getDecisionsByState"], fetcher, ); @@ -56,7 +72,9 @@ export function useCases(state: AmlExchangeBackend.AmlState) { if (data === undefined) return undefined; if (data.type !== "ok") return data; - return buildPaginatedResult(data.body.records, offset, setOffset, (d) => String(d.rowid)); + return buildPaginatedResult(data.body.records, offset, setOffset, (d) => + String(d.rowid), + ); } type PaginatedResult<T> = OperationOk<T> & { @@ -64,11 +82,15 @@ type PaginatedResult<T> = OperationOk<T> & { isFirstPage: boolean; loadNext(): void; loadFirst(): void; -} +}; //TODO: consider sending this to web-util -export function buildPaginatedResult<R, OffId>(data: R[], offset: OffId | undefined, setOffset: (o: OffId | undefined) => void, getId: (r: R) => OffId): PaginatedResult<R[]> { - +export function buildPaginatedResult<R, OffId>( + data: R[], + offset: OffId | undefined, + setOffset: (o: OffId | undefined) => void, + getId: (r: R) => OffId, +): PaginatedResult<R[]> { const isLastPage = data.length < PAGINATED_LIST_REQUEST; const isFirstPage = offset === undefined; @@ -83,7 +105,7 @@ export function buildPaginatedResult<R, OffId>(data: R[], offset: OffId | undefi isFirstPage, loadNext: () => { if (!result.length) return; - const id = getId(result[result.length - 1]) + const id = getId(result[result.length - 1]); setOffset(id); }, loadFirst: () => { diff --git a/packages/aml-backoffice-ui/src/i18n/bank.pot b/packages/aml-backoffice-ui/src/i18n/bank.pot index 66e98976f..39f9de5ce 100644 --- a/packages/aml-backoffice-ui/src/i18n/bank.pot +++ b/packages/aml-backoffice-ui/src/i18n/bank.pot @@ -1,5 +1,5 @@ # This file is part of GNU Taler -# (C) 2022 Taler Systems S.A. +# (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 diff --git a/packages/aml-backoffice-ui/src/i18n/fr.po b/packages/aml-backoffice-ui/src/i18n/fr.po index 203d55343..8148f6a0c 100644 --- a/packages/aml-backoffice-ui/src/i18n/fr.po +++ b/packages/aml-backoffice-ui/src/i18n/fr.po @@ -1,5 +1,5 @@ # This file is part of GNU Taler -# (C) 2022 Taler Systems S.A. +# (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 diff --git a/packages/aml-backoffice-ui/src/i18n/poheader b/packages/aml-backoffice-ui/src/i18n/poheader index a251e9584..d7a371934 100644 --- a/packages/aml-backoffice-ui/src/i18n/poheader +++ b/packages/aml-backoffice-ui/src/i18n/poheader @@ -1,5 +1,5 @@ # This file is part of GNU Taler -# (C) 2022 Taler Systems S.A. +# (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 diff --git a/packages/aml-backoffice-ui/src/i18n/strings-prelude b/packages/aml-backoffice-ui/src/i18n/strings-prelude index a0aeb8268..3ab0fd1e5 100644 --- a/packages/aml-backoffice-ui/src/i18n/strings-prelude +++ b/packages/aml-backoffice-ui/src/i18n/strings-prelude @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/i18n/strings.ts b/packages/aml-backoffice-ui/src/i18n/strings.ts index a779bbc49..4f7419eb4 100644 --- a/packages/aml-backoffice-ui/src/i18n/strings.ts +++ b/packages/aml-backoffice-ui/src/i18n/strings.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/index.html b/packages/aml-backoffice-ui/src/index.html index c1de73520..b7f73d0a2 100644 --- a/packages/aml-backoffice-ui/src/index.html +++ b/packages/aml-backoffice-ui/src/index.html @@ -1,6 +1,6 @@ <!-- This file is part of GNU Taler - (C) 2021--2022 Taler Systems S.A. + (C) 2021--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 diff --git a/packages/aml-backoffice-ui/src/index.tsx b/packages/aml-backoffice-ui/src/index.tsx index ad0d394d8..c6f6b4a8f 100644 --- a/packages/aml-backoffice-ui/src/index.tsx +++ b/packages/aml-backoffice-ui/src/index.tsx @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/pages.ts b/packages/aml-backoffice-ui/src/pages.ts deleted file mode 100644 index 1c8fdde4a..000000000 --- a/packages/aml-backoffice-ui/src/pages.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022 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 { TranslatedString } from "@gnu-taler/taler-util"; -import { CaseDetails } from "./pages/CaseDetails.js"; -import { Cases, HomeIcon, PeopleIcon } from "./pages/Cases.js"; -import { NewFormEntry } from "./pages/NewFormEntry.js"; -import { Officer } from "./pages/Officer.js"; -import { PageEntry, pageDefinition } from "./route.js"; -// import homeLogo from "./assets/home.svg"; -// import peopleLogo from "./assets/people.svg"; -const cases: PageEntry = { - url: "#/cases", - view: Cases, - name: "Cases" as TranslatedString, - Icon: HomeIcon, -}; - -const officer: PageEntry = { - url: "#/officer", - view: Officer, - name: "Officer" as TranslatedString, - Icon: PeopleIcon, -}; - -const account: PageEntry<{ account: string }> = { - url: pageDefinition("#/account/:account"), - view: CaseDetails, - name: "Account" as TranslatedString, - // icon: () => undefined, -}; - -const newFormEntry: PageEntry<{ account?: string; type?: string }> = { - url: pageDefinition("#/account/:account/new/:type?"), - view: NewFormEntry, - name: "New Form" as TranslatedString, - // icon: () => undefined, -}; - - -export const Pages = { - cases, - officer, - account, - newFormEntry, -}; diff --git a/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.stories.tsx b/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.stories.tsx index 0b055f682..0c82a4a0e 100644 --- a/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.stories.tsx +++ b/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.stories.tsx @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx b/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx index 77d4b8167..db034c996 100644 --- a/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx +++ b/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 @@ -24,17 +24,17 @@ import { buildCodecForObject, codecForNumber, codecForString, - codecOptional + codecOptional, } from "@gnu-taler/taler-util"; import { DefaultForm, + useExchangeApiContext, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { h } from "preact"; -import { useExchangeApiContext } from "../context/config.js"; import { BaseForm, FormMetadata, uiForms } from "../forms/declaration.js"; -import { Pages } from "../pages.js"; import { AmlExchangeBackend } from "../utils/types.js"; +import { privatePages } from "../Routing.js"; export function AntiMoneyLaunderingForm({ account, @@ -68,7 +68,10 @@ export function AntiMoneyLaunderingForm({ form={theForm.impl(initial)} onUpdate={() => {}} onSubmit={(formValue) => { - if (formValue.state === undefined || formValue.threshold === undefined) { + if ( + formValue.state === undefined || + formValue.threshold === undefined + ) { return; } const validatedForm = formValue as BaseForm; @@ -87,7 +90,7 @@ export function AntiMoneyLaunderingForm({ > <div class="mt-6 flex items-center justify-end gap-x-6"> <a - href={Pages.account.url({ account })} + href={privatePages.caseDetails.url({ cid: account })} class="text-sm font-semibold leading-6 text-gray-900" > <i18n.Translate>Cancel</i18n.Translate> @@ -133,7 +136,10 @@ export function parseJustification( s: string, listOfAllKnownForms: FormMetadata<BaseForm>[], ): - | OperationOk<{ justification: Justification; metadata: FormMetadata<BaseForm> }> + | OperationOk<{ + justification: Justification; + metadata: FormMetadata<BaseForm>; + }> | OperationFail<ParseJustificationFail> { try { const justification = JSON.parse(s); diff --git a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx index a91f3d107..576cdbbb9 100644 --- a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx +++ b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 @@ -34,13 +34,13 @@ import { VNode, h } from "preact"; import { useState } from "preact/hooks"; import { BaseForm, FormMetadata, uiForms } from "../forms/declaration.js"; import { useCaseDetails } from "../hooks/useCaseDetails.js"; -import { Pages } from "../pages.js"; import { AmlExchangeBackend } from "../utils/types.js"; import { Justification, parseJustification, } from "./AntiMoneyLaunderingForm.js"; import { ShowConsolidated } from "./ShowConsolidated.js"; +import { privatePages } from "../Routing.js"; export type AmlEvent = | AmlFormEvent @@ -201,7 +201,7 @@ export function CaseDetails({ account }: { account: string }) { return ( <div> <a - href={Pages.newFormEntry.url({ account })} + href={privatePages.caseNew.url({ cid: account })} class="m-4 block rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700" > <i18n.Translate>New AML form</i18n.Translate> diff --git a/packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx b/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx index 7c10132fa..c4bff1f9f 100644 --- a/packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx +++ b/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 @@ -23,35 +23,31 @@ import { } from "@gnu-taler/taler-util"; import { LocalNotificationBanner, + useExchangeApiContext, useLocalNotification, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; -import { useExchangeApiContext } from "../context/config.js"; +import { privatePages } from "../Routing.js"; import { uiForms } from "../forms/declaration.js"; -import { useOfficer } from "../hooks/useOfficer.js"; -import { Pages } from "../pages.js"; +import { useOfficer } from "../hooks/officer.js"; import { AntiMoneyLaunderingForm } from "./AntiMoneyLaunderingForm.js"; import { HandleAccountNotReady } from "./HandleAccountNotReady.js"; -export function NewFormEntry({ +export function CaseUpdate({ account, type, }: { - account?: string; - type?: string; + account: string; + type: string; }): VNode { const { i18n } = useTranslationContext(); const officer = useOfficer(); - const { api } = useExchangeApiContext(); + const { + lib: { exchange: api }, + } = useExchangeApiContext(); const [notification, notify, handleError] = useLocalNotification(); - if (!account) { - return <div>no account</div>; - } - if (!type) { - return <SelectForm account={account} />; - } if (officer.state !== "ready") { return <HandleAccountNotReady officer={officer} />; } @@ -78,7 +74,7 @@ export function NewFormEntry({ decision, ); if (resp.type === "ok") { - window.location.href = Pages.cases.url; + window.location.href = privatePages.cases.url({}); return; } switch (resp.case) { @@ -115,7 +111,7 @@ export function NewFormEntry({ ); } -function SelectForm({ account }: { account: string }) { +export function SelectForm({ account }: { account: string }) { const { i18n } = useTranslationContext(); return ( <div> @@ -124,7 +120,7 @@ function SelectForm({ account }: { account: string }) { return ( <a key={form.id} - href={Pages.newFormEntry.url({ account, type: form.id })} + href={privatePages.caseUpdate.url({ cid: account, type: form.id })} class="m-4 block rounded-md w-fit border-0 p-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-600" > {form.label} diff --git a/packages/aml-backoffice-ui/src/pages/Cases.stories.tsx b/packages/aml-backoffice-ui/src/pages/Cases.stories.tsx index 223cbbb84..dcbd366a4 100644 --- a/packages/aml-backoffice-ui/src/pages/Cases.stories.tsx +++ b/packages/aml-backoffice-ui/src/pages/Cases.stories.tsx @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/pages/Cases.tsx b/packages/aml-backoffice-ui/src/pages/Cases.tsx index 7ecc85e44..e928b831f 100644 --- a/packages/aml-backoffice-ui/src/pages/Cases.tsx +++ b/packages/aml-backoffice-ui/src/pages/Cases.tsx @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 @@ -28,11 +28,11 @@ import { import { VNode, h } from "preact"; import { useState } from "preact/hooks"; import { useCases } from "../hooks/useCases.js"; -import { Pages } from "../pages.js"; import { amlStateConverter } from "../utils/converter.js"; import { AmlExchangeBackend } from "../utils/types.js"; import { Officer } from "./Officer.js"; +import { privatePages } from "../Routing.js"; export function CasesUI({ records, @@ -130,7 +130,7 @@ export function CasesUI({ <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500 "> <div class="text-gray-900"> <a - href={Pages.account.url({ account: r.h_payto })} + href={privatePages.caseDetails.url({ cid: r.h_payto })} class="text-indigo-600 hover:text-indigo-900" > {r.h_payto.substring(0, 16)}... diff --git a/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx b/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx index 568ec81fa..9afd0d212 100644 --- a/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx +++ b/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 @@ -20,7 +20,7 @@ import { useTranslationContext, } from "@gnu-taler/web-util/browser"; import { VNode, h } from "preact"; -import { useSettings } from "../hooks/useSettings.js"; +import { usePreferences } from "../hooks/preferences.js"; export function CreateAccount({ onNewAccount, @@ -32,8 +32,8 @@ export function CreateAccount({ password: string; repeat: string; }>(); - const [settings] = useSettings(); - + const [settings] = usePreferences(); + return ( <div class="flex min-h-full flex-col "> <div class="sm:mx-auto sm:w-full sm:max-w-md"> diff --git a/packages/aml-backoffice-ui/src/pages/HandleAccountNotReady.tsx b/packages/aml-backoffice-ui/src/pages/HandleAccountNotReady.tsx index c86906929..b23798172 100644 --- a/packages/aml-backoffice-ui/src/pages/HandleAccountNotReady.tsx +++ b/packages/aml-backoffice-ui/src/pages/HandleAccountNotReady.tsx @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 @@ -15,7 +15,7 @@ */ import { assertUnreachable } from "@gnu-taler/taler-util"; import { VNode, h } from "preact"; -import { OfficerNotReady } from "../hooks/useOfficer.js"; +import { OfficerNotReady } from "../hooks/officer.js"; import { CreateAccount } from "./CreateAccount.js"; import { UnlockAccount } from "./UnlockAccount.js"; diff --git a/packages/aml-backoffice-ui/src/pages/Officer.tsx b/packages/aml-backoffice-ui/src/pages/Officer.tsx index eaa961b90..ad8ae1ed3 100644 --- a/packages/aml-backoffice-ui/src/pages/Officer.tsx +++ b/packages/aml-backoffice-ui/src/pages/Officer.tsx @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 @@ -13,22 +13,27 @@ 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 { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { + useExchangeApiContext, + useTranslationContext, +} from "@gnu-taler/web-util/browser"; import { h } from "preact"; -import { getInitialBackendBaseURL } from "../hooks/useBackend.js"; -import { useOfficer } from "../hooks/useOfficer.js"; -import { uiSettings } from "../settings.js"; +import { useOfficer } from "../hooks/officer.js"; import { HandleAccountNotReady } from "./HandleAccountNotReady.js"; +import { useSettingsContext } from "../context/settings.js"; export function Officer() { const officer = useOfficer(); - const { i18n } = useTranslationContext() + const settings = useSettingsContext(); + const { lib } = useExchangeApiContext(); + + const { i18n } = useTranslationContext(); if (officer.state !== "ready") { return <HandleAccountNotReady officer={officer} />; } - const url = new URL(getInitialBackendBaseURL()) - const signupEmail = uiSettings.signupEmail ?? `aml-signup@${url.hostname}` + const url = new URL("./", lib.exchange.baseUrl); + const signupEmail = settings.signupEmail ?? `aml-signup@${url.hostname}`; return ( <div> @@ -40,7 +45,11 @@ export function Officer() { </div> <p> <a - href={`mailto:${signupEmail}?subject=${encodeURIComponent("Request AML signup")}&body=${encodeURIComponent(`I want my AML account\n\n\nPubKey: ${officer.account.id}`)}`} + href={`mailto:${signupEmail}?subject=${encodeURIComponent( + "Request AML signup", + )}&body=${encodeURIComponent( + `I want my AML account\n\n\nPubKey: ${officer.account.id}`, + )}`} target="_blank" rel="noreferrer" class="m-4 block rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700" diff --git a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.stories.tsx b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.stories.tsx index fa88277ec..1cb50efd2 100644 --- a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.stories.tsx +++ b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.stories.tsx @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx index 15b109bee..c1f7e02cb 100644 --- a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx +++ b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx b/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx index 8066155ac..de634c9e0 100644 --- a/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx +++ b/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/pages/index.stories.ts b/packages/aml-backoffice-ui/src/pages/index.stories.ts index 22435178b..b2cbf485e 100644 --- a/packages/aml-backoffice-ui/src/pages/index.stories.ts +++ b/packages/aml-backoffice-ui/src/pages/index.stories.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 diff --git a/packages/aml-backoffice-ui/src/route.ts b/packages/aml-backoffice-ui/src/route.ts deleted file mode 100644 index ffd54d6af..000000000 --- a/packages/aml-backoffice-ui/src/route.ts +++ /dev/null @@ -1,239 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022 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 { TranslatedString } from "@gnu-taler/taler-util"; -import { createHashHistory } from "history"; -import { ComponentChildren, h as create, createContext, VNode } from "preact"; -import { useContext, useEffect, useState } from "preact/hooks"; - -type ContextType = { - onChange: (listener: () => void) => VoidFunction; -}; -const nullChangeListener = { onChange: () => () => {} }; -const Context = createContext<ContextType>(nullChangeListener); - -export const usePathChangeContext = (): ContextType => useContext(Context); - -export function HashPathProvider({ - children, -}: { - children: ComponentChildren; -}): VNode { - const history = createHashHistory(); - return create( - Context.Provider, - { value: { onChange: history.listen }, children }, - children, - ); -} - -type PageDefinition<DynamicPart extends Record<string, string>> = { - pattern: string; - (params: DynamicPart): string; -}; - -function replaceAll( - pattern: string, - vars: Record<string, string>, - values: Record<string, string>, -): string { - let result = pattern; - for (const v in vars) { - result = result.replace(vars[v], !values[v] ? "" : values[v]); - } - return result; -} - -export function pageDefinition<T extends Record<string, string>>( - pattern: string, -): PageDefinition<T> { - const patternParams = pattern.match(/(:[\w?]*)/g); - if (!patternParams) - throw Error( - `page definition pattern ${pattern} doesn't have any parameter`, - ); - - const vars = patternParams.reduce( - (prev, cur) => { - const pName = cur.match(/(\w+)/g); - - //skip things like :? in the path pattern - if (!pName || !pName[0]) return prev; - const name = pName[0]; - return { ...prev, [name]: cur }; - }, - {} as Record<string, string>, - ); - - const f = (values: T): string => replaceAll(pattern, vars, values); - f.pattern = pattern; - return f; -} - -export type PageEntry<T = unknown> = T extends Record<string, string> - ? { - url: PageDefinition<T>; - view: (props: T) => VNode; - name: TranslatedString; - Icon?: () => VNode; - } - : T extends unknown - ? { - url: string; - view: (props: {}) => VNode; - name: TranslatedString; - Icon?: () => VNode; - } - : never; - -export function Router({ - pageList, - onNotFound, -}: { - pageList: Array<PageEntry<any>>; - onNotFound: () => VNode; -}): VNode { - const current = useCurrentLocation(pageList); - if (current !== undefined) { - return create(current.page.view, current.values); - } - return onNotFound(); -} - -type Location = { - page: PageEntry<any>; - path: string; - values: Record<string, string>; -}; -export function useCurrentLocation( - pageList: Array<PageEntry<any>>, -): Location | undefined { - const [currentLocation, setCurrentLocation] = useState< - Location | null | undefined - >(null); - const path = usePathChangeContext(); - useEffect(() => { - return path.onChange(() => { - const result = doSync( - window.location.hash, - new URLSearchParams(window.location.search), - pageList, - ); - setCurrentLocation(result); - }); - }, []); - if (currentLocation === null) { - return doSync( - window.location.hash, - new URLSearchParams(window.location.search), - pageList, - ); - } - return currentLocation; -} - -export function useChangeLocation() { - const [location, setLocation] = useState(window.location.hash); - const path = usePathChangeContext(); - useEffect(() => { - return path.onChange(() => { - setLocation(window.location.hash); - }); - }, []); - return location; -} - -/** - * Search path in the pageList - * get the values from the path found - * add params from searchParams - * - * @param path - * @param params - */ -export function doSync( - path: string, - params: URLSearchParams, - pageList: Array<PageEntry<any>>, -): Location | undefined { - for (let idx = 0; idx < pageList.length; idx++) { - const page = pageList[idx]; - if (typeof page.url === "string") { - if (page.url === path) { - const values: Record<string, string> = {}; - params.forEach((v, k) => { - values[k] = v; - }); - return { page, values, path }; - } - } else { - const values = doestUrlMatchToRoute(path, page.url.pattern); - if (values !== undefined) { - params.forEach((v, k) => { - values[k] = v; - }); - return { page, values, path }; - } - } - } - return undefined; -} - -function doestUrlMatchToRoute( - url: string, - route: string, -): undefined | Record<string, string> { - const paramsPattern = /(?:\?([^#]*))?$/; - // const paramsPattern = /(?:\?([^#]*))?(#.*)?$/; - const params = url.match(paramsPattern); - const urlWithoutParams = url.replace(paramsPattern, ""); - - const result: Record<string, string> = {}; - if (params && params[1]) { - const paramList = params[1].split("&"); - for (let i = 0; i < paramList.length; i++) { - const idx = paramList[i].indexOf("="); - const name = paramList[i].substring(0, idx); - const value = paramList[i].substring(idx + 1); - result[decodeURIComponent(name)] = decodeURIComponent(value); - } - } - const urlSeg = urlWithoutParams.split("/"); - const routeSeg = route.split("/"); - let max = Math.max(urlSeg.length, routeSeg.length); - for (let i = 0; i < max; i++) { - if (routeSeg[i] && routeSeg[i].charAt(0) === ":") { - const param = routeSeg[i].replace(/(^:|[+*?]+$)/g, ""); - - const flags = (routeSeg[i].match(/[+*?]+$/) || EMPTY)[0] || ""; - const plus = ~flags.indexOf("+"); - const star = ~flags.indexOf("*"); - const val = urlSeg[i] || ""; - - if (!val && !star && (flags.indexOf("?") < 0 || plus)) { - return undefined; - } - result[param] = decodeURIComponent(val); - if (plus || star) { - result[param] = urlSeg.slice(i).map(decodeURIComponent).join("/"); - break; - } - } else if (routeSeg[i] !== urlSeg[i]) { - return undefined; - } - } - return result; -} -const EMPTY: Record<string, string> = {}; diff --git a/packages/aml-backoffice-ui/src/settings.ts b/packages/aml-backoffice-ui/src/settings.ts index 600fa0eee..a4a693d7d 100644 --- a/packages/aml-backoffice-ui/src/settings.ts +++ b/packages/aml-backoffice-ui/src/settings.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 @@ -14,17 +14,77 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ + import { + Codec, + buildCodecForObject, + canonicalizeBaseUrl, + codecForString, + codecOptional +} from "@gnu-taler/taler-util"; + export interface UiSettings { + // Where libeufin backend is localted + // default: window.origin without "webui/" backendBaseURL?: string; + // Shows a button "create random account" in the registration form + // Useful for testing + // default: false signupEmail?: string; } /** - * Global settings for the UI. + * Global settings for the bank UI. */ -const defaultSettings: UiSettings = {}; +const defaultSettings: UiSettings = { + backendBaseURL: buildDefaultBackendBaseURL(), + signupEmail: undefined, +}; + +const codecForBankUISettings = (): Codec<UiSettings> => + buildCodecForObject<UiSettings>() + .property("backendBaseURL", codecOptional(codecForString())) + .property("signupEmail", codecOptional(codecForString())) + .build("UiSettings"); + +function removeUndefineField<T extends object>(obj: T): T { + const keys = Object.keys(obj) as Array<keyof T>; + return keys.reduce((prev, cur) => { + if (typeof prev[cur] === "undefined") { + delete prev[cur]; + } + return prev; + }, obj); +} + +export function fetchSettings(listener: (s: UiSettings) => void): void { + fetch("./settings.json") + .then((resp) => resp.json()) + .then((json) => codecForBankUISettings().decode(json)) + .then((result) => + listener({ + ...defaultSettings, + ...removeUndefineField(result), + }), + ) + .catch((e) => { + console.log("failed to fetch settings", e); + listener(defaultSettings); + }); +} + +function buildDefaultBackendBaseURL(): string | undefined { + if (typeof window !== "undefined") { + const currentLocation = new URL( + window.location.pathname, + window.location.origin, + ).href; + /** + * By default, bank backend serves the html content + * from the /webui root. + */ + return canonicalizeBaseUrl(currentLocation.replace("/webui", "")); + } + throw Error("No default URL"); +} + -export const uiSettings: UiSettings = - "talerExchangeAmlSettings" in globalThis - ? (globalThis as any).talerExchangeAmlSettings - : defaultSettings; diff --git a/packages/aml-backoffice-ui/src/stories.test.ts b/packages/aml-backoffice-ui/src/stories.test.ts index 235370e16..a4f32cf43 100644 --- a/packages/aml-backoffice-ui/src/stories.test.ts +++ b/packages/aml-backoffice-ui/src/stories.test.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 @@ -19,15 +19,17 @@ * @author Sebastian Javier Marchano (sebasjm) */ import { TalerExchangeApi, setupI18n } from "@gnu-taler/taler-util"; -import { parseGroupImport } from "@gnu-taler/web-util/browser"; +import { + ExchangeApiProviderTesting, + ExchangeContextType, + parseGroupImport, +} from "@gnu-taler/web-util/browser"; import * as tests from "@gnu-taler/web-util/testing"; // import * as components from "./components/index.examples.js"; import * as pages from "./pages/index.stories.js"; import { ComponentChildren, VNode, h as create } from "preact"; -import { ExchangeApiContextTesting } from "./context/config.js"; -// import { BackendStateProviderTesting } from "./context/backend.js"; setupI18n("en", { en: {} }); @@ -66,5 +68,16 @@ function DefaultTestingContext({ supported_kyc_requirements: [], version: "asd", }; - return create(ExchangeApiContextTesting, { config, children }); + const value: ExchangeContextType = { + cancelRequest: () => null, + config, + url: new URL("/", "http://locahost"), + hints: [], + lib: { + exchange: undefined!, //FIXME: mock + }, + onActivity: () => null!, + }; + + return create(ExchangeApiProviderTesting, { value, children }); } diff --git a/packages/aml-backoffice-ui/src/stories.tsx b/packages/aml-backoffice-ui/src/stories.tsx index 017a31ffa..a66396696 100644 --- a/packages/aml-backoffice-ui/src/stories.tsx +++ b/packages/aml-backoffice-ui/src/stories.tsx @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 @@ -22,10 +22,14 @@ import { strings } from "./i18n/strings.js"; import * as pages from "./pages/index.stories.js"; -import { renderStories } from "@gnu-taler/web-util/browser"; +import { + ExchangeApiProviderTesting, + ExchangeContextType, + renderStories, +} from "@gnu-taler/web-util/browser"; +import { TalerExchangeApi } from "@gnu-taler/taler-util"; import { ComponentChildren, FunctionComponent, VNode, h } from "preact"; -import { ExchangeApiContextTesting } from "./context/config.js"; import "./scss/main.css"; function main(): void { @@ -40,24 +44,33 @@ function main(): void { function getWrapperForGroup(): FunctionComponent { return function All({ children }: { children?: ComponentChildren }): VNode { + const config: TalerExchangeApi.ExchangeVersionResponse = { + currency: "ARS", + currency_specification: { + alt_unit_names: {}, + name: "ARS", + num_fractional_input_digits: 2, + num_fractional_normal_digits: 2, + num_fractional_trailing_zero_digits: 2, + }, + name: "taler-exchange", + supported_kyc_requirements: [], + version: "asd", + }; + const value: ExchangeContextType = { + cancelRequest: () => null, + config, + url: new URL("/", "http://locahost"), + hints: [], + lib: { + exchange: undefined!, //FIXME: mock + }, + onActivity: () => null!, + }; return ( - <ExchangeApiContextTesting - config={{ - currency: "ARS", - currency_specification: { - alt_unit_names: {}, - name: "ARS", - num_fractional_input_digits: 2, - num_fractional_normal_digits: 2, - num_fractional_trailing_zero_digits: 2, - }, - name: "taler-exchange", - supported_kyc_requirements: [], - version: "asd", - }} - > + <ExchangeApiProviderTesting value={value}> {children} - </ExchangeApiContextTesting> + </ExchangeApiProviderTesting> ); }; } diff --git a/packages/aml-backoffice-ui/src/utils/QR.tsx b/packages/aml-backoffice-ui/src/utils/QR.tsx index 1dc1712b7..b382348a3 100644 --- a/packages/aml-backoffice-ui/src/utils/QR.tsx +++ b/packages/aml-backoffice-ui/src/utils/QR.tsx @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022 Taler Systems S.A. + (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 |