taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit e18d39aaec82c5af8ebe095f4c13f0f0b1fa4306
parent 1f33c7b37ecf4a6ca476b387fc526885753ad572
Author: Sebastian <sebasjm@gmail.com>
Date:   Fri, 26 Sep 2025 10:25:27 -0300

fix #10206

Diffstat:
Mpackages/aml-backoffice-ui/src/App.tsx | 95++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Dpackages/aml-backoffice-ui/src/components/ErrorLoadingWithDebug.tsx | 34----------------------------------
Mpackages/aml-backoffice-ui/src/components/MeasureList.tsx | 6+++---
Mpackages/aml-backoffice-ui/src/pages/AccountDetails.tsx | 8++++----
Mpackages/aml-backoffice-ui/src/pages/AccountList.tsx | 4++--
Mpackages/aml-backoffice-ui/src/pages/Dashboard.tsx | 4++--
Mpackages/aml-backoffice-ui/src/pages/Search.tsx | 6+++---
Mpackages/aml-backoffice-ui/src/pages/ShowCollectedInfo.tsx | 4++--
Mpackages/aml-backoffice-ui/src/pages/Transfers.tsx | 4++--
Mpackages/aml-backoffice-ui/src/pages/decision/Summary.tsx | 2+-
Mpackages/bank-ui/src/app.tsx | 116++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mpackages/bank-ui/src/components/Cashouts/index.ts | 6+++---
Mpackages/bank-ui/src/components/Cashouts/views.tsx | 5+++--
Dpackages/bank-ui/src/components/ErrorLoadingWithDebug.tsx | 24------------------------
Mpackages/bank-ui/src/components/Transactions/index.ts | 6+++---
Mpackages/bank-ui/src/hooks/preferences.ts | 10+++-------
Mpackages/bank-ui/src/pages/AccountPage/index.ts | 15+++++----------
Mpackages/bank-ui/src/pages/BankFrame.tsx | 10++++++----
Mpackages/bank-ui/src/pages/ConversionRateClassDetails.tsx | 7++++---
Mpackages/bank-ui/src/pages/OperationState/index.ts | 6+++---
Mpackages/bank-ui/src/pages/SolveChallengePage.tsx | 6+++---
Mpackages/bank-ui/src/pages/WireTransfer.tsx | 5+++--
Mpackages/bank-ui/src/pages/WithdrawalQRCode.tsx | 5+++--
Mpackages/bank-ui/src/pages/account/ShowAccountDetails.tsx | 7++++---
Mpackages/bank-ui/src/pages/admin/AccountList.tsx | 5+++--
Mpackages/bank-ui/src/pages/admin/AdminHome.tsx | 7++++---
Mpackages/bank-ui/src/pages/admin/ConversionClassList.tsx | 5+++--
Mpackages/bank-ui/src/pages/admin/RemoveAccount.tsx | 5+++--
Mpackages/bank-ui/src/pages/regional/ConversionConfig.tsx | 5+++--
Mpackages/bank-ui/src/pages/regional/CreateCashout.tsx | 113++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Mpackages/bank-ui/src/pages/regional/ShowCashoutDetails.tsx | 7++++---
Mpackages/bank-ui/src/utils.ts | 14+++++++-------
Mpackages/challenger-ui/src/app.tsx | 93+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mpackages/challenger-ui/src/pages/AskChallenge.tsx | 6+++---
Dpackages/challenger-ui/src/pages/ErrorLoadingWithDebug.tsx | 24------------------------
Mpackages/kyc-ui/src/app.tsx | 101+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Dpackages/kyc-ui/src/components/ErrorLoadingWithDebug.tsx | 24------------------------
Mpackages/kyc-ui/src/pages/Start.tsx | 4++--
Mpackages/merchant-backoffice-ui/src/Application.tsx | 113+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mpackages/web-util/src/components/ErrorLoading.tsx | 201+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mpackages/web-util/src/components/ErrorLoadingMerchant.tsx | 240+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mpackages/web-util/src/components/NotificationBanner.tsx | 64++++++++++++++++++++++++++++++++++++++++------------------------
Mpackages/web-util/src/context/bank-api.ts | 2+-
Mpackages/web-util/src/context/challenger-api.ts | 2+-
Apackages/web-util/src/context/common-preferences.ts | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/web-util/src/context/exchange-api.ts | 2+-
Mpackages/web-util/src/context/index.ts | 1+
Mpackages/web-util/src/context/navigation.ts | 2+-
Mpackages/web-util/src/hooks/useNotifications.ts | 16++++++++--------
49 files changed, 826 insertions(+), 691 deletions(-)

