diff options
Diffstat (limited to 'packages/demobank-ui/src')
46 files changed, 460 insertions, 662 deletions
diff --git a/packages/demobank-ui/src/Routing.tsx b/packages/demobank-ui/src/Routing.tsx index a8ed58db1..c85d74e17 100644 --- a/packages/demobank-ui/src/Routing.tsx +++ b/packages/demobank-ui/src/Routing.tsx @@ -27,12 +27,13 @@ import { TranslatedString, assertUnreachable, } from "@gnu-taler/taler-util"; +import { useEffect } from "preact/hooks"; import { useBankCoreApiContext } from "./context/config.js"; +import { useNavigationContext } from "./context/navigation.js"; import { useSettingsContext } from "./context/settings.js"; -import { useBackendState } from "./hooks/backend.js"; +import { useSessionState } from "./hooks/session.js"; import { AccountPage } from "./pages/AccountPage/index.js"; import { BankFrame } from "./pages/BankFrame.js"; -import { DownloadStats } from "./pages/DownloadStats.js"; import { LoginForm } from "./pages/LoginForm.js"; import { PublicHistoriesPage } from "./pages/PublicHistoriesPage.js"; import { RegistrationPage } from "./pages/RegistrationPage.js"; @@ -44,19 +45,18 @@ import { ShowAccountDetails } from "./pages/account/ShowAccountDetails.js"; import { UpdateAccountPassword } from "./pages/account/UpdateAccountPassword.js"; import { AdminHome } from "./pages/admin/AdminHome.js"; import { CreateNewAccount } from "./pages/admin/CreateNewAccount.js"; +import { DownloadStats } from "./pages/admin/DownloadStats.js"; import { RemoveAccount } from "./pages/admin/RemoveAccount.js"; -import { CreateCashout } from "./pages/business/CreateCashout.js"; -import { ShowCashoutDetails } from "./pages/business/ShowCashoutDetails.js"; +import { ConversionConfig } from "./pages/regional/ConversionConfig.js"; +import { CreateCashout } from "./pages/regional/CreateCashout.js"; +import { ShowCashoutDetails } from "./pages/regional/ShowCashoutDetails.js"; import { urlPattern, useCurrentLocation } from "./route.js"; -import { useNavigationContext } from "./context/navigation.js"; -import { useEffect } from "preact/hooks"; -import { ConversionConfig } from "./pages/ConversionConfig.js"; export function Routing(): VNode { - const backend = useBackendState(); + const session = useSessionState(); - if (backend.state.status === "loggedIn") { - const { isUserAdministrator, username } = backend.state; + if (session.state.status === "loggedIn") { + const { isUserAdministrator, username } = session.state; return ( <BankFrame account={username} routeAccountDetails={privatePages.myAccountDetails}> <PrivateRouting username={username} isAdmin={isUserAdministrator} /> @@ -67,7 +67,7 @@ export function Routing(): VNode { <BankFrame> <PublicRounting onLoggedUser={(username, token) => { - backend.logIn({ username, token: token }); + session.logIn({ username, token: token }); }} /> </BankFrame> diff --git a/packages/demobank-ui/src/components/app.tsx b/packages/demobank-ui/src/app.tsx index 97778e6d7..3a7fafccf 100644 --- a/packages/demobank-ui/src/components/app.tsx +++ b/packages/demobank-ui/src/app.tsx @@ -20,21 +20,20 @@ import { setGlobalLogLevelFromString, } from "@gnu-taler/taler-util"; import { Loading, TranslationProvider } from "@gnu-taler/web-util/browser"; -import { FunctionalComponent, h } from "preact"; +import { h } from "preact"; import { useEffect, useState } from "preact/hooks"; import { SWRConfig } from "swr"; -import { Routing } from "../Routing.js"; -import { BackendStateProvider } from "../context/backend.js"; -import { BankCoreApiProvider } from "../context/config.js"; -import { SettingsProvider } from "../context/settings.js"; -import { strings } from "../i18n/strings.js"; -import { BankFrame } from "../pages/BankFrame.js"; -import { BankUiSettings, fetchSettings } from "../settings.js"; -import { TalerWalletIntegrationBrowserProvider } from "../context/wallet-integration.js"; -import { BrowserHashNavigationProvider } from "../context/navigation.js"; +import { Routing } from "./Routing.js"; +import { BankCoreApiProvider } from "./context/config.js"; +import { BrowserHashNavigationProvider } from "./context/navigation.js"; +import { SettingsProvider } from "./context/settings.js"; +import { TalerWalletIntegrationBrowserProvider } from "./context/wallet-integration.js"; +import { strings } from "./i18n/strings.js"; +import { BankFrame } from "./pages/BankFrame.js"; +import { BankUiSettings, fetchSettings } from "./settings.js"; const WITH_LOCAL_STORAGE_CACHE = false; -const App: FunctionalComponent = () => { +export function App() { const [settings, setSettings] = useState<BankUiSettings>(); useEffect(() => { fetchSettings(setSettings); @@ -51,43 +50,41 @@ const App: FunctionalComponent = () => { de: strings["de"].completeness, }} > - <BackendStateProvider> - <BankCoreApiProvider baseUrl={baseUrl} frameOnError={BankFrame}> - <SWRConfig - value={{ - provider: WITH_LOCAL_STORAGE_CACHE - ? localStorageProvider - : undefined, - // normally, do not revalidate - revalidateOnFocus: false, - revalidateOnReconnect: false, - revalidateIfStale: false, - revalidateOnMount: undefined, - focusThrottleInterval: undefined, + <BankCoreApiProvider baseUrl={baseUrl} frameOnError={BankFrame}> + <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> - </BankCoreApiProvider> - </BackendStateProvider> + // do not go to loading again if already has data + keepPreviousData: true, + }} + > + <TalerWalletIntegrationBrowserProvider> + <BrowserHashNavigationProvider> + <Routing /> + </BrowserHashNavigationProvider> + </TalerWalletIntegrationBrowserProvider> + </SWRConfig> + </BankCoreApiProvider> </TranslationProvider> </SettingsProvider> ); @@ -108,8 +105,6 @@ function localStorageProvider(): Map<unknown, unknown> { return map; } -export default App; - function getInitialBackendBaseURL( backendFromSettings: string | undefined, ): string { diff --git a/packages/demobank-ui/src/components/Cashouts/state.ts b/packages/demobank-ui/src/components/Cashouts/state.ts index 344b93e14..8616faa1b 100644 --- a/packages/demobank-ui/src/components/Cashouts/state.ts +++ b/packages/demobank-ui/src/components/Cashouts/state.ts @@ -15,7 +15,7 @@ */ import { TalerError } from "@gnu-taler/taler-util"; -import { useCashouts } from "../../hooks/circuit.js"; +import { useCashouts } from "../../hooks/regional.js"; import { Props, State } from "./index.js"; export function useComponentState({ diff --git a/packages/demobank-ui/src/components/Cashouts/test.ts b/packages/demobank-ui/src/components/Cashouts/test.ts index 569cbc6f0..4bd6b5eac 100644 --- a/packages/demobank-ui/src/components/Cashouts/test.ts +++ b/packages/demobank-ui/src/components/Cashouts/test.ts @@ -22,7 +22,6 @@ import * as tests from "@gnu-taler/web-util/testing"; import { SwrMockEnvironment } from "@gnu-taler/web-util/testing"; import { expect } from "chai"; -import { CASHOUT_API_EXAMPLE } from "../../endpoints.js"; import { Props } from "./index.js"; import { useComponentState } from "./state.js"; import { buildNullRoutDefinition } from "../../route.js"; @@ -36,15 +35,15 @@ describe("Cashout states", () => { routeCashoutDetails: buildNullRoutDefinition(), }; - env.addRequestExpectation(CASHOUT_API_EXAMPLE.LIST_FIRST_PAGE, { - response: { - cashouts: [], - }, - }); + // env.addRequestExpectation(CASHOUT_API_EXAMPLE.LIST_FIRST_PAGE, { + // response: { + // cashouts: [], + // }, + // }); - env.addRequestExpectation(CASHOUT_API_EXAMPLE.MULTI_GET_EMPTY_FIRST_PAGE, { - response: [], - }); + // env.addRequestExpectation(CASHOUT_API_EXAMPLE.MULTI_GET_EMPTY_FIRST_PAGE, { + // response: [], + // }); const hookBehavior = await tests.hookBehaveLikeThis( useComponentState, diff --git a/packages/demobank-ui/src/components/Cashouts/views.tsx b/packages/demobank-ui/src/components/Cashouts/views.tsx index 09e986dd4..7f16d5840 100644 --- a/packages/demobank-ui/src/components/Cashouts/views.tsx +++ b/packages/demobank-ui/src/components/Cashouts/views.tsx @@ -29,7 +29,7 @@ import { } from "@gnu-taler/web-util/browser"; import { format } from "date-fns"; import { Fragment, VNode, h } from "preact"; -import { useConversionInfo } from "../../hooks/circuit.js"; +import { useConversionInfo } from "../../hooks/regional.js"; import { RenderAmount } from "../../pages/PaytoWireTransferForm.js"; import { ErrorLoadingWithDebug } from "../ErrorLoadingWithDebug.js"; import { State } from "./index.js"; diff --git a/packages/demobank-ui/src/components/Transactions/state.ts b/packages/demobank-ui/src/components/Transactions/state.ts index 40e1b0ced..3e9103b59 100644 --- a/packages/demobank-ui/src/components/Transactions/state.ts +++ b/packages/demobank-ui/src/components/Transactions/state.ts @@ -20,7 +20,7 @@ import { TalerError, parsePaytoUri, } from "@gnu-taler/taler-util"; -import { useTransactions } from "../../hooks/access.js"; +import { useTransactions } from "../../hooks/account.js"; import { Props, State, Transaction } from "./index.js"; export function useComponentState({ account, routeCreateWireTransfer }: Props): State { diff --git a/packages/demobank-ui/src/components/Transactions/test.ts b/packages/demobank-ui/src/components/Transactions/test.ts index 9ded218c1..d9442c742 100644 --- a/packages/demobank-ui/src/components/Transactions/test.ts +++ b/packages/demobank-ui/src/components/Transactions/test.ts @@ -23,7 +23,6 @@ import { TalerErrorCode } from "@gnu-taler/taler-util"; import * as tests from "@gnu-taler/web-util/testing"; import { SwrMockEnvironment } from "@gnu-taler/web-util/testing"; import { expect } from "chai"; -import { TRANSACTION_API_EXAMPLE } from "../../endpoints.js"; import { Props } from "./index.js"; import { useComponentState } from "./state.js"; @@ -36,63 +35,63 @@ describe("Transaction states", () => { routeCreateWireTransfer: undefined, }; - env.addRequestExpectation(TRANSACTION_API_EXAMPLE.LIST_FIRST_PAGE, { - response: { - data: { - transactions: [ - { - creditorIban: "DE159593", - creditorBic: "SANDBOXX", - creditorName: "exchange company", - debtorIban: "DE118695", - debtorBic: "SANDBOXX", - debtorName: "Name unknown", - amount: "1", - currency: "KUDOS", - subject: - "Taler Withdrawal N588V8XE9TR49HKAXFQ20P0EQ0EYW2AC9NNANV8ZP5P59N6N0410", - date: "2022-12-12Z", - uid: "8PPFR9EM", - direction: "DBIT", - pmtInfId: null, - msgId: null, - }, - { - creditorIban: "DE159593", - creditorBic: "SANDBOXX", - creditorName: "exchange company", - debtorIban: "DE118695", - debtorBic: "SANDBOXX", - debtorName: "Name unknown", - amount: "5.00", - currency: "KUDOS", - subject: "HNEWWT679TQC5P1BVXJS48FX9NW18FWM6PTK2N80Z8GVT0ACGNK0", - date: "2022-12-07Z", - uid: "7FZJC3RJ", - direction: "DBIT", - pmtInfId: null, - msgId: null, - }, - { - creditorIban: "DE118695", - creditorBic: "SANDBOXX", - creditorName: "Name unknown", - debtorIban: "DE579516", - debtorBic: "SANDBOXX", - debtorName: "The Bank", - amount: "100", - currency: "KUDOS", - subject: "Sign-up bonus", - date: "2022-12-07Z", - uid: "I31A06J8", - direction: "CRDT", - pmtInfId: null, - msgId: null, - }, - ], - }, - }, - }); + // env.addRequestExpectation(TRANSACTION_API_EXAMPLE.LIST_FIRST_PAGE, { + // response: { + // data: { + // transactions: [ + // { + // creditorIban: "DE159593", + // creditorBic: "SANDBOXX", + // creditorName: "exchange company", + // debtorIban: "DE118695", + // debtorBic: "SANDBOXX", + // debtorName: "Name unknown", + // amount: "1", + // currency: "KUDOS", + // subject: + // "Taler Withdrawal N588V8XE9TR49HKAXFQ20P0EQ0EYW2AC9NNANV8ZP5P59N6N0410", + // date: "2022-12-12Z", + // uid: "8PPFR9EM", + // direction: "DBIT", + // pmtInfId: null, + // msgId: null, + // }, + // { + // creditorIban: "DE159593", + // creditorBic: "SANDBOXX", + // creditorName: "exchange company", + // debtorIban: "DE118695", + // debtorBic: "SANDBOXX", + // debtorName: "Name unknown", + // amount: "5.00", + // currency: "KUDOS", + // subject: "HNEWWT679TQC5P1BVXJS48FX9NW18FWM6PTK2N80Z8GVT0ACGNK0", + // date: "2022-12-07Z", + // uid: "7FZJC3RJ", + // direction: "DBIT", + // pmtInfId: null, + // msgId: null, + // }, + // { + // creditorIban: "DE118695", + // creditorBic: "SANDBOXX", + // creditorName: "Name unknown", + // debtorIban: "DE579516", + // debtorBic: "SANDBOXX", + // debtorName: "The Bank", + // amount: "100", + // currency: "KUDOS", + // subject: "Sign-up bonus", + // date: "2022-12-07Z", + // uid: "I31A06J8", + // direction: "CRDT", + // pmtInfId: null, + // msgId: null, + // }, + // ], + // }, + // }, + // }); const hookBehavior = await tests.hookBehaveLikeThis( useComponentState, @@ -165,13 +164,13 @@ describe("Transaction states", () => { routeCreateWireTransfer: undefined, }; - env.addRequestExpectation(TRANSACTION_API_EXAMPLE.LIST_ERROR, { - response: { - error: { - code: TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED, - }, - }, - }); + // env.addRequestExpectation(TRANSACTION_API_EXAMPLE.LIST_ERROR, { + // response: { + // error: { + // code: TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED, + // }, + // }, + // }); const hookBehavior = await tests.hookBehaveLikeThis( useComponentState, diff --git a/packages/demobank-ui/src/context/backend.ts b/packages/demobank-ui/src/context/backend.ts deleted file mode 100644 index 18b4a1f03..000000000 --- a/packages/demobank-ui/src/context/backend.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022-2024 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -import { ComponentChildren, createContext, h, VNode } from "preact"; -import { useContext } from "preact/hooks"; -import { - BackendStateHandler, - defaultState, - useBackendState, -} from "../hooks/backend.js"; - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -export type Type = BackendStateHandler; - -const initial: Type = { - state: defaultState, - logOut() { - null; - }, - expired() { - null; - }, - logIn(_info) { - null; - }, -}; -const Context = createContext<Type>(initial); - -export const useBackendContext = (): Type => useContext(Context); - -export const BackendStateProvider = ({ - children, -}: { - children: ComponentChildren; -}): VNode => { - const value = useBackendState(); - - return h(Context.Provider, { - value, - children, - }); -}; - -export const BackendStateProviderTesting = ({ - children, - state, -}: { - children: ComponentChildren; - state: typeof defaultState; -}): VNode => { - const value: BackendStateHandler = { - state, - logIn: () => {}, - expired: () => {}, - logOut: () => {}, - }; - - return h(Context.Provider, { - value, - children, - }); -}; diff --git a/packages/demobank-ui/src/context/config.ts b/packages/demobank-ui/src/context/config.ts index e968b7ff4..72c176679 100644 --- a/packages/demobank-ui/src/context/config.ts +++ b/packages/demobank-ui/src/context/config.ts @@ -46,12 +46,12 @@ import { revalidateAccountDetails, revalidatePublicAccounts, revalidateTransactions, -} from "../hooks/access.js"; +} from "../hooks/account.js"; import { revalidateBusinessAccounts, revalidateCashouts, revalidateConversionInfo, -} from "../hooks/circuit.js"; +} from "../hooks/regional.js"; /** * @@ -156,6 +156,10 @@ export const BankCoreApiProvider = ({ children, }); }; + +/** + * + */ class CacheAwareTalerBankConversionHttpClient extends TalerBankConversionHttpClient { constructor(baseUrl: string, httpClient?: HttpRequestLibrary) { super(baseUrl, httpClient); diff --git a/packages/demobank-ui/src/endpoints.ts b/packages/demobank-ui/src/endpoints.ts deleted file mode 100644 index b68a36529..000000000 --- a/packages/demobank-ui/src/endpoints.ts +++ /dev/null @@ -1,48 +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/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -export const TRANSACTION_API_EXAMPLE = { - LIST_FIRST_PAGE: { - method: "get" as const, - url: '["access-api/accounts/myAccount/transactions",null,20]', - }, - LIST_ERROR: { - method: "get" as const, - url: '["access-api/accounts/myAccount/transactions",null,20]', - code: 500, - }, - LIST_NOT_FOUND: { - method: "get" as const, - url: '["access-api/accounts/myAccount/transactions",null,20]', - code: 404, - }, -}; - -export const CASHOUT_API_EXAMPLE = { - LIST_FIRST_PAGE: { - method: "get" as const, - url: '["circuit-api/cashouts","123"]', - }, - MULTI_GET_EMPTY_FIRST_PAGE: { - method: "get" as const, - url: "[[]]", - }, -}; diff --git a/packages/demobank-ui/src/hooks/access.ts b/packages/demobank-ui/src/hooks/account.ts index a101dc83e..61a11b1a5 100644 --- a/packages/demobank-ui/src/hooks/access.ts +++ b/packages/demobank-ui/src/hooks/account.ts @@ -22,7 +22,7 @@ import { } from "@gnu-taler/taler-util"; import { useEffect, useState } from "preact/hooks"; import { PAGE_SIZE } from "../utils.js"; -import { useBackendState } from "./backend.js"; +import { useSessionState } from "./session.js"; // FIX default import https://github.com/microsoft/TypeScript/issues/49189 import _useSWR, { SWRHook, mutate } from "swr"; @@ -43,7 +43,7 @@ export function revalidateAccountDetails() { } export function useAccountDetails(account: string) { - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const { api } = useBankCoreApiContext(); async function fetcher([username, token]: [string, AccessToken]) { @@ -114,7 +114,7 @@ export function revalidateTransactionDetails() { ); } export function useTransactionDetails(account: string, tid: number) { - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const token = credentials.status !== "loggedIn" ? undefined : credentials.token; const { api } = useBankCoreApiContext(); @@ -228,7 +228,7 @@ export function revalidateTransactions() { ); } export function useTransactions(account: string, initial?: number) { - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const token = credentials.status !== "loggedIn" ? undefined : credentials.token; diff --git a/packages/demobank-ui/src/hooks/async.ts b/packages/demobank-ui/src/hooks/async.ts deleted file mode 100644 index 556673992..000000000 --- a/packages/demobank-ui/src/hooks/async.ts +++ /dev/null @@ -1,80 +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/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ -import { useState } from "preact/hooks"; - -export interface Options { - slowTolerance: number; -} - -export interface AsyncOperationApi<T> { - request: (...a: Array<unknown>) => void; - cancel: () => void; - data: T | undefined; - isSlow: boolean; - isLoading: boolean; - error: string | undefined; -} - -export function useAsync<T>( - fn?: (...args: Array<unknown>) => Promise<T>, - { slowTolerance: tooLong }: Options = { slowTolerance: 1000 }, -): AsyncOperationApi<T> { - const [data, setData] = useState<T | undefined>(undefined); - const [isLoading, setLoading] = useState<boolean>(false); - const [error, setError] = useState<string | undefined>(undefined); - const [isSlow, setSlow] = useState(false); - - const request = async (...args: Array<unknown>) => { - if (!fn) return; - setLoading(true); - const handler = setTimeout(() => { - setSlow(true); - }, tooLong); - - try { - const result = await fn(...args); - setData(result); - } catch (error) { - if (error instanceof Error) { - setError(error.message); - } else { - setError(`Unknown error: ${error}`); - } - } - setLoading(false); - setSlow(false); - clearTimeout(handler); - }; - - function cancel() { - setLoading(false); - setSlow(false); - } - - return { - request, - cancel, - data, - isSlow, - isLoading, - error, - }; -} diff --git a/packages/demobank-ui/src/hooks/bank-state.ts b/packages/demobank-ui/src/hooks/bank-state.ts index 15daf9180..83bb009cf 100644 --- a/packages/demobank-ui/src/hooks/bank-state.ts +++ b/packages/demobank-ui/src/hooks/bank-state.ts @@ -141,6 +141,8 @@ const codecForChallenge = (): Codec<ChallengeInProgess> => .alternative("update-password", codecForChallengeUpdatePassword()) .build("ChallengeInProgess"); + + interface BankState { currentWithdrawalOperationId: string | undefined; currentChallenge: ChallengeInProgess | undefined; @@ -159,6 +161,14 @@ const defaultBankState: BankState = { const BANK_STATE_KEY = buildStorageKey("bank-app-state", codecForBankState()); +/** + * Client state saved in local storage. + * + * This information is saved in the client because + * the backend server session API is not enough. + * + * @returns tuple of [state, update(), reset()] + */ export function useBankState(): [ Readonly<BankState>, <T extends keyof BankState>(key: T, value: BankState[T]) => void, @@ -175,3 +185,4 @@ export function useBankState(): [ } return [value, updateField, reset]; } + diff --git a/packages/demobank-ui/src/hooks/form.ts b/packages/demobank-ui/src/hooks/form.ts new file mode 100644 index 000000000..26354b108 --- /dev/null +++ b/packages/demobank-ui/src/hooks/form.ts @@ -0,0 +1,100 @@ +/* + 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, TalerBankConversionApi, 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: any = form[fieldName]; + const currentError: any = errors ? errors[fieldName] : undefined; + function updater(newValue: any) { + updateForm({ ...form, [fieldName]: newValue }) + } + if (typeof currentValue === "object") { + const group = constructFormHandler(currentValue, updater, currentError) + // @ts-expect-error asdasd + prev[fieldName] = group + return prev; + } + const field: UIField = { + error: currentError, + value: currentValue, + onUpdate: updater + } + // @ts-expect-error asdasd + prev[fieldName] = field + return prev + }, {} as FormHandler<T>) + + return handler; +} + +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] +}
\ No newline at end of file diff --git a/packages/demobank-ui/src/hooks/index.ts b/packages/demobank-ui/src/hooks/index.ts deleted file mode 100644 index 2620f4697..000000000 --- a/packages/demobank-ui/src/hooks/index.ts +++ /dev/null @@ -1,60 +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/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { StateUpdater } from "preact/hooks"; -import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser"; -import { codecForBoolean } from "@gnu-taler/taler-util"; -export type ValueOrFunction<T> = T | ((p: T) => T); - -const calculateRootPath = () => { - const rootPath = - typeof window !== "undefined" - ? window.location.origin + window.location.pathname - : "/"; - return rootPath; -}; - -const BACKEND_URL_KEY = buildStorageKey("backend-url"); -const TRIED_LOGIN_KEY = buildStorageKey("tried-login", codecForBoolean()); - -export function useBackendURL( - url?: string, -): [string, boolean, StateUpdater<string>, () => void] { - const { value, update: setter } = useLocalStorage( - BACKEND_URL_KEY, - url || calculateRootPath(), - ); - - const { - value: triedToLog, - update: setTriedToLog, - reset: resetBackend, - } = useLocalStorage(TRIED_LOGIN_KEY); - - const checkedSetter = (v: ValueOrFunction<string>) => { - setTriedToLog(true); - const computedValue = - v instanceof Function ? v(value) : v.replace(/\/$/, ""); - return setter(computedValue); - }; - - return [value, !!triedToLog, checkedSetter, resetBackend]; -} diff --git a/packages/demobank-ui/src/hooks/preferences.ts b/packages/demobank-ui/src/hooks/preferences.ts index 454d840b2..454dc8d80 100644 --- a/packages/demobank-ui/src/hooks/preferences.ts +++ b/packages/demobank-ui/src/hooks/preferences.ts @@ -36,36 +36,6 @@ interface Preferences { showDebugInfo: boolean; } -export function getAllBooleanPreferences(): Array<keyof Preferences> { - return [ - "fastWithdrawal", - "showDebugInfo", - "showDemoDescription", - "showInstallWallet", - "showWithdrawalSuccess", - ]; -} - -export function getLabelForPreferences( - k: keyof Preferences, - i18n: ReturnType<typeof useTranslationContext>["i18n"], -): TranslatedString { - switch (k) { - case "maxWithdrawalAmount": - return i18n.str`Max withdrawal amount`; - case "showWithdrawalSuccess": - return i18n.str`Show withdrawal confirmation`; - case "showDemoDescription": - return i18n.str`Show demo description`; - case "showInstallWallet": - return i18n.str`Show install wallet first`; - case "fastWithdrawal": - return i18n.str`Use fast withdrawal form`; - case "showDebugInfo": - return i18n.str`Show debug info`; - } -} - export const codecForPreferences = (): Codec<Preferences> => buildCodecForObject<Preferences>() .property("showWithdrawalSuccess", codecForBoolean()) @@ -89,7 +59,11 @@ const BANK_PREFERENCES_KEY = buildStorageKey( "bank-preferences", codecForPreferences(), ); - +/** + * User preferences. + * + * @returns tuple of [state, update()] + */ export function usePreferences(): [ Readonly<Preferences>, <T extends keyof Preferences>(key: T, value: Preferences[T]) => void, @@ -105,3 +79,34 @@ export function usePreferences(): [ } return [value, updateField]; } + +export function getAllBooleanPreferences(): Array<keyof Preferences> { + return [ + "fastWithdrawal", + "showDebugInfo", + "showDemoDescription", + "showInstallWallet", + "showWithdrawalSuccess", + ]; +} + +export function getLabelForPreferences( + k: keyof Preferences, + i18n: ReturnType<typeof useTranslationContext>["i18n"], +): TranslatedString { + switch (k) { + case "maxWithdrawalAmount": + return i18n.str`Max withdrawal amount`; + case "showWithdrawalSuccess": + return i18n.str`Show withdrawal confirmation`; + case "showDemoDescription": + return i18n.str`Show demo description`; + case "showInstallWallet": + return i18n.str`Show install wallet first`; + case "fastWithdrawal": + return i18n.str`Use fast withdrawal form`; + case "showDebugInfo": + return i18n.str`Show debug info`; + } +} + diff --git a/packages/demobank-ui/src/hooks/circuit.ts b/packages/demobank-ui/src/hooks/regional.ts index f87cbd843..a9ebb30a2 100644 --- a/packages/demobank-ui/src/hooks/circuit.ts +++ b/packages/demobank-ui/src/hooks/regional.ts @@ -15,7 +15,7 @@ */ import { PAGE_SIZE } from "../utils.js"; -import { useBackendState } from "./backend.js"; +import { useSessionState } from "./session.js"; import { AccessToken, @@ -208,7 +208,7 @@ export function revalidateBusinessAccounts() { return mutate((key) => Array.isArray(key) && key[key.length - 1] === "getAccounts", undefined, { revalidate: true }); } export function useBusinessAccounts() { - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const token = credentials.status !== "loggedIn" ? undefined : credentials.token; const { api } = useBankCoreApiContext(); @@ -280,7 +280,7 @@ export function revalidateOnePendingCashouts() { ); } export function useOnePendingCashouts(account: string) { - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const { api, config } = useBankCoreApiContext(); const token = credentials.status !== "loggedIn" ? undefined : credentials.token; @@ -337,7 +337,7 @@ export function revalidateCashouts() { return mutate((key) => Array.isArray(key) && key[key.length - 1] === "useCashouts"); } export function useCashouts(account: string) { - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const { api, config } = useBankCoreApiContext(); const token = credentials.status !== "loggedIn" ? undefined : credentials.token; @@ -390,7 +390,7 @@ export function revalidateCashoutDetails() { ); } export function useCashoutDetails(cashoutId: number | undefined) { - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const creds = credentials.status !== "loggedIn" ? undefined : credentials; const { api } = useBankCoreApiContext(); @@ -444,7 +444,7 @@ export function useLastMonitorInfo( timeframe: TalerCorebankApi.MonitorTimeframeParam, ) { const { api } = useBankCoreApiContext(); - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const token = credentials.status !== "loggedIn" ? undefined : credentials.token; diff --git a/packages/demobank-ui/src/hooks/backend.ts b/packages/demobank-ui/src/hooks/session.ts index 8e9af533b..35f87e1be 100644 --- a/packages/demobank-ui/src/hooks/backend.ts +++ b/packages/demobank-ui/src/hooks/session.ts @@ -30,7 +30,7 @@ import { mutate } from "swr"; * Has the information to reach and * authenticate at the bank's backend. */ -export type BackendState = LoggedIn | LoggedOut | Expired; +export type SessionState = LoggedIn | LoggedOut | Expired; interface LoggedIn { status: "loggedIn"; @@ -47,55 +47,55 @@ interface LoggedOut { status: "loggedOut"; } -export const codecForBackendStateLoggedIn = (): Codec<LoggedIn> => +export const codecForSessionStateLoggedIn = (): Codec<LoggedIn> => buildCodecForObject<LoggedIn>() .property("status", codecForConstString("loggedIn")) .property("username", codecForString()) .property("token", codecForString() as Codec<AccessToken>) .property("isUserAdministrator", codecForBoolean()) - .build("BackendState.LoggedIn"); + .build("SessionState.LoggedIn"); -export const codecForBackendStateExpired = (): Codec<Expired> => +export const codecForSessionStateExpired = (): Codec<Expired> => buildCodecForObject<Expired>() .property("status", codecForConstString("expired")) .property("username", codecForString()) .property("isUserAdministrator", codecForBoolean()) - .build("BackendState.Expired"); + .build("SessionState.Expired"); -export const codecForBackendStateLoggedOut = (): Codec<LoggedOut> => +export const codecForSessionStateLoggedOut = (): Codec<LoggedOut> => buildCodecForObject<LoggedOut>() .property("status", codecForConstString("loggedOut")) - .build("BackendState.LoggedOut"); + .build("SessionState.LoggedOut"); -export const codecForBackendState = (): Codec<BackendState> => - buildCodecForUnion<BackendState>() +export const codecForSessionState = (): Codec<SessionState> => + buildCodecForUnion<SessionState>() .discriminateOn("status") - .alternative("loggedIn", codecForBackendStateLoggedIn()) - .alternative("loggedOut", codecForBackendStateLoggedOut()) - .alternative("expired", codecForBackendStateExpired()) - .build("BackendState"); + .alternative("loggedIn", codecForSessionStateLoggedIn()) + .alternative("loggedOut", codecForSessionStateLoggedOut()) + .alternative("expired", codecForSessionStateExpired()) + .build("SessionState"); -export const defaultState: BackendState = { +export const defaultState: SessionState = { status: "loggedOut", }; -export interface BackendStateHandler { - state: BackendState; +export interface SessionStateHandler { + state: SessionState; logOut(): void; expired(): void; logIn(info: { username: string; token: AccessToken }): void; } -const BACKEND_STATE_KEY = buildStorageKey("bank-state", codecForBackendState()); +const SESSION_STATE_KEY = buildStorageKey("bank-state", codecForSessionState()); /** * Return getters and setters for * login credentials and backend's * base URL. */ -export function useBackendState(): BackendStateHandler { +export function useSessionState(): SessionStateHandler { const { value: state, update } = useLocalStorage( - BACKEND_STATE_KEY, + SESSION_STATE_KEY, defaultState, ); @@ -106,7 +106,7 @@ export function useBackendState(): BackendStateHandler { }, expired() { if (state.status === "loggedOut") return; - const nextState: BackendState = { + const nextState: SessionState = { status: "expired", username: state.username, isUserAdministrator: state.username === "admin", @@ -115,7 +115,7 @@ export function useBackendState(): BackendStateHandler { }, logIn(info) { // admin is defined by the username - const nextState: BackendState = { + const nextState: SessionState = { status: "loggedIn", ...info, isUserAdministrator: info.username === "admin", diff --git a/packages/demobank-ui/src/index.tsx b/packages/demobank-ui/src/index.tsx index 5f00f1b68..f559288a3 100644 --- a/packages/demobank-ui/src/index.tsx +++ b/packages/demobank-ui/src/index.tsx @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import App from "./components/app.js"; +import { App } from "./app.js"; import { h, render } from "preact"; import "./scss/main.css"; diff --git a/packages/demobank-ui/src/pages/AccountPage/state.ts b/packages/demobank-ui/src/pages/AccountPage/state.ts index b531ac757..e84fef025 100644 --- a/packages/demobank-ui/src/pages/AccountPage/state.ts +++ b/packages/demobank-ui/src/pages/AccountPage/state.ts @@ -21,7 +21,7 @@ import { assertUnreachable, parsePaytoUri, } from "@gnu-taler/taler-util"; -import { useAccountDetails } from "../../hooks/access.js"; +import { useAccountDetails } from "../../hooks/account.js"; import { Props, State } from "./index.js"; export function useComponentState({ diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx index e1b8d6b83..427e9a156 100644 --- a/packages/demobank-ui/src/pages/BankFrame.tsx +++ b/packages/demobank-ui/src/pages/BankFrame.tsx @@ -28,8 +28,8 @@ import { ComponentChildren, VNode, h } from "preact"; import { useEffect, useErrorBoundary } from "preact/hooks"; import { useBankCoreApiContext } from "../context/config.js"; import { useSettingsContext } from "../context/settings.js"; -import { useAccountDetails } from "../hooks/access.js"; -import { useBackendState } from "../hooks/backend.js"; +import { useAccountDetails } from "../hooks/account.js"; +import { useSessionState } from "../hooks/session.js"; import { useBankState } from "../hooks/bank-state.js"; import { getAllBooleanPreferences, @@ -52,7 +52,7 @@ export function BankFrame({ children: ComponentChildren; }): VNode { const { i18n } = useTranslationContext(); - const backend = useBackendState(); + const session = useSessionState(); const settings = useSettingsContext(); const [preferences, updatePreferences] = usePreferences(); const [, , resetBankState] = useBankState(); @@ -86,10 +86,10 @@ export function BankFrame({ iconLinkURL={settings.iconLinkURL ?? "#"} profileURL={routeAccountDetails?.url({})} onLogout={ - backend.state.status !== "loggedIn" + session.state.status !== "loggedIn" ? undefined : () => { - backend.logOut(); + session.logOut(); resetBankState(); } } diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx index f0ca447e1..e62759415 100644 --- a/packages/demobank-ui/src/pages/LoginForm.tsx +++ b/packages/demobank-ui/src/pages/LoginForm.tsx @@ -27,7 +27,7 @@ import { import { VNode, h } from "preact"; import { useEffect, useRef, useState } from "preact/hooks"; import { useBankCoreApiContext } from "../context/config.js"; -import { useBackendState } from "../hooks/backend.js"; +import { useSessionState } from "../hooks/session.js"; import { RouteDefinition } from "../route.js"; import { undefinedIfEmpty } from "../utils.js"; import { doAutoFocus } from "./PaytoWireTransferForm.js"; @@ -44,10 +44,10 @@ export function LoginForm({ currentUser?: string; routeRegister?: RouteDefinition; }): VNode { - const backend = useBackendState(); + const session = useSessionState(); const sessionUser = - backend.state.status !== "loggedOut" ? backend.state.username : undefined; + session.state.status !== "loggedOut" ? session.state.username : undefined; const [username, setUsername] = useState<string | undefined>( currentUser ?? sessionUser, ); @@ -73,7 +73,7 @@ export function LoginForm({ }); async function doLogout() { - backend.logOut(); + session.logOut(); } const loginHandler = !username || !password ? undefined : withErrorHandler( @@ -86,7 +86,7 @@ export function LoginForm({ refreshable: true, }), (result) => { - backend.logIn({ username, token: result.body.access_token }) + session.logIn({ username, token: result.body.access_token }) }, (fail) => { switch (fail.case) { @@ -173,7 +173,7 @@ export function LoginForm({ </div> </div> - {backend.state.status !== "loggedOut" ? ( + {session.state.status !== "loggedOut" ? ( <div class="flex justify-between"> <button type="submit" diff --git a/packages/demobank-ui/src/pages/OperationState/state.ts b/packages/demobank-ui/src/pages/OperationState/state.ts index ad2d91ee4..693179d40 100644 --- a/packages/demobank-ui/src/pages/OperationState/state.ts +++ b/packages/demobank-ui/src/pages/OperationState/state.ts @@ -28,8 +28,8 @@ import { utils } from "@gnu-taler/web-util/browser"; import { useEffect, useState } from "preact/hooks"; import { mutate } from "swr"; import { useBankCoreApiContext } from "../../context/config.js"; -import { useWithdrawalDetails } from "../../hooks/access.js"; -import { useBackendState } from "../../hooks/backend.js"; +import { useWithdrawalDetails } from "../../hooks/account.js"; +import { useSessionState } from "../../hooks/session.js"; import { useBankState } from "../../hooks/bank-state.js"; import { usePreferences } from "../../hooks/preferences.js"; import { Props, State } from "./index.js"; @@ -43,7 +43,7 @@ export function useComponentState({ }: Props): utils.RecursiveState<State> { const [settings] = usePreferences(); const [bankState, updateBankState] = useBankState(); - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const creds = credentials.status !== "loggedIn" ? undefined : credentials; const { api } = useBankCoreApiContext(); diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx b/packages/demobank-ui/src/pages/PaymentOptions.tsx index ac7118697..48ecc7525 100644 --- a/packages/demobank-ui/src/pages/PaymentOptions.tsx +++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx @@ -21,9 +21,9 @@ import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js"; import { WalletWithdrawForm } from "./WalletWithdrawForm.js"; import { EmptyObject, RouteDefinition } from "../route.js"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { useWithdrawalDetails } from "../hooks/access.js"; +import { useWithdrawalDetails } from "../hooks/account.js"; import { useEffect } from "preact/hooks"; -import { useBackendState } from "../hooks/backend.js"; +import { useSessionState } from "../hooks/session.js"; function ShowOperationPendingTag({ woid, @@ -33,7 +33,7 @@ function ShowOperationPendingTag({ onOperationAlreadyCompleted?: () => void; }): VNode { const { i18n } = useTranslationContext(); - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const result = useWithdrawalDetails(woid); const loading = !result const error = diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx index aeb5dc0c9..f746094ce 100644 --- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx +++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx @@ -43,7 +43,7 @@ import { ComponentChildren, Fragment, Ref, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { mutate } from "swr"; import { useBankCoreApiContext } from "../context/config.js"; -import { useBackendState } from "../hooks/backend.js"; +import { useSessionState } from "../hooks/session.js"; import { useBankState } from "../hooks/bank-state.js"; import { EmptyObject, RouteDefinition } from "../route.js"; import { undefinedIfEmpty, validateIBAN, validateTalerBank } from "../utils.js"; @@ -80,7 +80,7 @@ export function PaytoWireTransferForm({ limit, }: Props): VNode { const [isRawPayto, setIsRawPayto] = useState(false); - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const { api, config, url } = useBankCoreApiContext(); const sendingToFixedAccount = withAccount !== undefined; diff --git a/packages/demobank-ui/src/pages/ProfileNavigation.tsx b/packages/demobank-ui/src/pages/ProfileNavigation.tsx index 8b7a8205f..10497f015 100644 --- a/packages/demobank-ui/src/pages/ProfileNavigation.tsx +++ b/packages/demobank-ui/src/pages/ProfileNavigation.tsx @@ -18,7 +18,7 @@ import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useBankCoreApiContext } from "../context/config.js"; import { useNavigationContext } from "../context/navigation.js"; -import { useBackendState } from "../hooks/backend.js"; +import { useSessionState } from "../hooks/session.js"; import { RouteDefinition } from "../route.js"; export function ProfileNavigation({ @@ -38,7 +38,7 @@ export function ProfileNavigation({ }): VNode { const { i18n } = useTranslationContext(); const { config } = useBankCoreApiContext(); - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const isAdminUser = credentials.status !== "loggedIn" ? false diff --git a/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx b/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx index f330cbc74..84d703cbe 100644 --- a/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx +++ b/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx @@ -19,7 +19,7 @@ import { Loading, useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { Transactions } from "../components/Transactions/index.js"; -import { usePublicAccounts } from "../hooks/access.js"; +import { usePublicAccounts } from "../hooks/account.js"; /** * Show histories of public accounts. diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx b/packages/demobank-ui/src/pages/QrCodeSection.tsx index 7091214e0..d4f5a5455 100644 --- a/packages/demobank-ui/src/pages/QrCodeSection.tsx +++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx @@ -30,7 +30,7 @@ import { useEffect } from "preact/hooks"; import { QR } from "../components/QR.js"; import { useBankCoreApiContext } from "../context/config.js"; import { useTalerWalletIntegrationAPI } from "../context/wallet-integration.js"; -import { useBackendState } from "../hooks/backend.js"; +import { useSessionState } from "../hooks/session.js"; export function QrCodeSection({ withdrawUri, @@ -42,7 +42,7 @@ export function QrCodeSection({ const { i18n } = useTranslationContext(); const walletInegrationApi = useTalerWalletIntegrationAPI(); const talerWithdrawUri = stringifyWithdrawUri(withdrawUri); - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const creds = credentials.status !== "loggedIn" ? undefined : credentials; useEffect(() => { diff --git a/packages/demobank-ui/src/pages/SolveChallengePage.tsx b/packages/demobank-ui/src/pages/SolveChallengePage.tsx index 1bafbc3eb..7e117f535 100644 --- a/packages/demobank-ui/src/pages/SolveChallengePage.tsx +++ b/packages/demobank-ui/src/pages/SolveChallengePage.tsx @@ -39,10 +39,10 @@ import { Fragment, VNode, h } from "preact"; import { useEffect, useState } from "preact/hooks"; import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js"; import { useBankCoreApiContext } from "../context/config.js"; -import { useWithdrawalDetails } from "../hooks/access.js"; -import { useBackendState } from "../hooks/backend.js"; +import { useWithdrawalDetails } from "../hooks/account.js"; +import { useSessionState } from "../hooks/session.js"; import { ChallengeInProgess, useBankState } from "../hooks/bank-state.js"; -import { useConversionInfo } from "../hooks/circuit.js"; +import { useConversionInfo } from "../hooks/regional.js"; import { RouteDefinition } from "../route.js"; import { undefinedIfEmpty } from "../utils.js"; import { RenderAmount } from "./PaytoWireTransferForm.js"; @@ -62,7 +62,7 @@ export function SolveChallengePage({ const [bankState, updateBankState] = useBankState(); const [code, setCode] = useState<string | undefined>(undefined); const [notification, notify, handleError] = useLocalNotification(); - const { state } = useBackendState(); + const { state } = useSessionState(); const creds = state.status !== "loggedIn" ? undefined : state; const { navigateTo } = useNavigationContext(); diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx index 078f4e5f5..caf205f31 100644 --- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx +++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx @@ -33,7 +33,7 @@ import { VNode, h } from "preact"; import { forwardRef } from "preact/compat"; import { useState } from "preact/hooks"; import { useBankCoreApiContext } from "../context/config.js"; -import { useBackendState } from "../hooks/backend.js"; +import { useSessionState } from "../hooks/session.js"; import { useBankState } from "../hooks/bank-state.js"; import { usePreferences } from "../hooks/preferences.js"; import { RouteDefinition } from "../route.js"; @@ -65,7 +65,7 @@ function OldWithdrawalForm({ const [bankState, updateBankState] = useBankState(); const { api } = useBankCoreApiContext(); - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const creds = credentials.status !== "loggedIn" ? undefined : credentials; const [amountStr, setAmountStr] = useState<string | undefined>( diff --git a/packages/demobank-ui/src/pages/WireTransfer.tsx b/packages/demobank-ui/src/pages/WireTransfer.tsx index 927968304..33f067e63 100644 --- a/packages/demobank-ui/src/pages/WireTransfer.tsx +++ b/packages/demobank-ui/src/pages/WireTransfer.tsx @@ -26,8 +26,8 @@ import { } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js"; -import { useAccountDetails } from "../hooks/access.js"; -import { useBackendState } from "../hooks/backend.js"; +import { useAccountDetails } from "../hooks/account.js"; +import { useSessionState } from "../hooks/session.js"; import { LoginForm } from "./LoginForm.js"; import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js"; import { RouteDefinition } from "../route.js"; @@ -54,7 +54,7 @@ export function WireTransfer({ onAuthorizationRequired: () => void; }): VNode { const { i18n } = useTranslationContext(); - const r = useBackendState(); + const r = useSessionState(); const account = r.state.status !== "loggedOut" ? r.state.username : "admin"; const result = useAccountDetails(account); diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx index 39b94b349..4efc82017 100644 --- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx +++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx @@ -36,9 +36,9 @@ import { import { ComponentChildren, Fragment, VNode, h } from "preact"; import { mutate } from "swr"; import { useBankCoreApiContext } from "../context/config.js"; -import { useBackendState } from "../hooks/backend.js"; import { useBankState } from "../hooks/bank-state.js"; import { usePreferences } from "../hooks/preferences.js"; +import { useSessionState } from "../hooks/session.js"; import { RouteDefinition } from "../route.js"; import { LoginForm } from "./LoginForm.js"; import { RenderAmount } from "./PaytoWireTransferForm.js"; @@ -68,7 +68,7 @@ export function WithdrawalConfirmationQuestion({ }: Props): VNode { const { i18n } = useTranslationContext(); const [settings] = usePreferences(); - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const creds = credentials.status !== "loggedIn" ? undefined : credentials; const [, updateBankState] = useBankState(); @@ -330,7 +330,7 @@ export function ShouldBeSameUser({ username: string; children: ComponentChildren; }): VNode { - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const { i18n } = useTranslationContext(); if (credentials.status === "loggedOut") { return ( diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx index b128bd99f..9765147d1 100644 --- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx +++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx @@ -30,7 +30,7 @@ import { } from "@gnu-taler/web-util/browser"; import { VNode, h } from "preact"; import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js"; -import { useWithdrawalDetails } from "../hooks/access.js"; +import { useWithdrawalDetails } from "../hooks/account.js"; import { RouteDefinition } from "../route.js"; import { QrCodeSection } from "./QrCodeSection.js"; import { WithdrawalConfirmationQuestion } from "./WithdrawalConfirmationQuestion.js"; diff --git a/packages/demobank-ui/src/pages/account/CashoutListForAccount.tsx b/packages/demobank-ui/src/pages/account/CashoutListForAccount.tsx index fe64778dd..2216b96fc 100644 --- a/packages/demobank-ui/src/pages/account/CashoutListForAccount.tsx +++ b/packages/demobank-ui/src/pages/account/CashoutListForAccount.tsx @@ -16,9 +16,9 @@ import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { Cashouts } from "../../components/Cashouts/index.js"; -import { useBackendState } from "../../hooks/backend.js"; +import { useSessionState } from "../../hooks/session.js"; import { ProfileNavigation } from "../ProfileNavigation.js"; -import { CreateCashout } from "../business/CreateCashout.js"; +import { CreateCashout } from "../regional/CreateCashout.js"; import { RouteDefinition } from "../../route.js"; interface Props { @@ -48,7 +48,7 @@ export function CashoutListForAccount({ }: Props): VNode { const { i18n } = useTranslationContext(); - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const accountIsTheCurrentUser = credentials.status === "loggedIn" diff --git a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx index 6aad8997a..8ab3998ad 100644 --- a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx +++ b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx @@ -33,8 +33,8 @@ import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js"; import { useBankCoreApiContext } from "../../context/config.js"; -import { useAccountDetails } from "../../hooks/access.js"; -import { useBackendState } from "../../hooks/backend.js"; +import { useAccountDetails } from "../../hooks/account.js"; +import { useSessionState } from "../../hooks/session.js"; import { useBankState } from "../../hooks/bank-state.js"; import { RouteDefinition } from "../../route.js"; import { LoginForm } from "../LoginForm.js"; @@ -65,7 +65,7 @@ export function ShowAccountDetails({ account: string; }): VNode { const { i18n } = useTranslationContext(); - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const creds = credentials.status !== "loggedIn" ? undefined : credentials; const { api } = useBankCoreApiContext(); const accountIsTheCurrentUser = diff --git a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx index 305f041ec..b9a334088 100644 --- a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx +++ b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx @@ -29,7 +29,7 @@ import { import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { useBankCoreApiContext } from "../../context/config.js"; -import { useBackendState } from "../../hooks/backend.js"; +import { useSessionState } from "../../hooks/session.js"; import { useBankState } from "../../hooks/bank-state.js"; import { RouteDefinition } from "../../route.js"; import { undefinedIfEmpty } from "../../utils.js"; @@ -62,7 +62,7 @@ export function UpdateAccountPassword({ account: string; }): VNode { const { i18n } = useTranslationContext(); - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const token = credentials.status !== "loggedIn" ? undefined : credentials.token; const { api } = useBankCoreApiContext(); diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx b/packages/demobank-ui/src/pages/admin/AccountForm.tsx index 5a5ce8c32..bce7afe11 100644 --- a/packages/demobank-ui/src/pages/admin/AccountForm.tsx +++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx @@ -33,7 +33,7 @@ import { import { ComponentChildren, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { VersionHint, useBankCoreApiContext } from "../../context/config.js"; -import { useBackendState } from "../../hooks/backend.js"; +import { useSessionState } from "../../hooks/session.js"; import { ErrorMessageMappingFor, TanChannel, @@ -92,7 +92,7 @@ export function AccountForm<PurposeType extends keyof ChangeByPurposeType>({ }): VNode { const { config, hints, url } = useBankCoreApiContext(); const { i18n } = useTranslationContext(); - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const [form, setForm] = useState<AccountFormData>({}); const [errors, setErrors] = useState< diff --git a/packages/demobank-ui/src/pages/admin/AccountList.tsx b/packages/demobank-ui/src/pages/admin/AccountList.tsx index e0d368f04..4e465d4b5 100644 --- a/packages/demobank-ui/src/pages/admin/AccountList.tsx +++ b/packages/demobank-ui/src/pages/admin/AccountList.tsx @@ -23,7 +23,7 @@ import { Loading, useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js"; import { useBankCoreApiContext } from "../../context/config.js"; -import { useBusinessAccounts } from "../../hooks/circuit.js"; +import { useBusinessAccounts } from "../../hooks/regional.js"; import { RenderAmount } from "../PaytoWireTransferForm.js"; import { RouteDefinition } from "../../route.js"; diff --git a/packages/demobank-ui/src/pages/admin/AdminHome.tsx b/packages/demobank-ui/src/pages/admin/AdminHome.tsx index 2cdc39261..752d86aa6 100644 --- a/packages/demobank-ui/src/pages/admin/AdminHome.tsx +++ b/packages/demobank-ui/src/pages/admin/AdminHome.tsx @@ -40,7 +40,7 @@ import { useState } from "preact/hooks"; import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js"; import { Transactions } from "../../components/Transactions/index.js"; import { useBankCoreApiContext } from "../../context/config.js"; -import { useConversionInfo, useLastMonitorInfo } from "../../hooks/circuit.js"; +import { useConversionInfo, useLastMonitorInfo } from "../../hooks/regional.js"; import { RouteDefinition } from "../../route.js"; import { RenderAmount } from "../PaytoWireTransferForm.js"; import { WireTransfer } from "../WireTransfer.js"; diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx index e09164ffb..3ae2b636c 100644 --- a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx +++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx @@ -30,7 +30,7 @@ import { import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { useBankCoreApiContext } from "../../context/config.js"; -import { useBackendState } from "../../hooks/backend.js"; +import { useSessionState } from "../../hooks/session.js"; import { RouteDefinition } from "../../route.js"; import { AccountForm } from "./AccountForm.js"; @@ -42,7 +42,7 @@ export function CreateNewAccount({ onCreateSuccess: () => void; }): VNode { const { i18n } = useTranslationContext(); - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const token = credentials.status !== "loggedIn" ? undefined : credentials.token; const { api } = useBankCoreApiContext(); diff --git a/packages/demobank-ui/src/pages/DownloadStats.tsx b/packages/demobank-ui/src/pages/admin/DownloadStats.tsx index 353238c13..66ef73d19 100644 --- a/packages/demobank-ui/src/pages/DownloadStats.tsx +++ b/packages/demobank-ui/src/pages/admin/DownloadStats.tsx @@ -29,10 +29,10 @@ import { } from "@gnu-taler/web-util/browser"; import { VNode, h } from "preact"; import { useState } from "preact/hooks"; -import { useBankCoreApiContext } from "../context/config.js"; -import { useBackendState } from "../hooks/backend.js"; -import { EmptyObject, RouteDefinition } from "../route.js"; -import { getTimeframesForDate } from "./admin/AdminHome.js"; +import { useBankCoreApiContext } from "../../context/config.js"; +import { useSessionState } from "../../hooks/session.js"; +import { EmptyObject, RouteDefinition } from "../../route.js"; +import { getTimeframesForDate } from "./AdminHome.js"; interface Props { routeCancel: RouteDefinition; @@ -54,7 +54,7 @@ type Options = { export function DownloadStats({ routeCancel }: Props): VNode { const { i18n } = useTranslationContext(); - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const creds = credentials.status !== "loggedIn" || !credentials.isUserAdministrator ? undefined diff --git a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx index 6f02eae8f..6039db326 100644 --- a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx +++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx @@ -35,8 +35,8 @@ import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js"; import { useBankCoreApiContext } from "../../context/config.js"; -import { useAccountDetails } from "../../hooks/access.js"; -import { useBackendState } from "../../hooks/backend.js"; +import { useAccountDetails } from "../../hooks/account.js"; +import { useSessionState } from "../../hooks/session.js"; import { undefinedIfEmpty } from "../../utils.js"; import { LoginForm } from "../LoginForm.js"; import { doAutoFocus } from "../PaytoWireTransferForm.js"; @@ -62,7 +62,7 @@ export function RemoveAccount({ const result = useAccountDetails(account); const [accountName, setAccountName] = useState<string | undefined>(); - const { state } = useBackendState(); + const { state } = useSessionState(); const token = state.status !== "loggedIn" ? undefined : state.token; const { api } = useBankCoreApiContext(); const [notification, notify, handleError] = useLocalNotification(); diff --git a/packages/demobank-ui/src/pages/ConversionConfig.tsx b/packages/demobank-ui/src/pages/regional/ConversionConfig.tsx index 2d52cd99f..63423353b 100644 --- a/packages/demobank-ui/src/pages/ConversionConfig.tsx +++ b/packages/demobank-ui/src/pages/regional/ConversionConfig.tsx @@ -34,13 +34,14 @@ import { } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useEffect, useState } from "preact/hooks"; -import { useBankCoreApiContext } from "../context/config.js"; -import { useBackendState } from "../hooks/backend.js"; -import { TransferCalculation, useCashinEstimator, useCashoutEstimator, useConversionInfo } from "../hooks/circuit.js"; -import { RouteDefinition } from "../route.js"; -import { undefinedIfEmpty } from "../utils.js"; -import { InputAmount, RenderAmount } from "./PaytoWireTransferForm.js"; -import { ProfileNavigation } from "./ProfileNavigation.js"; +import { useBankCoreApiContext } from "../../context/config.js"; +import { useSessionState } from "../../hooks/session.js"; +import { TransferCalculation, useCashinEstimator, useCashoutEstimator, useConversionInfo } from "../../hooks/regional.js"; +import { RouteDefinition } from "../../route.js"; +import { undefinedIfEmpty } from "../../utils.js"; +import { InputAmount, RenderAmount } from "../PaytoWireTransferForm.js"; +import { ProfileNavigation } from "../ProfileNavigation.js"; +import { FormErrors, FormStatus, FormValues, RecursivePartial, UIField, useFormState } from "../../hooks/form.js"; interface Props { routeMyAccountDetails: RouteDefinition; @@ -52,87 +53,8 @@ interface Props { onUpdateSuccess: () => void; } -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]>; -} - -type FormValues<T> = { - [k in keyof T]: - T[k] extends string ? (string | undefined) : - T[k] extends AmountJson ? (string | undefined) : - FormValues<T[k]>; -} - -type RecursivePartial<T> = { - [k in keyof T]?: - T[k] extends string ? (string) : - T[k] extends AmountJson ? (AmountJson) : - RecursivePartial<T[k]>; -} - -type FormErrors<T> = { - [k in keyof T]?: - T[k] extends string ? (TranslatedString) : - T[k] extends AmountJson ? (TranslatedString) : - FormErrors<T[k]>; -} - -type FormStatus<T> = { - status: "ok", - result: T, - errors: undefined, -} | { - status: "fail", - result: RecursivePartial<T>, - errors: FormErrors<T>, -} type FormType = { amount: AmountJson, conv: TalerBankConversionApi.ConversionRate } -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: any = form[fieldName]; - const currentError: any = errors ? errors[fieldName] : undefined; - function updater(newValue: any) { - updateForm({ ...form, [fieldName]: newValue }) - } - if (typeof currentValue === "object") { - const group = constructFormHandler(currentValue, updater, currentError) - // @ts-expect-error asdasd - prev[fieldName] = group - return prev; - } - const field: UIField = { - error: currentError, - value: currentValue, - onUpdate: updater - } - // @ts-expect-error asdasd - prev[fieldName] = field - return prev - }, {} as FormHandler<T>) - - return handler; -} - -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] -} function useComponentState({ onUpdateSuccess, @@ -143,23 +65,24 @@ function useComponentState({ routeMyAccountDetails, routeMyAccountPassword, }: Props): utils.RecursiveState<VNode> { + const { i18n } = useTranslationContext(); const result = useConversionInfo() const info = result && !(result instanceof TalerError) && result.type === "ok" ? result.body : undefined; - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const creds = credentials.status !== "loggedIn" || !credentials.isUserAdministrator ? undefined : credentials; if (!info) { - return <div>waiting...</div> + return <i18n.Translate>loading...</i18n.Translate> } if (!creds) { - return <div>only admin can setup conversion</div>; + return <i18n.Translate>only admin can setup conversion</i18n.Translate> } return () => { @@ -187,7 +110,7 @@ function useComponentState({ const [form, status] = useFormState<FormType>( initalState, - checkConversionForm(i18n, info.regional_currency, info.fiat_currency) + createFormValidator(i18n, info.regional_currency, info.fiat_currency) ) const { @@ -378,8 +301,6 @@ function useComponentState({ tiny={form?.conv?.cashin_tiny_amount} />} - - {section == "cashout" && <Fragment> <ConversionForm id="cashout" inputCurrency={info.regional_currency} @@ -392,9 +313,6 @@ function useComponentState({ /> </Fragment>} - - - {section == "detail" && <Fragment> <div class="px-6 pt-6"> <div class="justify-between items-center flex "> @@ -587,12 +505,16 @@ function useComponentState({ } } -/** - * Show histories of public accounts. - */ export const ConversionConfig = utils.recursive(useComponentState); -function checkConversionForm(i18n: InternationalizationAPI, regional: string, fiat: string) { +/** + * + * @param i18n + * @param regional + * @param fiat + * @returns form validator + */ +function createFormValidator(i18n: InternationalizationAPI, regional: string, fiat: string) { return function check(state: FormValues<FormType>): FormStatus<FormType> { const cashin_min_amount = Amounts.parse(`${fiat}:${state.conv.cashin_min_amount}`) @@ -905,82 +827,122 @@ function ConversionForm({ id, inputCurrency, outputCurrency, fee, minimum, ratio <section class="grid grid-cols-1 gap-y-3 text-gray-600"> <details class="group text-sm"> <summary class="flex cursor-pointer flex-row items-center justify-between "> - Rounding an amount of 1.24 with rounding value 0.1 + <i18n.Translate> + Rounding an amount of 1.24 with rounding value 0.1 + </i18n.Translate> <svg class="h-6 w-6 rotate-0 transform group-open:rotate-180" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"> <path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path> </svg> </summary> <p class="text-gray-900 my-4"> - Given the rounding value of 0.1 the possible values closest to 1.24 are: 1.1, 1.2, 1.3, 1.4. + <i18n.Translate> + Given the rounding value of 0.1 the possible values closest to 1.24 are: 1.1, 1.2, 1.3, 1.4. + </i18n.Translate> </p> <p class="text-gray-900 my-4"> - With the "zero" mode the value will be rounded to 1.2 + <i18n.Translate> + With the "zero" mode the value will be rounded to 1.2 + </i18n.Translate> </p> <p class="text-gray-900 my-4"> - With the "nearest" mode the value will be rounded to 1.2 + <i18n.Translate> + With the "nearest" mode the value will be rounded to 1.2 + </i18n.Translate> </p> <p class="text-gray-900 mt-4"> - With the "up" mode the value will be rounded to 1.3 + <i18n.Translate> + With the "up" mode the value will be rounded to 1.3 + </i18n.Translate> </p> </details> <details class="group "> <summary class="flex cursor-pointer flex-row items-center justify-between "> - Rounding an amount of 1.26 with rounding value 0.1 + <i18n.Translate> + Rounding an amount of 1.26 with rounding value 0.1 + </i18n.Translate> <svg class="h-6 w-6 rotate-0 transform group-open:rotate-180" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"> <path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path> </svg> </summary> <p class="text-gray-900 my-4"> - Given the rounding value of 0.1 the possible values closest to 1.24 are: 1.1, 1.2, 1.3, 1.4. + <i18n.Translate> + Given the rounding value of 0.1 the possible values closest to 1.24 are: 1.1, 1.2, 1.3, 1.4. + </i18n.Translate> </p> <p class="text-gray-900 my-4"> - With the "zero" mode the value will be rounded to 1.2 + <i18n.Translate> + With the "zero" mode the value will be rounded to 1.2 + </i18n.Translate> </p> <p class="text-gray-900 my-4"> - With the "nearest" mode the value will be rounded to 1.3 + <i18n.Translate> + With the "nearest" mode the value will be rounded to 1.3 + </i18n.Translate> </p> <p class="text-gray-900 my-4"> - With the "up" mode the value will be rounded to 1.3 + <i18n.Translate> + With the "up" mode the value will be rounded to 1.3 + </i18n.Translate> </p> </details> <details class="group "> <summary class="flex cursor-pointer flex-row items-center justify-between "> - Rounding an amount of 1.24 with rounding value 0.3 + <i18n.Translate> + Rounding an amount of 1.24 with rounding value 0.3 + </i18n.Translate> <svg class="h-6 w-6 rotate-0 transform group-open:rotate-180" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"> <path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path> </svg> </summary> <p class="text-gray-900 my-4"> - Given the rounding value of 0.3 the possible values closest to 1.24 are: 0.9, 1.2, 1.5, 1.8. + <i18n.Translate> + Given the rounding value of 0.3 the possible values closest to 1.24 are: 0.9, 1.2, 1.5, 1.8. + </i18n.Translate> </p> <p class="text-gray-900 my-4"> - With the "zero" mode the value will be rounded to 1.2 + <i18n.Translate> + With the "zero" mode the value will be rounded to 1.2 + </i18n.Translate> </p> <p class="text-gray-900 my-4"> - With the "nearest" mode the value will be rounded to 1.2 + <i18n.Translate> + With the "nearest" mode the value will be rounded to 1.2 + </i18n.Translate> </p> <p class="text-gray-900 my-4"> - With the "up" mode the value will be rounded to 1.5 + <i18n.Translate> + With the "up" mode the value will be rounded to 1.5 + </i18n.Translate> </p> </details> <details class="group "> <summary class="flex cursor-pointer flex-row items-center justify-between "> - Rounding an amount of 1.26 with rounding value 0.3 + <i18n.Translate> + Rounding an amount of 1.26 with rounding value 0.3 + </i18n.Translate> <svg class="h-6 w-6 rotate-0 transform group-open:rotate-180" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"> <path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path> </svg> </summary> <p class="text-gray-900 my-4"> - Given the rounding value of 0.3 the possible values closest to 1.24 are: 0.9, 1.2, 1.5, 1.8. + <i18n.Translate> + Given the rounding value of 0.3 the possible values closest to 1.24 are: 0.9, 1.2, 1.5, 1.8. + </i18n.Translate> </p> <p class="text-gray-900 my-4"> - With the "zero" mode the value will be rounded to 1.2 + <i18n.Translate> + With the "zero" mode the value will be rounded to 1.2 + </i18n.Translate> </p> <p class="text-gray-900 my-4"> - With the "nearest" mode the value will be rounded to 1.3 + <i18n.Translate> + With the "nearest" mode the value will be rounded to 1.3 + </i18n.Translate> </p> <p class="text-gray-900 my-4"> - With the "up" mode the value will be rounded to 1.3 + <i18n.Translate> + With the "up" mode the value will be rounded to 1.3 + </i18n.Translate> </p> </details> </section> diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx b/packages/demobank-ui/src/pages/regional/CreateCashout.tsx index 1a5fad1b1..a5b8f774a 100644 --- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx +++ b/packages/demobank-ui/src/pages/regional/CreateCashout.tsx @@ -38,10 +38,10 @@ import { Fragment, VNode, h } from "preact"; import { useEffect, useState } from "preact/hooks"; import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js"; import { VersionHint, useBankCoreApiContext } from "../../context/config.js"; -import { useAccountDetails } from "../../hooks/access.js"; -import { useBackendState } from "../../hooks/backend.js"; +import { useAccountDetails } from "../../hooks/account.js"; +import { useSessionState } from "../../hooks/session.js"; import { useBankState } from "../../hooks/bank-state.js"; -import { TransferCalculation, useCashoutEstimator, useConversionInfo, useEstimator } from "../../hooks/circuit.js"; +import { TransferCalculation, useCashoutEstimator, useConversionInfo, useEstimator } from "../../hooks/regional.js"; import { RouteDefinition } from "../../route.js"; import { TanChannel, undefinedIfEmpty } from "../../utils.js"; import { LoginForm } from "../LoginForm.js"; @@ -82,7 +82,7 @@ export function CreateCashout({ estimateByCredit: calculateFromCredit, estimateByDebit: calculateFromDebit, } = useCashoutEstimator(); - const { state: credentials } = useBackendState(); + const { state: credentials } = useSessionState(); const creds = credentials.status !== "loggedIn" ? undefined : credentials; const [, updateBankState] = useBankState(); diff --git a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx b/packages/demobank-ui/src/pages/regional/ShowCashoutDetails.tsx index 33115c16a..415f88868 100644 --- a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx +++ b/packages/demobank-ui/src/pages/regional/ShowCashoutDetails.tsx @@ -29,7 +29,7 @@ import { import { format } from "date-fns"; import { VNode, h } from "preact"; import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js"; -import { useCashoutDetails, useConversionInfo } from "../../hooks/circuit.js"; +import { useCashoutDetails, useConversionInfo } from "../../hooks/regional.js"; import { RouteDefinition } from "../../route.js"; import { RenderAmount } from "../PaytoWireTransferForm.js"; import { Time } from "../../components/Time.js"; diff --git a/packages/demobank-ui/src/stories.test.ts b/packages/demobank-ui/src/stories.test.ts index 747bf4083..8171c6d8f 100644 --- a/packages/demobank-ui/src/stories.test.ts +++ b/packages/demobank-ui/src/stories.test.ts @@ -30,7 +30,6 @@ 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 { BackendStateProviderTesting } from "./context/backend.js"; import { BankCoreApiProviderTesting } from "./context/config.js"; setupI18n("en", { en: {} }); @@ -57,15 +56,6 @@ function DefaultTestingContext({ }: { children: ComponentChildren; }): VNode { - const ctx1 = create(BackendStateProviderTesting, { - children, - state: { - status: "loggedIn", - username: "test", - token: "pwd" as AccessToken, - isUserAdministrator: false, - }, - }); const cfg: TalerCorebankApi.Config = { name: "libeufin-bank", allow_deletions: true, @@ -86,7 +76,7 @@ function DefaultTestingContext({ version: "1:0:0", }; const ctx2 = create(BankCoreApiProviderTesting, { - children: ctx1, + children: [], state: cfg, url: "http://localhost", }); |