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:
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>;
-}