diff --git a/packages/aml-backoffice-ui/src/App.tsx b/packages/aml-backoffice-ui/src/App.tsx @@ -21,6 +21,7 @@ import { } from "@gnu-taler/taler-util"; import { BrowserHashNavigationProvider, + CommonPreferenceProvider, ExchangeApiProvider, Loading, TranslationProvider, @@ -55,56 +56,60 @@ export function App(): VNode { const baseUrl = getInitialBackendBaseURL(settings.backendBaseURL); return ( <UiSettingsProvider value={settings}> - <TranslationProvider - source={strings} - completeness={{ - es: strings["es"].completeness ?? 0, - de: strings["de"].completeness ?? 0, - }} - > - <ExchangeApiProvider - baseUrl={new URL("/", baseUrl)} - frameOnError={ExchangeAmlFrame} - evictors={{ - exchange: evictExchangeSwrCache, + <CommonPreferenceProvider showDebug={false}> + <TranslationProvider + source={strings} + completeness={{ + es: strings["es"].completeness ?? 0, + de: strings["de"].completeness ?? 0, }} - preventCompression={preventCompression} > - <SWRConfig - value={{ - provider: WITH_LOCAL_STORAGE_CACHE - ? localStorageProvider - : undefined, - // normally, do not revalidate - revalidateOnFocus: false, - revalidateOnReconnect: false, - revalidateIfStale: false, - revalidateOnMount: undefined, - focusThrottleInterval: undefined, + <CommonPreferenceProvider showDebug={false}> + <ExchangeApiProvider + baseUrl={new URL("/", baseUrl)} + frameOnError={ExchangeAmlFrame} + evictors={{ + exchange: evictExchangeSwrCache, + }} + preventCompression={preventCompression} + > + <SWRConfig + value={{ + provider: WITH_LOCAL_STORAGE_CACHE + ? localStorageProvider + : undefined, + // normally, do not revalidate + revalidateOnFocus: false, + revalidateOnReconnect: false, + revalidateIfStale: false, + revalidateOnMount: undefined, + focusThrottleInterval: undefined, - // normally, do not refresh - refreshInterval: undefined, - dedupingInterval: 2000, - refreshWhenHidden: false, - refreshWhenOffline: false, + // normally, do not refresh + refreshInterval: undefined, + dedupingInterval: 2000, + refreshWhenHidden: false, + refreshWhenOffline: false, - // ignore errors - shouldRetryOnError: false, - errorRetryCount: 0, - errorRetryInterval: undefined, + // ignore errors + shouldRetryOnError: false, + errorRetryCount: 0, + errorRetryInterval: undefined, - // do not go to loading again if already has data - keepPreviousData: true, - }} - > - <BrowserHashNavigationProvider> - <UiFormsProvider> - <Routing /> - </UiFormsProvider> - </BrowserHashNavigationProvider> - </SWRConfig> - </ExchangeApiProvider> - </TranslationProvider> + // do not go to loading again if already has data + keepPreviousData: true, + }} + > + <BrowserHashNavigationProvider> + <UiFormsProvider> + <Routing /> + </UiFormsProvider> + </BrowserHashNavigationProvider> + </SWRConfig> + </ExchangeApiProvider> + </CommonPreferenceProvider> + </TranslationProvider> + </CommonPreferenceProvider> </UiSettingsProvider> ); } diff --git a/packages/aml-backoffice-ui/src/components/ErrorLoadingWithDebug.tsx b/packages/aml-backoffice-ui/src/components/ErrorLoadingWithDebug.tsx @@ -1,34 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022-2025 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 { TalerError } from "@gnu-taler/taler-util"; -import { ErrorLoading } from "@gnu-taler/web-util/browser"; -import { VNode, h } from "preact"; -import { usePreferences } from "../hooks/preferences.js"; - -export function ErrorLoadingWithDebug({ error }: { error: Error }): VNode { - const [pref] = usePreferences(); - if (error instanceof TalerError) { - return <ErrorLoading error={error} showDetail={pref.showDebugInfo} />; - } else { - console.error(error); - return ( - <ErrorLoading - error={TalerError.fromException(error)} - showDetail={pref.showDebugInfo} - /> - ); - } -} diff --git a/packages/aml-backoffice-ui/src/components/MeasureList.tsx b/packages/aml-backoffice-ui/src/components/MeasureList.tsx @@ -20,16 +20,16 @@ import { } from "@gnu-taler/taler-util"; import { Attention, + ErrorLoading, Loading, RouteDefinition, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, h } from "preact"; -import { ErrorLoadingWithDebug } from "./ErrorLoadingWithDebug.js"; import { useServerMeasures } from "../hooks/server-info.js"; +import { Profile } from "../pages/Profile.js"; import { computeMeasureInformation } from "../utils/computeAvailableMesaures.js"; import { CurrentMeasureTable } from "./MeasuresTable.js"; -import { Profile } from "../pages/Profile.js"; const TALER_SCREEN_ID = 124; @@ -43,7 +43,7 @@ export function MeasureList({ routeToNew }: { routeToNew: RouteDefinition }) { return <Loading />; } if (measures instanceof TalerError) { - return <ErrorLoadingWithDebug error={measures} />; + return <ErrorLoading error={measures} />; } if (measures.type === "fail") { diff --git a/packages/aml-backoffice-ui/src/pages/AccountDetails.tsx b/packages/aml-backoffice-ui/src/pages/AccountDetails.tsx @@ -25,6 +25,7 @@ import { import { Attention, CopyButton, + ErrorLoading, Loading, RouteDefinition, useTranslationContext, @@ -32,7 +33,6 @@ import { import { format } from "date-fns"; import { h, VNode } from "preact"; import { Fragment } from "preact/jsx-runtime"; -import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js"; import { ShowDecisionLimitInfo } from "../components/ShowDecisionLimitInfo.js"; import { ShowDefaultRules } from "../components/ShowDefaultRules.js"; import { ShowLegistimizationInfo } from "../components/ShowLegitimizationInfo.js"; @@ -89,7 +89,7 @@ export function AccountDetails({ return <Loading />; } if (details instanceof TalerError) { - return <ErrorLoadingWithDebug error={details} />; + return <ErrorLoading error={details} />; } if (details.type === "fail") { switch (details.case) { @@ -102,7 +102,7 @@ export function AccountDetails({ } } if (history instanceof TalerError) { - return <ErrorLoadingWithDebug error={history} />; + return <ErrorLoading error={history} />; } if (history.type === "fail") { switch (history.case) { @@ -115,7 +115,7 @@ export function AccountDetails({ } } if (legistimizations instanceof TalerError) { - return <ErrorLoadingWithDebug error={legistimizations} />; + return <ErrorLoading error={legistimizations} />; } const collectionEvents = details.body.details; diff --git a/packages/aml-backoffice-ui/src/pages/AccountList.tsx b/packages/aml-backoffice-ui/src/pages/AccountList.tsx @@ -20,6 +20,7 @@ import { } from "@gnu-taler/taler-util"; import { Attention, + ErrorLoading, InputToggle, Loading, Pagination, @@ -31,7 +32,6 @@ import { Fragment, VNode, h } from "preact"; import { useCurrentDecisions } from "../hooks/decisions.js"; import { useEffect, useState } from "preact/hooks"; -import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js"; import { useOfficer } from "../hooks/officer.js"; import { Profile } from "./Profile.js"; @@ -48,7 +48,7 @@ export function AccountList({ return <Loading />; } if (list instanceof TalerError) { - return <ErrorLoadingWithDebug error={list} />; + return <ErrorLoading error={list} />; } if (list.type === "fail") { diff --git a/packages/aml-backoffice-ui/src/pages/Dashboard.tsx b/packages/aml-backoffice-ui/src/pages/Dashboard.tsx @@ -21,13 +21,13 @@ import { TranslatedString, } from "@gnu-taler/taler-util"; import { + ErrorLoading, InternationalizationAPI, Loading, useExchangeApiContext, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, h, VNode } from "preact"; -import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js"; import { HandleAccountNotReady } from "../components/HandleAccountNotReady.js"; import { useOfficer } from "../hooks/officer.js"; import { usePreferences } from "../hooks/preferences.js"; @@ -79,7 +79,7 @@ function EventMetrics(): VNode { return <Loading />; } if (resp instanceof TalerError) { - return <ErrorLoadingWithDebug error={resp} />; + return <ErrorLoading error={resp} />; } return ( diff --git a/packages/aml-backoffice-ui/src/pages/Search.tsx b/packages/aml-backoffice-ui/src/pages/Search.tsx @@ -32,6 +32,7 @@ import { import { Attention, encodeCrockForURI, + ErrorLoading, FormDesign, FormUI, InternationalizationAPI, @@ -46,11 +47,10 @@ import { } from "@gnu-taler/web-util/browser"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; -import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js"; +import { HandleAccountNotReady } from "../components/HandleAccountNotReady.js"; import { useAccountDecisions } from "../hooks/decisions.js"; import { useOfficer } from "../hooks/officer.js"; import { ToInvestigateIcon } from "./AccountList.js"; -import { HandleAccountNotReady } from "../components/HandleAccountNotReady.js"; import { Profile } from "./Profile.js"; const TALER_SCREEN_ID = 111; @@ -144,7 +144,7 @@ function ShowResult({ return <Loading />; } if (history instanceof TalerError) { - return <ErrorLoadingWithDebug error={history} />; + return <ErrorLoading error={history} />; } if (history.type === "fail") { switch (history.case) { diff --git a/packages/aml-backoffice-ui/src/pages/ShowCollectedInfo.tsx b/packages/aml-backoffice-ui/src/pages/ShowCollectedInfo.tsx @@ -22,6 +22,7 @@ import { } from "@gnu-taler/taler-util"; import { Attention, + ErrorLoading, FormMetadata, FormUI, Loading, @@ -31,7 +32,6 @@ import { useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, h, VNode } from "preact"; -import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js"; import { useAccountInformation } from "../hooks/account.js"; import { Profile } from "./Profile.js"; @@ -56,7 +56,7 @@ export function ShowCollectedInfo({ return <Loading />; } if (details instanceof TalerError) { - return <ErrorLoadingWithDebug error={details} />; + return <ErrorLoading error={details} />; } if (details.type === "fail") { switch (details.case) { diff --git a/packages/aml-backoffice-ui/src/pages/Transfers.tsx b/packages/aml-backoffice-ui/src/pages/Transfers.tsx @@ -25,6 +25,7 @@ import { } from "@gnu-taler/taler-util"; import { Attention, + ErrorLoading, FormDesign, FormUI, Loading, @@ -38,7 +39,6 @@ import { } from "@gnu-taler/web-util/browser"; import { format } from "date-fns"; import { Fragment, h, VNode } from "preact"; -import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js"; import { useTransferList } from "../hooks/transfers.js"; import { Profile } from "./Profile.js"; @@ -108,7 +108,7 @@ export function Transfers({ return <Loading />; } if (resp instanceof Error) { - return <ErrorLoadingWithDebug error={resp} />; + return <ErrorLoading error={resp} />; } if (resp.type === "fail") { switch (resp.case) { diff --git a/packages/aml-backoffice-ui/src/pages/decision/Summary.tsx b/packages/aml-backoffice-ui/src/pages/decision/Summary.tsx @@ -241,7 +241,7 @@ export function Summary({ return ( <Fragment> - <LocalNotificationBanner notification={notification} showDebug /> + <LocalNotificationBanner notification={notification} /> {INVALID_RULES ? ( <Fragment> {!decision.deadline && ( diff --git a/packages/bank-ui/src/app.tsx b/packages/bank-ui/src/app.tsx @@ -26,11 +26,14 @@ import { import { BankApiProvider, BrowserHashNavigationProvider, + ErrorLoading, Loading, TalerWalletIntegrationBrowserProvider, TranslationProvider, + CommonPreferenceProvider, + useCommonPreference, } from "@gnu-taler/web-util/browser"; -import { h } from "preact"; +import { h, VNode, Fragment } from "preact"; import { useEffect, useState } from "preact/hooks"; import { SWRConfig } from "swr"; import { Routing } from "./Routing.js"; @@ -51,7 +54,6 @@ import { revalidateConversionRateClasses, } from "./hooks/regional.js"; const WITH_LOCAL_STORAGE_CACHE = false; - export function App() { const [settings, setSettings] = useState<UiSettings>(); useEffect(() => { @@ -75,48 +77,9 @@ export function App() { de: strings["de"].completeness, }} > - <BankApiProvider - baseUrl={new URL("/", baseUrl)} - frameOnError={BankFrame} - evictors={{ - bank: evictBankSwrCache, - conversion: evictConversionSwrCache, - }} - > - <SWRConfig - value={{ - provider: WITH_LOCAL_STORAGE_CACHE - ? localStorageProvider - : undefined, - // normally, do not revalidate - revalidateOnFocus: false, - revalidateOnReconnect: false, - revalidateIfStale: false, - revalidateOnMount: undefined, - focusThrottleInterval: undefined, - - // normally, do not refresh - refreshInterval: undefined, - dedupingInterval: 2000, - refreshWhenHidden: false, - refreshWhenOffline: false, - - // ignore errors - shouldRetryOnError: false, - errorRetryCount: 0, - errorRetryInterval: undefined, - - // do not go to loading again if already has data - keepPreviousData: true, - }} - > - <TalerWalletIntegrationBrowserProvider> - <BrowserHashNavigationProvider> - <Routing /> - </BrowserHashNavigationProvider> - </TalerWalletIntegrationBrowserProvider> - </SWRConfig> - </BankApiProvider> + <CommonPreferenceProvider showDebug={false}> + <SubApp baseUrl={baseUrl} /> + </CommonPreferenceProvider> </TranslationProvider> </SettingsProvider> ); @@ -220,16 +183,16 @@ const evictBankSwrCache: CacheEvictor<TalerCoreBankCacheEviction> = { return; case TalerCoreBankCacheEviction.UPDATE_CONVERSION_RATE_CLASS: case TalerCoreBankCacheEviction.CREATE_CONVERSION_RATE_CLASS: - case TalerCoreBankCacheEviction.DELETE_CONVERSION_RATE_CLASS: { - await Promise.all([ - revalidateConversionInfo(), - revalidateCashouts(), - revalidateTransactions(), - revalidateConversionRateClassDetails(), - revalidateConversionRateClasses() - ]); - - } + case TalerCoreBankCacheEviction.DELETE_CONVERSION_RATE_CLASS: + { + await Promise.all([ + revalidateConversionInfo(), + revalidateCashouts(), + revalidateTransactions(), + revalidateConversionRateClassDetails(), + revalidateConversionRateClasses(), + ]); + } return; default: assertUnreachable(op); @@ -250,3 +213,48 @@ const evictConversionSwrCache: CacheEvictor<TalerBankConversionCacheEviction> = } }, }; + +function SubApp({ baseUrl }: { baseUrl: string }) { + return ( + <BankApiProvider + baseUrl={new URL("/", baseUrl)} + frameOnError={BankFrame} + evictors={{ + bank: evictBankSwrCache, + conversion: evictConversionSwrCache, + }} + > + <SWRConfig + value={{ + provider: WITH_LOCAL_STORAGE_CACHE ? localStorageProvider : undefined, + // normally, do not revalidate + revalidateOnFocus: false, + revalidateOnReconnect: false, + revalidateIfStale: false, + revalidateOnMount: undefined, + focusThrottleInterval: undefined, + + // normally, do not refresh + refreshInterval: undefined, + dedupingInterval: 2000, + refreshWhenHidden: false, + refreshWhenOffline: false, + + // ignore errors + shouldRetryOnError: false, + errorRetryCount: 0, + errorRetryInterval: undefined, + + // do not go to loading again if already has data + keepPreviousData: true, + }} + > + <TalerWalletIntegrationBrowserProvider> + <BrowserHashNavigationProvider> + <Routing /> + </BrowserHashNavigationProvider> + </TalerWalletIntegrationBrowserProvider> + </SWRConfig> + </BankApiProvider> + ); +} diff --git a/packages/bank-ui/src/components/Cashouts/index.ts b/packages/bank-ui/src/components/Cashouts/index.ts @@ -21,9 +21,9 @@ import { TalerCorebankApi, TalerError, } from "@gnu-taler/taler-util"; -import { Loading, RouteDefinition, utils } from "@gnu-taler/web-util/browser"; +import { ErrorLoading, Loading, RouteDefinition, utils } from "@gnu-taler/web-util/browser"; import { VNode } from "preact"; -import { ErrorLoadingWithDebug } from "../ErrorLoadingWithDebug.js"; + import { useComponentState } from "./state.js"; import { FailedView, ReadyView } from "./views.js"; @@ -75,7 +75,7 @@ export interface Transaction { const viewMapping: utils.StateViewMap<State> = { loading: Loading, - "loading-error": ErrorLoadingWithDebug, + "loading-error": ErrorLoading, failed: FailedView, ready: ReadyView, }; diff --git a/packages/bank-ui/src/components/Cashouts/views.tsx b/packages/bank-ui/src/components/Cashouts/views.tsx @@ -23,6 +23,7 @@ import { } from "@gnu-taler/taler-util"; import { Attention, + ErrorLoading, Loading, Time, useTranslationContext, @@ -32,7 +33,7 @@ import { Fragment, VNode, h } from "preact"; import { RenderAmount } from "../../pages/PaytoWireTransferForm.js"; import { State } from "./index.js"; import { useConversionInfo } from "../../hooks/regional.js"; -import { ErrorLoadingWithDebug } from "../ErrorLoadingWithDebug.js"; + const TALER_SCREEN_ID = 3; @@ -81,7 +82,7 @@ export function ReadyView({ if (!conversionResp) { return <Loading />; } else if (conversionResp instanceof TalerError) { - return <ErrorLoadingWithDebug error={conversionResp} />; + return <ErrorLoading error={conversionResp} />; } else if (conversionResp.type === "fail") { switch (conversionResp.case) { case HttpStatusCode.NotImplemented: { diff --git a/packages/bank-ui/src/components/ErrorLoadingWithDebug.tsx b/packages/bank-ui/src/components/ErrorLoadingWithDebug.tsx @@ -1,24 +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 { TalerError } from "@gnu-taler/taler-util"; -import { ErrorLoading } from "@gnu-taler/web-util/browser"; -import { VNode, h } from "preact"; -import { usePreferences } from "../hooks/preferences.js"; - -export function ErrorLoadingWithDebug({ error }: { error: TalerError }): VNode { - const [pref] = usePreferences(); - return <ErrorLoading error={error} showDetail={pref.showDebugInfo} />; -} diff --git a/packages/bank-ui/src/components/Transactions/index.ts b/packages/bank-ui/src/components/Transactions/index.ts @@ -15,9 +15,9 @@ */ import { AbsoluteTime, AmountJson, TalerError } from "@gnu-taler/taler-util"; -import { Loading, RouteDefinition, utils } from "@gnu-taler/web-util/browser"; +import { ErrorLoading, Loading, RouteDefinition, utils } from "@gnu-taler/web-util/browser"; import { VNode } from "preact"; -import { ErrorLoadingWithDebug } from "../ErrorLoadingWithDebug.js"; + import { useComponentState } from "./state.js"; import { ReadyView } from "./views.js"; @@ -74,7 +74,7 @@ export interface Transaction { const viewMapping: utils.StateViewMap<State> = { loading: Loading, - "loading-error": ErrorLoadingWithDebug, + "loading-error": ErrorLoading, ready: ReadyView, }; diff --git a/packages/bank-ui/src/hooks/preferences.ts b/packages/bank-ui/src/hooks/preferences.ts @@ -30,11 +30,11 @@ import { codecOptionalDefault } from "@gnu-taler/taler-util"; const TALER_SCREEN_ID = 102; + interface Preferences { showWithdrawalSuccess: boolean; hideDemo: boolean; showInstallWallet: boolean; - showDebugInfo: boolean; allowsSimplePassword: boolean; fastWithdrawalForm: boolean; showCopyAccount: boolean; @@ -45,7 +45,6 @@ export const codecForPreferences = (): Codec<Preferences> => .property("showWithdrawalSuccess", codecForBoolean()) .property("hideDemo", codecOptionalDefault(codecForBoolean(), false)) .property("showInstallWallet", codecForBoolean()) - .property("showDebugInfo", codecForBoolean()) .property("allowsSimplePassword", codecForBoolean()) .property("fastWithdrawalForm", codecForBoolean()) .property("showCopyAccount", codecForBoolean()) @@ -56,7 +55,6 @@ const defaultPreferences: Preferences = { hideDemo: true, allowsSimplePassword: false, showInstallWallet: true, - showDebugInfo: false, fastWithdrawalForm: false, showCopyAccount: false, }; @@ -91,7 +89,6 @@ export function getAllBooleanPreferences( ): Array<keyof Preferences> { if (settings.showDemoDescription) { return [ - "showDebugInfo", "hideDemo", "showInstallWallet", "showWithdrawalSuccess", @@ -100,7 +97,6 @@ export function getAllBooleanPreferences( ]; } return [ - "showDebugInfo", "showInstallWallet", "showWithdrawalSuccess", "fastWithdrawalForm", @@ -125,7 +121,7 @@ export function getLabelForPreferences( return i18n.str`Show install wallet first`; case "allowsSimplePassword": return i18n.str`Remove password length validation on registration`; - case "showDebugInfo": - return i18n.str`Show debug info`; + // case "showDebugInfo": + // return i18n.str`Show debug info`; } } diff --git a/packages/bank-ui/src/pages/AccountPage/index.ts b/packages/bank-ui/src/pages/AccountPage/index.ts @@ -20,9 +20,9 @@ import { TalerCorebankApi, TalerError, } from "@gnu-taler/taler-util"; -import { Loading, RouteDefinition, utils } from "@gnu-taler/web-util/browser"; +import { ErrorLoading, Loading, RouteDefinition, utils } from "@gnu-taler/web-util/browser"; import { Fragment, VNode } from "preact"; -import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js"; + import { LoginForm } from "../LoginForm.js"; import { useComponentState } from "./state.js"; import { InvalidIbanView, ReadyView } from "./views.js"; @@ -30,7 +30,7 @@ import { IntAmountJson, IntAmounts } from "../regional/CreateCashout.js"; export interface Props { account: string; - + onOperationCreated: (wopid: string) => void; onClose: () => void; tab: "charge-wallet" | "wire-transfer" | undefined; @@ -80,7 +80,7 @@ export namespace State { tab: "charge-wallet" | "wire-transfer" | undefined; limit: IntAmountJson; balance: AmountJson; - + onOperationCreated: (wopid: string) => void; onClose: () => void; routeClose: RouteDefinition; @@ -109,7 +109,6 @@ export namespace State { status: "login"; reason: "not-found" | "forbidden"; routeRegister?: RouteDefinition; - } } @@ -125,11 +124,7 @@ const viewMapping: utils.StateViewMap<State> = { loading: Loading, login: LoginForm, "invalid-iban": InvalidIbanView, - "loading-error": (d) => { - return Fragment({ - children: [ErrorLoadingWithDebug({ error: d.error })], - })!; - }, + "loading-error": ErrorLoading, ready: ReadyView, }; diff --git a/packages/bank-ui/src/pages/BankFrame.tsx b/packages/bank-ui/src/pages/BankFrame.tsx @@ -31,6 +31,7 @@ import { notifyError, notifyException, useBankCoreApiContext, + useCommonPreference, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { ComponentChildren, Fragment, VNode, h } from "preact"; @@ -66,6 +67,7 @@ export function BankFrame({ const { i18n } = useTranslationContext(); const session = useSessionState(); const settings = useSettingsContext(); + const {showDebugInfo} = useCommonPreference(); const [preferences, updatePreferences] = usePreferences(); const [, , resetBankState] = useBankState(); const d = useBankCoreApiContext(); @@ -100,7 +102,7 @@ export function BankFrame({ iconLinkURL={settings.iconLinkURL ?? "#"} profileURL={routeAccountDetails?.url({})} notificationURL={ - preferences.showDebugInfo && routeNotifications + showDebugInfo && routeNotifications ? routeNotifications.url({}) : undefined } @@ -249,10 +251,10 @@ function AppActivity(): VNode { const d = useBankCoreApiContext(); const onBackendActivity = !d ? undefined : d.onActivity; const cancelRequest = !d ? undefined : d.cancelRequest; - const [pref] = usePreferences(); + const {showDebugInfo} = useCommonPreference(); useEffect(() => { // console.log("ASDASDS", onBackendActivity) - if (!pref.showDebugInfo) return; + if (!showDebugInfo) return; if (!onBackendActivity) return; return onBackendActivity((ev) => { switch (ev.type) { @@ -295,7 +297,7 @@ function AppActivity(): VNode { } }); }); - if (!pref.showDebugInfo || !lastEvent) return <Fragment />; + if (!showDebugInfo || !lastEvent) return <Fragment />; return ( <div data-status={status} diff --git a/packages/bank-ui/src/pages/ConversionRateClassDetails.tsx b/packages/bank-ui/src/pages/ConversionRateClassDetails.tsx @@ -10,6 +10,7 @@ import { } from "@gnu-taler/taler-util"; import { Attention, + ErrorLoading, InputText, InputToggle, Loading, @@ -23,7 +24,7 @@ import { } from "@gnu-taler/web-util/browser"; import { Fragment, h, VNode } from "preact"; import { useState, useEffect } from "preact/hooks"; -import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js"; + import { FormErrors, FormStatus, @@ -84,7 +85,7 @@ export function ConversionRateClassDetails({ return <Loading />; } if (detailsResult instanceof TalerError) { - return <ErrorLoadingWithDebug error={detailsResult} />; + return <ErrorLoading error={detailsResult} />; } if (detailsResult.type === "fail") { switch (detailsResult.case) { @@ -1052,7 +1053,7 @@ function AccountsOnConversionClass({ classId }: { classId: number }): VNode { return <Loading />; } if (userListResult instanceof TalerError) { - return <ErrorLoadingWithDebug error={userListResult} />; + return <ErrorLoading error={userListResult} />; } if (userListResult.type === "fail") { switch (userListResult.case) { diff --git a/packages/bank-ui/src/pages/OperationState/index.ts b/packages/bank-ui/src/pages/OperationState/index.ts @@ -23,9 +23,9 @@ import { TalerError, WithdrawUriResult, } from "@gnu-taler/taler-util"; -import { Loading, RouteDefinition, utils } from "@gnu-taler/web-util/browser"; +import { ErrorLoading, Loading, RouteDefinition, utils } from "@gnu-taler/web-util/browser"; import { VNode } from "preact"; -import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js"; + import { useComponentState } from "./state.js"; import { AbortedView, @@ -144,7 +144,7 @@ const viewMapping: utils.StateViewMap<State> = { "need-confirmation": NeedConfirmationView, aborted: AbortedView, confirmed: ConfirmedView, - "loading-error": ErrorLoadingWithDebug, + "loading-error": ErrorLoading, ready: ReadyView, }; diff --git a/packages/bank-ui/src/pages/SolveChallengePage.tsx b/packages/bank-ui/src/pages/SolveChallengePage.tsx @@ -44,7 +44,7 @@ // } from "@gnu-taler/web-util/browser"; // import { Fragment, VNode, h } from "preact"; // import { useEffect, useState } from "preact/hooks"; -// import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js"; +// // import { useWithdrawalDetails } from "../hooks/account.js"; // import { ChallengeInProgess, useBankState } from "../hooks/bank-state.js"; // import { useConversionInfo } from "../hooks/regional.js"; @@ -818,7 +818,7 @@ // return <Loading />; // } // if (details instanceof TalerError) { -// return <ErrorLoadingWithDebug error={details} />; +// return <ErrorLoading error={details} />; // } // if (details.type === "fail") { // switch (details.case) { @@ -884,7 +884,7 @@ // } // if (info instanceof TalerError) { -// return <ErrorLoadingWithDebug error={info} />; +// return <ErrorLoading error={info} />; // } // if (info.type === "fail") { // switch (info.case) { diff --git a/packages/bank-ui/src/pages/WireTransfer.tsx b/packages/bank-ui/src/pages/WireTransfer.tsx @@ -20,12 +20,13 @@ import { assertUnreachable, } from "@gnu-taler/taler-util"; import { + ErrorLoading, Loading, notifyInfo, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; -import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js"; + import { useAccountDetails } from "../hooks/account.js"; import { useSessionState } from "../hooks/session.js"; import { LoginForm } from "./LoginForm.js"; @@ -60,7 +61,7 @@ export function WireTransfer({ if (result instanceof TalerError) { return ( <Fragment> - <ErrorLoadingWithDebug error={result} /> + <ErrorLoading error={result} /> <LoginForm currentUser={account} /> </Fragment> ); diff --git a/packages/bank-ui/src/pages/WithdrawalQRCode.tsx b/packages/bank-ui/src/pages/WithdrawalQRCode.tsx @@ -25,13 +25,14 @@ import { } from "@gnu-taler/taler-util"; import { Attention, + ErrorLoading, Loading, RouteDefinition, notifyInfo, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { VNode, h } from "preact"; -import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js"; + import { useWithdrawalDetails } from "../hooks/account.js"; import { QrCodeSection } from "./QrCodeSection.js"; import { WithdrawalConfirmationQuestion } from "./WithdrawalConfirmationQuestion.js"; @@ -64,7 +65,7 @@ export function WithdrawalQRCode({ return <Loading />; } if (result instanceof TalerError) { - return <ErrorLoadingWithDebug error={result} />; + return <ErrorLoading error={result} />; } if (result.type === "fail") { switch (result.case) { diff --git a/packages/bank-ui/src/pages/account/ShowAccountDetails.tsx b/packages/bank-ui/src/pages/account/ShowAccountDetails.tsx @@ -28,6 +28,7 @@ import { Attention, ButtonBetter, CopyButton, + ErrorLoading, Loading, LocalNotificationBanner, RouteDefinition, @@ -41,7 +42,7 @@ import { } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; -import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js"; + import { useAccountDetails } from "../../hooks/account.js"; import { useBankState } from "../../hooks/bank-state.js"; import { usePreferences } from "../../hooks/preferences.js"; @@ -99,7 +100,7 @@ export function ShowAccountDetails({ if (result instanceof TalerError) { return ( <Fragment> - <ErrorLoadingWithDebug error={result} /> + <ErrorLoading error={result} /> <LoginForm currentUser={account} /> </Fragment> ); @@ -191,7 +192,7 @@ export function ShowAccountDetails({ return ( <Fragment> - <LocalNotificationBanner notification={notification} showDebug={true} /> + <LocalNotificationBanner notification={notification} /> {accountIsTheCurrentUser ? ( <ProfileNavigation current="details" diff --git a/packages/bank-ui/src/pages/admin/AccountList.tsx b/packages/bank-ui/src/pages/admin/AccountList.tsx @@ -20,13 +20,14 @@ import { assertUnreachable, } from "@gnu-taler/taler-util"; import { + ErrorLoading, Loading, RouteDefinition, useBankCoreApiContext, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; -import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js"; + import { useBusinessAccounts } from "../../hooks/regional.js"; import { RenderAmount } from "../PaytoWireTransferForm.js"; @@ -54,7 +55,7 @@ export function AccountList({ return <Loading />; } if (result instanceof TalerError) { - return <ErrorLoadingWithDebug error={result} />; + return <ErrorLoading error={result} />; } switch (result.case) { case "ok": diff --git a/packages/bank-ui/src/pages/admin/AdminHome.tsx b/packages/bank-ui/src/pages/admin/AdminHome.tsx @@ -26,6 +26,7 @@ import { } from "@gnu-taler/taler-util"; import { Attention, + ErrorLoading, RouteDefinition, useBankCoreApiContext, useTranslationContext, @@ -33,7 +34,7 @@ import { import { format, sub } from "date-fns"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; -import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js"; + import { Transactions } from "../../components/Transactions/index.js"; import { useConversionInfo, useLastMonitorInfo } from "../../hooks/regional.js"; import { RenderAmount } from "../PaytoWireTransferForm.js"; @@ -247,10 +248,10 @@ function Metrics({ const resp = useLastMonitorInfo(params.current, params.previous, metricType); if (!resp) return <Fragment />; if (resp instanceof TalerError) { - return <ErrorLoadingWithDebug error={resp} />; + return <ErrorLoading error={resp} />; } if (respInfo && respInfo instanceof TalerError) { - return <ErrorLoadingWithDebug error={respInfo} />; + return <ErrorLoading error={respInfo} />; } if (respInfo && respInfo.type === "fail") { switch (respInfo.case) { diff --git a/packages/bank-ui/src/pages/admin/ConversionClassList.tsx b/packages/bank-ui/src/pages/admin/ConversionClassList.tsx @@ -24,13 +24,14 @@ import { } from "@gnu-taler/taler-util"; import { Attention, + ErrorLoading, Loading, RouteDefinition, useBankCoreApiContext, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; -import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js"; + import { useConversionInfo, useConversionRateClasses, @@ -65,7 +66,7 @@ export function ConversionClassList({ return <Loading />; } if (result instanceof TalerError) { - return <ErrorLoadingWithDebug error={result} />; + return <ErrorLoading error={result} />; } if (result.type !== "ok") { diff --git a/packages/bank-ui/src/pages/admin/RemoveAccount.tsx b/packages/bank-ui/src/pages/admin/RemoveAccount.tsx @@ -23,6 +23,7 @@ import { import { Attention, ButtonBetter, + ErrorLoading, Loading, LocalNotificationBanner, RouteDefinition, @@ -36,7 +37,7 @@ import { } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; -import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js"; + import { useAccountDetails } from "../../hooks/account.js"; import { useSessionState } from "../../hooks/session.js"; import { undefinedIfEmpty } from "../../utils.js"; @@ -77,7 +78,7 @@ export function RemoveAccount({ if (result instanceof TalerError) { return ( <Fragment> - <ErrorLoadingWithDebug error={result} /> + <ErrorLoading error={result} /> <LoginForm currentUser={account} /> </Fragment> ); diff --git a/packages/bank-ui/src/pages/regional/ConversionConfig.tsx b/packages/bank-ui/src/pages/regional/ConversionConfig.tsx @@ -26,6 +26,7 @@ import { } from "@gnu-taler/taler-util"; import { Attention, + ErrorLoading, InternationalizationAPI, Loading, LocalNotificationBanner, @@ -56,7 +57,7 @@ import { useSessionState } from "../../hooks/session.js"; import { undefinedIfEmpty } from "../../utils.js"; import { InputAmount, RenderAmount } from "../PaytoWireTransferForm.js"; import { ProfileNavigation } from "../ProfileNavigation.js"; -import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js"; + import { DescribeConversion } from "../admin/ConversionClassList.js"; const TALER_SCREEN_ID = 126; @@ -101,7 +102,7 @@ function useComponentState({ return <Loading />; } if (resp instanceof TalerError) { - return <ErrorLoadingWithDebug error={resp} />; + return <ErrorLoading error={resp} />; } if (resp.type !== "ok") { diff --git a/packages/bank-ui/src/pages/regional/CreateCashout.tsx b/packages/bank-ui/src/pages/regional/CreateCashout.tsx @@ -14,12 +14,13 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ import { - AbsoluteTime, + AmountJson, Amounts, + ConversionRate, HttpStatusCode, + TalerConversionInfoConfig, TalerError, TalerErrorCode, - TranslatedString, assertUnreachable, encodeCrock, getRandomBytes, @@ -28,6 +29,7 @@ import { import { Attention, ButtonBetter, + ErrorLoading, Loading, LocalNotificationBanner, RouteDefinition, @@ -36,15 +38,13 @@ import { notifyInfo, useBankCoreApiContext, useChallengeHandler, - useLocalNotification, useLocalNotificationBetter, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useEffect, useState } from "preact/hooks"; -import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js"; + import { useAccountDetails } from "../../hooks/account.js"; -import { useBankState } from "../../hooks/bank-state.js"; import { TransCalc, TransferCalculation, @@ -60,8 +60,8 @@ import { RenderAmount, doAutoFocus, } from "../PaytoWireTransferForm.js"; -import { AmountJson } from "@gnu-taler/taler-util"; import { SolveMFAChallenges } from "../SolveMFA.js"; +import { TalerCorebankApi } from "@gnu-taler/taler-util"; const TALER_SCREEN_ID = 127; @@ -83,7 +83,6 @@ type ErrorFrom<T> = { }; const RANDOM_STRING = encodeCrock(getRandomBytes(32)); - export function CreateCashout({ account: accountName, onCashout, @@ -91,10 +90,7 @@ export function CreateCashout({ routeClose, }: Props): VNode { const { i18n } = useTranslationContext(); - const { - lib: { bank: api }, - config, - } = useBankCoreApiContext(); + const { config } = useBankCoreApiContext(); if (!config.allow_conversion) { return ( <Fragment> @@ -117,22 +113,15 @@ export function CreateCashout({ } const resultAccount = useAccountDetails(accountName); - const { - estimateByCredit: calculateFromCredit, - estimateByDebit: calculateFromDebit, - } = useCashoutEstimatorByUser(accountName); const { state: credentials } = useSessionState(); const creds = credentials.status !== "loggedIn" ? undefined : credentials; - const [form, setForm] = useState<Partial<FormType>>({ isDebit: true }); const rateResp = useConversionRateForUser(accountName, creds?.token); const conversionResp = useConversionInfo(); - const [notification, notifyOnError] = useLocalNotificationBetter(); - const mfa = useChallengeHandler(); if (!resultAccount) { return <Loading />; } else if (resultAccount instanceof TalerError) { - return <ErrorLoadingWithDebug error={resultAccount} />; + return <ErrorLoading error={resultAccount} />; } else if (resultAccount.type === "fail") { switch (resultAccount.case) { case HttpStatusCode.Unauthorized: @@ -147,7 +136,7 @@ export function CreateCashout({ if (!conversionResp) { return <Loading />; } else if (conversionResp instanceof TalerError) { - return <ErrorLoadingWithDebug error={conversionResp} />; + return <ErrorLoading error={conversionResp} />; } else if (conversionResp.type === "fail") { switch (conversionResp.case) { case HttpStatusCode.NotImplemented: { @@ -164,17 +153,11 @@ export function CreateCashout({ assertUnreachable(conversionResp); } } - const { - fiat_currency, - regional_currency, - fiat_currency_specification, - regional_currency_specification, - } = conversionResp.body; if (!rateResp) { return <Loading />; } else if (rateResp instanceof TalerError) { - return <ErrorLoadingWithDebug error={rateResp} />; + return <ErrorLoading error={rateResp} />; } else if (rateResp.type === "fail") { switch (rateResp.case) { case HttpStatusCode.NotImplemented: { @@ -197,15 +180,61 @@ export function CreateCashout({ <div>conversion enabled but server replied without conversion_rate</div> ); } + if (!creds) { + return <div>authentication required</div>; + } + + return ( + <CreateCashoutInternal + accountData={resultAccount.body} + onCashout={onCashout} + routeClose={routeClose} + focus={focus} + convConfig={conversionResp.body} + rate={rateResp.body} + session={creds} + /> + ); +} + +function CreateCashoutInternal({ + onCashout, + accountData, + focus, + routeClose, + convConfig: { + fiat_currency, + fiat_currency_specification, + regional_currency, + regional_currency_specification, + }, + session, + rate, +}: Omit<Props, "account"> & { + convConfig: TalerConversionInfoConfig; + rate: ConversionRate; + session: LoggedIn; + accountData: TalerCorebankApi.AccountData; +}): VNode { + const { + estimateByCredit: calculateFromCredit, + estimateByDebit: calculateFromDebit, + } = useCashoutEstimatorByUser(accountData.name); + const [form, setForm] = useState<Partial<FormType>>({ isDebit: true }); + const [notification, notifyOnError] = useLocalNotificationBetter(); + const mfa = useChallengeHandler(); + const { i18n } = useTranslationContext(); + const { + lib: { bank: api }, + } = useBankCoreApiContext(); const regionalZero = Amounts.zeroOfCurrency(regional_currency); const fiatZero = Amounts.zeroOfCurrency(fiat_currency!); const account = { - balance: Amounts.parseOrThrow(resultAccount.body.balance.amount), - balanceIsDebit: - resultAccount.body.balance.credit_debit_indicator == "debit", - debitThreshold: Amounts.parseOrThrow(resultAccount.body.debit_threshold), + balance: Amounts.parseOrThrow(accountData.balance.amount), + balanceIsDebit: accountData.balance.credit_debit_indicator == "debit", + debitThreshold: Amounts.parseOrThrow(accountData.debit_threshold), }; const balanceLimit = IntAmounts.toIntAmount( @@ -269,7 +298,7 @@ export function CreateCashout({ doAsync(); }, [form.amount, form.isDebit, notZero, higerThanMin, rate.cashout_fee]); - const calc = + const calc = calculationResult === "amount-is-too-small" ? zeroCalc : calculationResult; const balanceAfter = IntAmounts.toIntAmount( @@ -289,7 +318,9 @@ export function CreateCashout({ : calculationResult === "amount-is-too-small" ? i18n.str`Amount needs to be higher` : Amounts.isZero( - balanceLimit.deduce(calculationResult.debit).getResultZeroIfNegative(), + balanceLimit + .deduce(calculationResult.debit) + .getResultZeroIfNegative(), ) ? i18n.str`Balance is not enough` : Amounts.cmp(calculationResult.debit, rate.cashout_min_amount) < 0 @@ -309,9 +340,9 @@ export function CreateCashout({ ({ challengeIds, onChallengeRequired }) => makeSafeCall( i18n, - (creds: LoggedIn, calc: TransCalc, subject: string) => + (calc: TransCalc, subject: string) => api.createCashout( - creds, + session, { request_uid: RANDOM_STRING, amount_credit: Amounts.stringify(calc.credit), @@ -362,15 +393,15 @@ export function CreateCashout({ const subject = form.subject; const cashoutHandler = - !!errors || !creds || !subject + !!errors || !subject ? undefined - : () => notifyOnError(createCashout)(creds, calc, subject); + : () => notifyOnError(createCashout)(calc, subject); - const cashoutDisabled = !resultAccount.body.cashout_payto_uri; + const cashoutDisabled = !accountData.cashout_payto_uri; - const cashoutAccount = !resultAccount.body.cashout_payto_uri + const cashoutAccount = !accountData.cashout_payto_uri ? undefined - : parsePaytoUri(resultAccount.body.cashout_payto_uri); + : parsePaytoUri(accountData.cashout_payto_uri); const cashoutAccountName = !cashoutAccount ? undefined : cashoutAccount.targetPath; @@ -383,7 +414,7 @@ export function CreateCashout({ return ( <SolveMFAChallenges currentChallenge={mfa.pendingChallenge} - username={accountName} + username={accountData.name} description={i18n.str`Create cashout.`} onCancel={mfa.doCancelChallenge} onCompleted={repeatCashout} diff --git a/packages/bank-ui/src/pages/regional/ShowCashoutDetails.tsx b/packages/bank-ui/src/pages/regional/ShowCashoutDetails.tsx @@ -22,13 +22,14 @@ import { } from "@gnu-taler/taler-util"; import { Attention, + ErrorLoading, Loading, RouteDefinition, Time, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { VNode, h } from "preact"; -import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js"; + import { useCashoutDetails, useConversionInfo } from "../../hooks/regional.js"; import { RenderAmount } from "../PaytoWireTransferForm.js"; @@ -59,7 +60,7 @@ export function ShowCashoutDetails({ id, routeClose }: Props): VNode { return <Loading />; } if (result instanceof TalerError) { - return <ErrorLoadingWithDebug error={result} />; + return <ErrorLoading error={result} />; } if (result.type === "fail") { switch (result.case) { @@ -88,7 +89,7 @@ export function ShowCashoutDetails({ id, routeClose }: Props): VNode { } if (info instanceof TalerError) { - return <ErrorLoadingWithDebug error={info} />; + return <ErrorLoading error={info} />; } if (info.type === "fail") { switch (info.case) { diff --git a/packages/bank-ui/src/utils.ts b/packages/bank-ui/src/utils.ts @@ -142,7 +142,7 @@ export async function withRuntimeErrorHandling<T>( i18n.str`Operation failed, please report`, (error instanceof Error ? error.message - : JSON.stringify(error)) as TranslatedString, + : (error)) as TranslatedString, ); } } @@ -159,7 +159,7 @@ export function buildRequestErrorMessage( type: "error", title: i18n.str`Request timeout`, description: cause.message as TranslatedString, - debug: JSON.stringify(cause.errorDetail, undefined, 2), + debug: cause.errorDetail, when: AbsoluteTime.now(), }; break; @@ -169,7 +169,7 @@ export function buildRequestErrorMessage( type: "error", title: i18n.str`Request throttled`, description: cause.message as TranslatedString, - debug: JSON.stringify(cause.errorDetail, undefined, 2), + debug: cause.errorDetail, when: AbsoluteTime.now(), }; break; @@ -179,7 +179,7 @@ export function buildRequestErrorMessage( type: "error", title: i18n.str`Malformed response`, description: cause.message as TranslatedString, - debug: JSON.stringify(cause.errorDetail, undefined, 2), + debug: cause.errorDetail, when: AbsoluteTime.now(), }; break; @@ -189,7 +189,7 @@ export function buildRequestErrorMessage( type: "error", title: i18n.str`Network error`, description: cause.message as TranslatedString, - debug: JSON.stringify(cause.errorDetail, undefined, 2), + debug: cause.errorDetail, when: AbsoluteTime.now(), }; break; @@ -199,7 +199,7 @@ export function buildRequestErrorMessage( type: "error", title: i18n.str`Unexpected request error`, description: cause.message as TranslatedString, - debug: JSON.stringify(cause.errorDetail, undefined, 2), + debug: cause.errorDetail, when: AbsoluteTime.now(), }; break; @@ -209,7 +209,7 @@ export function buildRequestErrorMessage( type: "error", title: i18n.str`Unexpected error`, description: cause.message as TranslatedString, - debug: JSON.stringify(cause.errorDetail, undefined, 2), + debug: cause.errorDetail, when: AbsoluteTime.now(), }; break; diff --git a/packages/challenger-ui/src/app.tsx b/packages/challenger-ui/src/app.tsx @@ -25,6 +25,7 @@ import { import { BrowserHashNavigationProvider, ChallengerApiProvider, + CommonPreferenceProvider, Loading, TalerWalletIntegrationBrowserProvider, TranslationProvider, @@ -69,55 +70,57 @@ export function App(): VNode { const baseUrl = getInitialBackendBaseURL(settings.backendBaseURL); return ( <SettingsProvider value={settings}> - <TranslationProvider - source={strings} - completeness={{ - es: strings["es"].completeness, - de: strings["de"].completeness, - }} - > - <ChallengerApiProvider - baseUrl={new URL("/", baseUrl)} - frameOnError={Frame} - evictors={{ - challenger: evictBankSwrCache, + <CommonPreferenceProvider> + <TranslationProvider + source={strings} + completeness={{ + es: strings["es"].completeness, + de: strings["de"].completeness, }} > - <SWRConfig - value={{ - provider: WITH_LOCAL_STORAGE_CACHE - ? localStorageProvider - : undefined, - // normally, do not revalidate - revalidateOnFocus: false, - revalidateOnReconnect: false, - revalidateIfStale: false, - revalidateOnMount: undefined, - focusThrottleInterval: undefined, - - // normally, do not refresh - refreshInterval: undefined, - dedupingInterval: 2000, - refreshWhenHidden: false, - refreshWhenOffline: false, - - // ignore errors - shouldRetryOnError: false, - errorRetryCount: 0, - errorRetryInterval: undefined, - - // do not go to loading again if already has data - keepPreviousData: true, + <ChallengerApiProvider + baseUrl={new URL("/", baseUrl)} + frameOnError={Frame} + evictors={{ + challenger: evictBankSwrCache, }} > - <TalerWalletIntegrationBrowserProvider> - <BrowserHashNavigationProvider> - <Routing /> - </BrowserHashNavigationProvider> - </TalerWalletIntegrationBrowserProvider> - </SWRConfig> - </ChallengerApiProvider> - </TranslationProvider> + <SWRConfig + value={{ + provider: WITH_LOCAL_STORAGE_CACHE + ? localStorageProvider + : undefined, + // normally, do not revalidate + revalidateOnFocus: false, + revalidateOnReconnect: false, + revalidateIfStale: false, + revalidateOnMount: undefined, + focusThrottleInterval: undefined, + + // normally, do not refresh + refreshInterval: undefined, + dedupingInterval: 2000, + refreshWhenHidden: false, + refreshWhenOffline: false, + + // ignore errors + shouldRetryOnError: false, + errorRetryCount: 0, + errorRetryInterval: undefined, + + // do not go to loading again if already has data + keepPreviousData: true, + }} + > + <TalerWalletIntegrationBrowserProvider> + <BrowserHashNavigationProvider> + <Routing /> + </BrowserHashNavigationProvider> + </TalerWalletIntegrationBrowserProvider> + </SWRConfig> + </ChallengerApiProvider> + </TranslationProvider> + </CommonPreferenceProvider> </SettingsProvider> ); } diff --git a/packages/challenger-ui/src/pages/AskChallenge.tsx b/packages/challenger-ui/src/pages/AskChallenge.tsx @@ -27,6 +27,7 @@ import { Attention, Button, countryNameList, + ErrorLoading, FormDesign, FormUI, LocalNotificationBanner, @@ -43,7 +44,6 @@ import { SessionId, useSessionState } from "../hooks/session.js"; import { getAddressDescriptionFromAddrType } from "./AnswerChallenge.js"; -import { ErrorLoadingWithDebug } from "./ErrorLoadingWithDebug.js"; type Props = { onSendSuccesful: () => void; @@ -76,7 +76,7 @@ export function AskChallenge({ ); } if (result instanceof TalerError) { - return <ErrorLoadingWithDebug error={result} />; + return <ErrorLoading error={result} />; } if (result.type === "fail") { switch (result.case) { @@ -201,7 +201,7 @@ export function AskChallenge({ return ( <Fragment> - <LocalNotificationBanner notification={notification} showDebug={true} /> + <LocalNotificationBanner notification={notification} /> <div class="isolate bg-white px-6 py-12"> <div class="mx-auto max-w-2xl text-center"> diff --git a/packages/challenger-ui/src/pages/ErrorLoadingWithDebug.tsx b/packages/challenger-ui/src/pages/ErrorLoadingWithDebug.tsx @@ -1,24 +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 { TalerError } from "@gnu-taler/taler-util"; -import { ErrorLoading } from "@gnu-taler/web-util/browser"; -import { VNode, h } from "preact"; -import { usePreferences } from "../context/preferences.js"; - -export function ErrorLoadingWithDebug({ error }: { error: TalerError }): VNode { - const [pref] = usePreferences(); - return <ErrorLoading error={error} showDetail={pref.showDebugInfo} />; -} diff --git a/packages/kyc-ui/src/app.tsx b/packages/kyc-ui/src/app.tsx @@ -24,6 +24,7 @@ import { } from "@gnu-taler/taler-util"; import { BrowserHashNavigationProvider, + CommonPreferenceProvider, ExchangeApiProvider, Loading, TalerWalletIntegrationBrowserProvider, @@ -58,59 +59,61 @@ export function App(): VNode { const baseUrl = getInitialBackendBaseURL(settings.backendBaseURL); return ( <SettingsProvider value={settings}> - <TranslationProvider - source={strings} - completeness={{ - es: strings["es"].completeness, - de: strings["de"].completeness, - }} - > - <ExchangeApiProvider - baseUrl={new URL("/", baseUrl)} - frameOnError={Frame} - evictors={{ - exchange: evictExchangeSwrCache, + <CommonPreferenceProvider> + <TranslationProvider + source={strings} + completeness={{ + es: strings["es"].completeness, + de: strings["de"].completeness, }} > - <SWRConfig - value={{ - provider: WITH_LOCAL_STORAGE_CACHE - ? localStorageProvider - : undefined, - // normally, do not revalidate - revalidateOnFocus: false, - revalidateOnReconnect: false, - revalidateIfStale: false, - revalidateOnMount: undefined, - focusThrottleInterval: undefined, - - // normally, do not refresh - refreshInterval: undefined, - dedupingInterval: 2000, - refreshWhenHidden: false, - refreshWhenOffline: false, - - // ignore errors - shouldRetryOnError: false, - errorRetryCount: 0, - errorRetryInterval: undefined, - - // do not go to loading again if already has data - keepPreviousData: true, + <ExchangeApiProvider + baseUrl={new URL("/", baseUrl)} + frameOnError={Frame} + evictors={{ + exchange: evictExchangeSwrCache, }} > - <TalerWalletIntegrationBrowserProvider> - <BrowserHashNavigationProvider> - <UiFormsProvider value={forms}> - <NotifierProvider> - <Routing /> - </NotifierProvider> - </UiFormsProvider> - </BrowserHashNavigationProvider> - </TalerWalletIntegrationBrowserProvider> - </SWRConfig> - </ExchangeApiProvider> - </TranslationProvider> + <SWRConfig + value={{ + provider: WITH_LOCAL_STORAGE_CACHE + ? localStorageProvider + : undefined, + // normally, do not revalidate + revalidateOnFocus: false, + revalidateOnReconnect: false, + revalidateIfStale: false, + revalidateOnMount: undefined, + focusThrottleInterval: undefined, + + // normally, do not refresh + refreshInterval: undefined, + dedupingInterval: 2000, + refreshWhenHidden: false, + refreshWhenOffline: false, + + // ignore errors + shouldRetryOnError: false, + errorRetryCount: 0, + errorRetryInterval: undefined, + + // do not go to loading again if already has data + keepPreviousData: true, + }} + > + <TalerWalletIntegrationBrowserProvider> + <BrowserHashNavigationProvider> + <UiFormsProvider value={forms}> + <NotifierProvider> + <Routing /> + </NotifierProvider> + </UiFormsProvider> + </BrowserHashNavigationProvider> + </TalerWalletIntegrationBrowserProvider> + </SWRConfig> + </ExchangeApiProvider> + </TranslationProvider> + </CommonPreferenceProvider> </SettingsProvider> ); } diff --git a/packages/kyc-ui/src/components/ErrorLoadingWithDebug.tsx b/packages/kyc-ui/src/components/ErrorLoadingWithDebug.tsx @@ -1,24 +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 { TalerError } from "@gnu-taler/taler-util"; -import { ErrorLoading } from "@gnu-taler/web-util/browser"; -import { VNode, h } from "preact"; -import { usePreferences } from "../context/preferences.js"; - -export function ErrorLoadingWithDebug({ error }: { error: TalerError }): VNode { - const [pref] = usePreferences(); - return <ErrorLoading error={error} showDetail={pref.showDebugInfo} />; -} diff --git a/packages/kyc-ui/src/pages/Start.tsx b/packages/kyc-ui/src/pages/Start.tsx @@ -23,6 +23,7 @@ import { } from "@gnu-taler/taler-util"; import { Attention, + ErrorLoading, Loading, LocalNotificationBanner, useExchangeApiContext, @@ -31,7 +32,6 @@ import { } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; -import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js"; import { useKycInfo } from "../hooks/kyc.js"; import { FillForm } from "./FillForm.js"; @@ -53,7 +53,7 @@ function ShowReqList({ return <Loading />; } if (result instanceof TalerError) { - return <ErrorLoadingWithDebug error={result} />; + return <ErrorLoading error={result} />; } if (result.type === "fail") { diff --git a/packages/merchant-backoffice-ui/src/Application.tsx b/packages/merchant-backoffice-ui/src/Application.tsx @@ -29,6 +29,7 @@ import { } from "@gnu-taler/taler-util"; import { BrowserHashNavigationProvider, + CommonPreferenceProvider, ConfigResultFail, MerchantApiProvider, TalerWalletIntegrationBrowserProvider, @@ -97,63 +98,65 @@ export function Application(): VNode { const baseUrl = getInitialBackendBaseURL(settings.backendBaseURL); return ( <SettingsProvider value={settings}> - <TranslationProvider - source={strings} - completeness={{ - uk: strings["uk"].completeness, - tr: strings["tr"].completeness, - // ru: strings["ru"].completeness, - sv: strings["sv"].completeness, - it: strings["it"].completeness, - fr: strings["fr"].completeness, - es: strings["es"].completeness, - de: strings["de"].completeness, - }} - > - <MerchantApiProvider - baseUrl={new URL("./", baseUrl)} - frameOnError={OnConfigError} - evictors={{ - management: swrCacheEvictor, + <CommonPreferenceProvider showDebug={false}> + <TranslationProvider + source={strings} + completeness={{ + uk: strings["uk"].completeness, + tr: strings["tr"].completeness, + // ru: strings["ru"].completeness, + sv: strings["sv"].completeness, + it: strings["it"].completeness, + fr: strings["fr"].completeness, + es: strings["es"].completeness, + de: strings["de"].completeness, }} > - <SessionContextProvider> - <SWRConfig - value={{ - provider: WITH_LOCAL_STORAGE_CACHE - ? localStorageProvider - : undefined, - // normally, do not revalidate - revalidateOnFocus: false, - revalidateOnReconnect: false, - revalidateIfStale: false, - revalidateOnMount: undefined, - focusThrottleInterval: undefined, + <MerchantApiProvider + baseUrl={new URL("./", baseUrl)} + frameOnError={OnConfigError} + evictors={{ + management: swrCacheEvictor, + }} + > + <SessionContextProvider> + <SWRConfig + value={{ + provider: WITH_LOCAL_STORAGE_CACHE + ? localStorageProvider + : undefined, + // normally, do not revalidate + revalidateOnFocus: false, + revalidateOnReconnect: false, + revalidateIfStale: false, + revalidateOnMount: undefined, + focusThrottleInterval: undefined, - // normally, do not refresh - refreshInterval: undefined, - dedupingInterval: 2000, - refreshWhenHidden: false, - refreshWhenOffline: false, + // normally, do not refresh + refreshInterval: undefined, + dedupingInterval: 2000, + refreshWhenHidden: false, + refreshWhenOffline: false, - // ignore errors - shouldRetryOnError: false, - errorRetryCount: 0, - errorRetryInterval: undefined, + // ignore errors + shouldRetryOnError: false, + errorRetryCount: 0, + errorRetryInterval: undefined, - // do not go to loading again if already has data - keepPreviousData: true, - }} - > - <TalerWalletIntegrationBrowserProvider> - <BrowserHashNavigationProvider> - <Routing /> - </BrowserHashNavigationProvider> - </TalerWalletIntegrationBrowserProvider> - </SWRConfig> - </SessionContextProvider> - </MerchantApiProvider> - </TranslationProvider> + // do not go to loading again if already has data + keepPreviousData: true, + }} + > + <TalerWalletIntegrationBrowserProvider> + <BrowserHashNavigationProvider> + <Routing /> + </BrowserHashNavigationProvider> + </TalerWalletIntegrationBrowserProvider> + </SWRConfig> + </SessionContextProvider> + </MerchantApiProvider> + </TranslationProvider> + </CommonPreferenceProvider> </SettingsProvider> ); } @@ -392,17 +395,17 @@ const swrCacheEvictor = new (class } case TalerMerchantInstanceCacheEviction.CREATE_ACCESSTOKEN: { await Promise.all([revalidateInstanceAccessTokens()]); - return ; + return; } case TalerMerchantInstanceCacheEviction.DELETE_ACCESSTOKEN: { await Promise.all([revalidateInstanceAccessTokens()]); - return ; + return; } case TalerMerchantInstanceCacheEviction.LAST: { return; } default: { - assertUnreachable(op) + assertUnreachable(op); } } } diff --git a/packages/web-util/src/components/ErrorLoading.tsx b/packages/web-util/src/components/ErrorLoading.tsx @@ -15,117 +15,148 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { TalerError, TalerErrorCode, assertUnreachable } from "@gnu-taler/taler-util"; +import { + TalerError, + TalerErrorCode, + assertUnreachable, +} from "@gnu-taler/taler-util"; import { Fragment, VNode, h } from "preact"; -import { Attention } from "./Attention.js"; +import { useCommonPreference } from "../context/common-preferences.js"; import { useTranslationContext } from "../index.browser.js"; +import { Attention } from "./Attention.js"; -export function ErrorLoading({ error, showDetail }: { error: TalerError, showDetail?: boolean }): VNode { - const { i18n } = useTranslationContext() +export function DebugInfo({ error }: { error: any }): VNode { + const { i18n } = useTranslationContext(); + const { showDebugInfo, toggle } = useCommonPreference(); + return ( + <div class="text-[grey]"> + <button onClick={toggle}> + {!showDebugInfo ? ( + <i18n.Translate>Show more information</i18n.Translate> + ) : ( + <i18n.Translate>Hide debug info</i18n.Translate> + )} + </button> + {showDebugInfo && ( + <pre class="whitespace-break-spaces text-black"> + {JSON.stringify(error, undefined, 2)} + </pre> + )} + </div> + ); +} +export function ErrorLoading({ error }: { error: TalerError }): VNode { + const { i18n } = useTranslationContext(); switch (error.errorDetail.code) { ////////////////// // Every error that can be produce in a Http Request ////////////////// case TalerErrorCode.GENERIC_TIMEOUT: { if (error.hasErrorCode(TalerErrorCode.GENERIC_TIMEOUT)) { - const { requestMethod, requestUrl, timeoutMs } = error.errorDetail - return <Attention type="danger" title={i18n.str`The request reached a timeout, check your connection.`}> - {error.message} - {showDetail && - <pre class="whitespace-break-spaces "> - {JSON.stringify({ requestMethod, requestUrl, timeoutMs }, undefined, 2)} - </pre> - } - </Attention> + return ( + <Attention + type="danger" + title={i18n.str`The request reached a timeout, check your connection.`} + > + {error.message} + <DebugInfo error={error.errorDetail} /> + </Attention> + ); } - assertUnreachable(1 as never) + assertUnreachable(1 as never); } case TalerErrorCode.GENERIC_CLIENT_INTERNAL_ERROR: { if (error.hasErrorCode(TalerErrorCode.GENERIC_CLIENT_INTERNAL_ERROR)) { - const { requestMethod, requestUrl, timeoutMs } = error.errorDetail - return <Attention type="danger" title={i18n.str`The request was cancelled.`}> - {error.message} - {showDetail && - <pre class="whitespace-break-spaces "> - {JSON.stringify({ requestMethod, requestUrl, timeoutMs }, undefined, 2)} - </pre> - } - </Attention> + const { requestMethod, requestUrl, timeoutMs } = error.errorDetail; + return ( + <Attention type="danger" title={i18n.str`The request was cancelled.`}> + {error.message} + <DebugInfo error={error.errorDetail} /> + </Attention> + ); } - assertUnreachable(1 as never) + assertUnreachable(1 as never); } case TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT: { - if (error.hasErrorCode(TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT)) { - const { requestMethod, requestUrl, timeoutMs } = error.errorDetail - return <Attention type="danger" title={i18n.str`The request reached a timeout, check your connection.`}> - {error.message} - {showDetail && - <pre class="whitespace-break-spaces "> - {JSON.stringify({ requestMethod, requestUrl, timeoutMs }, undefined, 2)} - </pre> - } - </Attention> + if ( + error.hasErrorCode(TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT) + ) { + const { requestMethod, requestUrl, timeoutMs } = error.errorDetail; + return ( + <Attention + type="danger" + title={i18n.str`The request reached a timeout, check your connection.`} + > + {error.message} + <DebugInfo error={error.errorDetail} /> + </Attention> + ); } - assertUnreachable(1 as never) + assertUnreachable(1 as never); } case TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED: { if (error.hasErrorCode(TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED)) { - const { requestMethod, requestUrl, throttleStats } = error.errorDetail - return <Attention type="danger" title={i18n.str`Too many requests were made to the server, and this action was throttled.`}> - {error.message} - {showDetail && - <pre class="whitespace-break-spaces "> - {JSON.stringify({ requestMethod, requestUrl, throttleStats }, undefined, 2)} - </pre> - } - </Attention> + const { requestMethod, requestUrl, throttleStats } = error.errorDetail; + return ( + <Attention + type="danger" + title={i18n.str`Too many requests were made to the server, and this action was throttled.`} + > + {error.message} + <DebugInfo error={error.errorDetail} /> + </Attention> + ); } - assertUnreachable(1 as never) + assertUnreachable(1 as never); } case TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE: { - if (error.hasErrorCode(TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE)) { - const { requestMethod, requestUrl, httpStatusCode, validationError } = error.errorDetail - return <Attention type="danger" title={i18n.str`The server's response was malformed.`}> - {error.message} - {showDetail && - <pre class="whitespace-break-spaces "> - {JSON.stringify({ requestMethod, requestUrl, httpStatusCode, validationError }, undefined, 2)} - </pre> - } - </Attention> + if ( + error.hasErrorCode(TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE) + ) { + const { requestMethod, requestUrl, httpStatusCode, validationError } = + error.errorDetail; + return ( + <Attention + type="danger" + title={i18n.str`The server's response was malformed.`} + > + {error.message} + <DebugInfo error={error.errorDetail} /> + </Attention> + ); } - assertUnreachable(1 as never) + assertUnreachable(1 as never); } case TalerErrorCode.WALLET_NETWORK_ERROR: { if (error.hasErrorCode(TalerErrorCode.WALLET_NETWORK_ERROR)) { - const { requestMethod, requestUrl } = error.errorDetail - return <Attention type="danger" title={i18n.str`Could not complete the request due to a network problem.`}> - {error.message} - {showDetail && - <pre class="whitespace-break-spaces "> - {JSON.stringify({ requestMethod, requestUrl }, undefined, 2)} - </pre> - } - </Attention> + const { requestMethod, requestUrl } = error.errorDetail; + return ( + <Attention + type="danger" + title={i18n.str`Could not complete the request due to a network problem.`} + > + {error.message} + <DebugInfo error={error.errorDetail} /> + </Attention> + ); } - assertUnreachable(1 as never) + assertUnreachable(1 as never); } case TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR: { if (error.hasErrorCode(TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR)) { - const { requestMethod, requestUrl, httpStatusCode, errorResponse } = error.errorDetail - return <Attention type="danger" title={i18n.str`Unexpected request error`}> - {error.message} - {showDetail && - <pre class="whitespace-break-spaces "> - {JSON.stringify({ requestMethod, requestUrl, httpStatusCode, errorResponse }, undefined, 2)} - </pre> - } - </Attention> + const { requestMethod, requestUrl, httpStatusCode, errorResponse } = + error.errorDetail; + return ( + <Attention type="danger" title={i18n.str`Unexpected request error`}> + {error.message} + <DebugInfo error={error.errorDetail} /> + </Attention> + ); } - assertUnreachable(1 as never) + assertUnreachable(1 as never); } ////////////////// - // Every other error + // Every other error ////////////////// // case TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR: { // return <Attention type="danger" title={i18n.str``}> @@ -134,14 +165,12 @@ export function ErrorLoading({ error, showDetail }: { error: TalerError, showDet ////////////////// // Default message for unhandled case ////////////////// - default: return <Attention type="danger" title={i18n.str`Unexpected error`}> - {error.message} - {showDetail && - <pre class="whitespace-break-spaces "> - {JSON.stringify(error.errorDetail, undefined, 2)} - </pre> - } - </Attention> + default: + return ( + <Attention type="danger" title={i18n.str`Unexpected error`}> + {error.message} + <DebugInfo error={error.errorDetail} /> + </Attention> + ); } } - diff --git a/packages/web-util/src/components/ErrorLoadingMerchant.tsx b/packages/web-util/src/components/ErrorLoadingMerchant.tsx @@ -15,114 +15,184 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { TalerError, TalerErrorCode, assertUnreachable } from "@gnu-taler/taler-util"; +import { + TalerError, + TalerErrorCode, + assertUnreachable, +} from "@gnu-taler/taler-util"; import { Fragment, VNode, h } from "preact"; import { Attention } from "./Attention.js"; import { useTranslationContext } from "../index.browser.js"; +import { useCommonPreference } from "../context/common-preferences.js"; -export function ErrorLoading({ error, showDetail }: { error: TalerError, showDetail?: boolean }): VNode { - const { i18n } = useTranslationContext() +export function ErrorLoading({ error }: { error: TalerError }): VNode { + const { showDebugInfo, toggle } = useCommonPreference(); + const { i18n } = useTranslationContext(); switch (error.errorDetail.code) { ////////////////// // Every error that can be produce in a Http Request ////////////////// case TalerErrorCode.GENERIC_TIMEOUT: { if (error.hasErrorCode(TalerErrorCode.GENERIC_TIMEOUT)) { - const { requestMethod, requestUrl, timeoutMs } = error.errorDetail - return <Attention type="danger" title={i18n.str`The request reached a timeout, check your connection.`}> - {error.message} - {showDetail && - <pre class="whitespace-break-spaces "> - {JSON.stringify({ requestMethod, requestUrl, timeoutMs }, undefined, 2)} - </pre> - } - </Attention> + const { requestMethod, requestUrl, timeoutMs } = error.errorDetail; + return ( + <Attention + type="danger" + title={i18n.str`The request reached a timeout, check your connection.`} + > + {error.message} + {showDebugInfo && ( + <pre class="whitespace-break-spaces "> + {JSON.stringify( + { requestMethod, requestUrl, timeoutMs }, + undefined, + 2, + )} + </pre> + )} + </Attention> + ); } - assertUnreachable(1 as never) + assertUnreachable(1 as never); } case TalerErrorCode.GENERIC_CLIENT_INTERNAL_ERROR: { if (error.hasErrorCode(TalerErrorCode.GENERIC_CLIENT_INTERNAL_ERROR)) { - const { requestMethod, requestUrl, timeoutMs } = error.errorDetail - return <Attention type="danger" title={i18n.str`The request was cancelled.`}> - {error.message} - {showDetail && - <pre class="whitespace-break-spaces "> - {JSON.stringify({ requestMethod, requestUrl, timeoutMs }, undefined, 2)} - </pre> - } - </Attention> + const { requestMethod, requestUrl, timeoutMs } = error.errorDetail; + return ( + <Attention type="danger" title={i18n.str`The request was cancelled.`}> + {error.message} + {showDebugInfo && ( + <pre class="whitespace-break-spaces "> + {JSON.stringify( + { requestMethod, requestUrl, timeoutMs }, + undefined, + 2, + )} + </pre> + )} + </Attention> + ); } - assertUnreachable(1 as never) + assertUnreachable(1 as never); } case TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT: { - if (error.hasErrorCode(TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT)) { - const { requestMethod, requestUrl, timeoutMs } = error.errorDetail - return <Attention type="danger" title={i18n.str`The request reached a timeout, check your connection.`}> - {error.message} - {showDetail && - <pre class="whitespace-break-spaces "> - {JSON.stringify({ requestMethod, requestUrl, timeoutMs }, undefined, 2)} - </pre> - } - </Attention> + if ( + error.hasErrorCode(TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT) + ) { + const { requestMethod, requestUrl, timeoutMs } = error.errorDetail; + return ( + <Attention + type="danger" + title={i18n.str`The request reached a timeout, check your connection.`} + > + {error.message} + {showDebugInfo && ( + <pre class="whitespace-break-spaces "> + {JSON.stringify( + { requestMethod, requestUrl, timeoutMs }, + undefined, + 2, + )} + </pre> + )} + </Attention> + ); } - assertUnreachable(1 as never) + assertUnreachable(1 as never); } case TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED: { if (error.hasErrorCode(TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED)) { - const { requestMethod, requestUrl, throttleStats } = error.errorDetail - return <Attention type="danger" title={i18n.str`Too many requests were made to the server, and this action was throttled.`}> - {error.message} - {showDetail && - <pre class="whitespace-break-spaces "> - {JSON.stringify({ requestMethod, requestUrl, throttleStats }, undefined, 2)} - </pre> - } - </Attention> + const { requestMethod, requestUrl, throttleStats } = error.errorDetail; + return ( + <Attention + type="danger" + title={i18n.str`Too many requests were made to the server, and this action was throttled.`} + > + {error.message} + {showDebugInfo && ( + <pre class="whitespace-break-spaces "> + {JSON.stringify( + { requestMethod, requestUrl, throttleStats }, + undefined, + 2, + )} + </pre> + )} + </Attention> + ); } - assertUnreachable(1 as never) + assertUnreachable(1 as never); } case TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE: { - if (error.hasErrorCode(TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE)) { - const { requestMethod, requestUrl, httpStatusCode, validationError } = error.errorDetail - return <Attention type="danger" title={i18n.str`The server's response was malformed.`}> - {error.message} - {showDetail && - <pre class="whitespace-break-spaces "> - {JSON.stringify({ requestMethod, requestUrl, httpStatusCode, validationError }, undefined, 2)} - </pre> - } - </Attention> + if ( + error.hasErrorCode(TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE) + ) { + const { requestMethod, requestUrl, httpStatusCode, validationError } = + error.errorDetail; + return ( + <Attention + type="danger" + title={i18n.str`The server's response was malformed.`} + > + {error.message} + {showDebugInfo && ( + <pre class="whitespace-break-spaces "> + {JSON.stringify( + { + requestMethod, + requestUrl, + httpStatusCode, + validationError, + }, + undefined, + 2, + )} + </pre> + )} + </Attention> + ); } - assertUnreachable(1 as never) + assertUnreachable(1 as never); } case TalerErrorCode.WALLET_NETWORK_ERROR: { if (error.hasErrorCode(TalerErrorCode.WALLET_NETWORK_ERROR)) { - const { requestMethod, requestUrl } = error.errorDetail - return <Attention type="danger" title={i18n.str`Due to a network problem the request could not be finished.`}> - {error.message} - {showDetail && - <pre class="whitespace-break-spaces "> - {JSON.stringify({ requestMethod, requestUrl }, undefined, 2)} - </pre> - } - </Attention> + const { requestMethod, requestUrl } = error.errorDetail; + return ( + <Attention + type="danger" + title={i18n.str`Due to a network problem the request could not be finished.`} + > + {error.message} + {showDebugInfo && ( + <pre class="whitespace-break-spaces "> + {JSON.stringify({ requestMethod, requestUrl }, undefined, 2)} + </pre> + )} + </Attention> + ); } - assertUnreachable(1 as never) + assertUnreachable(1 as never); } case TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR: { if (error.hasErrorCode(TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR)) { - const { requestMethod, requestUrl, httpStatusCode, errorResponse } = error.errorDetail - return <Attention type="danger" title={i18n.str`Unexpected request error`}> - {error.message} - {showDetail && - <pre class="whitespace-break-spaces "> - {JSON.stringify({ requestMethod, requestUrl, httpStatusCode, errorResponse }, undefined, 2)} - </pre> - } - </Attention> + const { requestMethod, requestUrl, httpStatusCode, errorResponse } = + error.errorDetail; + return ( + <Attention type="danger" title={i18n.str`Unexpected request error`}> + {error.message} + {showDebugInfo && ( + <pre class="whitespace-break-spaces "> + {JSON.stringify( + { requestMethod, requestUrl, httpStatusCode, errorResponse }, + undefined, + 2, + )} + </pre> + )} + </Attention> + ); } - assertUnreachable(1 as never) + assertUnreachable(1 as never); } ////////////////// // Every other error @@ -134,14 +204,16 @@ export function ErrorLoading({ error, showDetail }: { error: TalerError, showDet ////////////////// // Default message for unhandled case ////////////////// - default: return <Attention type="danger" title={i18n.str`Unexpected error`}> - {error.message} - {showDetail && - <pre class="whitespace-break-spaces "> - {JSON.stringify(error.errorDetail, undefined, 2)} - </pre> - } - </Attention> + default: + return ( + <Attention type="danger" title={i18n.str`Unexpected error`}> + {error.message} + {showDebugInfo && ( + <pre class="whitespace-break-spaces "> + {JSON.stringify(error.errorDetail, undefined, 2)} + </pre> + )} + </Attention> + ); } } - diff --git a/packages/web-util/src/components/NotificationBanner.tsx b/packages/web-util/src/components/NotificationBanner.tsx @@ -1,33 +1,49 @@ import { h, Fragment, VNode } from "preact"; import { Attention } from "./Attention.js"; -import { Notification } from "../index.browser.js"; +import { DebugInfo, Notification, useTranslationContext } from "../index.browser.js"; +import { useCommonPreference } from "../context/common-preferences.js"; -export function LocalNotificationBanner({ notification, showDebug }: { notification?: Notification, showDebug?: boolean }): VNode { - if (!notification) return <Fragment /> +export function LocalNotificationBanner({ + notification, +}: { + notification?: Notification; +}): VNode { + if (!notification) return <Fragment />; switch (notification.message.type) { case "error": - return <div class="relative"> - <div class="fixed top-0 left-0 right-0 z-20 w-full p-4"> - <Attention type="danger" title={notification.message.title} onClose={() => { - notification.acknowledge() - }}> - {notification.message.description && - <div class="mt-2 text-sm text-red-700"> - {notification.message.description} - </div> - } - {showDebug && <pre class="whitespace-break-spaces "> - {notification.message.debug} - </pre>} - </Attention> + return ( + <div class="relative"> + <div class="fixed top-0 left-0 right-0 z-20 w-full p-4"> + <Attention + type="danger" + title={notification.message.title} + onClose={() => { + notification.acknowledge(); + }} + > + {notification.message.description && ( + <div class="mt-2 text-sm text-red-700"> + {notification.message.description} + </div> + )} + <DebugInfo error={notification.message.debug} /> + </Attention> + </div> </div> - </div> + ); case "info": - return <div class="relative"> - <div class="fixed top-0 left-0 right-0 z-20 w-full p-4"> - <Attention type="success" title={notification.message.title} onClose={() => { - notification.acknowledge(); - }} /></div></div> + return ( + <div class="relative"> + <div class="fixed top-0 left-0 right-0 z-20 w-full p-4"> + <Attention + type="success" + title={notification.message.title} + onClose={() => { + notification.acknowledge(); + }} + /> + </div> + </div> + ); } } - diff --git a/packages/web-util/src/context/bank-api.ts b/packages/web-util/src/context/bank-api.ts @@ -134,7 +134,7 @@ export const BankApiProvider = ({ } if (checked.type === "error") { return h(frameOnError, { - children: h(ErrorLoading, { error: checked.error, showDetail: true }), + children: h(ErrorLoading, { error: checked.error }), }); } if (checked.type === "incompatible") { diff --git a/packages/web-util/src/context/challenger-api.ts b/packages/web-util/src/context/challenger-api.ts @@ -136,7 +136,7 @@ export const ChallengerApiProvider = ({ } if (checked.type === "error") { return h(frameOnError, { - children: h(ErrorLoading, { error: checked.error, showDetail: true }), + children: h(ErrorLoading, { error: checked.error }), }); } if (checked.type === "incompatible") { diff --git a/packages/web-util/src/context/common-preferences.ts b/packages/web-util/src/context/common-preferences.ts @@ -0,0 +1,66 @@ +/* + 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, useState } from "preact/hooks"; + +const TALER_SCREEN_ID = 102; + +interface Type { + showDebugInfo: boolean; + toggle(): void; +} + +const initial: Type = { + showDebugInfo: true, + toggle() {}, +}; +// @ts-expect-error asd +const Context = createContext<Type>(undefined); + +export const CommonPreferenceProvider = ({ + showDebug, + children, +}: { + showDebug?: boolean; + children: ComponentChildren; +}): VNode => { + const [value, update] = useState<boolean>(showDebug ?? false); + + return h(Context.Provider, { + value: { + showDebugInfo: !!value, + toggle() { + update(!value); + }, + }, + children, + }); +}; + +export const useCommonPreference = (): Type => useContext(Context); + +// /** +// * User common preferences. +// * +// * @returns tuple of [state, update()] +// */ +// export function useCommonPreference(): [boolean, () => void] { +// const {showDebugInfo, toggle} = useContext(Context); + +// return [showDebugInfo, toggle]; +// } + diff --git a/packages/web-util/src/context/exchange-api.ts b/packages/web-util/src/context/exchange-api.ts @@ -158,7 +158,7 @@ export const ExchangeApiProvider = ({ } if (checked.type === "error") { return h(frameOnError, { - children: h(ErrorLoading, { error: checked.error, showDetail: true }), + children: h(ErrorLoading, { error: checked.error }), }); } if (checked.type === "incompatible") { diff --git a/packages/web-util/src/context/index.ts b/packages/web-util/src/context/index.ts @@ -9,4 +9,5 @@ export * from "./challenger-api.js"; export * from "./merchant-api.js"; export * from "./exchange-api.js"; export * from "./navigation.js"; +export * from "./common-preferences.js"; export * from "./wallet-integration.js"; diff --git a/packages/web-util/src/context/navigation.ts b/packages/web-util/src/context/navigation.ts @@ -30,7 +30,7 @@ import { * @author Sebastian Javier Marchano (sebasjm) */ -export type Type = { +type Type = { path: string; params: Record<string, string[]>; navigateTo: (path: AppLocation) => void; diff --git a/packages/web-util/src/hooks/useNotifications.ts b/packages/web-util/src/hooks/useNotifications.ts @@ -411,7 +411,7 @@ function buildUnifiedRequestErrorMessage( type: "error", title: i18n.str`Request timeout`, description: cause.message as TranslatedString, - debug: JSON.stringify(cause.errorDetail, undefined, 2), + debug: cause.errorDetail, when: AbsoluteTime.now(), }; break; @@ -421,7 +421,7 @@ function buildUnifiedRequestErrorMessage( type: "error", title: i18n.str`Request cancelled`, description: cause.message as TranslatedString, - debug: JSON.stringify(cause.errorDetail, undefined, 2), + debug: cause.errorDetail, when: AbsoluteTime.now(), }; break; @@ -431,7 +431,7 @@ function buildUnifiedRequestErrorMessage( type: "error", title: i18n.str`Request timeout`, description: cause.message as TranslatedString, - debug: JSON.stringify(cause.errorDetail, undefined, 2), + debug: cause.errorDetail, when: AbsoluteTime.now(), }; break; @@ -441,7 +441,7 @@ function buildUnifiedRequestErrorMessage( type: "error", title: i18n.str`Request throttled`, description: cause.message as TranslatedString, - debug: JSON.stringify(cause.errorDetail, undefined, 2), + debug: cause.errorDetail, when: AbsoluteTime.now(), }; break; @@ -451,7 +451,7 @@ function buildUnifiedRequestErrorMessage( type: "error", title: i18n.str`Malformed response`, description: cause.message as TranslatedString, - debug: JSON.stringify(cause.errorDetail, undefined, 2), + debug: cause.errorDetail, when: AbsoluteTime.now(), }; break; @@ -461,7 +461,7 @@ function buildUnifiedRequestErrorMessage( type: "error", title: i18n.str`Network error`, description: cause.message as TranslatedString, - debug: JSON.stringify(cause.errorDetail, undefined, 2), + debug: cause.errorDetail, when: AbsoluteTime.now(), }; break; @@ -471,7 +471,7 @@ function buildUnifiedRequestErrorMessage( type: "error", title: i18n.str`Unexpected request error`, description: cause.message as TranslatedString, - debug: JSON.stringify(cause.errorDetail, undefined, 2), + debug: cause.errorDetail, when: AbsoluteTime.now(), }; break; @@ -481,7 +481,7 @@ function buildUnifiedRequestErrorMessage( type: "error", title: i18n.str`Unexpected error`, description: cause.message as TranslatedString, - debug: JSON.stringify(cause.errorDetail, undefined, 2), + debug: cause.errorDetail, when: AbsoluteTime.now(), }; break;