taler-typescript-core

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

commit c3b580a6f5b84748537d8e4f15c757da7f7912a5
parent b885a68037c35040c3c409201cdbb6815c97a7bf
Author: Florian Dold <dold@taler.net>
Date:   Mon, 24 Aug 2026 02:28:59 +0200

bank web UI: preserve route parameters and show not-found pages

Diffstat:
Mpackages/libeufin-bank-webui/src/Routing.tsx | 104+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Dpackages/libeufin-bank-webui/src/pages/ShowNotifications.tsx | 22----------------------
2 files changed, 67 insertions(+), 59 deletions(-)

diff --git a/packages/libeufin-bank-webui/src/Routing.tsx b/packages/libeufin-bank-webui/src/Routing.tsx @@ -34,7 +34,6 @@ import { assertUnreachable, createRFC8959AccessTokenEncoded, } from "@gnu-taler/taler-util"; -import { useEffect } from "preact/hooks"; import { useBankChallengeHandlerContext } from "./context/challenge.js"; import { useRefreshSessionBeforeExpires, @@ -57,7 +56,6 @@ import { ConversionConfig } from "./pages/regional/ConversionConfig.js"; import { CreateCashout } from "./pages/regional/CreateCashout.js"; import { ShowCashoutDetails } from "./pages/regional/ShowCashoutDetails.js"; import { RegistrationPage } from "./pages/RegistrationPage.js"; -import { ShowNotifications } from "./pages/ShowNotifications.js"; import { WireTransfer } from "./pages/WireTransfer.js"; import { WithdrawalOperationPage } from "./pages/WithdrawalOperationPage.js"; @@ -73,7 +71,6 @@ export function Routing(): VNode { return ( <BankFrame account={username} - routeNotifications={privatePages.notifications} routeAccountDetails={privatePages.myAccountDetails} > <PrivateRouting username={username} isAdmin={isUserAdministrator} /> @@ -81,7 +78,7 @@ export function Routing(): VNode { ); } return ( - <BankFrame routeNotifications={privatePages.notifications}> + <BankFrame> <PublicRounting onLoggedUser={(username, token, expiration) => { session.logIn({ username, token, expiration }); @@ -92,9 +89,10 @@ export function Routing(): VNode { } const publicPages = { - login: urlPattern(/\/login/, () => "#/login"), - register: urlPattern(/\/register/, () => "#/register"), - publicAccounts: urlPattern(/\/public-accounts/, () => "#/public-accounts"), + root: urlPattern(/^\/?$/, () => "#/login"), + login: urlPattern(/^\/login$/, () => "#/login"), + register: urlPattern(/^\/register$/, () => "#/register"), + publicAccounts: urlPattern(/^\/public-accounts$/, () => "#/public-accounts"), operationDetails: urlPattern<{ wopid: string }>( /\/operation\/(?<wopid>[a-zA-Z0-9-]+)/, ({ wopid }) => `#/operation/${wopid}`, @@ -119,12 +117,6 @@ function PublicRounting({ const mfa = useBankChallengeHandlerContext(); - useEffect(() => { - if (location === undefined) { - navigateTo(publicPages.login.url({})); - } - }, [location]); - const tokenRequest = { scope: "readwrite", duration: SESSION_DURATION, @@ -186,7 +178,7 @@ function PublicRounting({ ); switch (location.name) { - case undefined: + case "root": case "login": { return ( <Fragment> @@ -222,12 +214,21 @@ function PublicRounting({ </Fragment> ); } + case undefined: { + return ( + <NotFound + home={publicPages.login.url({})} + homeLabel={i18n.str`Sign in`} + /> + ); + } default: assertUnreachable(location); } } const privatePages = { + root: urlPattern(/^\/?$/, () => "#/account"), homeChargeWallet: urlPattern( /\/account\/charge-wallet/, () => "#/account/charge-wallet", @@ -236,9 +237,8 @@ const privatePages = { account?: string; subject?: string; amount?: string; - }>(/\/account\/wire-transfer/, () => "#/account/wire-transfer"), - home: urlPattern(/\/account/, () => "#/account"), - notifications: urlPattern(/\/notifications/, () => "#/notifications"), + }>(/^\/account\/wire-transfer$/, () => "#/account/wire-transfer"), + home: urlPattern(/^\/account$/, () => "#/account"), cashoutCreate: urlPattern(/\/new-cashout/, () => "#/new-cashout"), cashoutDetails: urlPattern<{ cid: string }>( /\/cashout\/(?<cid>[a-zA-Z0-9]+)/, @@ -249,8 +249,14 @@ const privatePages = { subject?: string; amount?: string; }>( - /\/wire-transfer\/(?<account>[a-zA-Z0-9]+)/, - ({ account }) => `#/wire-transfer/${account}`, + /^\/wire-transfer\/(?<account>[a-zA-Z0-9._~-]+)$/, + ({ account, amount, subject }) => { + const params = new URLSearchParams(); + if (amount) params.set("amount", amount); + if (subject) params.set("subject", subject); + const query = params.toString(); + return `#/wire-transfer/${encodeURIComponent(account ?? "")}${query ? `?${query}` : ""}`; + }, ), publicAccountList: urlPattern(/\/public-accounts/, () => "#/public-accounts"), statsDownload: urlPattern(/\/download-stats/, () => "#/download-stats"), @@ -264,20 +270,20 @@ const privatePages = { myAccountCashouts: urlPattern(/\/my-cashouts/, () => "#/my-cashouts"), conversionConfig: urlPattern(/\/conversion$/, () => "#/conversion"), accountDetails: urlPattern<{ account: string }>( - /\/profile\/(?<account>[a-zA-Z0-9_-]+)\/details/, - ({ account }) => `#/profile/${account}/details`, + /^\/profile\/(?<account>[a-zA-Z0-9._~-]+)\/details$/, + ({ account }) => `#/profile/${encodeURIComponent(account)}/details`, ), accountChangePassword: urlPattern<{ account: string }>( - /\/profile\/(?<account>[a-zA-Z0-9_-]+)\/change-password/, - ({ account }) => `#/profile/${account}/change-password`, + /^\/profile\/(?<account>[a-zA-Z0-9._~-]+)\/change-password$/, + ({ account }) => `#/profile/${encodeURIComponent(account)}/change-password`, ), accountDelete: urlPattern<{ account: string }>( - /\/profile\/(?<account>[a-zA-Z0-9_-]+)\/delete/, - ({ account }) => `#/profile/${account}/delete`, + /^\/profile\/(?<account>[a-zA-Z0-9._~-]+)\/delete$/, + ({ account }) => `#/profile/${encodeURIComponent(account)}/delete`, ), accountCashouts: urlPattern<{ account: string }>( - /\/profile\/(?<account>[a-zA-Z0-9_-]+)\/cashouts/, - ({ account }) => `#/profile/${account}/cashouts`, + /^\/profile\/(?<account>[a-zA-Z0-9._~-]+)\/cashouts$/, + ({ account }) => `#/profile/${encodeURIComponent(account)}/cashouts`, ), startOperation: urlPattern<{ wopid: string }>( /\/start-operation\/(?<wopid>[a-zA-Z0-9-]+)/, @@ -305,12 +311,8 @@ function PrivateRouting({ isAdmin: boolean; }): VNode { const { navigateTo } = useNavigationContext(); + const { i18n } = useTranslationContext(); const location = useCurrentLocation(privatePages); - useEffect(() => { - if (location === undefined) { - navigateTo(privatePages.home.url({})); - } - }, [location]); switch (location.name) { case "operationDetails": { @@ -453,7 +455,7 @@ function PrivateRouting({ /> ); } - case undefined: + case "root": case "home": { if (isAdmin) { return ( @@ -513,8 +515,8 @@ function PrivateRouting({ return ( <WireTransfer toAccount={location.values.account} - withAmount={location.values.amount} - withSubject={location.values.subject} + withAmount={location.params.amount?.[0]} + withSubject={location.params.subject?.[0]} routeCancel={privatePages.home} onSuccess={() => navigateTo(privatePages.home.url({}))} /> @@ -602,10 +604,38 @@ function PrivateRouting({ /> ); } - case "notifications": { - return <ShowNotifications />; + case undefined: { + return ( + <NotFound home={privatePages.home.url({})} homeLabel={i18n.str`Home`} /> + ); } default: assertUnreachable(location); } } + +function NotFound({ + home, + homeLabel, +}: { + home: string; + homeLabel: string; +}): VNode { + const { i18n } = useTranslationContext(); + return ( + <section class="mx-auto max-w-xl py-12 text-center"> + <h1 class="text-2xl font-bold text-gray-900"> + <i18n.Translate>Page not found</i18n.Translate> + </h1> + <p class="mt-3 text-gray-600"> + <i18n.Translate>The requested bank page does not exist.</i18n.Translate> + </p> + <a + class="mt-6 inline-block rounded-md bg-indigo-600 px-4 py-2 font-semibold text-white" + href={home} + > + {homeLabel} + </a> + </section> + ); +} diff --git a/packages/libeufin-bank-webui/src/pages/ShowNotifications.tsx b/packages/libeufin-bank-webui/src/pages/ShowNotifications.tsx @@ -1,22 +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 { Time } from "@gnu-taler/web-util/browser"; -import { VNode, h } from "preact"; - -export function ShowNotifications(): VNode { - return <div>tbd</div>; -}