gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated (34b57402f -> 236d4347f)


From: gnunet
Subject: [taler-wallet-core] branch master updated (34b57402f -> 236d4347f)
Date: Wed, 24 Jan 2024 21:14:18 +0100

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a change to branch master
in repository wallet-core.

    from 34b57402f embedded wallet: log request timing
     new 579128ce4 update header
     new 236d4347f many changes

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 packages/demobank-ui/.eslintrc.cjs                 |   28 +
 packages/demobank-ui/build.mjs                     |    2 +-
 packages/demobank-ui/copyleft-header.js            |    2 +-
 packages/demobank-ui/dev.mjs                       |    2 +-
 packages/demobank-ui/package.json                  |   32 +-
 packages/demobank-ui/src/Routing.tsx               |  772 ++-
 .../demobank-ui/src/components/Cashouts/index.ts   |   23 +-
 .../demobank-ui/src/components/Cashouts/state.ts   |   13 +-
 .../src/components/Cashouts/stories.tsx            |    2 +-
 .../demobank-ui/src/components/Cashouts/test.ts    |    7 +-
 .../demobank-ui/src/components/Cashouts/views.tsx  |  203 +-
 .../src/components/EmptyComponentExample/index.ts  |    2 +-
 .../src/components/EmptyComponentExample/state.ts  |    4 +-
 .../components/EmptyComponentExample/stories.tsx   |    2 +-
 .../src/components/EmptyComponentExample/test.ts   |    2 +-
 .../src/components/EmptyComponentExample/views.tsx |   17 +-
 .../src/components/ErrorLoadingWithDebug.tsx       |   17 +-
 packages/demobank-ui/src/components/QR.tsx         |    2 +-
 .../src/components/Transactions/index.ts           |    2 +-
 .../src/components/Transactions/state.ts           |   62 +-
 .../src/components/Transactions/stories.tsx        |    2 +-
 .../src/components/Transactions/test.ts            |   15 +-
 .../src/components/Transactions/views.tsx          |  220 +-
 packages/demobank-ui/src/components/app.tsx        |   58 +-
 .../demobank-ui/src/components/index.examples.ts   |    2 +-
 packages/demobank-ui/src/context/backend.ts        |    4 +-
 packages/demobank-ui/src/context/config.ts         |  177 +-
 packages/demobank-ui/src/context/settings.ts       |    4 +-
 packages/demobank-ui/src/declaration.d.ts          |    8 +-
 packages/demobank-ui/src/endpoints.ts              |    2 +-
 packages/demobank-ui/src/forms/simplest.ts         |   66 -
 packages/demobank-ui/src/hooks/access.ts           |  169 +-
 packages/demobank-ui/src/hooks/async.ts            |   17 +-
 packages/demobank-ui/src/hooks/backend.ts          |   22 +-
 packages/demobank-ui/src/hooks/bank-state.ts       |  101 +-
 packages/demobank-ui/src/hooks/circuit.ts          |  312 +-
 packages/demobank-ui/src/hooks/index.ts            |    4 +-
 packages/demobank-ui/src/hooks/preferences.ts      |   52 +-
 packages/demobank-ui/src/i18n/bank.pot             |    2 +-
 packages/demobank-ui/src/i18n/fr.po                |    2 +-
 packages/demobank-ui/src/i18n/poheader             |    2 +-
 packages/demobank-ui/src/i18n/strings.ts           | 7195 ++++++--------------
 packages/demobank-ui/src/index.html                |   44 +-
 packages/demobank-ui/src/index.tsx                 |   10 +-
 packages/demobank-ui/src/pages.ts                  |   44 -
 .../demobank-ui/src/pages/AccountPage/index.ts     |   43 +-
 .../demobank-ui/src/pages/AccountPage/state.ts     |   60 +-
 .../demobank-ui/src/pages/AccountPage/stories.tsx  |    2 +-
 packages/demobank-ui/src/pages/AccountPage/test.ts |   17 +-
 .../demobank-ui/src/pages/AccountPage/views.tsx    |  132 +-
 .../demobank-ui/src/pages/BankFrame.stories.tsx    |    2 +-
 packages/demobank-ui/src/pages/BankFrame.tsx       |  246 +-
 packages/demobank-ui/src/pages/DownloadStats.tsx   |  498 +-
 packages/demobank-ui/src/pages/LoginForm.tsx       |  215 +-
 .../demobank-ui/src/pages/OperationState/index.ts  |  101 +-
 .../demobank-ui/src/pages/OperationState/state.ts  |  145 +-
 .../src/pages/OperationState/stories.tsx           |    2 +-
 .../demobank-ui/src/pages/OperationState/test.ts   |   17 +-
 .../demobank-ui/src/pages/OperationState/views.tsx |  499 +-
 .../src/pages/PaymentOptions.stories.tsx           |    2 +-
 packages/demobank-ui/src/pages/PaymentOptions.tsx  |  186 +-
 .../src/pages/PaytoWireTransferForm.stories.tsx    |    2 +-
 .../src/pages/PaytoWireTransferForm.tsx            |  637 +-
 .../demobank-ui/src/pages/ProfileNavigation.tsx    |  214 +-
 .../demobank-ui/src/pages/PublicHistoriesPage.tsx  |   34 +-
 .../src/pages/QrCodeSection.stories.tsx            |    2 +-
 packages/demobank-ui/src/pages/QrCodeSection.tsx   |  123 +-
 .../demobank-ui/src/pages/RegistrationPage.tsx     |  259 +-
 .../demobank-ui/src/pages/SolveChallengePage.tsx   |  808 ++-
 .../demobank-ui/src/pages/WalletWithdrawForm.tsx   |  369 +-
 packages/demobank-ui/src/pages/WireTransfer.tsx    |   60 +-
 .../src/pages/WithdrawalConfirmationQuestion.tsx   |  327 +-
 .../src/pages/WithdrawalOperationPage.tsx          |   49 +-
 .../demobank-ui/src/pages/WithdrawalQRCode.tsx     |  300 +-
 .../src/pages/account/CashoutListForAccount.tsx    |   77 +-
 .../src/pages/account/ShowAccountDetails.tsx       |  220 +-
 .../src/pages/account/UpdateAccountPassword.tsx    |  173 +-
 .../demobank-ui/src/pages/admin/AccountForm.tsx    |  807 ++-
 .../demobank-ui/src/pages/admin/AccountList.tsx    |  288 +-
 packages/demobank-ui/src/pages/admin/AdminHome.tsx |  650 +-
 .../src/pages/admin/CreateNewAccount.tsx           |  230 +-
 .../demobank-ui/src/pages/admin/RemoveAccount.tsx  |  194 +-
 .../src/pages/business/CreateCashout.tsx           |  546 +-
 .../src/pages/business/ShowCashoutDetails.tsx      |  138 +-
 packages/demobank-ui/src/pages/index.stories.tsx   |    2 +-
 packages/demobank-ui/src/pages/rnd.ts              |   36 +-
 packages/demobank-ui/src/route.ts                  |  335 +-
 packages/demobank-ui/src/settings.json             |    2 +-
 packages/demobank-ui/src/settings.ts               |   62 +-
 packages/demobank-ui/src/stories.test.ts           |   11 +-
 packages/demobank-ui/src/stories.tsx               |    2 +-
 packages/demobank-ui/src/utils.ts                  |   87 +-
 packages/demobank-ui/test.mjs                      |    6 +-
 packages/pogen/src/po2ts.ts                        |    2 +-
 packages/taler-util/src/http-client/types.ts       |    3 +-
 pnpm-lock.yaml                                     |  726 +-
 96 files changed, 9471 insertions(+), 10207 deletions(-)
 create mode 100644 packages/demobank-ui/.eslintrc.cjs
 delete mode 100644 packages/demobank-ui/src/forms/simplest.ts
 delete mode 100644 packages/demobank-ui/src/pages.ts

diff --git a/packages/demobank-ui/.eslintrc.cjs 
b/packages/demobank-ui/.eslintrc.cjs
new file mode 100644
index 000000000..05618b499
--- /dev/null
+++ b/packages/demobank-ui/.eslintrc.cjs
@@ -0,0 +1,28 @@
+module.exports = {
+  extends: [
+    'eslint:recommended',
+    'plugin:@typescript-eslint/recommended',
+    'plugin:react/recommended',
+  ],
+  parser: '@typescript-eslint/parser',
+  plugins: ['@typescript-eslint', 'header'],
+  root: true,
+  rules: {
+    "react/no-unknown-property": 0,
+    "react/no-unescaped-entities": 0,
+    "@typescript-eslint/no-namespace": 0,
+    "@typescript-eslint/no-unused-vars": [2,{argsIgnorePattern:"^_"}],
+    "header/header": [2,"copyleft-header.js"]
+  },
+  parserOptions: {
+    ecmaVersion: 6,
+    sourceType: 'module',
+    jsx: true,
+  },
+  settings: {
+    react: {
+      version: "18",
+      pragma: "h",
+    }
+  },
+};
diff --git a/packages/demobank-ui/build.mjs b/packages/demobank-ui/build.mjs
index 64ddc3774..04a6f646b 100755
--- a/packages/demobank-ui/build.mjs
+++ b/packages/demobank-ui/build.mjs
@@ -1,7 +1,7 @@
 #!/usr/bin/env node
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
diff --git a/packages/demobank-ui/copyleft-header.js 
b/packages/demobank-ui/copyleft-header.js
index 2635717c5..7fa276bea 100644
--- a/packages/demobank-ui/copyleft-header.js
+++ b/packages/demobank-ui/copyleft-header.js
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
diff --git a/packages/demobank-ui/dev.mjs b/packages/demobank-ui/dev.mjs
index c5ea318e7..7b4f719ae 100755
--- a/packages/demobank-ui/dev.mjs
+++ b/packages/demobank-ui/dev.mjs
@@ -1,7 +1,7 @@
 #!/usr/bin/env node
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
diff --git a/packages/demobank-ui/package.json 
b/packages/demobank-ui/package.json
index 22c4dc874..14fb28081 100644
--- a/packages/demobank-ui/package.json
+++ b/packages/demobank-ui/package.json
@@ -20,29 +20,13 @@
     "@gnu-taler/taler-util": "workspace:*",
     "@gnu-taler/web-util": "workspace:*",
     "date-fns": "2.29.3",
-    "history": "4.10.1",
     "jed": "1.1.1",
     "preact": "10.11.3",
-    "preact-router": "3.2.1",
     "qrcode-generator": "^1.4.4",
     "swr": "2.0.3"
   },
-  "eslintConfig": {
-    "plugins": [
-      "header"
-    ],
-    "rules": {
-      "header/header": [
-        2,
-        "copyleft-header.js"
-      ]
-    },
-    "extends": [
-      "prettier"
-    ]
-  },
   "devDependencies": {
-    "@creativebulma/bulma-tooltip": "^1.2.0",
+    "eslint": "^8.56.0",
     "@gnu-taler/pogen": "^0.0.5",
     "@tailwindcss/forms": "^0.5.3",
     "@tailwindcss/typography": "^0.5.9",
@@ -50,19 +34,15 @@
     "@types/history": "^4.7.8",
     "@types/mocha": "^10.0.1",
     "@types/node": "^18.11.17",
-    "@typescript-eslint/eslint-plugin": "^5.41.0",
-    "@typescript-eslint/parser": "^5.41.0",
+    "@typescript-eslint/eslint-plugin": "^6.19.0",
+    "@typescript-eslint/parser": "^6.19.0",
     "autoprefixer": "^10.4.14",
-    "bulma": "^0.9.4",
-    "bulma-checkbox": "^1.1.1",
-    "bulma-radio": "^1.1.1",
     "chai": "^4.3.6",
     "esbuild": "^0.19.9",
-    "eslint-config-preact": "^1.2.0",
-    "mocha": "^9.2.0",
+    "eslint-config-prettier": "^9.1.0",
+    "eslint-plugin-react": "^7.33.2",
+    "mocha": "9.2.0",
     "po2json": "^0.4.5",
-    "preact-render-to-string": "^5.2.6",
-    "sass": "1.56.1",
     "tailwindcss": "^3.3.2",
     "typescript": "5.3.3"
   },
diff --git a/packages/demobank-ui/src/Routing.tsx 
b/packages/demobank-ui/src/Routing.tsx
index 4caa1dff0..e73493d60 100644
--- a/packages/demobank-ui/src/Routing.tsx
+++ b/packages/demobank-ui/src/Routing.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -14,386 +14,470 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { createHashHistory } from "history";
+import {
+  LocalNotificationBanner,
+  useLocalNotification,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
-import { Route, Router, route } from "preact-router";
-import { useEffect } from "preact/hooks";
 
+import {
+  AccessToken,
+  HttpStatusCode,
+  TranslatedString,
+  assertUnreachable,
+} from "@gnu-taler/taler-util";
+import { useBankCoreApiContext } from "./context/config.js";
+import { useSettingsContext } from "./context/settings.js";
 import { useBackendState } from "./hooks/backend.js";
+import { AccountPage } from "./pages/AccountPage/index.js";
 import { BankFrame } from "./pages/BankFrame.js";
-import { WithdrawalOperationPage } from "./pages/WithdrawalOperationPage.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";
-import { AdminHome } from "./pages/admin/AdminHome.js";
-import { CreateCashout } from "./pages/business/CreateCashout.js";
+import { SolveChallengePage } from "./pages/SolveChallengePage.js";
+import { WireTransfer } from "./pages/WireTransfer.js";
+import { WithdrawalOperationPage } from "./pages/WithdrawalOperationPage.js";
+import { CashoutListForAccount } from 
"./pages/account/CashoutListForAccount.js";
 import { ShowAccountDetails } from "./pages/account/ShowAccountDetails.js";
 import { UpdateAccountPassword } from 
"./pages/account/UpdateAccountPassword.js";
-import { RemoveAccount } from "./pages/admin/RemoveAccount.js";
+import { AdminHome } from "./pages/admin/AdminHome.js";
 import { CreateNewAccount } from "./pages/admin/CreateNewAccount.js";
-import { CashoutListForAccount } from 
"./pages/account/CashoutListForAccount.js";
+import { RemoveAccount } from "./pages/admin/RemoveAccount.js";
+import { CreateCashout } from "./pages/business/CreateCashout.js";
 import { ShowCashoutDetails } from "./pages/business/ShowCashoutDetails.js";
-import { WireTransfer } from "./pages/WireTransfer.js";
-import { AccountPage } from "./pages/AccountPage/index.js";
-import { useSettingsContext } from "./context/settings.js";
-import { useBankCoreApiContext } from "./context/config.js";
-import { DownloadStats } from "./pages/DownloadStats.js";
-import { SolveChallengePage } from "./pages/SolveChallengePage.js";
+import { RouteParamsType, urlPattern, useCurrentLocation } from "./route.js";
 
 export function Routing(): VNode {
-  const history = createHashHistory();
   const backend = useBackendState();
+
+  if (backend.state.status === "loggedIn") {
+    const { isUserAdministrator, username } = backend.state;
+    return (
+      <BankFrame account={username}>
+        <PrivateRouting username={username} isAdmin={isUserAdministrator} />
+      </BankFrame>
+    );
+  }
+  return (
+    <BankFrame>
+      <PublicRounting
+        onLoggedUser={(username, token) => {
+          backend.logIn({ username, token: token });
+        }}
+      />
+    </BankFrame>
+  );
+}
+
+const publicPages = {
+  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}`,
+  ),
+  solveSecondFactor: urlPattern(/\/2fa/, () => "#/2fa"),
+};
+
+function PublicRounting({
+  onLoggedUser,
+}: {
+  onLoggedUser: (username: string, token: AccessToken) => void;
+}): VNode {
   const settings = useSettingsContext();
-  const { config } = useBankCoreApiContext();
   const { i18n } = useTranslationContext();
+  const [loc, routeTo] = useCurrentLocation(publicPages);
+  const { api } = useBankCoreApiContext();
+  const [notification, notify, handleError] = useLocalNotification();
 
-  if (backend.state.status === "loggedOut") {
-    return <BankFrame >
-      <Router history={history}>
-        <Route
-          path="/login"
-          component={() => (
-            <Fragment>
-              <div class="sm:mx-auto sm:w-full sm:max-w-sm">
-                <h2 class="text-center text-2xl font-bold leading-9 
tracking-tight text-gray-900">{i18n.str`Welcome to ${settings.bankName}!`}</h2>
-              </div>
+  if (loc === undefined) {
+    routeTo("login", {});
+    return <Fragment />;
+  }
 
-              <LoginForm
-                onRegister={() => {
-                  route("/register");
-                }}
-              />
-            </Fragment>
-          )}
-        />
-        <Route
-          path="/public-accounts"
-          component={() => <PublicHistoriesPage />}
-        />
-        <Route
-          path="/operation/:wopid"
-          component={({ wopid }: { wopid: string }) => (
-            <WithdrawalOperationPage
-              operationId={wopid}
-              onAuthorizationRequired={() => {
-                route(`/2fa`)
-              }}
-              onContinue={() => {
-                route("/account");
-              }}
-            />
-          )}
+  async function doAutomaticLogin(username: string, password: string) {
+    await handleError(async () => {
+      const resp = await api
+        .getAuthenticationAPI(username)
+        .createAccessToken(password, {
+          scope: "readwrite",
+          duration: { d_us: "forever" },
+          refreshable: true,
+        });
+      if (resp.type === "ok") {
+        onLoggedUser(username, resp.body.access_token);
+      } else {
+        switch (resp.case) {
+          case HttpStatusCode.Unauthorized:
+            return notify({
+              type: "error",
+              title: i18n.str`Wrong credentials for "${username}"`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case HttpStatusCode.NotFound:
+            return notify({
+              type: "error",
+              title: i18n.str`Account not found`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          default:
+            assertUnreachable(resp);
+        }
+      }
+    });
+  }
+
+  switch (loc.name) {
+    case "login": {
+      return (
+        <Fragment>
+          <div class="sm:mx-auto sm:w-full sm:max-w-sm">
+            <h2 class="text-center text-2xl font-bold leading-9 tracking-tight 
text-gray-900">{i18n.str`Welcome to ${settings.bankName}!`}</h2>
+          </div>
+
+          <LoginForm routeRegister={publicPages.register} />
+        </Fragment>
+      );
+    }
+    case "publicAccounts": {
+      return <PublicHistoriesPage />;
+    }
+    case "operationDetails": {
+      const { wopid } = loc.values as RouteParamsType<
+        typeof loc.parent,
+        typeof loc.name
+      >;
+
+      return (
+        <WithdrawalOperationPage
+          operationId={wopid}
+          onOperationAborted={() => routeTo("login", {})}
+          routeClose={publicPages.login}
+          onAuthorizationRequired={() => routeTo("solveSecondFactor", {})}
         />
-        {config.allow_registrations &&
-          <Route
-            path="/register"
-            component={() => (
-              <RegistrationPage
-                onComplete={() => {
-                  route("/account");
-                }}
-                onCancel={() => {
-                  route("/account");
-                }}
-              />
-            )}
+      );
+    }
+    case "register": {
+      return (
+        <Fragment>
+          <LocalNotificationBanner notification={notification} />
+          <RegistrationPage
+            onRegistrationSuccesful={doAutomaticLogin}
+            routeCancel={publicPages.login}
           />
-        }
-        <Route default component={Redirect} to="/login" />
-      </Router>
-    </BankFrame>
+        </Fragment>
+      );
+    }
+    case "solveSecondFactor": {
+      return (
+        <SolveChallengePage
+          onChallengeCompleted={() => routeTo("login", {})}
+          routeClose={publicPages.login}
+        />
+      );
+    }
+    default:
+      assertUnreachable(loc.name);
   }
-  const { isUserAdministrator, username } = backend.state
+}
 
-  return (
-    <BankFrame account={username}>
-      <Router history={history}>
-        <Route
-          path="/operation/:wopid"
-          component={({ wopid }: { wopid: string }) => (
-            <WithdrawalOperationPage
-              operationId={wopid}
-              onContinue={() => {
-                route("/account");
-              }}
-              onAuthorizationRequired={() => {
-                route(`/2fa`)
-              }}
-            />
-          )}
+export const privatePages = {
+  home: urlPattern(/\/account/, () => "#/account"),
+  homeChargeWallet: urlPattern(
+    /\/account\/charge-wallet/,
+    () => "#/account/charge-wallet",
+  ),
+  homeWireTransfer: urlPattern(
+    /\/account\/wire-transfer/,
+    () => "#/account/wire-transfer",
+  ),
+  solveSecondFactor: urlPattern(/\/2fa/, () => "#/2fa"),
+  cashoutCreate: urlPattern(/\/new-cashout/, () => "#/new-cashout"),
+  cashoutDetails: urlPattern<{ cid: string }>(
+    /\/cashout\/(?<cid>[a-zA-Z0-9]+)/,
+    ({ cid }) => `#/cashout/${cid}`,
+  ),
+  wireTranserCreate: urlPattern<{ destination: string }>(
+    /\/wire-transfer\/(?<destination>[a-zA-Z0-9]+)/,
+    ({ destination }) => `#/wire-transfer/${destination}`,
+  ),
+  publicAccountList: urlPattern(/\/public-accounts/, () => 
"#/public-accounts"),
+  statsDownload: urlPattern(/\/download-stats/, () => "#/download-stats"),
+  accountCreate: urlPattern(/\/new-account/, () => "#/new-account"),
+  myAccountDelete: urlPattern(
+    /\/delete-my-account/,
+    () => "#/delete-my-account",
+  ),
+  myAccountDetails: urlPattern(/\/my-profile/, () => "#/my-profile"),
+  myAccountPassword: urlPattern(/\/my-password/, () => "#/my-password"),
+  myAccountCashouts: urlPattern(/\/my-cashouts/, () => "#/my-cashouts"),
+  accountDetails: urlPattern<{ account: string }>(
+    /\/profile\/(?<account>[a-zA-Z0-9]+)\/details/,
+    ({ account }) => `#/profile/${account}/details`,
+  ),
+  accountChangePassword: urlPattern<{ account: string }>(
+    /\/profile\/(?<account>[a-zA-Z0-9]+)\/change-password/,
+    ({ account }) => `#/profile/${account}/change-password`,
+  ),
+  accountDelete: urlPattern<{ account: string }>(
+    /\/profile\/(?<account>[a-zA-Z0-9]+)\/delete/,
+    ({ account }) => `#/profile/${account}/delete`,
+  ),
+  accountCashouts: urlPattern<{ account: string }>(
+    /\/profile\/(?<account>[a-zA-Z0-9]+)\/cashouts/,
+    ({ account }) => `#/profile/${account}/cashouts`,
+  ),
+  operationDetails: urlPattern<{ wopid: string }>(
+    /\/operation\/(?<wopid>[a-zA-Z0-9]+)/,
+    ({ wopid }) => `#/operation/${wopid}`,
+  ),
+};
+
+function PrivateRouting({
+  username,
+  isAdmin,
+}: {
+  username: string;
+  isAdmin: boolean;
+}): VNode {
+  const [loc, routeTo] = useCurrentLocation(privatePages);
+
+  if (loc === undefined) {
+    routeTo("home", {});
+    return <Fragment />;
+  }
+
+  switch (loc.name) {
+    case "operationDetails": {
+      const { wopid } = loc.values as RouteParamsType<
+        typeof loc.parent,
+        typeof loc.name
+      >;
+
+      return (
+        <WithdrawalOperationPage
+          operationId={wopid}
+          onOperationAborted={() => routeTo("home", {})}
+          routeClose={privatePages.home}
+          onAuthorizationRequired={() => routeTo("solveSecondFactor", {})}
         />
-        <Route
-          path="/2fa"
-          component={({ }: {}) => (
-            <SolveChallengePage
-              onContinue={() => {
-                route("/account");
-              }}
-            />
-          )}
+      );
+    }
+    case "solveSecondFactor": {
+      return (
+        <SolveChallengePage
+          onChallengeCompleted={() => routeTo("home", {})}
+          routeClose={privatePages.home}
         />
-        <Route
-          path="/public-accounts"
-          component={() => <PublicHistoriesPage />}
+      );
+    }
+    case "publicAccountList": {
+      return <PublicHistoriesPage />;
+    }
+    case "statsDownload": {
+      return <DownloadStats routeCancel={privatePages.home} />;
+    }
+    case "accountCreate": {
+      return (
+        <CreateNewAccount
+          routeCancel={privatePages.home}
+          onCreateSuccess={() => routeTo("home", {})}
         />
-        <Route
-          path="/download-stats"
-          component={() => <DownloadStats
-            onCancel={() => {
-              route("/account")
-            }}
-          />}
+      );
+    }
+    case "accountDetails": {
+      const { account } = loc.values as RouteParamsType<
+        typeof loc.parent,
+        typeof loc.name
+      >;
+      return (
+        <ShowAccountDetails
+          account={account}
+          onUpdateSuccess={() => routeTo("home", {})}
+          onAuthorizationRequired={() => routeTo("solveSecondFactor", {})}
+          routeClose={privatePages.home}
         />
-
-        <Route
-          path="/new-account"
-          component={() => <CreateNewAccount
-            onCancel={() => {
-              route("/account")
-            }}
-            onCreateSuccess={() => {
-              route("/account")
-            }}
-          />}
+      );
+    }
+    case "accountChangePassword": {
+      const { account } = loc.values as RouteParamsType<
+        typeof loc.parent,
+        typeof loc.name
+      >;
+      return (
+        <UpdateAccountPassword
+          focus
+          account={account}
+          onUpdateSuccess={() => routeTo("home", {})}
+          onAuthorizationRequired={() => routeTo("solveSecondFactor", {})}
+          routeClose={privatePages.home}
         />
-
-        <Route
-          path="/profile/:account/details"
-          component={({ account }: { account: string }) => (
-            <ShowAccountDetails
-              account={account}
-              onUpdateSuccess={() => {
-                route("/account")
-              }}
-              onAuthorizationRequired={() => {
-                route(`/2fa`)
-              }}
-              onClear={() => {
-                route("/account")
-              }}
-            />
-          )}
+      );
+    }
+    case "accountDelete": {
+      const { account } = loc.values as RouteParamsType<
+        typeof loc.parent,
+        typeof loc.name
+      >;
+      return (
+        <RemoveAccount
+          account={account}
+          onUpdateSuccess={() => routeTo("home", {})}
+          onAuthorizationRequired={() => routeTo("solveSecondFactor", {})}
+          routeCancel={privatePages.home}
         />
-
-        <Route
-          path="/profile/:account/change-password"
-          component={({ account }: { account: string }) => (
-            <UpdateAccountPassword
-              focus
-              account={account}
-              onUpdateSuccess={() => {
-                route("/account")
-              }}
-              onAuthorizationRequired={() => {
-                route(`/2fa`)
-              }}
-              onCancel={() => {
-                route("/account")
-              }}
-            />
-          )}
+      );
+    }
+    case "accountCashouts": {
+      const { account } = loc.values as RouteParamsType<
+        typeof loc.parent,
+        typeof loc.name
+      >;
+      return (
+        <CashoutListForAccount
+          account={account}
+          routeCashoutDetails={privatePages.cashoutDetails}
+          routeClose={privatePages.home}
+          onAuthorizationRequired={() => routeTo("solveSecondFactor", {})}
         />
-        <Route
-          path="/profile/:account/delete"
-          component={({ account }: { account: string }) => (
-            <RemoveAccount
-              account={account}
-              onUpdateSuccess={() => {
-                route("/account")
-              }}
-              onAuthorizationRequired={() => {
-                route(`/2fa`)
-              }}
-              onCancel={() => {
-                route("/account")
-              }}
-            />
-          )}
+      );
+    }
+    case "myAccountDelete": {
+      return (
+        <RemoveAccount
+          account={username}
+          onUpdateSuccess={() => routeTo("home", {})}
+          onAuthorizationRequired={() => routeTo("solveSecondFactor", {})}
+          routeCancel={privatePages.home}
         />
-
-        <Route
-          path="/profile/:account/cashouts"
-          component={({ account }: { account: string }) => (
-            <CashoutListForAccount
-              account={account}
-              onSelected={(cid) => {
-                route(`/cashout/${cid}`)
-              }}
-              onAuthorizationRequired={() => {
-                route(`/2fa`)
-              }}
-              onClose={() => {
-                route("/account")
-              }}
-            />
-          )}
+      );
+    }
+    case "myAccountDetails": {
+      return (
+        <ShowAccountDetails
+          account={username}
+          onUpdateSuccess={() => routeTo("home", {})}
+          onAuthorizationRequired={() => routeTo("solveSecondFactor", {})}
+          routeClose={privatePages.home}
         />
-
-        <Route
-          path="/delete-my-account"
-          component={() => (
-            <RemoveAccount
-              account={username}
-              onUpdateSuccess={() => {
-                route("/")
-              }}
-              onAuthorizationRequired={() => {
-                route(`/2fa`)
-              }}
-              onCancel={() => {
-                route("/account")
-              }}
-            />
-          )}
+      );
+    }
+    case "myAccountPassword": {
+      return (
+        <UpdateAccountPassword
+          focus
+          account={username}
+          onUpdateSuccess={() => routeTo("home", {})}
+          onAuthorizationRequired={() => routeTo("solveSecondFactor", {})}
+          routeClose={privatePages.home}
         />
-        <Route
-          path="/my-profile"
-          component={() => (
-            <ShowAccountDetails
-              account={username}
-              onUpdateSuccess={() => {
-                route("/account")
-              }}
-              onAuthorizationRequired={() => {
-                route(`/2fa`)
-              }}
-              onClear={() => {
-                route("/account")
-              }}
-            />
-          )}
+      );
+    }
+    case "myAccountCashouts": {
+      return (
+        <CashoutListForAccount
+          account={username}
+          routeCashoutDetails={privatePages.cashoutDetails}
+          onAuthorizationRequired={() => routeTo("solveSecondFactor", {})}
+          routeClose={privatePages.home}
         />
-        <Route
-          path="/my-password"
-          component={() => (
-            <UpdateAccountPassword
-              focus
-              account={username}
-              onUpdateSuccess={() => {
-                route("/account")
-              }}
-              onAuthorizationRequired={() => {
-                route(`/2fa`)
-              }}
-              onCancel={() => {
-                route("/account")
-              }}
-            />
-          )}
+      );
+    }
+    case "home": {
+      if (isAdmin) {
+        return (
+          <AdminHome
+            onAuthorizationRequired={() => routeTo("solveSecondFactor", {})}
+            routeCreate={privatePages.accountCreate}
+            routeRemoveAccount={privatePages.accountDelete}
+            routeShowAccount={privatePages.accountDetails}
+            routeShowCashoutsAccount={privatePages.accountCashouts}
+            routeUpdatePasswordAccount={privatePages.accountChangePassword}
+          />
+        );
+      }
+      return (
+        <AccountPage
+          account={username}
+          tab={undefined}
+          routeChargeWallet={privatePages.homeChargeWallet}
+          routeWireTransfer={privatePages.homeWireTransfer}
+          routeClose={privatePages.home}
+          onClose={() => routeTo("home", {})}
+          onAuthorizationRequired={() => routeTo("solveSecondFactor", {})}
+          onOperationCreated={(wopid) => routeTo("operationDetails", { wopid 
})}
         />
-
-        <Route
-          path="/my-cashouts"
-          component={() => (
-            <CashoutListForAccount
-              account={username}
-              onSelected={(cid) => {
-                route(`/cashout/${cid}`)
-              }}
-              onAuthorizationRequired={() => {
-                route(`/2fa`)
-              }}
-              onClose={() => {
-                route("/account");
-              }}
-            />
-          )}
+      );
+    }
+    case "cashoutCreate": {
+      return (
+        <CreateCashout
+          account={username}
+          onAuthorizationRequired={() => routeTo("solveSecondFactor", {})}
+          routeClose={privatePages.home}
         />
-
-        <Route
-          path="/new-cashout"
-          component={() => (
-            <CreateCashout
-              account={username}
-              onAuthorizationRequired={() => {
-                route(`/2fa`)
-              }}
-              onCancel={() => {
-                route("/account");
-              }}
-            />
-          )}
+      );
+    }
+    case "cashoutDetails": {
+      const { cid } = loc.values as RouteParamsType<
+        typeof loc.parent,
+        typeof loc.name
+      >;
+      return (
+        <ShowCashoutDetails
+          id={cid}
+          routeClose={privatePages.myAccountCashouts}
         />
-
-        <Route
-          path="/cashout/:cid"
-          component={({ cid }: { cid: string }) => (
-            <ShowCashoutDetails
-              id={cid}
-              onCancel={() => {
-                route("/my-cashouts");
-              }}
-            />
-          )}
+      );
+    }
+    case "wireTranserCreate": {
+      const { destination } = loc.values as RouteParamsType<
+        typeof loc.parent,
+        typeof loc.name
+      >;
+      return (
+        <WireTransfer
+          toAccount={destination}
+          onAuthorizationRequired={() => routeTo("solveSecondFactor", {})}
+          routeCancel={privatePages.home}
+          onSuccess={() => routeTo("home", {})}
         />
-
-
-        <Route
-          path="/wire-transfer/:dest"
-          component={({ dest }: { dest: string }) => (
-            <WireTransfer
-              toAccount={dest}
-              onAuthorizationRequired={() => {
-                route(`/2fa`)
-              }}
-              onCancel={() => {
-                route("/account")
-              }}
-              onSuccess={() => {
-                route("/account")
-              }}
-            />
-          )}
+      );
+    }
+    case "homeChargeWallet": {
+      return (
+        <AccountPage
+          account={username}
+          tab="charge-wallet"
+          routeChargeWallet={privatePages.homeChargeWallet}
+          routeWireTransfer={privatePages.homeWireTransfer}
+          routeClose={privatePages.home}
+          onClose={() => routeTo("home", {})}
+          onAuthorizationRequired={() => routeTo("solveSecondFactor", {})}
+          onOperationCreated={(wopid) => routeTo("operationDetails", { wopid 
})}
         />
-
-        <Route
-          path="/account"
-          component={() => {
-            if (isUserAdministrator) {
-              return <AdminHome
-                onAuthorizationRequired={() => {
-                  route(`/2fa`)
-                }}
-                onCreateAccount={() => {
-                  route("/new-account")
-                }}
-                onShowAccountDetails={(aid) => {
-                  route(`/profile/${aid}/details`)
-                }}
-                onRemoveAccount={(aid) => {
-                  route(`/profile/${aid}/delete`)
-                }}
-                onShowCashoutForAccount={(aid) => {
-                  route(`/profile/${aid}/cashouts`)
-                }}
-                onUpdateAccountPassword={(aid) => {
-                  route(`/profile/${aid}/change-password`)
-
-                }}
-              />;
-            } else {
-              return <AccountPage
-                account={username}
-                onAuthorizationRequired={() => {
-                  route(`/2fa`)
-                }}
-                goToConfirmOperation={(wopid) => {
-                  route(`/operation/${wopid}`);
-                }}
-              />
-            }
-          }}
+      );
+    }
+    case "homeWireTransfer": {
+      return (
+        <AccountPage
+          account={username}
+          tab="wire-transfer"
+          routeChargeWallet={privatePages.homeChargeWallet}
+          routeWireTransfer={privatePages.homeWireTransfer}
+          routeClose={privatePages.home}
+          onClose={() => routeTo("home", {})}
+          onAuthorizationRequired={() => routeTo("solveSecondFactor", {})}
+          onOperationCreated={(wopid) => routeTo("operationDetails", { wopid 
})}
         />
-        <Route default component={Redirect} to="/account" />
-      </Router>
-    </BankFrame>
-  );
-}
-
-function Redirect({ to }: { to: string }): VNode {
-  useEffect(() => {
-    route(to, true);
-  }, []);
-  return <div>being redirected to {to}</div>;
+      );
+    }
+    default:
+      assertUnreachable(loc.name);
+  }
 }
diff --git a/packages/demobank-ui/src/components/Cashouts/index.ts 
b/packages/demobank-ui/src/components/Cashouts/index.ts
index 571f23184..2c6bf681c 100644
--- a/packages/demobank-ui/src/components/Cashouts/index.ts
+++ b/packages/demobank-ui/src/components/Cashouts/index.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -15,17 +15,28 @@
  */
 
 import { Loading, utils } from "@gnu-taler/web-util/browser";
-import { AbsoluteTime, AmountJson, TalerCoreBankErrorsByMethod, 
TalerCorebankApi, TalerError } from "@gnu-taler/taler-util";
+import {
+  AbsoluteTime,
+  AmountJson,
+  TalerCoreBankErrorsByMethod,
+  TalerCorebankApi,
+  TalerError,
+} from "@gnu-taler/taler-util";
 import { ErrorLoadingWithDebug } from "../ErrorLoadingWithDebug.js";
 import { useComponentState } from "./state.js";
 import { FailedView, ReadyView } from "./views.js";
+import { RouteDefinition } from "../../route.js";
 
 export interface Props {
   account: string;
-  onSelected: (id: number) => void;
+  routeCashoutDetails: RouteDefinition<{ cid: string }>;
 }
 
-export type State = State.Loading | State.Failed | State.LoadingUriError | 
State.Ready;
+export type State =
+  | State.Loading
+  | State.Failed
+  | State.LoadingUriError
+  | State.Ready;
 
 export namespace State {
   export interface Loading {
@@ -50,7 +61,7 @@ export namespace State {
     status: "ready";
     error: undefined;
     cashouts: (TalerCorebankApi.CashoutStatusResponse & { id: number })[];
-    onSelected: (id: number) => void;
+    routeCashoutDetails: RouteDefinition<{ cid: string }>;
   }
 }
 
@@ -65,7 +76,7 @@ export interface Transaction {
 const viewMapping: utils.StateViewMap<State> = {
   loading: Loading,
   "loading-error": ErrorLoadingWithDebug,
-  "failed": FailedView,
+  failed: FailedView,
   ready: ReadyView,
 };
 
diff --git a/packages/demobank-ui/src/components/Cashouts/state.ts 
b/packages/demobank-ui/src/components/Cashouts/state.ts
index 814755541..344b93e14 100644
--- a/packages/demobank-ui/src/components/Cashouts/state.ts
+++ b/packages/demobank-ui/src/components/Cashouts/state.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -18,7 +18,10 @@ import { TalerError } from "@gnu-taler/taler-util";
 import { useCashouts } from "../../hooks/circuit.js";
 import { Props, State } from "./index.js";
 
-export function useComponentState({ account, onSelected }: Props): State {
+export function useComponentState({
+  account,
+  routeCashoutDetails,
+}: Props): State {
   const result = useCashouts(account);
   if (!result) {
     return {
@@ -35,14 +38,14 @@ export function useComponentState({ account, onSelected }: 
Props): State {
   if (result.type === "fail") {
     return {
       status: "failed",
-      error: result
-    }
+      error: result,
+    };
   }
 
   return {
     status: "ready",
     error: undefined,
     cashouts: result.body.cashouts,
-    onSelected,
+    routeCashoutDetails,
   };
 }
diff --git a/packages/demobank-ui/src/components/Cashouts/stories.tsx 
b/packages/demobank-ui/src/components/Cashouts/stories.tsx
index 0415b2362..37ab64108 100644
--- a/packages/demobank-ui/src/components/Cashouts/stories.tsx
+++ b/packages/demobank-ui/src/components/Cashouts/stories.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
diff --git a/packages/demobank-ui/src/components/Cashouts/test.ts 
b/packages/demobank-ui/src/components/Cashouts/test.ts
index 423803cd2..569cbc6f0 100644
--- a/packages/demobank-ui/src/components/Cashouts/test.ts
+++ b/packages/demobank-ui/src/components/Cashouts/test.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -25,6 +25,7 @@ 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";
 
 describe("Cashout states", () => {
   it.skip("should query backend and render transactions", async () => {
@@ -32,9 +33,7 @@ describe("Cashout states", () => {
 
     const props: Props = {
       account: "123",
-      onSelected: () => {
-        null;
-      },
+      routeCashoutDetails: buildNullRoutDefinition(),
     };
 
     env.addRequestExpectation(CASHOUT_API_EXAMPLE.LIST_FIRST_PAGE, {
diff --git a/packages/demobank-ui/src/components/Cashouts/views.tsx 
b/packages/demobank-ui/src/components/Cashouts/views.tsx
index cb1c24aa7..d036ec7d2 100644
--- a/packages/demobank-ui/src/components/Cashouts/views.tsx
+++ b/packages/demobank-ui/src/components/Cashouts/views.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -14,8 +14,17 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { Amounts, HttpStatusCode, TalerError, assertUnreachable } from 
"@gnu-taler/taler-util";
-import { Attention, Loading, useTranslationContext } from 
"@gnu-taler/web-util/browser";
+import {
+  Amounts,
+  HttpStatusCode,
+  TalerError,
+  assertUnreachable,
+} from "@gnu-taler/taler-util";
+import {
+  Attention,
+  Loading,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
 import { format } from "date-fns";
 import { Fragment, VNode, h } from "preact";
 import { useConversionInfo } from "../../hooks/circuit.js";
@@ -26,87 +35,132 @@ import { State } from "./index.js";
 export function FailedView({ error }: State.Failed) {
   const { i18n } = useTranslationContext();
   switch (error.case) {
-    case HttpStatusCode.NotImplemented: return <Attention type="danger"
-      title={i18n.str`Cashout not supported.`}>
-      <div class="mt-2 text-sm text-red-700">
-        {error.detail.hint}
-      </div>
-    </Attention>
     case HttpStatusCode.NotImplemented: {
-      return <Attention type="danger" title={i18n.str`Cashout not 
implemented`}>
-      </Attention>;
+      return (
+        <Attention
+          type="danger"
+          title={i18n.str`Cashout not implemented`}
+        ></Attention>
+      );
     }
-    default: assertUnreachable(error.case)
+    default:
+      assertUnreachable(error.case);
   }
 }
 
-export function ReadyView({ cashouts, onSelected }: State.Ready): VNode {
+export function ReadyView({
+  cashouts,
+  routeCashoutDetails,
+}: State.Ready): VNode {
   const { i18n, dateLocale } = useTranslationContext();
   const resp = useConversionInfo();
   if (!resp) {
-    return <Loading />
+    return <Loading />;
   }
   if (resp instanceof TalerError) {
-    return <ErrorLoadingWithDebug error={resp} />
+    return <ErrorLoadingWithDebug error={resp} />;
   }
   if (resp.type === "fail") {
     switch (resp.case) {
       case HttpStatusCode.NotImplemented: {
-        return <Attention type="danger" title={i18n.str`Cashout not 
implemented`}>
-        </Attention>;
+        return (
+          <Attention
+            type="danger"
+            title={i18n.str`Cashout not implemented`}
+          ></Attention>
+        );
       }
-      default: assertUnreachable(resp.case)
+      default:
+        assertUnreachable(resp.case);
     }
   }
 
-  if (!cashouts.length) return <div />
-  const txByDate = cashouts.reduce((prev, cur) => {
-    const d = cur.creation_time.t_s === "never"
-      ? ""
-      : format(cur.creation_time.t_s * 1000, "dd/MM/yyyy", { locale: 
dateLocale })
-    if (!prev[d]) {
-      prev[d] = []
-    }
-    prev[d].push(cur)
-    return prev
-  }, {} as Record<string, typeof cashouts>)
+  if (!cashouts.length) return <div />;
+  const txByDate = cashouts.reduce(
+    (prev, cur) => {
+      const d =
+        cur.creation_time.t_s === "never"
+          ? ""
+          : format(cur.creation_time.t_s * 1000, "dd/MM/yyyy", {
+              locale: dateLocale,
+            });
+      if (!prev[d]) {
+        prev[d] = [];
+      }
+      prev[d].push(cur);
+      return prev;
+    },
+    {} as Record<string, typeof cashouts>,
+  );
   return (
     <div class="px-4 mt-4">
       <div class="sm:flex sm:items-center">
         <div class="sm:flex-auto">
-          <h1 class="text-base font-semibold leading-6 
text-gray-900"><i18n.Translate>Latest cashouts</i18n.Translate></h1>
+          <h1 class="text-base font-semibold leading-6 text-gray-900">
+            <i18n.Translate>Latest cashouts</i18n.Translate>
+          </h1>
         </div>
       </div>
       <div class="-mx-4 mt-5 ring-1 ring-gray-300 sm:mx-0 rounded-lg min-w-fit 
bg-white">
         <table class="min-w-full divide-y divide-gray-300">
           <thead>
             <tr>
-              <th scope="col" class="                     pl-2 py-3.5 
text-left text-sm font-semibold text-gray-900">{i18n.str`Created`}</th>
-              <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 
text-left text-sm font-semibold text-gray-900">{i18n.str`Total debit`}</th>
-              <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 
text-left text-sm font-semibold text-gray-900">{i18n.str`Total credit`}</th>
-              <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 
text-left text-sm font-semibold text-gray-900">{i18n.str`Subject`}</th>
+              <th
+                scope="col"
+                class="                     pl-2 py-3.5 text-left text-sm 
font-semibold text-gray-900"
+              >{i18n.str`Created`}</th>
+              <th
+                scope="col"
+                class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm 
font-semibold text-gray-900"
+              >{i18n.str`Total debit`}</th>
+              <th
+                scope="col"
+                class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm 
font-semibold text-gray-900"
+              >{i18n.str`Total credit`}</th>
+              <th
+                scope="col"
+                class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm 
font-semibold text-gray-900"
+              >{i18n.str`Subject`}</th>
             </tr>
           </thead>
           <tbody>
             {Object.entries(txByDate).map(([date, txs], idx) => {
-              return <Fragment key={idx}>
-                <tr class="border-t border-gray-200">
-                  <th colSpan={6} scope="colgroup" class="bg-gray-50 py-2 pl-4 
pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-3">
-                    {date}
-                  </th>
-                </tr>
-                {txs.map(item => {
-                  const creationTime = item.creation_time.t_s === "never" ? "" 
: format(item.creation_time.t_s * 1000, "HH:mm:ss", { locale: dateLocale })
-                  return (<tr key={idx} class="border-b border-gray-200 
hover:bg-gray-200 last:border-none">
-
-                    <td onClick={(e) => {
-                      e.preventDefault();
-                      onSelected(item.id);
-                    }} class="relative py-2 pl-2 pr-2 text-sm ">
-                      <div class="font-medium 
text-gray-900">{creationTime}</div>
-                      {//FIXME: implement responsive view
-                      }
-                      {/* <dl class="font-normal sm:hidden">
+              return (
+                <Fragment key={idx}>
+                  <tr class="border-t border-gray-200">
+                    <th
+                      colSpan={6}
+                      scope="colgroup"
+                      class="bg-gray-50 py-2 pl-4 pr-3 text-left text-sm 
font-semibold text-gray-900 sm:pl-3"
+                    >
+                      {date}
+                    </th>
+                  </tr>
+                  {txs.map((item) => {
+                    const creationTime =
+                      item.creation_time.t_s === "never"
+                        ? ""
+                        : format(item.creation_time.t_s * 1000, "HH:mm:ss", {
+                            locale: dateLocale,
+                          });
+                    return (
+                      <tr
+                        key={idx}
+                        class="border-b border-gray-200 hover:bg-gray-200 
last:border-none"
+                      >
+                        <a
+                          href={routeCashoutDetails.url({
+                            cid: String(item.id),
+                          })}
+                        >
+                          <td class="relative py-2 pl-2 pr-2 text-sm ">
+                            <div class="font-medium text-gray-900">
+                              {creationTime}
+                            </div>
+                            {
+                              //FIXME: implement responsive view
+                            }
+                            {/* <dl class="font-normal sm:hidden">
                         <dt class="sr-only 
sm:hidden"><i18n.Translate>Amount</i18n.Translate></dt>
                         <dd class="mt-1 truncate text-gray-700">
                           {item.negative ? i18n.str`sent` : 
i18n.str`received`} {item.amount ? (
@@ -127,34 +181,33 @@ export function ReadyView({ cashouts, onSelected }: 
State.Ready): VNode {
                           </pre>
                         </dd>
                       </dl> */}
-                    </td>
-                    <td onClick={(e) => {
-                      e.preventDefault();
-                      onSelected(item.id);
-                    }} class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-red-600 cursor-pointer"><RenderAmount 
value={Amounts.parseOrThrow(item.amount_debit)} 
spec={resp.body.regional_currency_specification} /></td>
-                    <td onClick={(e) => {
-                      e.preventDefault();
-                      onSelected(item.id);
-                    }} class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-green-600 cursor-pointer"><RenderAmount 
value={Amounts.parseOrThrow(item.amount_credit)} 
spec={resp.body.fiat_currency_specification} /></td>
-
-                    <td onClick={(e) => {
-                      e.preventDefault();
-                      onSelected(item.id);
-                    }} class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500 break-all min-w-md">
-                      {item.subject}
-                    </td>
-                  </tr>)
-                })}
-              </Fragment>
+                          </td>
+                          <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-red-600 cursor-pointer">
+                            <RenderAmount
+                              value={Amounts.parseOrThrow(item.amount_debit)}
+                              spec={resp.body.regional_currency_specification}
+                            />
+                          </td>
+                          <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-green-600 cursor-pointer">
+                            <RenderAmount
+                              value={Amounts.parseOrThrow(item.amount_credit)}
+                              spec={resp.body.fiat_currency_specification}
+                            />
+                          </td>
 
+                          <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500 break-all min-w-md">
+                            {item.subject}
+                          </td>
+                        </a>
+                      </tr>
+                    );
+                  })}
+                </Fragment>
+              );
             })}
           </tbody>
-
         </table>
-
-
       </div>
     </div>
   );
-
 }
diff --git a/packages/demobank-ui/src/components/EmptyComponentExample/index.ts 
b/packages/demobank-ui/src/components/EmptyComponentExample/index.ts
index d80e6bdf9..da84f9921 100644
--- a/packages/demobank-ui/src/components/EmptyComponentExample/index.ts
+++ b/packages/demobank-ui/src/components/EmptyComponentExample/index.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
diff --git a/packages/demobank-ui/src/components/EmptyComponentExample/state.ts 
b/packages/demobank-ui/src/components/EmptyComponentExample/state.ts
index e147a7ccf..057664983 100644
--- a/packages/demobank-ui/src/components/EmptyComponentExample/state.ts
+++ b/packages/demobank-ui/src/components/EmptyComponentExample/state.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -17,7 +17,7 @@
 // import { wxApi } from "../../wxApi.js";
 import { Props, State } from "./index.js";
 
-export function useComponentState({ p }: Props): State {
+export function useComponentState({ p: _p }: Props): State {
   return {
     status: "ready",
     error: undefined,
diff --git 
a/packages/demobank-ui/src/components/EmptyComponentExample/stories.tsx 
b/packages/demobank-ui/src/components/EmptyComponentExample/stories.tsx
index 628e97c02..160acdf79 100644
--- a/packages/demobank-ui/src/components/EmptyComponentExample/stories.tsx
+++ b/packages/demobank-ui/src/components/EmptyComponentExample/stories.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
diff --git a/packages/demobank-ui/src/components/EmptyComponentExample/test.ts 
b/packages/demobank-ui/src/components/EmptyComponentExample/test.ts
index eae4d4ca2..629948d91 100644
--- a/packages/demobank-ui/src/components/EmptyComponentExample/test.ts
+++ b/packages/demobank-ui/src/components/EmptyComponentExample/test.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
diff --git 
a/packages/demobank-ui/src/components/EmptyComponentExample/views.tsx 
b/packages/demobank-ui/src/components/EmptyComponentExample/views.tsx
index 5e0a5f0d2..457933a5f 100644
--- a/packages/demobank-ui/src/components/EmptyComponentExample/views.tsx
+++ b/packages/demobank-ui/src/components/EmptyComponentExample/views.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -15,20 +15,11 @@
  */
 
 import { h, VNode } from "preact";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { State } from "./index.js";
 
-export function LoadingUriView({ error }: State.LoadingUriError): VNode {
-  const { i18n } = useTranslationContext();
-
-  return (
-    <div>
-    </div>
-  );
+export function LoadingUriView(): VNode {
+  return <div></div>;
 }
 
-export function ReadyView({ error }: State.Ready): VNode {
-  const { i18n } = useTranslationContext();
-
+export function ReadyView(): VNode {
   return <div />;
 }
diff --git a/packages/demobank-ui/src/components/ErrorLoadingWithDebug.tsx 
b/packages/demobank-ui/src/components/ErrorLoadingWithDebug.tsx
index 25e79e9e0..8679af050 100644
--- a/packages/demobank-ui/src/components/ErrorLoadingWithDebug.tsx
+++ b/packages/demobank-ui/src/components/ErrorLoadingWithDebug.tsx
@@ -1,3 +1,18 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022-2024 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
 import { TalerError } from "@gnu-taler/taler-util";
 import { ErrorLoading } from "@gnu-taler/web-util/browser";
 import { VNode, h } from "preact";
@@ -5,5 +20,5 @@ import { usePreferences } from "../hooks/preferences.js";
 
 export function ErrorLoadingWithDebug({ error }: { error: TalerError }): VNode 
{
   const [pref] = usePreferences();
-  return <ErrorLoading error={error} showDetail={pref.showDebugInfo} />
+  return <ErrorLoading error={error} showDetail={pref.showDebugInfo} />;
 }
diff --git a/packages/demobank-ui/src/components/QR.tsx 
b/packages/demobank-ui/src/components/QR.tsx
index 945a08867..b039bbd1e 100644
--- a/packages/demobank-ui/src/components/QR.tsx
+++ b/packages/demobank-ui/src/components/QR.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
diff --git a/packages/demobank-ui/src/components/Transactions/index.ts 
b/packages/demobank-ui/src/components/Transactions/index.ts
index b6a78deb1..b0e67d4d9 100644
--- a/packages/demobank-ui/src/components/Transactions/index.ts
+++ b/packages/demobank-ui/src/components/Transactions/index.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
diff --git a/packages/demobank-ui/src/components/Transactions/state.ts 
b/packages/demobank-ui/src/components/Transactions/state.ts
index c85fba85b..56eaefb8d 100644
--- a/packages/demobank-ui/src/components/Transactions/state.ts
+++ b/packages/demobank-ui/src/components/Transactions/state.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -14,7 +14,12 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { AbsoluteTime, Amounts, TalerError, parsePaytoUri } from 
"@gnu-taler/taler-util";
+import {
+  AbsoluteTime,
+  Amounts,
+  TalerError,
+  parsePaytoUri,
+} from "@gnu-taler/taler-util";
 import { useTransactions } from "../../hooks/access.js";
 import { Props, State, Transaction } from "./index.js";
 
@@ -33,29 +38,38 @@ export function useComponentState({ account }: Props): 
State {
     };
   }
 
-  const transactions = result.data.type === "fail" ? [] : 
result.data.body.transactions
-    .map((tx) => {
+  const transactions =
+    result.data.type === "fail"
+      ? []
+      : result.data.body.transactions
+          .map((tx) => {
+            const negative = tx.direction === "debit";
+            const cp = parsePaytoUri(
+              negative ? tx.creditor_payto_uri : tx.debtor_payto_uri,
+            );
+            const counterpart =
+              (cp === undefined || !cp.isKnown
+                ? undefined
+                : cp.targetType === "iban"
+                  ? cp.iban
+                  : cp.targetType === "x-taler-bank"
+                    ? cp.account
+                    : cp.targetType === "bitcoin"
+                      ? `${cp.targetPath.substring(0, 6)}...`
+                      : undefined) ?? "unkown";
 
-      const negative = tx.direction === "debit";
-      const cp = parsePaytoUri(negative ? tx.creditor_payto_uri : 
tx.debtor_payto_uri);
-      const counterpart = (cp === undefined || !cp.isKnown ? undefined :
-        cp.targetType === "iban" ? cp.iban :
-          cp.targetType === "x-taler-bank" ? cp.account :
-            cp.targetType === "bitcoin" ? `${cp.targetPath.substring(0, 
6)}...` : undefined) ??
-        "unkown";
-
-      const when = AbsoluteTime.fromProtocolTimestamp(tx.date);
-      const amount = Amounts.parse(tx.amount);
-      const subject = tx.subject;
-      return {
-        negative,
-        counterpart,
-        when,
-        amount,
-        subject,
-      };
-    })
-    .filter((x): x is Transaction => x !== undefined);
+            const when = AbsoluteTime.fromProtocolTimestamp(tx.date);
+            const amount = Amounts.parse(tx.amount);
+            const subject = tx.subject;
+            return {
+              negative,
+              counterpart,
+              when,
+              amount,
+              subject,
+            };
+          })
+          .filter((x): x is Transaction => x !== undefined);
 
   return {
     status: "ready",
diff --git a/packages/demobank-ui/src/components/Transactions/stories.tsx 
b/packages/demobank-ui/src/components/Transactions/stories.tsx
index 17e234cc7..95014574b 100644
--- a/packages/demobank-ui/src/components/Transactions/stories.tsx
+++ b/packages/demobank-ui/src/components/Transactions/stories.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
diff --git a/packages/demobank-ui/src/components/Transactions/test.ts 
b/packages/demobank-ui/src/components/Transactions/test.ts
index ed20b8369..cf33a0b1c 100644
--- a/packages/demobank-ui/src/components/Transactions/test.ts
+++ b/packages/demobank-ui/src/components/Transactions/test.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -19,14 +19,13 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { ErrorType } from "@gnu-taler/web-util/browser";
+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";
-import { HttpStatusCode, TalerError, TalerErrorCode } from 
"@gnu-taler/taler-util";
 
 describe("Transaction states", () => {
   it.skip("should query backend and render transactions", async () => {
@@ -36,7 +35,6 @@ describe("Transaction states", () => {
       account: "myAccount",
     };
 
-    //@ts-ignore
     env.addRequestExpectation(TRANSACTION_API_EXAMPLE.LIST_FIRST_PAGE, {
       response: {
         data: {
@@ -183,10 +181,15 @@ describe("Transaction states", () => {
         },
         ({ status, error }) => {
           expect(status).equals("loading-error");
-          if (error === undefined || 
!error.hasErrorCode(TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED)) {
+          if (
+            error === undefined ||
+            !error.hasErrorCode(TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED)
+          ) {
             throw Error("not the expected error");
           }
-          
expect(error.errorDetail.code).deep.equal(TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED);
+          expect(error.errorDetail.code).deep.equal(
+            TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED,
+          );
         },
       ],
       env.buildTestingContext(),
diff --git a/packages/demobank-ui/src/components/Transactions/views.tsx 
b/packages/demobank-ui/src/components/Transactions/views.tsx
index f3ffcd157..1d63cc2cb 100644
--- a/packages/demobank-ui/src/components/Transactions/views.tsx
+++ b/packages/demobank-ui/src/components/Transactions/views.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -20,99 +20,179 @@ import { Fragment, h, VNode } from "preact";
 import { useBankCoreApiContext } from "../../context/config.js";
 import { RenderAmount } from "../../pages/PaytoWireTransferForm.js";
 import { State } from "./index.js";
+import { privatePages } from "../../Routing.js";
 
-
-export function ReadyView({ transactions, onNext, onPrev }: State.Ready): 
VNode {
+export function ReadyView({
+  transactions,
+  onNext,
+  onPrev,
+}: State.Ready): VNode {
   const { i18n, dateLocale } = useTranslationContext();
   const { config } = useBankCoreApiContext();
-  if (!transactions.length) return <div />
-  const txByDate = transactions.reduce((prev, cur) => {
-    const d = cur.when.t_ms === "never"
-      ? ""
-      : format(cur.when.t_ms, "dd/MM/yyyy", { locale: dateLocale })
-    if (!prev[d]) {
-      prev[d] = []
-    }
-    prev[d].push(cur)
-    return prev
-  }, {} as Record<string, typeof transactions>)
+  if (!transactions.length) return <div />;
+  const txByDate = transactions.reduce(
+    (prev, cur) => {
+      const d =
+        cur.when.t_ms === "never"
+          ? ""
+          : format(cur.when.t_ms, "dd/MM/yyyy", { locale: dateLocale });
+      if (!prev[d]) {
+        prev[d] = [];
+      }
+      prev[d].push(cur);
+      return prev;
+    },
+    {} as Record<string, typeof transactions>,
+  );
   return (
     <div class="px-4 mt-4">
       <div class="sm:flex sm:items-center">
         <div class="sm:flex-auto">
-          <h1 class="text-base font-semibold leading-6 
text-gray-900"><i18n.Translate>Latest transactions</i18n.Translate></h1>
+          <h1 class="text-base font-semibold leading-6 text-gray-900">
+            <i18n.Translate>Latest transactions</i18n.Translate>
+          </h1>
         </div>
       </div>
       <div class="-mx-4 mt-5 ring-1 ring-gray-300 sm:mx-0 rounded-lg min-w-fit 
bg-white">
         <table class="min-w-full divide-y divide-gray-300">
           <thead>
             <tr>
-              <th scope="col" class="pl-2 py-3.5 text-left text-sm 
font-semibold text-gray-900 ">{i18n.str`Date`}</th>
-              <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 
text-left text-sm font-semibold text-gray-900 ">{i18n.str`Amount`}</th>
-              <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 
text-left text-sm font-semibold text-gray-900 ">{i18n.str`Counterpart`}</th>
-              <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 
text-left text-sm font-semibold text-gray-900 ">{i18n.str`Subject`}</th>
+              <th
+                scope="col"
+                class="pl-2 py-3.5 text-left text-sm font-semibold 
text-gray-900 "
+              >{i18n.str`Date`}</th>
+              <th
+                scope="col"
+                class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm 
font-semibold text-gray-900 "
+              >{i18n.str`Amount`}</th>
+              <th
+                scope="col"
+                class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm 
font-semibold text-gray-900 "
+              >{i18n.str`Counterpart`}</th>
+              <th
+                scope="col"
+                class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm 
font-semibold text-gray-900 "
+              >{i18n.str`Subject`}</th>
             </tr>
           </thead>
           <tbody>
             {Object.entries(txByDate).map(([date, txs], idx) => {
-              return <Fragment>
-                <tr class="border-t border-gray-200">
-                  <th colSpan={4} scope="colgroup" class="bg-gray-50 py-2 pl-4 
pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-3">
-                    {date}
-                  </th>
-                </tr>
-                {txs.map(item => {
-                  const time = item.when.t_ms === "never" ? "" : 
format(item.when.t_ms, "HH:mm:ss", { locale: dateLocale })
-                  return (<tr key={idx} class="border-b border-gray-200 
last:border-none">
-                    <td class="relative py-2 pl-2 pr-2 text-sm ">
-                      <div class="font-medium text-gray-900">{time}</div>
-                      <dl class="font-normal sm:hidden">
-                        <dt class="sr-only 
sm:hidden"><i18n.Translate>Amount</i18n.Translate></dt>
-                        <dd class="mt-1 truncate text-gray-700">
-                          {item.negative ? i18n.str`sent` : 
i18n.str`received`} {item.amount ? (
-                            <span data-negative={item.negative ? "true" : 
"false"} class="data-[negative=false]:text-green-600 
data-[negative=true]:text-red-600">
-                              <RenderAmount value={item.amount} 
spec={config.currency_specification} />
-                            </span>
-                          ) : (
-                            <span style={{ color: "grey" 
}}>&lt;{i18n.str`invalid value`}&gt;</span>
-                          )}</dd>
+              return (
+                <Fragment key={idx}>
+                  <tr class="border-t border-gray-200">
+                    <th
+                      colSpan={4}
+                      scope="colgroup"
+                      class="bg-gray-50 py-2 pl-4 pr-3 text-left text-sm 
font-semibold text-gray-900 sm:pl-3"
+                    >
+                      {date}
+                    </th>
+                  </tr>
+                  {txs.map((item) => {
+                    const time =
+                      item.when.t_ms === "never"
+                        ? ""
+                        : format(item.when.t_ms, "HH:mm:ss", {
+                            locale: dateLocale,
+                          });
+                    return (
+                      <tr
+                        key={idx}
+                        class="border-b border-gray-200 last:border-none"
+                      >
+                        <td class="relative py-2 pl-2 pr-2 text-sm ">
+                          <div class="font-medium text-gray-900">{time}</div>
+                          <dl class="font-normal sm:hidden">
+                            <dt class="sr-only sm:hidden">
+                              <i18n.Translate>Amount</i18n.Translate>
+                            </dt>
+                            <dd class="mt-1 truncate text-gray-700">
+                              {item.negative
+                                ? i18n.str`sent`
+                                : i18n.str`received`}{" "}
+                              {item.amount ? (
+                                <span
+                                  data-negative={
+                                    item.negative ? "true" : "false"
+                                  }
+                                  class="data-[negative=false]:text-green-600 
data-[negative=true]:text-red-600"
+                                >
+                                  <RenderAmount
+                                    value={item.amount}
+                                    spec={config.currency_specification}
+                                  />
+                                </span>
+                              ) : (
+                                <span style={{ color: "grey" }}>
+                                  &lt;{i18n.str`invalid value`}&gt;
+                                </span>
+                              )}
+                            </dd>
 
-                        <dt class="sr-only 
sm:hidden"><i18n.Translate>Counterpart</i18n.Translate></dt>
-                        <dd class="mt-1 truncate text-gray-500 sm:hidden">
-                          {item.negative ? i18n.str`to` : i18n.str`from`} <a 
href={`#/wire-transfer/${item.counterpart}`} class="text-indigo-600 
hover:text-indigo-900">
+                            <dt class="sr-only sm:hidden">
+                              <i18n.Translate>Counterpart</i18n.Translate>
+                            </dt>
+                            <dd class="mt-1 truncate text-gray-500 sm:hidden">
+                              {item.negative ? i18n.str`to` : 
i18n.str`from`}{" "}
+                              <a
+                                href={privatePages.wireTranserCreate.url({
+                                  destination: item.counterpart,
+                                })}
+                                class="text-indigo-600 hover:text-indigo-900"
+                              >
+                                {item.counterpart}
+                              </a>
+                            </dd>
+                            <dd class="mt-1 text-gray-500 sm:hidden">
+                              <pre class="break-words w-56 
whitespace-break-spaces p-2 rounded-md mx-auto my-2 bg-gray-100">
+                                {item.subject}
+                              </pre>
+                            </dd>
+                          </dl>
+                        </td>
+                        <td
+                          data-negative={item.negative ? "true" : "false"}
+                          class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500 "
+                        >
+                          {item.amount ? (
+                            <RenderAmount
+                              value={item.amount}
+                              negative={item.negative}
+                              withColor
+                              spec={config.currency_specification}
+                            />
+                          ) : (
+                            <span style={{ color: "grey" }}>
+                              &lt;{i18n.str`invalid value`}&gt;
+                            </span>
+                          )}
+                        </td>
+                        <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500">
+                          <a
+                            href={privatePages.wireTranserCreate.url({
+                              destination: item.counterpart,
+                            })}
+                            class="text-indigo-600 hover:text-indigo-900"
+                          >
                             {item.counterpart}
                           </a>
-                        </dd>
-                        <dd class="mt-1 text-gray-500 sm:hidden" >
-                          <pre class="break-words w-56 whitespace-break-spaces 
p-2 rounded-md mx-auto my-2 bg-gray-100">
-                            {item.subject}
-                          </pre>
-                        </dd>
-                      </dl>
-                    </td>
-                    <td data-negative={item.negative ? "true" : "false"}
-                      class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500 ">
-                      {item.amount ? (<RenderAmount value={item.amount} 
negative={item.negative} withColor spec={config.currency_specification} />
-                      ) : (
-                        <span style={{ color: "grey" }}>&lt;{i18n.str`invalid 
value`}&gt;</span>
-                      )}
-                    </td>
-                    <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500">
-                      <a href={`#/wire-transfer/${item.counterpart}`} 
class="text-indigo-600 hover:text-indigo-900">
-                        {item.counterpart}
-                      </a>
-                    </td>
-                    <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500 break-all min-w-md">{item.subject}</td>
-                  </tr>)
-                })}
-              </Fragment>
-
+                        </td>
+                        <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500 break-all min-w-md">
+                          {item.subject}
+                        </td>
+                      </tr>
+                    );
+                  })}
+                </Fragment>
+              );
             })}
           </tbody>
-
         </table>
 
-        <nav class="flex items-center justify-between border-t border-gray-200 
bg-white px-4 py-3 sm:px-6 rounded-lg" aria-label="Pagination">
+        <nav
+          class="flex items-center justify-between border-t border-gray-200 
bg-white px-4 py-3 sm:px-6 rounded-lg"
+          aria-label="Pagination"
+        >
           <div class="flex flex-1 justify-between sm:justify-end">
             <button
               class="relative disabled:bg-gray-100 disabled:text-gray-500 
inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold 
text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 
focus-visible:outline-offset-0"
diff --git a/packages/demobank-ui/src/components/app.tsx 
b/packages/demobank-ui/src/components/app.tsx
index c3e579810..49508965b 100644
--- a/packages/demobank-ui/src/components/app.tsx
+++ b/packages/demobank-ui/src/components/app.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -17,32 +17,38 @@
 import {
   canonicalizeBaseUrl,
   getGlobalLogLevel,
-  setGlobalLogLevelFromString
+  setGlobalLogLevelFromString,
 } from "@gnu-taler/taler-util";
 import { Loading, TranslationProvider } from "@gnu-taler/web-util/browser";
-import { Fragment, FunctionalComponent, h } from "preact";
+import { FunctionalComponent, 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 { strings, StringsType } from "../i18n/strings.js";
-import { BankUiSettings, fetchSettings } from "../settings.js";
-import { Routing } from "../Routing.js";
-import { BankFrame } from "../pages/BankFrame.js";
-import { useEffect, useState } from "preact/hooks";
 import { SettingsProvider } from "../context/settings.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 = () => {
-  const [settings, setSettings] = useState<BankUiSettings>()
+  const [settings, setSettings] = useState<BankUiSettings>();
   useEffect(() => {
-    fetchSettings(setSettings)
-  }, [])
+    fetchSettings(setSettings);
+  }, []);
   if (!settings) return <Loading />;
 
   const baseUrl = getInitialBackendBaseURL(settings.backendBaseURL);
   return (
     <SettingsProvider value={settings}>
-      <TranslationProvider source={strings} completness={{ "es": 
strings["es"].completeness, "de": strings["de"].completeness }}>
+      <TranslationProvider
+        source={strings}
+        completness={{
+          es: strings["es"].completeness,
+          de: strings["de"].completeness,
+        }}
+      >
         <BackendStateProvider>
           <BankCoreApiProvider baseUrl={baseUrl} frameOnError={BankFrame}>
             <SWRConfig
@@ -63,7 +69,7 @@ const App: FunctionalComponent = () => {
                 refreshWhenHidden: false,
                 refreshWhenOffline: false,
 
-                //ignore errors
+                // ignore errors
                 shouldRetryOnError: false,
                 errorRetryCount: 0,
                 errorRetryInterval: undefined,
@@ -76,13 +82,15 @@ const App: FunctionalComponent = () => {
             </SWRConfig>
           </BankCoreApiProvider>
         </BackendStateProvider>
-      </TranslationProvider >
+      </TranslationProvider>
     </SettingsProvider>
   );
 };
 
-(window as any).setGlobalLogLevelFromString = setGlobalLogLevelFromString;
-(window as any).getGlobalLevel = getGlobalLogLevel;
+// @ts-expect-error creating a new property for window object
+window.setGlobalLogLevelFromString = setGlobalLogLevelFromString;
+// @ts-expect-error creating a new property for window object
+window.getGlobalLevel = getGlobalLogLevel;
 
 function localStorageProvider(): Map<unknown, unknown> {
   const map = new Map(JSON.parse(localStorage.getItem("app-cache") || "[]"));
@@ -96,7 +104,9 @@ function localStorageProvider(): Map<unknown, unknown> {
 
 export default App;
 
-function getInitialBackendBaseURL(backendFromSettings: string | undefined): 
string {
+function getInitialBackendBaseURL(
+  backendFromSettings: string | undefined,
+): string {
   const overrideUrl =
     typeof localStorage !== "undefined"
       ? localStorage.getItem("corebank-api-base-url")
@@ -104,23 +114,23 @@ function getInitialBackendBaseURL(backendFromSettings: 
string | undefined): stri
   let result: string;
 
   if (!overrideUrl) {
-    //normal path
+    // normal path
     if (!backendFromSettings) {
       console.error(
         "ERROR: backendBaseURL was overridden by a setting file and missing. 
Setting value to 'window.origin'",
       );
-      result = window.origin
+      result = window.origin;
     } else {
       result = backendFromSettings;
     }
   } else {
     // testing/development path
-    result = overrideUrl
+    result = overrideUrl;
   }
   try {
-    return canonicalizeBaseUrl(result)
+    return canonicalizeBaseUrl(result);
   } catch (e) {
-    //fall back
-    return canonicalizeBaseUrl(window.origin)
+    // fall back
+    return canonicalizeBaseUrl(window.origin);
   }
-}
\ No newline at end of file
+}
diff --git a/packages/demobank-ui/src/components/index.examples.ts 
b/packages/demobank-ui/src/components/index.examples.ts
index 348d5c653..20e013070 100644
--- a/packages/demobank-ui/src/components/index.examples.ts
+++ b/packages/demobank-ui/src/components/index.examples.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
diff --git a/packages/demobank-ui/src/context/backend.ts 
b/packages/demobank-ui/src/context/backend.ts
index eae187c6d..18b4a1f03 100644
--- a/packages/demobank-ui/src/context/backend.ts
+++ b/packages/demobank-ui/src/context/backend.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -37,7 +37,7 @@ const initial: Type = {
   expired() {
     null;
   },
-  logIn(info) {
+  logIn(_info) {
     null;
   },
 };
diff --git a/packages/demobank-ui/src/context/config.ts 
b/packages/demobank-ui/src/context/config.ts
index 0bf920006..f2e37a15f 100644
--- a/packages/demobank-ui/src/context/config.ts
+++ b/packages/demobank-ui/src/context/config.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -14,13 +14,37 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { AccessToken, HttpStatusCode, LibtoolVersion, OperationAlternative, 
OperationFail, OperationOk, TalerCorebankApi, TalerCoreBankHttpClient, 
TalerError, TalerErrorCode, UserAndToken } from "@gnu-taler/taler-util";
+import {
+  AccessToken,
+  LibtoolVersion,
+  TalerCorebankApi,
+  TalerCoreBankHttpClient,
+  TalerError,
+  UserAndToken,
+} from "@gnu-taler/taler-util";
 import { HttpRequestLibrary } from "@gnu-taler/taler-util/http";
-import { BrowserHttpLib, ErrorLoading, useTranslationContext } from 
"@gnu-taler/web-util/browser";
-import { ComponentChildren, createContext, FunctionComponent, h, VNode } from 
"preact";
+import {
+  BrowserHttpLib,
+  ErrorLoading,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
+import {
+  ComponentChildren,
+  createContext,
+  FunctionComponent,
+  h,
+  VNode,
+} from "preact";
 import { useContext, useEffect, useState } from "preact/hooks";
-import { revalidateAccountDetails, revalidatePublicAccounts, 
revalidateTransactions } from "../hooks/access.js";
-import { revalidateBusinessAccounts, revalidateCashouts } from 
"../hooks/circuit.js";
+import {
+  revalidateAccountDetails,
+  revalidatePublicAccounts,
+  revalidateTransactions,
+} from "../hooks/access.js";
+import {
+  revalidateBusinessAccounts,
+  revalidateCashouts,
+} from "../hooks/circuit.js";
 
 /**
  *
@@ -28,13 +52,15 @@ import { revalidateBusinessAccounts, revalidateCashouts } 
from "../hooks/circuit
  */
 
 export type Type = {
-  url: URL,
-  config: TalerCorebankApi.Config,
-  api: TalerCoreBankHttpClient,
-  hints: VersionHint[]
+  url: URL;
+  config: TalerCorebankApi.Config;
+  api: TalerCoreBankHttpClient;
+  hints: VersionHint[];
 };
 
-const Context = createContext<Type>(undefined as any);
+// FIXME: below
+// @ts-expect-error default value to undefined, should it be another thing?
+const Context = createContext<Type>(undefined);
 
 export const useBankCoreApiContext = (): Type => useContext(Context);
 
@@ -45,36 +71,46 @@ export enum VersionHint {
   CASHOUT_BEFORE_2FA,
 }
 
-export type ConfigResult = undefined
-  | { type: "ok", config: TalerCorebankApi.Config, hints: VersionHint[] }
-  | { type: "incompatible", result: TalerCorebankApi.Config, supported: string 
}
-  | { type: "error", error: TalerError }
+export type ConfigResult =
+  | undefined
+  | { type: "ok"; config: TalerCorebankApi.Config; hints: VersionHint[] }
+  | { type: "incompatible"; result: TalerCorebankApi.Config; supported: string 
}
+  | { type: "error"; error: TalerError };
 
 export const BankCoreApiProvider = ({
   baseUrl,
   children,
   frameOnError,
 }: {
-  baseUrl: string,
+  baseUrl: string;
   children: ComponentChildren;
-  frameOnError: FunctionComponent<{ children: ComponentChildren }>,
+  frameOnError: FunctionComponent<{ children: ComponentChildren }>;
 }): VNode => {
-  const [checked, setChecked] = useState<ConfigResult>()
+  const [checked, setChecked] = useState<ConfigResult>();
   const { i18n } = useTranslationContext();
-  const url = new URL(baseUrl)
-  const api = new CacheAwareApi(url.href, new BrowserHttpLib())
+  const url = new URL(baseUrl);
+  const api = new CacheAwareApi(url.href, new BrowserHttpLib());
   useEffect(() => {
-    api.getConfig()
+    api
+      .getConfig()
       .then((resp) => {
         if (api.isCompatible(resp.body.version)) {
           setChecked({ type: "ok", config: resp.body, hints: [] });
         } else {
-          //this API supports version 3.0.3
-          const compare = LibtoolVersion.compare("3:0:3", resp.body.version)
+          // this API supports version 3.0.3
+          const compare = LibtoolVersion.compare("3:0:3", resp.body.version);
           if (compare?.compatible ?? false) {
-            setChecked({ type: "ok", config: resp.body, hints: 
[VersionHint.CASHOUT_BEFORE_2FA] });
+            setChecked({
+              type: "ok",
+              config: resp.body,
+              hints: [VersionHint.CASHOUT_BEFORE_2FA],
+            });
           } else {
-            setChecked({ type: "incompatible", result: resp.body, supported: 
api.PROTOCOL_VERSION })
+            setChecked({
+              type: "incompatible",
+              result: resp.body,
+              supported: api.PROTOCOL_VERSION,
+            });
           }
         }
       })
@@ -86,17 +122,28 @@ export const BankCoreApiProvider = ({
   }, []);
 
   if (checked === undefined) {
-    return h(frameOnError, { children: h("div", {}, "loading...") })
+    return h(frameOnError, { children: h("div", {}, "loading...") });
   }
   if (checked.type === "error") {
-    return h(frameOnError, { children: h(ErrorLoading, { error: checked.error, 
showDetail: true }) })
+    return h(frameOnError, {
+      children: h(ErrorLoading, { error: checked.error, showDetail: true }),
+    });
   }
   if (checked.type === "incompatible") {
-    return h(frameOnError, { children: h("div", {}, i18n.str`the bank backend 
is not supported. supported version "${checked.supported}", server version 
"${checked.result.version}"`) })
+    return h(frameOnError, {
+      children: h(
+        "div",
+        {},
+        i18n.str`the bank backend is not supported. supported version 
"${checked.supported}", server version "${checked.result.version}"`,
+      ),
+    });
   }
   const value: Type = {
-    url, config: checked.config, api: api, hints: checked.hints,
-  }
+    url,
+    config: checked.config,
+    api: api,
+    hints: checked.hints,
+  };
   return h(Context.Provider, {
     value,
     children,
@@ -105,52 +152,71 @@ export const BankCoreApiProvider = ({
 
 export class CacheAwareApi extends TalerCoreBankHttpClient {
   constructor(baseUrl: string, httpClient?: HttpRequestLibrary) {
-    super(baseUrl, httpClient)
+    super(baseUrl, httpClient);
   }
   async deleteAccount(auth: UserAndToken, cid?: string | undefined) {
-    const resp = await super.deleteAccount(auth, cid)
+    const resp = await super.deleteAccount(auth, cid);
     if (resp.type === "ok") {
-      revalidatePublicAccounts()
-      revalidateBusinessAccounts()
+      revalidatePublicAccounts();
+      revalidateBusinessAccounts();
     }
     return resp;
   }
-  async createAccount(auth: AccessToken, body: 
TalerCorebankApi.RegisterAccountRequest) {
-    const resp = await super.createAccount(auth, body)
+  async createAccount(
+    auth: AccessToken,
+    body: TalerCorebankApi.RegisterAccountRequest,
+  ) {
+    const resp = await super.createAccount(auth, body);
     if (resp.type === "ok") {
-      revalidatePublicAccounts()
-      revalidateBusinessAccounts()
+      revalidatePublicAccounts();
+      revalidateBusinessAccounts();
     }
     return resp;
   }
-  async updateAccount(auth: UserAndToken, body: 
TalerCorebankApi.AccountReconfiguration, cid?: string | undefined) {
-    const resp = await super.updateAccount(auth, body, cid)
+  async updateAccount(
+    auth: UserAndToken,
+    body: TalerCorebankApi.AccountReconfiguration,
+    cid?: string | undefined,
+  ) {
+    const resp = await super.updateAccount(auth, body, cid);
     if (resp.type === "ok") {
-      revalidateAccountDetails()
+      revalidateAccountDetails();
     }
     return resp;
   }
-  async createTransaction(auth: UserAndToken, body: 
TalerCorebankApi.CreateTransactionRequest, cid?: string | undefined) {
-    const resp = await super.createTransaction(auth, body, cid)
+  async createTransaction(
+    auth: UserAndToken,
+    body: TalerCorebankApi.CreateTransactionRequest,
+    cid?: string | undefined,
+  ) {
+    const resp = await super.createTransaction(auth, body, cid);
     if (resp.type === "ok") {
-      revalidateAccountDetails()
-      revalidateTransactions()
+      revalidateAccountDetails();
+      revalidateTransactions();
     }
     return resp;
   }
-  async confirmWithdrawalById(auth: UserAndToken, wid: string, cid?: string | 
undefined) {
-    const resp = await super.confirmWithdrawalById(auth, wid, cid)
+  async confirmWithdrawalById(
+    auth: UserAndToken,
+    wid: string,
+    cid?: string | undefined,
+  ) {
+    const resp = await super.confirmWithdrawalById(auth, wid, cid);
     if (resp.type === "ok") {
-      revalidateAccountDetails()
-      revalidateTransactions()
+      revalidateAccountDetails();
+      revalidateTransactions();
     }
     return resp;
   }
-  async createCashout(auth: UserAndToken, body: 
TalerCorebankApi.CashoutRequest, cid?: string | undefined) {
-    const resp = await super.createCashout(auth, body, cid)
+  async createCashout(
+    auth: UserAndToken,
+    body: TalerCorebankApi.CashoutRequest,
+    cid?: string | undefined,
+  ) {
+    const resp = await super.createCashout(auth, body, cid);
     if (resp.type === "ok") {
-      revalidateAccountDetails()
-      revalidateCashouts()
+      revalidateAccountDetails();
+      revalidateCashouts();
     }
     return resp;
   }
@@ -163,12 +229,13 @@ export const BankCoreApiProviderTesting = ({
 }: {
   children: ComponentChildren;
   state: TalerCorebankApi.Config;
-  url: string,
+  url: string;
 }): VNode => {
   const value: Type = {
     url: new URL(url),
     config: state,
-    api: undefined as any,
+    // @ts-expect-error this API is not being used, not really needed
+    api: undefined,
     hints: [],
   };
 
diff --git a/packages/demobank-ui/src/context/settings.ts 
b/packages/demobank-ui/src/context/settings.ts
index a14c14d15..053fcbd12 100644
--- a/packages/demobank-ui/src/context/settings.ts
+++ b/packages/demobank-ui/src/context/settings.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -34,7 +34,7 @@ export const SettingsProvider = ({
   children,
   value,
 }: {
-  value: BankUiSettings,
+  value: BankUiSettings;
   children: ComponentChildren;
 }): VNode => {
   return h(Context.Provider, {
diff --git a/packages/demobank-ui/src/declaration.d.ts 
b/packages/demobank-ui/src/declaration.d.ts
index c8ba3d576..581cbcd07 100644
--- a/packages/demobank-ui/src/declaration.d.ts
+++ b/packages/demobank-ui/src/declaration.d.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -19,15 +19,15 @@ declare module "*.css" {
   export default mapping;
 }
 declare module "*.svg" {
-  const content: any;
+  const content: string;
   export default content;
 }
 declare module "*.jpeg" {
-  const content: any;
+  const content: string;
   export default content;
 }
 declare module "*.png" {
-  const content: any;
+  const content: string;
   export default content;
 }
 
diff --git a/packages/demobank-ui/src/endpoints.ts 
b/packages/demobank-ui/src/endpoints.ts
index b28c76613..b68a36529 100644
--- a/packages/demobank-ui/src/endpoints.ts
+++ b/packages/demobank-ui/src/endpoints.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
diff --git a/packages/demobank-ui/src/forms/simplest.ts 
b/packages/demobank-ui/src/forms/simplest.ts
deleted file mode 100644
index 38211b681..000000000
--- a/packages/demobank-ui/src/forms/simplest.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import {
-  AbsoluteTime,
-  AmountJson,
-  TranslatedString
-} from "@gnu-taler/taler-util";
-import { DoubleColumnForm, FormState } from "@gnu-taler/web-util/browser";
-
-export namespace Data {
-  export interface WithResolution {
-    when: AbsoluteTime;
-    threshold: AmountJson;
-    state: string;
-  }
-  export interface Form extends WithResolution {
-    comment: string;
-  }
-}
-
-const design: DoubleColumnForm = [
-  {
-    title: "Simple form" as TranslatedString,
-    fields: [
-      {
-        type: "textArea",
-        props: {
-          name: "comment",
-          label: "Comments" as TranslatedString,
-        },
-      },
-    ],
-  },
-  {
-    title: "Resolution" as TranslatedString,
-    description: `Current state is and threshold at ` as TranslatedString,
-    fields: [
-      {
-        type: "absoluteTime",
-        props: {
-          name: "when",
-          label: "Decision Time" as TranslatedString,
-        },
-      },
-      {
-        type: "amount",
-        props: {
-          name: "threshold",
-          label: "New threshold" as TranslatedString,
-        },
-      },
-    ],
-  }
-  ,
-];
-
-function formBehavior(v: Partial<Data.Form>): FormState<Data.Form> {
-  return {
-    when: {
-      disabled: true,
-    },
-    threshold: {
-      // disabled: v.state === AmlExchangeBackend.AmlState.frozen,
-    },
-  };
-}
-
-
diff --git a/packages/demobank-ui/src/hooks/access.ts 
b/packages/demobank-ui/src/hooks/access.ts
index 80ef1874f..85d030245 100644
--- a/packages/demobank-ui/src/hooks/access.ts
+++ b/packages/demobank-ui/src/hooks/access.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -14,7 +14,12 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { AccessToken, TalerCoreBankResultByMethod, TalerHttpError, 
WithdrawalOperationStatus } from "@gnu-taler/taler-util";
+import {
+  AccessToken,
+  TalerCoreBankResultByMethod,
+  TalerHttpError,
+  WithdrawalOperationStatus,
+} from "@gnu-taler/taler-util";
 import { useEffect, useState } from "preact/hooks";
 import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js";
 import { useBackendState } from "./backend.js";
@@ -24,14 +29,17 @@ import _useSWR, { SWRHook, mutate } from "swr";
 import { useBankCoreApiContext } from "../context/config.js";
 const useSWR = _useSWR as unknown as SWRHook;
 
-
 export interface InstanceTemplateFilter {
-  //FIXME: add filter to the template list
+  // FIXME: add filter to the template list
   position?: string;
 }
 
 export function revalidateAccountDetails() {
-  mutate(key => Array.isArray(key) && key[key.length - 1] === "getAccount", 
undefined, { revalidate: true })
+  mutate(
+    (key) => Array.isArray(key) && key[key.length - 1] === "getAccount",
+    undefined,
+    { revalidate: true },
+  );
 }
 
 export function useAccountDetails(account: string) {
@@ -39,32 +47,44 @@ export function useAccountDetails(account: string) {
   const { api } = useBankCoreApiContext();
 
   async function fetcher([username, token]: [string, AccessToken]) {
-    return await api.getAccount({ username, token })
+    return await api.getAccount({ username, token });
   }
-  const token = credentials.status !== "loggedIn" ? undefined : 
credentials.token
-  const { data, error } = useSWR<TalerCoreBankResultByMethod<"getAccount">, 
TalerHttpError>(
-    [account, token, "getAccount"], fetcher, {
-  });
+  const token =
+    credentials.status !== "loggedIn" ? undefined : credentials.token;
+  const { data, error } = useSWR<
+    TalerCoreBankResultByMethod<"getAccount">,
+    TalerHttpError
+  >([account, token, "getAccount"], fetcher, {});
 
-  if (data) return data
+  if (data) return data;
   if (error) return error;
   return undefined;
 }
 
 export function revalidateWithdrawalDetails() {
-  mutate(key => Array.isArray(key) && key[key.length - 1] === 
"getWithdrawalById")
+  mutate(
+    (key) => Array.isArray(key) && key[key.length - 1] === "getWithdrawalById",
+  );
 }
 
 export function useWithdrawalDetails(wid: string) {
   const { api } = useBankCoreApiContext();
-  const [latestStatus, setLatestStatus] = useState<WithdrawalOperationStatus>()
-
-  async function fetcher([wid, old_state]: [string, WithdrawalOperationStatus 
| undefined]) {
-    return await api.getWithdrawalById(wid, old_state === undefined ? 
undefined : { old_state, timeoutMs: 15000 })
+  const [latestStatus, setLatestStatus] = 
useState<WithdrawalOperationStatus>();
+
+  async function fetcher([wid, old_state]: [
+    string,
+    WithdrawalOperationStatus | undefined,
+  ]) {
+    return await api.getWithdrawalById(
+      wid,
+      old_state === undefined ? undefined : { old_state, timeoutMs: 15000 },
+    );
   }
 
-  const { data, error } = 
useSWR<TalerCoreBankResultByMethod<"getWithdrawalById">, TalerHttpError>(
-    [wid, latestStatus, "getWithdrawalById"], fetcher, {
+  const { data, error } = useSWR<
+    TalerCoreBankResultByMethod<"getWithdrawalById">,
+    TalerHttpError
+  >([wid, latestStatus, "getWithdrawalById"], fetcher, {
     refreshInterval: 3000,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
@@ -76,13 +96,14 @@ export function useWithdrawalDetails(wid: string) {
     keepPreviousData: true,
   });
 
-  const currentStatus = data !== undefined && data.type === "ok" ? 
data.body.status : undefined;
+  const currentStatus =
+    data !== undefined && data.type === "ok" ? data.body.status : undefined;
 
   useEffect(() => {
     if (currentStatus !== undefined && currentStatus !== latestStatus) {
-      setLatestStatus(currentStatus)
+      setLatestStatus(currentStatus);
     }
-  }, [currentStatus])
+  }, [currentStatus]);
 
   if (data) return data;
   if (error) return error;
@@ -90,19 +111,28 @@ export function useWithdrawalDetails(wid: string) {
 }
 
 export function revalidateTransactionDetails() {
-  mutate(key => Array.isArray(key) && key[key.length - 1] === 
"getTransactionById")
+  mutate(
+    (key) => Array.isArray(key) && key[key.length - 1] === 
"getTransactionById",
+  );
 }
 export function useTransactionDetails(account: string, tid: number) {
   const { state: credentials } = useBackendState();
-  const token = credentials.status !== "loggedIn" ? undefined : 
credentials.token
+  const token =
+    credentials.status !== "loggedIn" ? undefined : credentials.token;
   const { api } = useBankCoreApiContext();
 
-  async function fetcher([username, token, txid]: [string, AccessToken, 
number]) {
-    return await api.getTransactionById({ username, token }, txid)
+  async function fetcher([username, token, txid]: [
+    string,
+    AccessToken,
+    number,
+  ]) {
+    return await api.getTransactionById({ username, token }, txid);
   }
 
-  const { data, error } = 
useSWR<TalerCoreBankResultByMethod<"getTransactionById">, TalerHttpError>(
-    [account, token, tid, "getTransactionById"], fetcher, {
+  const { data, error } = useSWR<
+    TalerCoreBankResultByMethod<"getTransactionById">,
+    TalerHttpError
+  >([account, token, tid, "getTransactionById"], fetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
@@ -120,22 +150,37 @@ export function useTransactionDetails(account: string, 
tid: number) {
 }
 
 export function revalidatePublicAccounts() {
-  mutate(key => Array.isArray(key) && key[key.length - 1] === 
"getPublicAccounts")
+  mutate(
+    (key) => Array.isArray(key) && key[key.length - 1] === "getPublicAccounts",
+  );
 }
-export function usePublicAccounts(filterAccount: string | undefined, initial?: 
number) {
-  const [offset, setOffset] = useState<number | undefined>(initial);
+export function usePublicAccounts(
+  filterAccount: string | undefined,
+  initial?: number,
+) {
+  // const [offset, setOffset] = useState<number | undefined>(initial);
+  const offset = undefined;
+
   const { api } = useBankCoreApiContext();
 
-  async function fetcher([account, txid]: [string | undefined, number | 
undefined]) {
-    return await api.getPublicAccounts({ account }, {
-      limit: MAX_RESULT_SIZE,
-      offset: txid ? String(txid) : undefined,
-      order: "asc"
-    })
+  async function fetcher([account, txid]: [
+    string | undefined,
+    number | undefined,
+  ]) {
+    return await api.getPublicAccounts(
+      { account },
+      {
+        limit: MAX_RESULT_SIZE,
+        offset: txid ? String(txid) : undefined,
+        order: "asc",
+      },
+    );
   }
 
-  const { data, error } = 
useSWR<TalerCoreBankResultByMethod<"getPublicAccounts">, TalerHttpError>(
-    [filterAccount, offset, "getPublicAccounts"], fetcher, {
+  const { data, error } = useSWR<
+    TalerCoreBankResultByMethod<"getPublicAccounts">,
+    TalerHttpError
+  >([filterAccount, offset, "getPublicAccounts"], fetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
@@ -147,8 +192,7 @@ export function usePublicAccounts(filterAccount: string | 
undefined, initial?: n
     keepPreviousData: true,
   });
 
-  const isLastPage =
-    data && data.body.public_accounts.length < PAGE_SIZE;
+  const isLastPage = data && data.body.public_accounts.length < PAGE_SIZE;
   const isFirstPage = !initial;
 
   const pagination = {
@@ -156,7 +200,7 @@ export function usePublicAccounts(filterAccount: string | 
undefined, initial?: n
     isFirstPage,
     loadMore: () => {
       if (isLastPage || data?.type !== "ok") return;
-      const list = data.body.public_accounts
+      const list = data.body.public_accounts;
       if (list.length < MAX_RESULT_SIZE) {
         // setOffset(list[list.length-1].account_name);
       }
@@ -168,7 +212,7 @@ export function usePublicAccounts(filterAccount: string | 
undefined, initial?: n
 
   // const public_accountslist = data?.type !== "ok" ? [] : 
data.body.public_accounts;
   if (data) {
-    return { ok: true, data: data.body, ...pagination }
+    return { ok: true, data: data.body, ...pagination };
   }
   if (error) {
     return error;
@@ -177,25 +221,39 @@ export function usePublicAccounts(filterAccount: string | 
undefined, initial?: n
 }
 
 export function revalidateTransactions() {
-  mutate(key => Array.isArray(key) && key[key.length - 1] === 
"getTransactions", undefined, { revalidate: true })
+  mutate(
+    (key) => Array.isArray(key) && key[key.length - 1] === "getTransactions",
+    undefined,
+    { revalidate: true },
+  );
 }
 export function useTransactions(account: string, initial?: number) {
   const { state: credentials } = useBackendState();
-  const token = credentials.status !== "loggedIn" ? undefined : 
credentials.token
+  const token =
+    credentials.status !== "loggedIn" ? undefined : credentials.token;
 
   const [offset, setOffset] = useState<number | undefined>(initial);
   const { api } = useBankCoreApiContext();
 
-  async function fetcher([username, token, txid]: [string, AccessToken, number 
| undefined]) {
-    return await api.getTransactions({ username, token }, {
-      limit: MAX_RESULT_SIZE,
-      offset: txid ? String(txid) : undefined,
-      order: "dec"
-    })
+  async function fetcher([username, token, txid]: [
+    string,
+    AccessToken,
+    number | undefined,
+  ]) {
+    return await api.getTransactions(
+      { username, token },
+      {
+        limit: MAX_RESULT_SIZE,
+        offset: txid ? String(txid) : undefined,
+        order: "dec",
+      },
+    );
   }
 
-  const { data, error } = 
useSWR<TalerCoreBankResultByMethod<"getTransactions">, TalerHttpError>(
-    [account, token, offset, "getTransactions"], fetcher, {
+  const { data, error } = useSWR<
+    TalerCoreBankResultByMethod<"getTransactions">,
+    TalerHttpError
+  >([account, token, offset, "getTransactions"], fetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
     refreshWhenOffline: false,
@@ -203,8 +261,7 @@ export function useTransactions(account: string, initial?: 
number) {
     revalidateIfStale: false,
     revalidateOnFocus: false,
     revalidateOnReconnect: false,
-  }
-  );
+  });
 
   const isLastPage =
     data && data.type === "ok" && data.body.transactions.length < PAGE_SIZE;
@@ -215,7 +272,7 @@ export function useTransactions(account: string, initial?: 
number) {
     isFirstPage,
     loadMore: () => {
       if (isLastPage || data?.type !== "ok") return;
-      const list = data.body.transactions
+      const list = data.body.transactions;
       if (list.length < MAX_RESULT_SIZE) {
         setOffset(list[list.length - 1].row_id);
       }
@@ -226,7 +283,7 @@ export function useTransactions(account: string, initial?: 
number) {
   };
 
   if (data) {
-    return { ok: true, data, ...pagination }
+    return { ok: true, data, ...pagination };
   }
   if (error) {
     return error;
diff --git a/packages/demobank-ui/src/hooks/async.ts 
b/packages/demobank-ui/src/hooks/async.ts
index b968cfb84..556673992 100644
--- a/packages/demobank-ui/src/hooks/async.ts
+++ b/packages/demobank-ui/src/hooks/async.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -19,14 +19,13 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 import { useState } from "preact/hooks";
-// import { cancelPendingRequest } from "./backend";
 
 export interface Options {
   slowTolerance: number;
 }
 
 export interface AsyncOperationApi<T> {
-  request: (...a: any) => void;
+  request: (...a: Array<unknown>) => void;
   cancel: () => void;
   data: T | undefined;
   isSlow: boolean;
@@ -35,15 +34,15 @@ export interface AsyncOperationApi<T> {
 }
 
 export function useAsync<T>(
-  fn?: (...args: any) => Promise<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<any>(undefined);
+  const [error, setError] = useState<string | undefined>(undefined);
   const [isSlow, setSlow] = useState(false);
 
-  const request = async (...args: any) => {
+  const request = async (...args: Array<unknown>) => {
     if (!fn) return;
     setLoading(true);
     const handler = setTimeout(() => {
@@ -54,7 +53,11 @@ export function useAsync<T>(
       const result = await fn(...args);
       setData(result);
     } catch (error) {
-      setError(error);
+      if (error instanceof Error) {
+        setError(error.message);
+      } else {
+        setError(`Unknown error: ${error}`);
+      }
     }
     setLoading(false);
     setSlow(false);
diff --git a/packages/demobank-ui/src/hooks/backend.ts 
b/packages/demobank-ui/src/hooks/backend.ts
index 46918ac10..8e9af533b 100644
--- a/packages/demobank-ui/src/hooks/backend.ts
+++ b/packages/demobank-ui/src/hooks/backend.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -21,12 +21,9 @@ import {
   buildCodecForUnion,
   codecForBoolean,
   codecForConstString,
-  codecForString
+  codecForString,
 } from "@gnu-taler/taler-util";
-import {
-  buildStorageKey,
-  useLocalStorage
-} from "@gnu-taler/web-util/browser";
+import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser";
 import { mutate } from "swr";
 
 /**
@@ -86,13 +83,10 @@ export interface BackendStateHandler {
   state: BackendState;
   logOut(): void;
   expired(): void;
-  logIn(info: { username: string, token: AccessToken }): void;
+  logIn(info: { username: string; token: AccessToken }): void;
 }
 
-const BACKEND_STATE_KEY = buildStorageKey(
-  "bank-state",
-  codecForBackendState(),
-);
+const BACKEND_STATE_KEY = buildStorageKey("bank-state", 
codecForBackendState());
 
 /**
  * Return getters and setters for
@@ -120,18 +114,18 @@ export function useBackendState(): BackendStateHandler {
       update(nextState);
     },
     logIn(info) {
-      //admin is defined by the username
+      // admin is defined by the username
       const nextState: BackendState = {
         status: "loggedIn",
         ...info,
         isUserAdministrator: info.username === "admin",
       };
       update(nextState);
-      cleanAllCache()
+      cleanAllCache();
     },
   };
 }
 
 function cleanAllCache(): void {
-  mutate(() => true, undefined, { revalidate: false })
+  mutate(() => true, undefined, { revalidate: false });
 }
diff --git a/packages/demobank-ui/src/hooks/bank-state.ts 
b/packages/demobank-ui/src/hooks/bank-state.ts
index 99d835c9c..87f7a49f0 100644
--- a/packages/demobank-ui/src/hooks/bank-state.ts
+++ b/packages/demobank-ui/src/hooks/bank-state.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -25,32 +25,44 @@ import {
   codecForConstString,
   codecForString,
   codecForTanTransmission,
-  codecOptional
+  codecOptional,
 } from "@gnu-taler/taler-util";
 import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser";
 
 export type ChallengeInProgess =
-  DeleteAccountChallenge |
-  UpdateAccountChallenge |
-  UpdatePasswordChallenge |
-  CreateTransactionChallenge |
-  ConfirmWithdrawalChallenge |
-  CashoutChallenge;
+  | DeleteAccountChallenge
+  | UpdateAccountChallenge
+  | UpdatePasswordChallenge
+  | CreateTransactionChallenge
+  | ConfirmWithdrawalChallenge
+  | CashoutChallenge;
 
 type BaseChallenge<OpType extends string, ReqType> = {
-  id: string,
-  operation: OpType,
-  sent: AbsoluteTime,
-  info?: TalerCorebankApi.TanTransmission,
-  request: ReqType
-}
+  id: string;
+  operation: OpType;
+  sent: AbsoluteTime;
+  info?: TalerCorebankApi.TanTransmission;
+  request: ReqType;
+};
 
-type DeleteAccountChallenge = BaseChallenge<"delete-account", string>
-type UpdateAccountChallenge = BaseChallenge<"update-account", 
TalerCorebankApi.AccountReconfiguration>
-type UpdatePasswordChallenge = BaseChallenge<"update-password", 
TalerCorebankApi.AccountPasswordChange>
-type CreateTransactionChallenge = BaseChallenge<"create-transaction", 
TalerCorebankApi.CreateTransactionRequest>
-type ConfirmWithdrawalChallenge = BaseChallenge<"confirm-withdrawal", string>
-type CashoutChallenge = BaseChallenge<"create-cashout", 
TalerCorebankApi.CashoutRequest>
+type DeleteAccountChallenge = BaseChallenge<"delete-account", string>;
+type UpdateAccountChallenge = BaseChallenge<
+  "update-account",
+  TalerCorebankApi.AccountReconfiguration
+>;
+type UpdatePasswordChallenge = BaseChallenge<
+  "update-password",
+  TalerCorebankApi.AccountPasswordChange
+>;
+type CreateTransactionChallenge = BaseChallenge<
+  "create-transaction",
+  TalerCorebankApi.CreateTransactionRequest
+>;
+type ConfirmWithdrawalChallenge = BaseChallenge<"confirm-withdrawal", string>;
+type CashoutChallenge = BaseChallenge<
+  "create-cashout",
+  TalerCorebankApi.CashoutRequest
+>;
 
 const codecForChallengeUpdatePassword = (): Codec<UpdatePasswordChallenge> =>
   buildCodecForObject<UpdatePasswordChallenge>()
@@ -79,23 +91,25 @@ const codecForChallengeUpdateAccount = (): 
Codec<UpdateAccountChallenge> =>
     .property("request", codecForAny())
     .build("UpdateAccountChallenge");
 
-const codecForChallengeCreateTransaction = (): 
Codec<CreateTransactionChallenge> =>
-  buildCodecForObject<CreateTransactionChallenge>()
-    .property("operation", codecForConstString("create-transaction"))
-    .property("id", codecForString())
-    .property("sent", codecForAbsoluteTime)
-    .property("info", codecOptional(codecForTanTransmission()))
-    .property("request", codecForAny())
-    .build("CreateTransactionChallenge");
-
-const codecForChallengeConfirmWithdrawal = (): 
Codec<ConfirmWithdrawalChallenge> =>
-  buildCodecForObject<ConfirmWithdrawalChallenge>()
-    .property("operation", codecForConstString("confirm-withdrawal"))
-    .property("id", codecForString())
-    .property("sent", codecForAbsoluteTime)
-    .property("info", codecOptional(codecForTanTransmission()))
-    .property("request", codecForString())
-    .build("ConfirmWithdrawalChallenge");
+const codecForChallengeCreateTransaction =
+  (): Codec<CreateTransactionChallenge> =>
+    buildCodecForObject<CreateTransactionChallenge>()
+      .property("operation", codecForConstString("create-transaction"))
+      .property("id", codecForString())
+      .property("sent", codecForAbsoluteTime)
+      .property("info", codecOptional(codecForTanTransmission()))
+      .property("request", codecForAny())
+      .build("CreateTransactionChallenge");
+
+const codecForChallengeConfirmWithdrawal =
+  (): Codec<ConfirmWithdrawalChallenge> =>
+    buildCodecForObject<ConfirmWithdrawalChallenge>()
+      .property("operation", codecForConstString("confirm-withdrawal"))
+      .property("id", codecForString())
+      .property("sent", codecForAbsoluteTime)
+      .property("info", codecOptional(codecForTanTransmission()))
+      .property("request", codecForString())
+      .build("ConfirmWithdrawalChallenge");
 
 const codecForChallengeCashout = (): Codec<CashoutChallenge> =>
   buildCodecForObject<CashoutChallenge>()
@@ -117,7 +131,6 @@ const codecForChallenge = (): Codec<ChallengeInProgess> =>
     .alternative("update-password", codecForChallengeUpdatePassword())
     .build("ChallengeInProgess");
 
-
 interface BankState {
   currentWithdrawalOperationId: string | undefined;
   currentChallenge: ChallengeInProgess | undefined;
@@ -134,27 +147,21 @@ const defaultBankState: BankState = {
   currentChallenge: undefined,
 };
 
-const BANK_STATE_KEY = buildStorageKey(
-  "bank-app-state",
-  codecForBankState(),
-);
+const BANK_STATE_KEY = buildStorageKey("bank-app-state", codecForBankState());
 
 export function useBankState(): [
   Readonly<BankState>,
   <T extends keyof BankState>(key: T, value: BankState[T]) => void,
   () => void,
 ] {
-  const { value, update } = useLocalStorage(
-    BANK_STATE_KEY,
-    defaultBankState,
-  );
+  const { value, update } = useLocalStorage(BANK_STATE_KEY, defaultBankState);
 
   function updateField<T extends keyof BankState>(k: T, v: BankState[T]) {
     const newValue = { ...value, [k]: v };
     update(newValue);
   }
   function reset() {
-    update(defaultBankState)
+    update(defaultBankState);
   }
   return [value, updateField, reset];
 }
diff --git a/packages/demobank-ui/src/hooks/circuit.ts 
b/packages/demobank-ui/src/hooks/circuit.ts
index 8bff6858d..0c306259b 100644
--- a/packages/demobank-ui/src/hooks/circuit.ts
+++ b/packages/demobank-ui/src/hooks/circuit.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -14,15 +14,24 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { useState } from "preact/hooks";
 import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js";
 import { useBackendState } from "./backend.js";
 
-import { AccessToken, AmountJson, AmountString, Amounts, OperationOk, 
TalerBankConversionResultByMethod, TalerCoreBankErrorsByMethod, 
TalerCoreBankResultByMethod, TalerCorebankApi, TalerError, TalerHttpError, 
opFixedSuccess } from "@gnu-taler/taler-util";
+import {
+  AccessToken,
+  AmountJson,
+  Amounts,
+  OperationOk,
+  TalerBankConversionResultByMethod,
+  TalerCoreBankErrorsByMethod,
+  TalerCoreBankResultByMethod,
+  TalerCorebankApi,
+  TalerError,
+  TalerHttpError,
+  opFixedSuccess,
+} from "@gnu-taler/taler-util";
 import _useSWR, { SWRHook, mutate } from "swr";
 import { useBankCoreApiContext } from "../context/config.js";
-import { assertUnreachable } from "../pages/WithdrawalOperationPage.js";
-import { format, getDate, getDay, getHours, getMonth, getYear, set, sub } from 
"date-fns";
 
 // FIX default import https://github.com/microsoft/TypeScript/issues/49189
 const useSWR = _useSWR as unknown as SWRHook;
@@ -43,16 +52,21 @@ type CashoutEstimators = {
 };
 
 export function revalidateConversionInfo() {
-  mutate(key => Array.isArray(key) && key[key.length - 1] === 
"getConversionInfoAPI")
+  mutate(
+    (key) =>
+      Array.isArray(key) && key[key.length - 1] === "getConversionInfoAPI",
+  );
 }
 export function useConversionInfo() {
-  const { api, config } = useBankCoreApiContext()
+  const { api, config } = useBankCoreApiContext();
 
   async function fetcher() {
-    return await api.getConversionInfoAPI().getConfig()
+    return await api.getConversionInfoAPI().getConfig();
   }
-  const { data, error } = 
useSWR<TalerBankConversionResultByMethod<"getConfig">, TalerHttpError>(
-    !config.allow_conversion ? undefined : ["getConversionInfoAPI"], fetcher, {
+  const { data, error } = useSWR<
+    TalerBankConversionResultByMethod<"getConfig">,
+    TalerHttpError
+  >(!config.allow_conversion ? undefined : ["getConversionInfoAPI"], fetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
@@ -64,25 +78,23 @@ export function useConversionInfo() {
     keepPreviousData: true,
   });
 
-  if (data) return data
+  if (data) return data;
   if (error) return error;
   return undefined;
-
 }
 
 export function useEstimator(): CashoutEstimators {
-  const { state } = useBackendState();
   const { api } = useBankCoreApiContext();
   return {
     estimateByCredit: async (fiatAmount, fee) => {
       const resp = await api.getConversionInfoAPI().getCashoutRate({
-        credit: fiatAmount
+        credit: fiatAmount,
       });
       if (resp.type === "fail") {
         // can't happen
         // not-supported: it should not be able to call this function
         // wrong-calculation: we are using just one parameter
-        throw TalerError.fromDetail(resp.detail.code, {}, resp.detail.hint)
+        throw TalerError.fromDetail(resp.detail.code, {}, resp.detail.hint);
       }
       const credit = Amounts.parseOrThrow(resp.body.amount_credit);
       const debit = Amounts.parseOrThrow(resp.body.amount_debit);
@@ -96,13 +108,13 @@ export function useEstimator(): CashoutEstimators {
     },
     estimateByDebit: async (regionalAmount, fee) => {
       const resp = await api.getConversionInfoAPI().getCashoutRate({
-        debit: regionalAmount
+        debit: regionalAmount,
       });
       if (resp.type === "fail") {
         // can't happen
         // not-supported: it should not be able to call this function
         // wrong-calculation: we are using just one parameter
-        throw TalerError.fromDetail(resp.detail.code, {}, resp.detail.hint)
+        throw TalerError.fromDetail(resp.detail.code, {}, resp.detail.hint);
       }
       const credit = Amounts.parseOrThrow(resp.body.amount_credit);
       const debit = Amounts.parseOrThrow(resp.body.amount_debit);
@@ -118,26 +130,34 @@ export function useEstimator(): CashoutEstimators {
 }
 
 export function revalidateBusinessAccounts() {
-  mutate(key => Array.isArray(key) && key[key.length - 1] === "getAccounts")
+  mutate((key) => Array.isArray(key) && key[key.length - 1] === "getAccounts");
 }
 export function useBusinessAccounts() {
   const { state: credentials } = useBackendState();
-  const token = credentials.status !== "loggedIn" ? undefined : 
credentials.token
+  const token =
+    credentials.status !== "loggedIn" ? undefined : credentials.token;
   const { api } = useBankCoreApiContext();
 
-  const [offset, setOffset] = useState<string | undefined>();
+  // const [offset, setOffset] = useState<string | undefined>();
+  const offset = undefined;
 
   function fetcher([token, offset]: [AccessToken, string]) {
-    //FIXME: add account name filter
-    return api.getAccounts(token, {}, {
-      limit: MAX_RESULT_SIZE,
-      offset,
-      order: "asc"
-    })
+    // FIXME: add account name filter
+    return api.getAccounts(
+      token,
+      {},
+      {
+        limit: MAX_RESULT_SIZE,
+        offset,
+        order: "asc",
+      },
+    );
   }
 
-  const { data, error } = useSWR<TalerCoreBankResultByMethod<"getAccounts">, 
TalerHttpError>(
-    [token, offset, "getAccounts"], fetcher, {
+  const { data, error } = useSWR<
+    TalerCoreBankResultByMethod<"getAccounts">,
+    TalerHttpError
+  >([token, offset, "getAccounts"], fetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
@@ -147,8 +167,7 @@ export function useBusinessAccounts() {
     errorRetryInterval: 1,
     shouldRetryOnError: false,
     keepPreviousData: true,
-  },
-  );
+  });
 
   const isLastPage =
     data && data.type === "ok" && data.body.accounts.length < PAGE_SIZE;
@@ -159,10 +178,9 @@ export function useBusinessAccounts() {
     isFirstPage,
     loadMore: () => {
       if (isLastPage || data?.type !== "ok") return;
-      const list = data.body.accounts
+      const list = data.body.accounts;
       if (list.length < MAX_RESULT_SIZE) {
-        //FIXME: define pagination
-
+        // FIXME: define pagination
         // setOffset(list[list.length - 1].row_id);
       }
     },
@@ -176,44 +194,66 @@ export function useBusinessAccounts() {
   return undefined;
 }
 
-type CashoutWithId = TalerCorebankApi.CashoutStatusResponse & { id: number }
+type CashoutWithId = TalerCorebankApi.CashoutStatusResponse & { id: number };
 function notUndefined(c: CashoutWithId | undefined): c is CashoutWithId {
-  return c !== undefined
+  return c !== undefined;
 }
 export function revalidateOnePendingCashouts() {
-  mutate(key => Array.isArray(key) && key[key.length - 1] === 
"useOnePendingCashouts")
+  mutate(
+    (key) =>
+      Array.isArray(key) && key[key.length - 1] === "useOnePendingCashouts",
+  );
 }
 export function useOnePendingCashouts(account: string) {
   const { state: credentials } = useBackendState();
   const { api, config } = useBankCoreApiContext();
-  const token = credentials.status !== "loggedIn" ? undefined : 
credentials.token
+  const token =
+    credentials.status !== "loggedIn" ? undefined : credentials.token;
 
   async function fetcher([username, token]: [string, AccessToken]) {
-    const list = await api.getAccountCashouts({ username, token })
+    const list = await api.getAccountCashouts({ username, token });
     if (list.type !== "ok") {
       return list;
     }
-    const pendingCashout = list.body.cashouts.find(c => c.status === "pending")
-    if (!pendingCashout) return opFixedSuccess(undefined)
-    const cashoutInfo = await api.getCashoutById({ username, token }, 
pendingCashout?.cashout_id)
+    const pendingCashout = list.body.cashouts.find(
+      (c) => c.status === "pending",
+    );
+    if (!pendingCashout) return opFixedSuccess(undefined);
+    const cashoutInfo = await api.getCashoutById(
+      { username, token },
+      pendingCashout?.cashout_id,
+    );
     if (cashoutInfo.type !== "ok") {
       return cashoutInfo;
     }
-    return opFixedSuccess({ ...cashoutInfo.body, id: pendingCashout.cashout_id 
})
+    return opFixedSuccess({
+      ...cashoutInfo.body,
+      id: pendingCashout.cashout_id,
+    });
   }
 
-  const { data, error } = useSWR<OperationOk<CashoutWithId | undefined> | 
TalerCoreBankErrorsByMethod<"getAccountCashouts"> | 
TalerCoreBankErrorsByMethod<"getCashoutById">, TalerHttpError>(
-    !config.allow_conversion ? undefined : [account, token, 
"useOnePendingCashouts"], fetcher, {
-    refreshInterval: 0,
-    refreshWhenHidden: false,
-    revalidateOnFocus: false,
-    revalidateOnReconnect: false,
-    refreshWhenOffline: false,
-    errorRetryCount: 0,
-    errorRetryInterval: 1,
-    shouldRetryOnError: false,
-    keepPreviousData: true,
-  });
+  const { data, error } = useSWR<
+    | OperationOk<CashoutWithId | undefined>
+    | TalerCoreBankErrorsByMethod<"getAccountCashouts">
+    | TalerCoreBankErrorsByMethod<"getCashoutById">,
+    TalerHttpError
+  >(
+    !config.allow_conversion
+      ? undefined
+      : [account, token, "useOnePendingCashouts"],
+    fetcher,
+    {
+      refreshInterval: 0,
+      refreshWhenHidden: false,
+      revalidateOnFocus: false,
+      revalidateOnReconnect: false,
+      refreshWhenOffline: false,
+      errorRetryCount: 0,
+      errorRetryInterval: 1,
+      shouldRetryOnError: false,
+      keepPreviousData: true,
+    },
+  );
 
   if (data) return data;
   if (error) return error;
@@ -221,42 +261,54 @@ export function useOnePendingCashouts(account: string) {
 }
 
 export function revalidateCashouts() {
-  mutate(key => Array.isArray(key) && key[key.length - 1] === "useCashouts")
+  mutate((key) => Array.isArray(key) && key[key.length - 1] === "useCashouts");
 }
 export function useCashouts(account: string) {
   const { state: credentials } = useBackendState();
   const { api, config } = useBankCoreApiContext();
-  const token = credentials.status !== "loggedIn" ? undefined : 
credentials.token
+  const token =
+    credentials.status !== "loggedIn" ? undefined : credentials.token;
 
   async function fetcher([username, token]: [string, AccessToken]) {
-    const list = await api.getAccountCashouts({ username, token })
+    const list = await api.getAccountCashouts({ username, token });
     if (list.type !== "ok") {
       return list;
     }
-    const all: Array<CashoutWithId | undefined> = await 
Promise.all(list.body.cashouts.map(c => {
-      return api.getCashoutById({ username, token }, c.cashout_id).then(r => {
-        if (r.type === "fail") {
-          return undefined
-        }
-        return { ...r.body, id: c.cashout_id }
-      })
-    }))
-    const cashouts = all.filter(notUndefined)
-    return { type: "ok" as const, body: { cashouts } }
+    const all: Array<CashoutWithId | undefined> = await Promise.all(
+      list.body.cashouts.map((c) => {
+        return api
+          .getCashoutById({ username, token }, c.cashout_id)
+          .then((r) => {
+            if (r.type === "fail") {
+              return undefined;
+            }
+            return { ...r.body, id: c.cashout_id };
+          });
+      }),
+    );
+    const cashouts = all.filter(notUndefined);
+    return { type: "ok" as const, body: { cashouts } };
   }
 
-  const { data, error } = useSWR<OperationOk<{ cashouts: CashoutWithId[] }> | 
TalerCoreBankErrorsByMethod<"getAccountCashouts">, TalerHttpError>(
-    !config.allow_conversion ? undefined : [account, token, "useCashouts"], 
fetcher, {
-    refreshInterval: 0,
-    refreshWhenHidden: false,
-    revalidateOnFocus: false,
-    revalidateOnReconnect: false,
-    refreshWhenOffline: false,
-    errorRetryCount: 0,
-    errorRetryInterval: 1,
-    shouldRetryOnError: false,
-    keepPreviousData: true,
-  });
+  const { data, error } = useSWR<
+    | OperationOk<{ cashouts: CashoutWithId[] }>
+    | TalerCoreBankErrorsByMethod<"getAccountCashouts">,
+    TalerHttpError
+  >(
+    !config.allow_conversion ? undefined : [account, token, "useCashouts"],
+    fetcher,
+    {
+      refreshInterval: 0,
+      refreshWhenHidden: false,
+      revalidateOnFocus: false,
+      revalidateOnReconnect: false,
+      refreshWhenOffline: false,
+      errorRetryCount: 0,
+      errorRetryInterval: 1,
+      shouldRetryOnError: false,
+      keepPreviousData: true,
+    },
+  );
 
   if (data) return data;
   if (error) return error;
@@ -264,72 +316,98 @@ export function useCashouts(account: string) {
 }
 
 export function revalidateCashoutDetails() {
-  mutate(key => Array.isArray(key) && key[key.length - 1] === "getCashoutById")
+  mutate(
+    (key) => Array.isArray(key) && key[key.length - 1] === "getCashoutById",
+  );
 }
 export function useCashoutDetails(cashoutId: number | undefined) {
   const { state: credentials } = useBackendState();
-  const creds = credentials.status !== "loggedIn" ? undefined : credentials
+  const creds = credentials.status !== "loggedIn" ? undefined : credentials;
   const { api } = useBankCoreApiContext();
 
   async function fetcher([username, token, id]: [string, AccessToken, number]) 
{
-    return api.getCashoutById({ username, token }, id)
+    return api.getCashoutById({ username, token }, id);
   }
 
-  const { data, error } = 
useSWR<TalerCoreBankResultByMethod<"getCashoutById">, TalerHttpError>(
-    cashoutId === undefined ? undefined : [creds?.username, creds?.token, 
cashoutId, "getCashoutById"], fetcher, {
-    refreshInterval: 0,
-    refreshWhenHidden: false,
-    revalidateOnFocus: false,
-    revalidateOnReconnect: false,
-    refreshWhenOffline: false,
-    errorRetryCount: 0,
-    errorRetryInterval: 1,
-    shouldRetryOnError: false,
-    keepPreviousData: true,
-  });
+  const { data, error } = useSWR<
+    TalerCoreBankResultByMethod<"getCashoutById">,
+    TalerHttpError
+  >(
+    cashoutId === undefined
+      ? undefined
+      : [creds?.username, creds?.token, cashoutId, "getCashoutById"],
+    fetcher,
+    {
+      refreshInterval: 0,
+      refreshWhenHidden: false,
+      revalidateOnFocus: false,
+      revalidateOnReconnect: false,
+      refreshWhenOffline: false,
+      errorRetryCount: 0,
+      errorRetryInterval: 1,
+      shouldRetryOnError: false,
+      keepPreviousData: true,
+    },
+  );
 
   if (data) return data;
   if (error) return error;
   return undefined;
 }
 export type MonitorMetrics = {
-  lastHour: TalerCoreBankResultByMethod<"getMonitor">,
-  lastDay: TalerCoreBankResultByMethod<"getMonitor">,
-  lastMonth: TalerCoreBankResultByMethod<"getMonitor">,
-}
+  lastHour: TalerCoreBankResultByMethod<"getMonitor">;
+  lastDay: TalerCoreBankResultByMethod<"getMonitor">;
+  lastMonth: TalerCoreBankResultByMethod<"getMonitor">;
+};
 
-export type LastMonitor = { current: 
TalerCoreBankResultByMethod<"getMonitor">, previous: 
TalerCoreBankResultByMethod<"getMonitor"> }
+export type LastMonitor = {
+  current: TalerCoreBankResultByMethod<"getMonitor">;
+  previous: TalerCoreBankResultByMethod<"getMonitor">;
+};
 export function revalidateLastMonitorInfo() {
-  mutate(key => Array.isArray(key) && key[key.length - 1] === 
"useLastMonitorInfo")
+  mutate(
+    (key) => Array.isArray(key) && key[key.length - 1] === 
"useLastMonitorInfo",
+  );
 }
-export function useLastMonitorInfo(currentMoment: number, previousMoment: 
number, timeframe: TalerCorebankApi.MonitorTimeframeParam) {
-  const { api, config } = useBankCoreApiContext();
+export function useLastMonitorInfo(
+  currentMoment: number,
+  previousMoment: number,
+  timeframe: TalerCorebankApi.MonitorTimeframeParam,
+) {
+  const { api } = useBankCoreApiContext();
   const { state: credentials } = useBackendState();
-  const token = credentials.status !== "loggedIn" ? undefined : 
credentials.token
+  const token =
+    credentials.status !== "loggedIn" ? undefined : credentials.token;
 
-  async function fetcher([token, timeframe]: [AccessToken, 
TalerCorebankApi.MonitorTimeframeParam]) {
+  async function fetcher([token, timeframe]: [
+    AccessToken,
+    TalerCorebankApi.MonitorTimeframeParam,
+  ]) {
     const [current, previous] = await Promise.all([
       api.getMonitor(token, { timeframe, which: currentMoment }),
       api.getMonitor(token, { timeframe, which: previousMoment }),
-    ])
+    ]);
     return {
       current,
       previous,
-    }
+    };
   }
 
   const { data, error } = useSWR<LastMonitor, TalerHttpError>(
-    !token ? undefined : [token, timeframe, "useLastMonitorInfo"], fetcher, {
-    refreshInterval: 0,
-    refreshWhenHidden: false,
-    revalidateOnFocus: false,
-    revalidateOnReconnect: false,
-    refreshWhenOffline: false,
-    errorRetryCount: 0,
-    errorRetryInterval: 1,
-    shouldRetryOnError: false,
-    keepPreviousData: true,
-  });
+    !token ? undefined : [token, timeframe, "useLastMonitorInfo"],
+    fetcher,
+    {
+      refreshInterval: 0,
+      refreshWhenHidden: false,
+      revalidateOnFocus: false,
+      revalidateOnReconnect: false,
+      refreshWhenOffline: false,
+      errorRetryCount: 0,
+      errorRetryInterval: 1,
+      shouldRetryOnError: false,
+      keepPreviousData: true,
+    },
+  );
 
   if (data) return data;
   if (error) return error;
diff --git a/packages/demobank-ui/src/hooks/index.ts 
b/packages/demobank-ui/src/hooks/index.ts
index e9c68812c..2620f4697 100644
--- a/packages/demobank-ui/src/hooks/index.ts
+++ b/packages/demobank-ui/src/hooks/index.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -26,7 +26,7 @@ export type ValueOrFunction<T> = T | ((p: T) => T);
 
 const calculateRootPath = () => {
   const rootPath =
-    typeof window !== undefined
+    typeof window !== "undefined"
       ? window.location.origin + window.location.pathname
       : "/";
   return rootPath;
diff --git a/packages/demobank-ui/src/hooks/preferences.ts 
b/packages/demobank-ui/src/hooks/preferences.ts
index d303ac0d8..454d840b2 100644
--- a/packages/demobank-ui/src/hooks/preferences.ts
+++ b/packages/demobank-ui/src/hooks/preferences.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -19,9 +19,13 @@ import {
   TranslatedString,
   buildCodecForObject,
   codecForBoolean,
-  codecForNumber
+  codecForNumber,
 } from "@gnu-taler/taler-util";
-import { buildStorageKey, useLocalStorage, useTranslationContext } from 
"@gnu-taler/web-util/browser";
+import {
+  buildStorageKey,
+  useLocalStorage,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
 
 interface Preferences {
   showWithdrawalSuccess: boolean;
@@ -30,31 +34,45 @@ interface Preferences {
   maxWithdrawalAmount: number;
   fastWithdrawal: boolean;
   showDebugInfo: boolean;
-
 }
 
 export function getAllBooleanPreferences(): Array<keyof Preferences> {
-  return ["fastWithdrawal", "showDebugInfo", "showDemoDescription", 
"showInstallWallet", "showWithdrawalSuccess"]
+  return [
+    "fastWithdrawal",
+    "showDebugInfo",
+    "showDemoDescription",
+    "showInstallWallet",
+    "showWithdrawalSuccess",
+  ];
 }
 
-export function getLabelForPreferences(k: keyof Preferences, i18n: 
ReturnType<typeof useTranslationContext>["i18n"]): TranslatedString {
+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`
+    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()))
-    .property("showDemoDescription", (codecForBoolean()))
-    .property("showInstallWallet", (codecForBoolean()))
-    .property("fastWithdrawal", (codecForBoolean()))
-    .property("showDebugInfo", (codecForBoolean()))
+    .property("showWithdrawalSuccess", codecForBoolean())
+    .property("showDemoDescription", codecForBoolean())
+    .property("showInstallWallet", codecForBoolean())
+    .property("fastWithdrawal", codecForBoolean())
+    .property("showDebugInfo", codecForBoolean())
     .property("maxWithdrawalAmount", codecForNumber())
     .build("Settings");
 
diff --git a/packages/demobank-ui/src/i18n/bank.pot 
b/packages/demobank-ui/src/i18n/bank.pot
index 634453a1d..05a5a34bf 100644
--- a/packages/demobank-ui/src/i18n/bank.pot
+++ b/packages/demobank-ui/src/i18n/bank.pot
@@ -1,5 +1,5 @@
 # This file is part of GNU Taler
-# (C) 2022 Taler Systems S.A.
+# (C) 2022-2024 Taler Systems S.A.
 #
 # GNU Taler is free software; you can redistribute it and/or modify it under 
the
 # terms of the GNU General Public License as published by the Free Software
diff --git a/packages/demobank-ui/src/i18n/fr.po 
b/packages/demobank-ui/src/i18n/fr.po
index 1649f839e..fd253396a 100644
--- a/packages/demobank-ui/src/i18n/fr.po
+++ b/packages/demobank-ui/src/i18n/fr.po
@@ -1,5 +1,5 @@
 # This file is part of GNU Taler
-# (C) 2022 Taler Systems S.A.
+# (C) 2022-2024 Taler Systems S.A.
 #
 # GNU Taler is free software; you can redistribute it and/or modify it under 
the
 # terms of the GNU General Public License as published by the Free Software
diff --git a/packages/demobank-ui/src/i18n/poheader 
b/packages/demobank-ui/src/i18n/poheader
index a251e9584..d7a371934 100644
--- a/packages/demobank-ui/src/i18n/poheader
+++ b/packages/demobank-ui/src/i18n/poheader
@@ -1,5 +1,5 @@
 # This file is part of GNU Taler
-# (C) 2022 Taler Systems S.A.
+# (C) 2022-2024 Taler Systems S.A.
 #
 # GNU Taler is free software; you can redistribute it and/or modify it under 
the
 # terms of the GNU General Public License as published by the Free Software
diff --git a/packages/demobank-ui/src/i18n/strings.ts 
b/packages/demobank-ui/src/i18n/strings.ts
index ddff053eb..fbd1acb35 100644
--- a/packages/demobank-ui/src/i18n/strings.ts
+++ b/packages/demobank-ui/src/i18n/strings.ts
@@ -1,5268 +1,2295 @@
+/*
+ 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/>
+ */
 export interface StringsType {
   // X-Domain or 'messages'
   domain: string;
   lang: string;
-  completeness: number,
-  'plural_forms': string;
+  completeness: number;
+  plural_forms: string;
   locale_data: {
-    messages: Record<string, any>
-  }
+    messages: Record<string, unknown>;
+  };
 }
-export const strings: Record<string,StringsType> = {};
+export const strings: Record<string, StringsType> = {};
 
-strings['it'] = {
-  "locale_data": {
-    "messages": {
+strings["it"] = {
+  locale_data: {
+    messages: {
       "": {
-        "domain": "messages",
-        "plural_forms": "nplurals=2; plural=n != 1;",
-        "lang": "it"
+        domain: "messages",
+        plural_forms: "nplurals=2; plural=n != 1;",
+        lang: "it",
       },
-      "Operation failed, please report": [
-        "Registrazione"
+      "Operation failed, please report": ["Registrazione"],
+      "Request timeout": [""],
+      "Request throttled": [""],
+      "Malformed response": [""],
+      "Network error": [""],
+      "Unexpected request error": [""],
+      "Unexpected error": [""],
+      "IBAN numbers usually have more that 4 digits": [""],
+      "IBAN numbers usually have less that 34 digits": [""],
+      "IBAN country code not found": [""],
+      "IBAN number is not valid, checksum is wrong": [""],
+      "Max withdrawal amount": ["Questo ritiro è stato annullato!"],
+      "Show withdrawal confirmation": ["Questo ritiro è stato annullato!"],
+      "Show demo description": [""],
+      "Show install wallet first": [""],
+      "Use fast withdrawal form": ["Ritira contante"],
+      "Show debug info": [""],
+      "The reserve operation has been confirmed previously and can't be 
aborted":
+        [""],
+      "The operation id is invalid.": [""],
+      "The operation was not found.": ["Lista conti pubblici non trovata."],
+      "If you have a Taler wallet installed in this device": [""],
+      "You will see the details of the operation in your wallet including the 
fees (if applies). If you still don't have one you can install it following 
instructions in":
+        [""],
+      "this page": [""],
+      Withdraw: ["Prelevare"],
+      "Or if you have the wallet in another device": [""],
+      "Scan the QR below to start the withdrawal.": ["Chiudi il ritiro Taler"],
+      required: [""],
+      "IBAN should have just uppercased letters and numbers": [""],
+      "not valid": [""],
+      "should be greater than 0": [""],
+      "balance is not enough": [""],
+      "does not follow the pattern": [""],
+      'only "IBAN" target are supported': [""],
+      'use the "amount" parameter to specify the amount to be transferred': [
+        "",
+      ],
+      "the amount is not valid": [""],
+      'use the "message" parameter to specify a reference text for the 
transfer':
+        [""],
+      "The request was invalid or the payto://-URI used unacceptable 
features.":
+        [""],
+      "Not enough permission to complete the operation.": [
+        "La banca sta creando l'operazione...",
+      ],
+      'The destination account "%1$s" was not found.': [
+        "Lista conti pubblici non trovata.",
       ],
-      "Request timeout": [
-        ""
+      "The origin and the destination of the transfer can't be the same.": 
[""],
+      "Your balance is not enough.": [""],
+      'The origin account "%1$s" was not found.': [
+        "Lista conti pubblici non trovata.",
       ],
-      "Request throttled": [
-        ""
+      "Using a form": [""],
+      "Import payto:// URI": [""],
+      Recipient: [""],
+      "IBAN of the recipient's account": [""],
+      "Transfer subject": [
+        "Trasferisci fondi a un altro conto di questa banca:",
+      ],
+      subject: ["Soggetto"],
+      "some text to identify the transfer": [""],
+      Amount: ["Importo"],
+      "amount to transfer": ["Somma da ritirare"],
+      "payto URI:": [""],
+      "uniform resource identifier of the target account": [""],
+      "payto://iban/[receiver-iban]?message=[subject]&amount=[%1$s:X.Y]": [""],
+      Cancel: [""],
+      Send: [""],
+      "Missing username": [""],
+      "Missing password": [""],
+      'Wrong credentials for "%1$s"': ["Credenziali invalide."],
+      "Account not found": [""],
+      Username: [""],
+      "username of the account": [
+        "Trasferisci fondi a un altro conto di questa banca:",
+      ],
+      Password: [""],
+      "password of the account": ["Storico dei conti pubblici"],
+      Check: [""],
+      "Log in": [""],
+      Register: ["Registrati"],
+      "Wire transfer completed!": ["Bonifico"],
+      "The withdrawal has been aborted previously and can't be confirmed": 
[""],
+      "The withdrawal operation can't be confirmed before a wallet accepted 
the transaction.":
+        [""],
+      "Your balance is not enough for the operation.": [""],
+      "Confirm the withdrawal operation": ["Conferma il ritiro"],
+      "Wire transfer details": ["Bonifico"],
+      "Taler Exchange operator's account": [""],
+      "Taler Exchange operator's name": [""],
+      Transfer: [""],
+      "Authentication required": [""],
+      "This operation was created with other username": [""],
+      "Operation aborted": [""],
+      "The wire transfer to the Taler Exchange operator's account was aborted, 
your balance was not affected.":
+        [""],
+      "You can close this page now or continue to the account page.": [""],
+      Continue: [""],
+      "Withdrawal confirmed": ["Questo ritiro è stato annullato!"],
+      "The wire transfer to the Taler operator has been initiated. You will 
soon receive the requested amount in your Taler wallet.":
+        [""],
+      Done: [""],
+      "Operation canceled": [""],
+      "The operation is marked as 'selected' but some step in the withdrawal 
failed":
+        [""],
+      "The account is selected but no withdrawal identification found.": [""],
+      "There is a withdrawal identification but no account has been selected 
or the selected account is invalid.":
+        [""],
+      "No withdrawal ID found and no account has been selected or the selected 
account is invalid.":
+        [""],
+      "Operation not found": [""],
+      "This operation is not known by the server. The operation id is wrong or 
the server deleted the operation information before reaching here.":
+        [""],
+      "Cotinue to dashboard": [""],
+      "The Withdrawal URI is not valid": ["Questo ritiro è stato annullato!"],
+      'the bank backend is not supported. supported version "%1$s", server 
version "%2$s"':
+        [""],
+      "Internal error, please report.": ["Registrazione"],
+      Preferences: [""],
+      "Welcome, %1$s": [""],
+      "Latest transactions": ["Ultime transazioni:"],
+      Date: ["Data"],
+      Counterpart: ["Controparte"],
+      Subject: ["Soggetto"],
+      sent: [""],
+      received: [""],
+      "invalid value": [""],
+      to: [""],
+      from: [""],
+      "First page": [""],
+      Next: [""],
+      "History of public accounts": ["Storico dei conti pubblici"],
+      "Currently, the bank is not accepting new registrations!": [""],
+      "Missing name": ["indirizzo Payto"],
+      "Use letters and numbers only, and start with a lowercase letter": [""],
+      "Passwords don't match": [""],
+      "Server replied with invalid phone or email.": [""],
+      "Registration is disabled because the bank ran out of bonus credit.": [
+        "",
+      ],
+      "No enough permission to create that account.": [""],
+      "That account id is already taken.": [""],
+      "That username is already taken.": [""],
+      "That username can't be used because is reserved.": [""],
+      "Only admin is allow to set debt limit.": [""],
+      "No information for the selected authentication channel.": [""],
+      "Authentication channel is not supported.": [""],
+      "Only admin can create accounts with second factor authentication.": 
[""],
+      "Account registration": [""],
+      "Repeat password": [""],
+      Name: [""],
+      "Create a random temporary user": [""],
+      "Make a wire transfer": ["Chiudi il bonifico"],
+      "Wire transfer created!": ["Bonifico"],
+      Accounts: ["Importo"],
+      "A list of all business account in the bank.": [""],
+      "Create account": [""],
+      Balance: [""],
+      Actions: [""],
+      unknown: [""],
+      "change password": [""],
+      remove: [""],
+      "Select a section": [""],
+      "Last hour": [""],
+      "Last day": [""],
+      "Last month": [""],
+      "Last year": [""],
+      "Last Year": [""],
+      "Trading volume on %1$s compared to %2$s": [""],
+      Cashin: [""],
+      Cashout: [""],
+      Payin: [""],
+      Payout: [""],
+      "download stats as CSV": [""],
+      "Descreased by": [""],
+      "Increased by": [""],
+      "Unable to create a cashout": [""],
+      "The bank configuration does not support cashout operations.": [""],
+      invalid: [""],
+      "need to be higher due to fees": [""],
+      "the total transfer at destination will be zero": [""],
+      "Cashout created": [""],
+      "Duplicated request detected, check if the operation succeded or try 
again.":
+        [""],
+      "The conversion rate was incorrectly applied": [""],
+      "The account does not have sufficient funds": [""],
+      "Cashouts are not supported": [""],
+      "Missing cashout URI in the profile": [""],
+      "Sending the confirmation message failed, retry later or contact the 
administrator.":
+        [""],
+      "Convertion rate": [""],
+      Fee: [""],
+      "To account": [""],
+      "No cashout account": [""],
+      "Before doing a cashout you need to complete your profile": [""],
+      "Amount to send": ["Somma da ritirare"],
+      "Amount to receive": ["Somma da ritirare"],
+      "Total cost": [""],
+      "Balance left": [""],
+      "Before fee": [""],
+      "Total cashout transfer": [""],
+      "No cashout channel available": [""],
+      "Before doing a cashout the server need to provide an second channel to 
confirm the operation":
+        [""],
+      "Second factor authentication": [""],
+      Email: [""],
+      "add a email in your profile to enable this option": [""],
+      SMS: [""],
+      "add a phone number in your profile to enable this option": [""],
+      Details: [""],
+      Delete: [""],
+      Credentials: ["Credenziali invalide."],
+      Cashouts: [""],
+      "it doesnt have the pattern of an IBAN number": [""],
+      "it doesnt have the pattern of an email": [""],
+      "should start with +": [""],
+      "phone number can't have other than numbers": [""],
+      "account identification in the bank": [""],
+      "name of the person owner the account": [""],
+      "Internal IBAN": [""],
+      "if empty a random account number will be assigned": [""],
+      "account identification for bank transfer": [""],
+      Phone: [""],
+      "Cashout IBAN": [""],
+      "account number where the money is going to be sent when doing cashouts":
+        [""],
+      "Max debt": [""],
+      "how much is user able to transfer after zero balance": [""],
+      "Is this a Taler Exchange?": [""],
+      "This server doesn't support second factor authentication.": [""],
+      "Enable second factor authentication": [""],
+      "Using email": [""],
+      "Using SMS": [""],
+      "Is this account public?": [""],
+      "public accounts have their balance publicly accesible": [""],
+      "Account updated": [""],
+      "The rights to change the account are not sufficient": [""],
+      "The username was not found": [""],
+      "You can't change the legal name, please contact the your account 
administrator.":
+        [""],
+      "You can't change the debt limit, please contact the your account 
administrator.":
+        [""],
+      "You can't change the cashout address, please contact the your account 
administrator.":
+        [""],
+      "You can't change the contact data, please contact the your account 
administrator.":
+        [""],
+      'Account "%1$s"': [""],
+      "Change details": [""],
+      Update: [""],
+      "password doesn't match": [""],
+      "Password changed": [""],
+      "Not authorized to change the password, maybe the session is invalid.": [
+        "",
       ],
-      "Malformed response": [
-        ""
+      "You need to provide the old password. If you don't have it contact your 
account administrator.":
+        [""],
+      "Your current password doesn't match, can't change to a new password.": [
+        "",
+      ],
+      "Update password": [""],
+      "New password": [""],
+      "Type it again": [""],
+      "repeat the same password": [""],
+      "Current password": [""],
+      "your current password, for security": [""],
+      Change: [""],
+      "Can't delete the account": [""],
+      "The account can't be delete while still holding some balance. First 
make sure that the owner make a complete cashout.":
+        [""],
+      "Account removed": [""],
+      "No enough permission to delete the account.": [""],
+      "The username was not found.": [""],
+      "Can't delete a reserved username.": [""],
+      "Can't delete an account with balance different than zero.": [""],
+      "name doesn't match": [""],
+      "You are going to remove the account": [""],
+      "This step can't be undone.": [""],
+      'Deleting account "%1$s"': [""],
+      Verification: [""],
+      "enter the account name that is going to be deleted": [""],
+      'Account created with password "%1$s". The user must change the password 
on the next login.':
+        [""],
+      "Server replied that phone or email is invalid": [""],
+      "The rights to perform the operation are not sufficient": [""],
+      "Account username is already taken": [""],
+      "Account id is already taken": [""],
+      "Bank ran out of bonus credit.": [""],
+      "Account username can't be used because is reserved": [""],
+      "Can't create accounts": [""],
+      "Only system admin can create accounts.": [""],
+      "New business account": [""],
+      Create: [""],
+      "Cashout not supported.": [""],
+      "Account not found.": ["Lista conti pubblici non trovata."],
+      "Latest cashouts": ["Ultime transazioni:"],
+      Created: [""],
+      Confirmed: ["Conferma"],
+      "Total debit": [""],
+      "Total credit": [""],
+      Status: [""],
+      never: [""],
+      "Cashout for account %1$s": [""],
+      "This cashout not found. Maybe already aborted.": [""],
+      "Cashout not found. It may be also mean that it was already aborted.": [
+        "",
+      ],
+      "Cashout was already confimed.": [""],
+      "Cashout operation is not supported.": [""],
+      "The cashout operation is already aborted.": [""],
+      "Missing destination account.": [""],
+      "Too many failed attempts.": [""],
+      "The code for this cashout is invalid.": [""],
+      "Cashout detail": [""],
+      Debited: [""],
+      Credited: [""],
+      "Enter the confirmation code": [""],
+      Abort: ["Annulla"],
+      Confirm: ["Conferma"],
+      "Unauthorized to make the operation, maybe the session has expired or 
the password changed.":
+        [""],
+      "The operation was rejected due to insufficient funds.": [""],
+      "Do not show this again": [""],
+      Close: [""],
+      "On this device": [""],
+      'If you are using a web browser on desktop you should access your wallet 
with the GNU Taler WebExtension now or click the link if your WebExtension have 
the "Inject Taler support" option enabled.':
+        [""],
+      Start: [""],
+      "On a mobile phone": [""],
+      "Scan the QR code with your mobile device.": [
+        "Usa questo codice QR per ritirare contante nel tuo wallet:",
+      ],
+      "There is an operation already": [""],
+      "Complete or cancel the operation in": ["Conferma il ritiro"],
+      "Server responded with an invalid  withdraw URI": [""],
+      "Withdraw URI: %1$s": ["Prelevare"],
+      "The operation was rejected due to insufficient funds": [""],
+      "Prepare your wallet": [""],
+      "After using your wallet you will need to confirm or cancel the 
operation on this site.":
+        [""],
+      "You need a GNU Taler Wallet": ["Ritira contante nel portafoglio Taler"],
+      "If you don't have one yet you can follow the instruction in": [""],
+      "Send money": [""],
+      "to a %1$s wallet": [""],
+      "Withdraw digital money into your mobile wallet or browser extension": [
+        "",
       ],
-      "Network error": [
-        ""
+      "operation ready": [""],
+      "to another bank account": [
+        "Trasferisci fondi a un altro conto di questa banca:",
       ],
-      "Unexpected request error": [
-        ""
+      "Make a wire transfer to an account with known bank account number.": [
+        "",
+      ],
+      "Transfer details": ["Effettua un bonifico"],
+      "This is a demo bank": [""],
+      "This part of the demo shows how a bank that supports Taler directly 
would work. In addition to using your own bank account, you can also see the 
transaction history of some %1$s.":
+        [""],
+      "This part of the demo shows how a bank that supports Taler directly 
would work.":
+        [""],
+      "Pending account delete operation": [""],
+      "Pending account update operation": [""],
+      "Pending password update operation": [""],
+      "Pending transaction operation": [""],
+      "Pending withdrawal operation": [""],
+      "Pending cashout operation": [""],
+      "You can complete or cancel the operation in": [""],
+      "Download bank stats": [""],
+      "Include hour metric": [""],
+      "Include day metric": [""],
+      "Include month metric": [""],
+      "Include year metric": [""],
+      "Include table header": [""],
+      "Add previous metric for compare": [""],
+      "Fail on first error": [""],
+      Download: [""],
+      "downloading... %1$s": [""],
+      "Download completed": [""],
+      "click here to save the file in your computer": [""],
+      "Challenge not found.": [""],
+      "This user is not authorized to complete this challenge.": [""],
+      "Too many attemps, try another code.": [""],
+      "The confirmation code is wrong, try again.": [""],
+      "The operation expired.": [""],
+      "The operation failed.": ["Questo ritiro è stato annullato!"],
+      "The operation needs another confirmation to complete.": [""],
+      "Account delete": [""],
+      "Account update": [""],
+      "Password update": [""],
+      "Wire transfer": ["Bonifico"],
+      Withdrawal: ["Prelevare"],
+      "Confirm the operation": ["Conferma il ritiro"],
+      "Send again": [""],
+      "Send code": [""],
+      "Operation details": [""],
+      "Challenge details": [""],
+      "Sent at": [""],
+      "To phone": [""],
+      "To email": [""],
+      "Welcome to %1$s!": [""],
+    },
+  },
+  domain: "messages",
+  plural_forms: "nplurals=2; plural=n != 1;",
+  lang: "it",
+  completeness: 14,
+};
+
+strings["fr"] = {
+  locale_data: {
+    messages: {
+      "": {
+        domain: "messages",
+        plural_forms: "nplurals=2; plural=n > 1;",
+        lang: "fr",
+      },
+      "Operation failed, please report": [""],
+      "Request timeout": [""],
+      "Request throttled": [""],
+      "Malformed response": [""],
+      "Network error": [""],
+      "Unexpected request error": [""],
+      "Unexpected error": [""],
+      "IBAN numbers usually have more that 4 digits": [""],
+      "IBAN numbers usually have less that 34 digits": [""],
+      "IBAN country code not found": [""],
+      "IBAN number is not valid, checksum is wrong": [""],
+      "Max withdrawal amount": [""],
+      "Show withdrawal confirmation": [""],
+      "Show demo description": [""],
+      "Show install wallet first": [""],
+      "Use fast withdrawal form": [""],
+      "Show debug info": [""],
+      "The reserve operation has been confirmed previously and can't be 
aborted":
+        [""],
+      "The operation id is invalid.": [""],
+      "The operation was not found.": [""],
+      "If you have a Taler wallet installed in this device": [""],
+      "You will see the details of the operation in your wallet including the 
fees (if applies). If you still don't have one you can install it following 
instructions in":
+        [""],
+      "this page": [""],
+      Withdraw: [""],
+      "Or if you have the wallet in another device": [""],
+      "Scan the QR below to start the withdrawal.": [""],
+      required: [""],
+      "IBAN should have just uppercased letters and numbers": [""],
+      "not valid": [""],
+      "should be greater than 0": [""],
+      "balance is not enough": [""],
+      "does not follow the pattern": [""],
+      'only "IBAN" target are supported': [""],
+      'use the "amount" parameter to specify the amount to be transferred': [
+        "",
+      ],
+      "the amount is not valid": [""],
+      'use the "message" parameter to specify a reference text for the 
transfer':
+        [""],
+      "The request was invalid or the payto://-URI used unacceptable 
features.":
+        [""],
+      "Not enough permission to complete the operation.": [""],
+      'The destination account "%1$s" was not found.': [""],
+      "The origin and the destination of the transfer can't be the same.": 
[""],
+      "Your balance is not enough.": [""],
+      'The origin account "%1$s" was not found.': [""],
+      "Using a form": [""],
+      "Import payto:// URI": [""],
+      Recipient: [""],
+      "IBAN of the recipient's account": [""],
+      "Transfer subject": [""],
+      subject: [""],
+      "some text to identify the transfer": [""],
+      Amount: [""],
+      "amount to transfer": [""],
+      "payto URI:": [""],
+      "uniform resource identifier of the target account": [""],
+      "payto://iban/[receiver-iban]?message=[subject]&amount=[%1$s:X.Y]": [""],
+      Cancel: [""],
+      Send: [""],
+      "Missing username": [""],
+      "Missing password": [""],
+      'Wrong credentials for "%1$s"': [""],
+      "Account not found": [""],
+      Username: [""],
+      "username of the account": [""],
+      Password: [""],
+      "password of the account": [""],
+      Check: [""],
+      "Log in": [""],
+      Register: [""],
+      "Wire transfer completed!": [""],
+      "The withdrawal has been aborted previously and can't be confirmed": 
[""],
+      "The withdrawal operation can't be confirmed before a wallet accepted 
the transaction.":
+        [""],
+      "Your balance is not enough for the operation.": [""],
+      "Confirm the withdrawal operation": [""],
+      "Wire transfer details": [""],
+      "Taler Exchange operator's account": [""],
+      "Taler Exchange operator's name": [""],
+      Transfer: [""],
+      "Authentication required": [""],
+      "This operation was created with other username": [""],
+      "Operation aborted": [""],
+      "The wire transfer to the Taler Exchange operator's account was aborted, 
your balance was not affected.":
+        [""],
+      "You can close this page now or continue to the account page.": [""],
+      Continue: [""],
+      "Withdrawal confirmed": [""],
+      "The wire transfer to the Taler operator has been initiated. You will 
soon receive the requested amount in your Taler wallet.":
+        [""],
+      Done: [""],
+      "Operation canceled": [""],
+      "The operation is marked as 'selected' but some step in the withdrawal 
failed":
+        [""],
+      "The account is selected but no withdrawal identification found.": [""],
+      "There is a withdrawal identification but no account has been selected 
or the selected account is invalid.":
+        [""],
+      "No withdrawal ID found and no account has been selected or the selected 
account is invalid.":
+        [""],
+      "Operation not found": [""],
+      "This operation is not known by the server. The operation id is wrong or 
the server deleted the operation information before reaching here.":
+        [""],
+      "Cotinue to dashboard": [""],
+      "The Withdrawal URI is not valid": [""],
+      'the bank backend is not supported. supported version "%1$s", server 
version "%2$s"':
+        [""],
+      "Internal error, please report.": [""],
+      Preferences: [""],
+      "Welcome, %1$s": [""],
+      "Latest transactions": [""],
+      Date: [""],
+      Counterpart: [""],
+      Subject: [""],
+      sent: [""],
+      received: [""],
+      "invalid value": [""],
+      to: [""],
+      from: [""],
+      "First page": [""],
+      Next: [""],
+      "History of public accounts": [""],
+      "Currently, the bank is not accepting new registrations!": [""],
+      "Missing name": [""],
+      "Use letters and numbers only, and start with a lowercase letter": [""],
+      "Passwords don't match": [""],
+      "Server replied with invalid phone or email.": [""],
+      "Registration is disabled because the bank ran out of bonus credit.": [
+        "",
+      ],
+      "No enough permission to create that account.": [""],
+      "That account id is already taken.": [""],
+      "That username is already taken.": [""],
+      "That username can't be used because is reserved.": [""],
+      "Only admin is allow to set debt limit.": [""],
+      "No information for the selected authentication channel.": [""],
+      "Authentication channel is not supported.": [""],
+      "Only admin can create accounts with second factor authentication.": 
[""],
+      "Account registration": [""],
+      "Repeat password": [""],
+      Name: [""],
+      "Create a random temporary user": [""],
+      "Make a wire transfer": [""],
+      "Wire transfer created!": [""],
+      Accounts: [""],
+      "A list of all business account in the bank.": [""],
+      "Create account": [""],
+      Balance: [""],
+      Actions: [""],
+      unknown: [""],
+      "change password": [""],
+      remove: [""],
+      "Select a section": [""],
+      "Last hour": [""],
+      "Last day": [""],
+      "Last month": [""],
+      "Last year": [""],
+      "Last Year": [""],
+      "Trading volume on %1$s compared to %2$s": [""],
+      Cashin: [""],
+      Cashout: [""],
+      Payin: [""],
+      Payout: [""],
+      "download stats as CSV": [""],
+      "Descreased by": [""],
+      "Increased by": [""],
+      "Unable to create a cashout": [""],
+      "The bank configuration does not support cashout operations.": [""],
+      invalid: [""],
+      "need to be higher due to fees": [""],
+      "the total transfer at destination will be zero": [""],
+      "Cashout created": [""],
+      "Duplicated request detected, check if the operation succeded or try 
again.":
+        [""],
+      "The conversion rate was incorrectly applied": [""],
+      "The account does not have sufficient funds": [""],
+      "Cashouts are not supported": [""],
+      "Missing cashout URI in the profile": [""],
+      "Sending the confirmation message failed, retry later or contact the 
administrator.":
+        [""],
+      "Convertion rate": [""],
+      Fee: [""],
+      "To account": [""],
+      "No cashout account": [""],
+      "Before doing a cashout you need to complete your profile": [""],
+      "Amount to send": [""],
+      "Amount to receive": [""],
+      "Total cost": [""],
+      "Balance left": [""],
+      "Before fee": [""],
+      "Total cashout transfer": [""],
+      "No cashout channel available": [""],
+      "Before doing a cashout the server need to provide an second channel to 
confirm the operation":
+        [""],
+      "Second factor authentication": [""],
+      Email: [""],
+      "add a email in your profile to enable this option": [""],
+      SMS: [""],
+      "add a phone number in your profile to enable this option": [""],
+      Details: [""],
+      Delete: [""],
+      Credentials: [""],
+      Cashouts: [""],
+      "it doesnt have the pattern of an IBAN number": [""],
+      "it doesnt have the pattern of an email": [""],
+      "should start with +": [""],
+      "phone number can't have other than numbers": [""],
+      "account identification in the bank": [""],
+      "name of the person owner the account": [""],
+      "Internal IBAN": [""],
+      "if empty a random account number will be assigned": [""],
+      "account identification for bank transfer": [""],
+      Phone: [""],
+      "Cashout IBAN": [""],
+      "account number where the money is going to be sent when doing cashouts":
+        [""],
+      "Max debt": [""],
+      "how much is user able to transfer after zero balance": [""],
+      "Is this a Taler Exchange?": [""],
+      "This server doesn't support second factor authentication.": [""],
+      "Enable second factor authentication": [""],
+      "Using email": [""],
+      "Using SMS": [""],
+      "Is this account public?": [""],
+      "public accounts have their balance publicly accesible": [""],
+      "Account updated": [""],
+      "The rights to change the account are not sufficient": [""],
+      "The username was not found": [""],
+      "You can't change the legal name, please contact the your account 
administrator.":
+        [""],
+      "You can't change the debt limit, please contact the your account 
administrator.":
+        [""],
+      "You can't change the cashout address, please contact the your account 
administrator.":
+        [""],
+      "You can't change the contact data, please contact the your account 
administrator.":
+        [""],
+      'Account "%1$s"': [""],
+      "Change details": [""],
+      Update: [""],
+      "password doesn't match": [""],
+      "Password changed": [""],
+      "Not authorized to change the password, maybe the session is invalid.": [
+        "",
       ],
-      "Unexpected error": [
-        ""
+      "You need to provide the old password. If you don't have it contact your 
account administrator.":
+        [""],
+      "Your current password doesn't match, can't change to a new password.": [
+        "",
+      ],
+      "Update password": [""],
+      "New password": [""],
+      "Type it again": [""],
+      "repeat the same password": [""],
+      "Current password": [""],
+      "your current password, for security": [""],
+      Change: [""],
+      "Can't delete the account": [""],
+      "The account can't be delete while still holding some balance. First 
make sure that the owner make a complete cashout.":
+        [""],
+      "Account removed": [""],
+      "No enough permission to delete the account.": [""],
+      "The username was not found.": [""],
+      "Can't delete a reserved username.": [""],
+      "Can't delete an account with balance different than zero.": [""],
+      "name doesn't match": [""],
+      "You are going to remove the account": [""],
+      "This step can't be undone.": [""],
+      'Deleting account "%1$s"': [""],
+      Verification: [""],
+      "enter the account name that is going to be deleted": [""],
+      'Account created with password "%1$s". The user must change the password 
on the next login.':
+        [""],
+      "Server replied that phone or email is invalid": [""],
+      "The rights to perform the operation are not sufficient": [""],
+      "Account username is already taken": [""],
+      "Account id is already taken": [""],
+      "Bank ran out of bonus credit.": [""],
+      "Account username can't be used because is reserved": [""],
+      "Can't create accounts": [""],
+      "Only system admin can create accounts.": [""],
+      "New business account": [""],
+      Create: [""],
+      "Cashout not supported.": [""],
+      "Account not found.": [""],
+      "Latest cashouts": [""],
+      Created: [""],
+      Confirmed: [""],
+      "Total debit": [""],
+      "Total credit": [""],
+      Status: [""],
+      never: [""],
+      "Cashout for account %1$s": [""],
+      "This cashout not found. Maybe already aborted.": [""],
+      "Cashout not found. It may be also mean that it was already aborted.": [
+        "",
+      ],
+      "Cashout was already confimed.": [""],
+      "Cashout operation is not supported.": [""],
+      "The cashout operation is already aborted.": [""],
+      "Missing destination account.": [""],
+      "Too many failed attempts.": [""],
+      "The code for this cashout is invalid.": [""],
+      "Cashout detail": [""],
+      Debited: [""],
+      Credited: [""],
+      "Enter the confirmation code": [""],
+      Abort: [""],
+      Confirm: [""],
+      "Unauthorized to make the operation, maybe the session has expired or 
the password changed.":
+        [""],
+      "The operation was rejected due to insufficient funds.": [""],
+      "Do not show this again": [""],
+      Close: [""],
+      "On this device": [""],
+      'If you are using a web browser on desktop you should access your wallet 
with the GNU Taler WebExtension now or click the link if your WebExtension have 
the "Inject Taler support" option enabled.':
+        [""],
+      Start: [""],
+      "On a mobile phone": [""],
+      "Scan the QR code with your mobile device.": [""],
+      "There is an operation already": [""],
+      "Complete or cancel the operation in": [""],
+      "Server responded with an invalid  withdraw URI": [""],
+      "Withdraw URI: %1$s": [""],
+      "The operation was rejected due to insufficient funds": [""],
+      "Prepare your wallet": [""],
+      "After using your wallet you will need to confirm or cancel the 
operation on this site.":
+        [""],
+      "You need a GNU Taler Wallet": [""],
+      "If you don't have one yet you can follow the instruction in": [""],
+      "Send money": [""],
+      "to a %1$s wallet": [""],
+      "Withdraw digital money into your mobile wallet or browser extension": [
+        "",
       ],
+      "operation ready": [""],
+      "to another bank account": [""],
+      "Make a wire transfer to an account with known bank account number.": [
+        "",
+      ],
+      "Transfer details": [""],
+      "This is a demo bank": [""],
+      "This part of the demo shows how a bank that supports Taler directly 
would work. In addition to using your own bank account, you can also see the 
transaction history of some %1$s.":
+        [""],
+      "This part of the demo shows how a bank that supports Taler directly 
would work.":
+        [""],
+      "Pending account delete operation": [""],
+      "Pending account update operation": [""],
+      "Pending password update operation": [""],
+      "Pending transaction operation": [""],
+      "Pending withdrawal operation": [""],
+      "Pending cashout operation": [""],
+      "You can complete or cancel the operation in": [""],
+      "Download bank stats": [""],
+      "Include hour metric": [""],
+      "Include day metric": [""],
+      "Include month metric": [""],
+      "Include year metric": [""],
+      "Include table header": [""],
+      "Add previous metric for compare": [""],
+      "Fail on first error": [""],
+      Download: [""],
+      "downloading... %1$s": [""],
+      "Download completed": [""],
+      "click here to save the file in your computer": [""],
+      "Challenge not found.": [""],
+      "This user is not authorized to complete this challenge.": [""],
+      "Too many attemps, try another code.": [""],
+      "The confirmation code is wrong, try again.": [""],
+      "The operation expired.": [""],
+      "The operation failed.": [""],
+      "The operation needs another confirmation to complete.": [""],
+      "Account delete": [""],
+      "Account update": [""],
+      "Password update": [""],
+      "Wire transfer": [""],
+      Withdrawal: [""],
+      "Confirm the operation": [""],
+      "Send again": [""],
+      "Send code": [""],
+      "Operation details": [""],
+      "Challenge details": [""],
+      "Sent at": [""],
+      "To phone": [""],
+      "To email": [""],
+      "Welcome to %1$s!": [""],
+    },
+  },
+  domain: "messages",
+  plural_forms: "nplurals=2; plural=n > 1;",
+  lang: "fr",
+  completeness: 0,
+};
+
+strings["es"] = {
+  locale_data: {
+    messages: {
+      "": {
+        domain: "messages",
+        plural_forms: "nplurals=2; plural=n != 1;",
+        lang: "es",
+      },
+      "Operation failed, please report": [
+        "La operaicón falló, por favor reportelo",
+      ],
+      "Request timeout": ["La petición al servidor agoto su tiempo"],
+      "Request throttled": ["La petición al servidor interrumpida"],
+      "Malformed response": ["Respuesta malformada"],
+      "Network error": ["Error de conexión"],
+      "Unexpected request error": ["Error de pedido inesperado"],
+      "Unexpected error": ["Error inesperado"],
       "IBAN numbers usually have more that 4 digits": [
-        ""
+        "Los números IBAN usualmente tienen mas de 4 digitos",
       ],
       "IBAN numbers usually have less that 34 digits": [
-        ""
-      ],
-      "IBAN country code not found": [
-        ""
+        "Los números IBAN usualmente tienen menos de 34 digitos",
       ],
+      "IBAN country code not found": ["Código de pais de IBAN no encontrado"],
       "IBAN number is not valid, checksum is wrong": [
-        ""
-      ],
-      "Max withdrawal amount": [
-        "Questo ritiro è stato annullato!"
-      ],
-      "Show withdrawal confirmation": [
-        "Questo ritiro è stato annullato!"
-      ],
-      "Show demo description": [
-        ""
-      ],
-      "Show install wallet first": [
-        ""
-      ],
-      "Use fast withdrawal form": [
-        "Ritira contante"
-      ],
-      "Show debug info": [
-        ""
-      ],
-      "The reserve operation has been confirmed previously and can't be 
aborted": [
-        ""
-      ],
-      "The operation id is invalid.": [
-        ""
-      ],
-      "The operation was not found.": [
-        "Lista conti pubblici non trovata."
-      ],
+        "El número IBAN no es válido, falló la verificación",
+      ],
+      "Max withdrawal amount": ["Monto máximo de extracción"],
+      "Show withdrawal confirmation": ["Mostrar confirmación de extracción"],
+      "Show demo description": ["Mostrar descripción de demo"],
+      "Show install wallet first": ["Mostrar instalar la billetera primero"],
+      "Use fast withdrawal form": ["Usar formulario de extracción rápida"],
+      "Show debug info": ["Mostrar información de depuración"],
+      "The reserve operation has been confirmed previously and can't be 
aborted":
+        [
+          "La operación en la reserva ya ha sido confirmada previamente y no 
puede ser abortada",
+        ],
+      "The operation id is invalid.": ["El id de operación es invalido."],
+      "The operation was not found.": ["La operación no se encontró."],
       "If you have a Taler wallet installed in this device": [
-        ""
-      ],
-      "You will see the details of the operation in your wallet including the 
fees (if applies). If you still don't have one you can install it following 
instructions in": [
-        ""
-      ],
-      "this page": [
-        ""
-      ],
-      "Withdraw": [
-        "Prelevare"
-      ],
+        "Si tienes una billetera Taler instalada en este dispositivo",
+      ],
+      "You will see the details of the operation in your wallet including the 
fees (if applies). If you still don't have one you can install it following 
instructions in":
+        [
+          "Veras los detalles de la operación en tu billetera incluyendo 
comisiones (si aplicán). Si todavía no tienes una puedes instalarla siguiendo 
las instrucciones en",
+        ],
+      "this page": ["esta página"],
+      Withdraw: ["Retirar"],
       "Or if you have the wallet in another device": [
-        ""
+        "O si tienes la billetera en otro dispositivo",
       ],
       "Scan the QR below to start the withdrawal.": [
-        "Chiudi il ritiro Taler"
-      ],
-      "required": [
-        ""
+        "Escanea el QR debajo para comenzar la extracción.",
       ],
+      required: ["requerido"],
       "IBAN should have just uppercased letters and numbers": [
-        ""
-      ],
-      "not valid": [
-        ""
-      ],
-      "should be greater than 0": [
-        ""
-      ],
-      "balance is not enough": [
-        ""
-      ],
-      "does not follow the pattern": [
-        ""
-      ],
-      "only \"IBAN\" target are supported": [
-        ""
-      ],
-      "use the \"amount\" parameter to specify the amount to be transferred": [
-        ""
-      ],
-      "the amount is not valid": [
-        ""
-      ],
-      "use the \"message\" parameter to specify a reference text for the 
transfer": [
-        ""
-      ],
-      "The request was invalid or the payto://-URI used unacceptable 
features.": [
-        ""
-      ],
+        "IBAN debería tener letras mayúsculas y números",
+      ],
+      "not valid": ["no válido"],
+      "should be greater than 0": ["Debería ser mas grande que 0"],
+      "balance is not enough": ["el saldo no es suficiente"],
+      "does not follow the pattern": ["no tiene un patrón valido"],
+      'only "IBAN" target are supported': [
+        'solo cuentas "IBAN" son soportadas',
+      ],
+      'use the "amount" parameter to specify the amount to be transferred': [
+        'usa el parámetro "amount" para indicar el monto a ser transferido',
+      ],
+      "the amount is not valid": ["el monto no es válido"],
+      'use the "message" parameter to specify a reference text for the 
transfer':
+        [
+          'usa el parámetro "message" para indicar un texto de referencia en 
la transferencia',
+        ],
+      "The request was invalid or the payto://-URI used unacceptable 
features.":
+        [
+          "El pedido era inválido o el URI payto:// usado tiene 
características inaceptables.",
+        ],
       "Not enough permission to complete the operation.": [
-        "La banca sta creando l'operazione..."
+        "Sin permisos suficientes para completar la operación.",
       ],
-      "The destination account \"%1$s\" was not found.": [
-        "Lista conti pubblici non trovata."
+      'The destination account "%1$s" was not found.': [
+        'La cuenta de destino "%1$s" no fue encontrada.',
       ],
       "The origin and the destination of the transfer can't be the same.": [
-        ""
-      ],
-      "Your balance is not enough.": [
-        ""
-      ],
-      "The origin account \"%1$s\" was not found.": [
-        "Lista conti pubblici non trovata."
-      ],
-      "Using a form": [
-        ""
+        "El origen y destino de la transferencia no puede ser la misma.",
       ],
-      "Import payto:// URI": [
-        ""
-      ],
-      "Recipient": [
-        ""
+      "Your balance is not enough.": ["El saldo no es suficiente."],
+      'The origin account "%1$s" was not found.': [
+        'La cuenta origen "%1$s" no fue encontrada.',
       ],
+      "Using a form": ["Usando un formulario"],
+      "Import payto:// URI": ["Importando un URI payto://"],
+      Recipient: ["Destinatario"],
       "IBAN of the recipient's account": [
-        ""
-      ],
-      "Transfer subject": [
-        "Trasferisci fondi a un altro conto di questa banca:"
-      ],
-      "subject": [
-        "Soggetto"
+        "Numero IBAN de la cuenta destinataria",
       ],
+      "Transfer subject": ["Asunto de transferencia"],
+      subject: ["asunto"],
       "some text to identify the transfer": [
-        ""
-      ],
-      "Amount": [
-        "Importo"
-      ],
-      "amount to transfer": [
-        "Somma da ritirare"
-      ],
-      "payto URI:": [
-        ""
+        "algún texto para identificar la transferencia",
       ],
+      Amount: ["Monto"],
+      "amount to transfer": ["monto a transferir"],
+      "payto URI:": ["payto URI:"],
       "uniform resource identifier of the target account": [
-        ""
+        "identificador de recurso uniforme de la cuenta destino",
       ],
       "payto://iban/[receiver-iban]?message=[subject]&amount=[%1$s:X.Y]": [
-        ""
-      ],
-      "Cancel": [
-        ""
-      ],
-      "Send": [
-        ""
-      ],
-      "Missing username": [
-        ""
-      ],
-      "Missing password": [
-        ""
-      ],
-      "Wrong credentials for \"%1$s\"": [
-        "Credenziali invalide."
-      ],
-      "Account not found": [
-        ""
-      ],
-      "Username": [
-        ""
-      ],
-      "username of the account": [
-        "Trasferisci fondi a un altro conto di questa banca:"
-      ],
-      "Password": [
-        ""
-      ],
-      "password of the account": [
-        "Storico dei conti pubblici"
-      ],
-      "Check": [
-        ""
-      ],
-      "Log in": [
-        ""
-      ],
-      "Register": [
-        "Registrati"
-      ],
-      "Wire transfer completed!": [
-        "Bonifico"
-      ],
+        "payto://iban/[iban-destinatario]?message=[asunto]&amount=[%1$s:X.Y]",
+      ],
+      Cancel: ["Cancelar"],
+      Send: ["Envíar"],
+      "Missing username": ["Falta nombre de usuario"],
+      "Missing password": ["Falta contraseña"],
+      'Wrong credentials for "%1$s"': ['Credenciales incorrectas para "%1$s"'],
+      "Account not found": ["Cuenta no encontrada"],
+      Username: ["Nombre de usuario"],
+      "username of the account": ["nombre de usuario de la cuenta"],
+      Password: ["Contraseña"],
+      "password of the account": ["contraseña de la cuenta"],
+      Check: ["Verificar"],
+      "Log in": ["Acceso"],
+      Register: ["Registrarse"],
+      "Wire transfer completed!": ["Transferencia bancaria completada!"],
       "The withdrawal has been aborted previously and can't be confirmed": [
-        ""
-      ],
-      "The withdrawal operation can't be confirmed before a wallet accepted 
the transaction.": [
-        ""
+        "La extracción fue abortada anteriormente y no puede ser confirmada",
       ],
+      "The withdrawal operation can't be confirmed before a wallet accepted 
the transaction.":
+        [
+          "La operación de extracción no puede ser confirmada antes de que una 
billetera acepte la transaccion.",
+        ],
       "Your balance is not enough for the operation.": [
-        ""
+        "El saldo no es suficiente para la operación.",
       ],
       "Confirm the withdrawal operation": [
-        "Conferma il ritiro"
-      ],
-      "Wire transfer details": [
-        "Bonifico"
+        "Confirme la operación de extracción",
       ],
+      "Wire transfer details": ["Detalle de transferencia bancaria"],
       "Taler Exchange operator's account": [
-        ""
+        "Cuenta del operador del Taler Exchange",
       ],
       "Taler Exchange operator's name": [
-        ""
-      ],
-      "Transfer": [
-        ""
-      ],
-      "Authentication required": [
-        ""
+        "Nombre del operador del Taler Exchange",
       ],
+      Transfer: ["Transferencia"],
+      "Authentication required": ["Autenticación requerida"],
       "This operation was created with other username": [
-        ""
-      ],
-      "Operation aborted": [
-        ""
-      ],
-      "The wire transfer to the Taler Exchange operator's account was aborted, 
your balance was not affected.": [
-        ""
+        "Esta operación fue creada con otro usuario",
       ],
+      "Operation aborted": ["Operación abortada"],
+      "The wire transfer to the Taler Exchange operator's account was aborted, 
your balance was not affected.":
+        [
+          "La transferencia bancaria a la cuenta del operador del Taler 
Exchange fue abortada, su saldo no fue afectado.",
+        ],
       "You can close this page now or continue to the account page.": [
-        ""
-      ],
-      "Continue": [
-        ""
-      ],
-      "Withdrawal confirmed": [
-        "Questo ritiro è stato annullato!"
-      ],
-      "The wire transfer to the Taler operator has been initiated. You will 
soon receive the requested amount in your Taler wallet.": [
-        ""
-      ],
-      "Done": [
-        ""
-      ],
-      "Operation canceled": [
-        ""
-      ],
-      "The operation is marked as 'selected' but some step in the withdrawal 
failed": [
-        ""
-      ],
+        "Ya puedes cerrar esta pagina or continuar a la página de estado de 
cuenta.",
+      ],
+      Continue: ["Continuar"],
+      "Withdrawal confirmed": ["La extracción fue confirmada"],
+      "The wire transfer to the Taler operator has been initiated. You will 
soon receive the requested amount in your Taler wallet.":
+        [
+          "La transferencia bancaria al operador Taler fue iniciada. Pronto 
recibirás el monto pedido en tu billetera Taler.",
+        ],
+      Done: ["Listo"],
+      "Operation canceled": ["Operación cancelada"],
+      "The operation is marked as 'selected' but some step in the withdrawal 
failed":
+        [
+          "La operación está marcada como 'seleccionada' pero algunos pasos en 
la extracción fallaron",
+        ],
       "The account is selected but no withdrawal identification found.": [
-        ""
-      ],
-      "There is a withdrawal identification but no account has been selected 
or the selected account is invalid.": [
-        ""
-      ],
-      "No withdrawal ID found and no account has been selected or the selected 
account is invalid.": [
-        ""
-      ],
-      "Operation not found": [
-        ""
-      ],
-      "This operation is not known by the server. The operation id is wrong or 
the server deleted the operation information before reaching here.": [
-        ""
-      ],
-      "Cotinue to dashboard": [
-        ""
-      ],
-      "The Withdrawal URI is not valid": [
-        "Questo ritiro è stato annullato!"
-      ],
-      "the bank backend is not supported. supported version \"%1$s\", server 
version \"%2$s\"": [
-        ""
-      ],
+        "La cuenta está seleccionada pero no se encontró el identificador de 
extracción.",
+      ],
+      "There is a withdrawal identification but no account has been selected 
or the selected account is invalid.":
+        [
+          "Hay un identificador de extracción pero la cuenta no ha sido 
seleccionada o la selccionada es inválida.",
+        ],
+      "No withdrawal ID found and no account has been selected or the selected 
account is invalid.":
+        [
+          "No hay un identificador de extracción y ninguna cuenta a sido 
seleccionada o la seleccionada es inválida.",
+        ],
+      "Operation not found": ["Operación no encontrada"],
+      "This operation is not known by the server. The operation id is wrong or 
the server deleted the operation information before reaching here.":
+        [
+          "Esta operación no es conocida por el servidor. El identificador de 
operación es incorrecto o el server borró la información de la operación antes 
de llegar hasta aquí.",
+        ],
+      "Cotinue to dashboard": ["Continuar al panel"],
+      "The Withdrawal URI is not valid": ["El URI de estracción no es válido"],
+      'the bank backend is not supported. supported version "%1$s", server 
version "%2$s"':
+        [
+          'El servidor de bank no esta spoportado. Version soportada "%1$s", 
version del server "%2$s"',
+        ],
       "Internal error, please report.": [
-        "Registrazione"
-      ],
-      "Preferences": [
-        ""
-      ],
-      "Welcome, %1$s": [
-        ""
-      ],
-      "Latest transactions": [
-        "Ultime transazioni:"
-      ],
-      "Date": [
-        "Data"
-      ],
-      "Counterpart": [
-        "Controparte"
-      ],
-      "Subject": [
-        "Soggetto"
-      ],
-      "sent": [
-        ""
-      ],
-      "received": [
-        ""
-      ],
-      "invalid value": [
-        ""
-      ],
-      "to": [
-        ""
-      ],
-      "from": [
-        ""
-      ],
-      "First page": [
-        ""
-      ],
-      "Next": [
-        ""
-      ],
-      "History of public accounts": [
-        "Storico dei conti pubblici"
-      ],
+        "Error interno, por favor reporte el error.",
+      ],
+      Preferences: ["Preferencias"],
+      "Welcome, %1$s": ["Bienvenido/a, %1$s"],
+      "Latest transactions": ["Últimas transacciones"],
+      Date: ["Fecha"],
+      Counterpart: ["Contraparte"],
+      Subject: ["Asunto"],
+      sent: ["enviado"],
+      received: ["recibido"],
+      "invalid value": ["valor inválido"],
+      to: ["hacia"],
+      from: ["desde"],
+      "First page": ["Primera página"],
+      Next: ["Siguiente"],
+      "History of public accounts": ["Historial de cuentas públicas"],
       "Currently, the bank is not accepting new registrations!": [
-        ""
-      ],
-      "Missing name": [
-        "indirizzo Payto"
+        "Actualmente, el banco no está aceptado nuevos registros!",
       ],
+      "Missing name": ["Falta nombre"],
       "Use letters and numbers only, and start with a lowercase letter": [
-        ""
-      ],
-      "Passwords don't match": [
-        ""
+        "Solo use letras y números, y comience con una letra minúscula",
       ],
+      "Passwords don't match": ["La contraseña no coincide"],
       "Server replied with invalid phone or email.": [
-        ""
+        "El servidor repondio con teléfono o dirección de correo inválido.",
       ],
       "Registration is disabled because the bank ran out of bonus credit.": [
-        ""
+        "El registro está deshabilitado porque el banco se quedó sin crédito 
bonus.",
       ],
       "No enough permission to create that account.": [
-        ""
+        "Sin permisos suficientes para crear esa cuenta.",
       ],
       "That account id is already taken.": [
-        ""
+        "El identificador de cuenta ya está tomado.",
       ],
       "That username is already taken.": [
-        ""
+        "El nombre de usuario ya está tomado.",
       ],
       "That username can't be used because is reserved.": [
-        ""
+        "El nombre de usuario no puede ser usado porque esta reservado.",
       ],
       "Only admin is allow to set debt limit.": [
-        ""
+        "Solo el administrador tiene permitido cambiar el límite de deuda.",
       ],
       "No information for the selected authentication channel.": [
-        ""
+        "No hay información para el canal de autenticación seleccionado.",
       ],
       "Authentication channel is not supported.": [
-        ""
+        "Canal de autenticación no esta soportado.",
       ],
       "Only admin can create accounts with second factor authentication.": [
-        ""
-      ],
-      "Account registration": [
-        ""
-      ],
-      "Repeat password": [
-        ""
-      ],
-      "Name": [
-        ""
-      ],
-      "Create a random temporary user": [
-        ""
-      ],
-      "Make a wire transfer": [
-        "Chiudi il bonifico"
-      ],
-      "Wire transfer created!": [
-        "Bonifico"
-      ],
-      "Accounts": [
-        "Importo"
-      ],
+        "Solo el administrador puede crear cuentas con el segundo factor de 
autenticación.",
+      ],
+      "Account registration": ["Registro de cuenta"],
+      "Repeat password": ["Repita la contraseña"],
+      Name: ["Nombre"],
+      "Create a random temporary user": ["Crear un usuario aleatorio 
temporal"],
+      "Make a wire transfer": ["Hacer una transferencia bancaria"],
+      "Wire transfer created!": ["Transferencia bancaria creada!"],
+      Accounts: ["Cuentas"],
       "A list of all business account in the bank.": [
-        ""
-      ],
-      "Create account": [
-        ""
-      ],
-      "Balance": [
-        ""
-      ],
-      "Actions": [
-        ""
-      ],
-      "unknown": [
-        ""
-      ],
-      "change password": [
-        ""
-      ],
-      "remove": [
-        ""
-      ],
-      "Select a section": [
-        ""
-      ],
-      "Last hour": [
-        ""
-      ],
-      "Last day": [
-        ""
-      ],
-      "Last month": [
-        ""
-      ],
-      "Last year": [
-        ""
-      ],
-      "Last Year": [
-        ""
-      ],
+        "Una lista de todas las cuentas en el banco.",
+      ],
+      "Create account": ["Crear cuenta"],
+      Balance: ["Saldo"],
+      Actions: ["Acciones"],
+      unknown: ["desconocido"],
+      "change password": ["cambiar contraseña"],
+      remove: ["elimiar"],
+      "Select a section": ["Seleccione una sección"],
+      "Last hour": ["Última hora"],
+      "Last day": ["Último día"],
+      "Last month": ["Último mes"],
+      "Last year": ["Último año"],
+      "Last Year": ["Último Año"],
       "Trading volume on %1$s compared to %2$s": [
-        ""
-      ],
-      "Cashin": [
-        ""
-      ],
-      "Cashout": [
-        ""
-      ],
-      "Payin": [
-        ""
-      ],
-      "Payout": [
-        ""
-      ],
-      "download stats as CSV": [
-        ""
-      ],
-      "Descreased by": [
-        ""
-      ],
-      "Increased by": [
-        ""
-      ],
-      "Unable to create a cashout": [
-        ""
-      ],
+        "Vólumen de comercio en %1$s comparado con %2$s",
+      ],
+      Cashin: ["Ingreso"],
+      Cashout: ["Egreso"],
+      Payin: ["Envios de dinero"],
+      Payout: ["Recibos de dinero"],
+      "download stats as CSV": ["descargar estadísticas en CSV"],
+      "Descreased by": ["Descendiente por"],
+      "Increased by": ["Ascendente por"],
+      "Unable to create a cashout": ["Imposible crear un egreso"],
       "The bank configuration does not support cashout operations.": [
-        ""
-      ],
-      "invalid": [
-        ""
+        "La configuración del banco no soporta operaciones de egreso.",
       ],
+      invalid: ["inválido"],
       "need to be higher due to fees": [
-        ""
+        "necesita ser mayor debido a las comisiones",
       ],
       "the total transfer at destination will be zero": [
-        ""
-      ],
-      "Cashout created": [
-        ""
-      ],
-      "Duplicated request detected, check if the operation succeded or try 
again.": [
-        ""
+        "el total de la transferencia en destino será cero",
       ],
+      "Cashout created": ["Egreso creado"],
+      "Duplicated request detected, check if the operation succeded or try 
again.":
+        [
+          "Se detectó una petición duplicada, verifique si la operación tuvo 
éxito o intente otra vez.",
+        ],
       "The conversion rate was incorrectly applied": [
-        ""
+        "La tasa de conversión se aplicó incorrectamente",
       ],
       "The account does not have sufficient funds": [
-        ""
-      ],
-      "Cashouts are not supported": [
-        ""
+        "La cuenta no tiene fondos suficientes",
       ],
+      "Cashouts are not supported": ["Egresos no están soportados"],
       "Missing cashout URI in the profile": [
-        ""
-      ],
-      "Sending the confirmation message failed, retry later or contact the 
administrator.": [
-        ""
-      ],
-      "Convertion rate": [
-        ""
-      ],
-      "Fee": [
-        ""
-      ],
-      "To account": [
-        ""
-      ],
-      "No cashout account": [
-        ""
-      ],
+        "Falta dirección de egreso en el perfíl",
+      ],
+      "Sending the confirmation message failed, retry later or contact the 
administrator.":
+        [
+          "El envío del mensaje de confirmación falló, intente mas tarde o 
contacte al administrador.",
+        ],
+      "Convertion rate": ["Tasa de conversión"],
+      Fee: ["Comisión"],
+      "To account": ["Hacia cuenta"],
+      "No cashout account": ["No hay cuenta de egreso"],
       "Before doing a cashout you need to complete your profile": [
-        ""
-      ],
-      "Amount to send": [
-        "Somma da ritirare"
-      ],
-      "Amount to receive": [
-        "Somma da ritirare"
-      ],
-      "Total cost": [
-        ""
-      ],
-      "Balance left": [
-        ""
-      ],
-      "Before fee": [
-        ""
-      ],
-      "Total cashout transfer": [
-        ""
-      ],
-      "No cashout channel available": [
-        ""
-      ],
-      "Before doing a cashout the server need to provide an second channel to 
confirm the operation": [
-        ""
-      ],
-      "Second factor authentication": [
-        ""
-      ],
-      "Email": [
-        ""
-      ],
+        "Antes de hacer un egreso necesita completar su perfíl",
+      ],
+      "Amount to send": ["Monto a enviar"],
+      "Amount to receive": ["Monto a recibir"],
+      "Total cost": ["Costo total"],
+      "Balance left": ["Saldo remanente"],
+      "Before fee": ["Antes de comisión"],
+      "Total cashout transfer": ["Total de egreso"],
+      "No cashout channel available": ["No hay canal de egreso disponible"],
+      "Before doing a cashout the server need to provide an second channel to 
confirm the operation":
+        [
+          "Antes de hacer un egreso el servidor necesita proveer un segundo 
canal para confirmar la operación",
+        ],
+      "Second factor authentication": ["Segundo factor de autenticación"],
+      Email: ["Correo eletrónico"],
       "add a email in your profile to enable this option": [
-        ""
-      ],
-      "SMS": [
-        ""
+        "agrege un correo en su perfíl para habilitar esta opción",
       ],
+      SMS: ["SMS"],
       "add a phone number in your profile to enable this option": [
-        ""
-      ],
-      "Details": [
-        ""
-      ],
-      "Delete": [
-        ""
-      ],
-      "Credentials": [
-        "Credenziali invalide."
-      ],
-      "Cashouts": [
-        ""
+        "agregue un número de teléfono para habilitar esta opción",
       ],
+      Details: ["Detalles"],
+      Delete: ["Borrar"],
+      Credentials: ["Credenciales"],
+      Cashouts: ["Egresos"],
       "it doesnt have the pattern of an IBAN number": [
-        ""
+        "no tiene el patrón de un número IBAN",
       ],
       "it doesnt have the pattern of an email": [
-        ""
-      ],
-      "should start with +": [
-        ""
+        "no tiene el patrón de un correo electrónico",
       ],
+      "should start with +": ["debería comenzar con un +"],
       "phone number can't have other than numbers": [
-        ""
+        "número de teléfono no puede tener otra cosa que numeros",
       ],
       "account identification in the bank": [
-        ""
+        "identificador de cuenta en el banco",
       ],
       "name of the person owner the account": [
-        ""
-      ],
-      "Internal IBAN": [
-        ""
+        "nombre de la persona dueña de la cuenta",
       ],
+      "Internal IBAN": ["IBAN interno"],
       "if empty a random account number will be assigned": [
-        ""
+        "si está vacío un número de cuenta aleatorio será asignado",
       ],
       "account identification for bank transfer": [
-        ""
-      ],
-      "Phone": [
-        ""
-      ],
-      "Cashout IBAN": [
-        ""
-      ],
-      "account number where the money is going to be sent when doing 
cashouts": [
-        ""
-      ],
-      "Max debt": [
-        ""
-      ],
+        "identificador de cuenta para transferencia bancaria",
+      ],
+      Phone: ["Teléfono"],
+      "Cashout IBAN": ["IBAN de egreso"],
+      "account number where the money is going to be sent when doing cashouts":
+        [
+          "numero de cuenta donde el dinero será enviado cuando se ejecuten 
egresos",
+        ],
+      "Max debt": ["Máxima deuda"],
       "how much is user able to transfer after zero balance": [
-        ""
-      ],
-      "Is this a Taler Exchange?": [
-        ""
+        "cuanto tiene habilitado a transferir despues de un saldo en cero",
       ],
+      "Is this a Taler Exchange?": ["Es un Taler Exchange?"],
       "This server doesn't support second factor authentication.": [
-        ""
+        "Este servidor no tiene soporte para segundo factor de autenticación.",
       ],
       "Enable second factor authentication": [
-        ""
-      ],
-      "Using email": [
-        ""
-      ],
-      "Using SMS": [
-        ""
-      ],
-      "Is this account public?": [
-        ""
+        "Hábilitar segundo factor de autenticación",
       ],
+      "Using email": ["Usando correo eletrónico"],
+      "Using SMS": ["Usando SMS"],
+      "Is this account public?": ["Es una cuenta pública?"],
       "public accounts have their balance publicly accesible": [
-        ""
-      ],
-      "Account updated": [
-        ""
+        "las cuentas públicas tienen su saldo accesible al público",
       ],
+      "Account updated": ["Cuenta actualizada"],
       "The rights to change the account are not sufficient": [
-        ""
-      ],
-      "The username was not found": [
-        ""
-      ],
-      "You can't change the legal name, please contact the your account 
administrator.": [
-        ""
-      ],
-      "You can't change the debt limit, please contact the your account 
administrator.": [
-        ""
-      ],
-      "You can't change the cashout address, please contact the your account 
administrator.": [
-        ""
-      ],
-      "You can't change the contact data, please contact the your account 
administrator.": [
-        ""
-      ],
-      "Account \"%1$s\"": [
-        ""
-      ],
-      "Change details": [
-        ""
-      ],
-      "Update": [
-        ""
-      ],
-      "password doesn't match": [
-        ""
-      ],
-      "Password changed": [
-        ""
-      ],
+        "Los permisos para cambiar la cuenta no son suficientes",
+      ],
+      "The username was not found": ["El nombre de usaurio no se encontró"],
+      "You can't change the legal name, please contact the your account 
administrator.":
+        [
+          "No puede cambiar el nombre legal, por favor contacte el 
administrador de la cuenta.",
+        ],
+      "You can't change the debt limit, please contact the your account 
administrator.":
+        [
+          "No puede cambiar el límite de deuda, por favor contacte el 
administrador de la cuenta.",
+        ],
+      "You can't change the cashout address, please contact the your account 
administrator.":
+        [
+          "No puede cambiar la dirección de egreso, por favor contacte al 
administrador de la cuenta.",
+        ],
+      "You can't change the contact data, please contact the your account 
administrator.":
+        [
+          "No puede cambiar los datos de contacto, por favor contacte al 
administrador de la cuenta.",
+        ],
+      'Account "%1$s"': ['Cuenta "%1$s"'],
+      "Change details": ["Cambiar detalles"],
+      Update: ["Actualizar"],
+      "password doesn't match": ["la contraseña no coincide"],
+      "Password changed": ["La contraseña cambió"],
       "Not authorized to change the password, maybe the session is invalid.": [
-        ""
-      ],
-      "You need to provide the old password. If you don't have it contact your 
account administrator.": [
-        ""
+        "No está autorizado a cambiar el password, quizá la sesión es 
invalida.",
       ],
+      "You need to provide the old password. If you don't have it contact your 
account administrator.":
+        [
+          "Se necesita el password viejo para cambiar la contraseña. Si no lo 
tiene contacte a su administrador.",
+        ],
       "Your current password doesn't match, can't change to a new password.": [
-        ""
-      ],
-      "Update password": [
-        ""
-      ],
-      "New password": [
-        ""
-      ],
-      "Type it again": [
-        ""
-      ],
-      "repeat the same password": [
-        ""
-      ],
-      "Current password": [
-        ""
+        "Su actual contraseña no coincide, no puede cambiar a una nueva 
contraseña.",
       ],
+      "Update password": ["Actualizar contraseña"],
+      "New password": ["Nueva contraseña"],
+      "Type it again": ["Escribalo otra vez"],
+      "repeat the same password": ["repita la misma contraseña"],
+      "Current password": ["Contraseña actual"],
       "your current password, for security": [
-        ""
-      ],
-      "Change": [
-        ""
-      ],
-      "Can't delete the account": [
-        ""
-      ],
-      "The account can't be delete while still holding some balance. First 
make sure that the owner make a complete cashout.": [
-        ""
-      ],
-      "Account removed": [
-        ""
-      ],
+        "su actual contraseña, por seguridad",
+      ],
+      Change: ["Cambiar"],
+      "Can't delete the account": ["No se puede eliminar la cuenta"],
+      "The account can't be delete while still holding some balance. First 
make sure that the owner make a complete cashout.":
+        [
+          "La cuenta no puede ser eliminada mientras tiene saldo. Primero 
aseguresé que el dueño haga un egreso completo.",
+        ],
+      "Account removed": ["Cuenta eliminada"],
       "No enough permission to delete the account.": [
-        ""
-      ],
-      "The username was not found.": [
-        ""
+        "No tiene permisos suficientes para eliminar la cuenta.",
       ],
+      "The username was not found.": ["El nombr ede usuario no se encontró."],
       "Can't delete a reserved username.": [
-        ""
+        "No se puede eliminar un nombre de usuario reservado.",
       ],
       "Can't delete an account with balance different than zero.": [
-        ""
-      ],
-      "name doesn't match": [
-        ""
-      ],
-      "You are going to remove the account": [
-        ""
-      ],
-      "This step can't be undone.": [
-        ""
-      ],
-      "Deleting account \"%1$s\"": [
-        ""
-      ],
-      "Verification": [
-        ""
+        "No se puede eliminar una cuenta con saldo diferente a cero.",
       ],
+      "name doesn't match": ["el nombre no coincide"],
+      "You are going to remove the account": ["Está por eliminar la cuenta"],
+      "This step can't be undone.": ["Este paso no puede ser deshecho."],
+      'Deleting account "%1$s"': ['Borrando cuenta "%1$s"'],
+      Verification: ["Verificación"],
       "enter the account name that is going to be deleted": [
-        ""
-      ],
-      "Account created with password \"%1$s\". The user must change the 
password on the next login.": [
-        ""
+        "ingrese el nombre de cuenta que será eliminado",
       ],
+      'Account created with password "%1$s". The user must change the password 
on the next login.':
+        [
+          'Cuenta creada con contraseña "%1$s". El usuario debe cambiar la 
contraseña en el siguiente ingreso.',
+        ],
       "Server replied that phone or email is invalid": [
-        ""
+        "El servidor respondió que el teléfono o correo eletrónico es 
invalido",
       ],
       "The rights to perform the operation are not sufficient": [
-        ""
+        "Los permisos para ejecutar la operación no son suficientes",
       ],
       "Account username is already taken": [
-        ""
-      ],
-      "Account id is already taken": [
-        ""
+        "El nombre del usuario ya está tomado",
       ],
+      "Account id is already taken": ["El id de cuenta ya está tomado"],
       "Bank ran out of bonus credit.": [
-        ""
+        "El banco no tiene mas crédito de bonus.",
       ],
       "Account username can't be used because is reserved": [
-        ""
-      ],
-      "Can't create accounts": [
-        ""
+        "El nombre de usuario de la cuenta no puede userse porque está 
reservado",
       ],
+      "Can't create accounts": ["No puede crear cuentas"],
       "Only system admin can create accounts.": [
-        ""
-      ],
-      "New business account": [
-        ""
-      ],
-      "Create": [
-        ""
-      ],
-      "Cashout not supported.": [
-        ""
-      ],
-      "Account not found.": [
-        "Lista conti pubblici non trovata."
-      ],
-      "Latest cashouts": [
-        "Ultime transazioni:"
-      ],
-      "Created": [
-        ""
-      ],
-      "Confirmed": [
-        "Conferma"
-      ],
-      "Total debit": [
-        ""
-      ],
-      "Total credit": [
-        ""
-      ],
-      "Status": [
-        ""
-      ],
-      "never": [
-        ""
-      ],
-      "Cashout for account %1$s": [
-        ""
-      ],
+        "Solo los administradores del sistema pueden crear cuentas.",
+      ],
+      "New business account": ["Nueva cuenta"],
+      Create: ["Crear"],
+      "Cashout not supported.": ["Egreso no soportado."],
+      "Account not found.": ["Cuenta no encontrada."],
+      "Latest cashouts": ["Últimos egresos"],
+      Created: ["Creado"],
+      Confirmed: ["Confirmado"],
+      "Total debit": ["Débito total"],
+      "Total credit": ["Crédito total"],
+      Status: ["Estado"],
+      never: ["nunca"],
+      "Cashout for account %1$s": ["Egreso para cuenta %1$s"],
       "This cashout not found. Maybe already aborted.": [
-        ""
+        "Este egreso no se encontró. Quizá fue abortado.",
       ],
       "Cashout not found. It may be also mean that it was already aborted.": [
-        ""
-      ],
-      "Cashout was already confimed.": [
-        ""
+        "Egreso no econtrado. También puede significar que ya ha sido 
abortado.",
       ],
+      "Cashout was already confimed.": ["Egreso ya fue confirmado."],
       "Cashout operation is not supported.": [
-        ""
+        "Operación de egreso no soportada.",
       ],
       "The cashout operation is already aborted.": [
-        ""
-      ],
-      "Missing destination account.": [
-        ""
-      ],
-      "Too many failed attempts.": [
-        ""
+        "La operación de egreso ya está abortada.",
       ],
+      "Missing destination account.": ["Falta cuenta destino."],
+      "Too many failed attempts.": ["Demasiados intentos fallidos."],
       "The code for this cashout is invalid.": [
-        ""
+        "El código para este egreso es invalido.",
+      ],
+      "Cashout detail": ["Detalles de egreso"],
+      Debited: ["Debitado"],
+      Credited: ["Acreditado"],
+      "Enter the confirmation code": ["Ingresar el código de confirmación"],
+      Abort: ["Abortar"],
+      Confirm: ["Confirmar"],
+      "Unauthorized to make the operation, maybe the session has expired or 
the password changed.":
+        [
+          "No autorizado para hacer la operación, quizá la sesión haya 
expirado or cambió la contraseña.",
+        ],
+      "The operation was rejected due to insufficient funds.": [
+        "La operación fue rechazada debido a saldo insuficiente.",
+      ],
+      "Do not show this again": ["No mostrar otra vez"],
+      Close: ["Cerrar"],
+      "On this device": ["En este dispositivo"],
+      'If you are using a web browser on desktop you should access your wallet 
with the GNU Taler WebExtension now or click the link if your WebExtension have 
the "Inject Taler support" option enabled.':
+        [
+          'Si esta usando un explorador web de escritorio deberías acceder 
ahora a tu billletera con la GNU Taler WebExtension o hacer click en el link si 
tu extensión tiene la configuración "Inyectar soporte para Taler" habilitada.',
+        ],
+      Start: ["Comenzar"],
+      "On a mobile phone": ["En un dispotivo mobile"],
+      "Scan the QR code with your mobile device.": [
+        "Escanear el código QR con tu dispotivo móvil.",
       ],
-      "Cashout detail": [
-        ""
+      "There is an operation already": ["Ya hay una operación"],
+      "Complete or cancel the operation in": [
+        "Completa o cancela la operación en",
       ],
-      "Debited": [
-        ""
+      "Server responded with an invalid  withdraw URI": [
+        "El servidor respondió con una URI de extracción inválida",
       ],
-      "Credited": [
-        ""
+      "Withdraw URI: %1$s": ["URI de extracción: %1$s"],
+      "The operation was rejected due to insufficient funds": [
+        "La operación fue rechazada debido a fundos insuficientes",
+      ],
+      "Prepare your wallet": ["Prepare su billetera"],
+      "After using your wallet you will need to confirm or cancel the 
operation on this site.":
+        [
+          "Despues de usar tu billetera necesitarás confirmar o cancelar la 
operación en este sitio.",
+        ],
+      "You need a GNU Taler Wallet": ["Necesitas una GNU Taler Wallet"],
+      "If you don't have one yet you can follow the instruction in": [
+        "Si no tienes una todavía puedes seguir las instrucciones en",
       ],
-      "Enter the confirmation code": [
-        ""
+      "Send money": ["Enviar dinero"],
+      "to a %1$s wallet": ["a una billetera %1$s"],
+      "Withdraw digital money into your mobile wallet or browser extension": [
+        "Extraer dinero digital a tu billetera móvil o extesión web",
       ],
-      "Abort": [
-        "Annulla"
+      "operation ready": ["operación lista"],
+      "to another bank account": ["a otra cuenta bancaria"],
+      "Make a wire transfer to an account with known bank account number.": [
+        "Hacer una transferencia bancaria a una cuenta con un número de cuenta 
conocido.",
+      ],
+      "Transfer details": ["Detalles de transferencia"],
+      "This is a demo bank": ["Este es un banco de demostración"],
+      "This part of the demo shows how a bank that supports Taler directly 
would work. In addition to using your own bank account, you can also see the 
transaction history of some %1$s.":
+        [
+          "Esta parte de la demostración muestra cómo funciona un banco que 
soporta Taler directamente. Además de usar tu propia cuenta de banco, también 
podrás ver el historial de transacciones de algunas %1$s.",
+        ],
+      "This part of the demo shows how a bank that supports Taler directly 
would work.":
+        [
+          "Esta parte de la demostración muetra como un banco que soporta 
Taler directamente funcionaría.",
+        ],
+      "Pending account delete operation": [
+        "Operación pendiente de eliminación de cuenta",
       ],
-      "Confirm": [
-        "Conferma"
+      "Pending account update operation": [
+        "Operación pendiente de actualización de cuenta",
       ],
-      "Unauthorized to make the operation, maybe the session has expired or 
the password changed.": [
-        ""
+      "Pending password update operation": [
+        "Operación pendiente de actualización de password",
       ],
-      "The operation was rejected due to insufficient funds.": [
-        ""
+      "Pending transaction operation": ["Operación pendiente de transacción"],
+      "Pending withdrawal operation": ["Operación pendiente de extracción"],
+      "Pending cashout operation": ["Operación pendiente de egreso"],
+      "You can complete or cancel the operation in": [
+        "Puedes completar o cancelar la operación en",
+      ],
+      "Download bank stats": ["Descargar estadísticas del banco"],
+      "Include hour metric": ["Incluir métrica horaria"],
+      "Include day metric": ["Incluir métrica diaria"],
+      "Include month metric": ["Incluir métrica mensual"],
+      "Include year metric": ["Incluir métrica anual"],
+      "Include table header": ["Incluir encabezado de tabla"],
+      "Add previous metric for compare": [
+        "Agregar métrica previa para comparar",
       ],
-      "Do not show this again": [
-        ""
-      ],
-      "Close": [
-        ""
-      ],
-      "On this device": [
-        ""
-      ],
-      "If you are using a web browser on desktop you should access your wallet 
with the GNU Taler WebExtension now or click the link if your WebExtension have 
the \"Inject Taler support\" option enabled.": [
-        ""
-      ],
-      "Start": [
-        ""
-      ],
-      "On a mobile phone": [
-        ""
-      ],
-      "Scan the QR code with your mobile device.": [
-        "Usa questo codice QR per ritirare contante nel tuo wallet:"
-      ],
-      "There is an operation already": [
-        ""
-      ],
-      "Complete or cancel the operation in": [
-        "Conferma il ritiro"
-      ],
-      "Server responded with an invalid  withdraw URI": [
-        ""
-      ],
-      "Withdraw URI: %1$s": [
-        "Prelevare"
-      ],
-      "The operation was rejected due to insufficient funds": [
-        ""
-      ],
-      "Prepare your wallet": [
-        ""
-      ],
-      "After using your wallet you will need to confirm or cancel the 
operation on this site.": [
-        ""
-      ],
-      "You need a GNU Taler Wallet": [
-        "Ritira contante nel portafoglio Taler"
-      ],
-      "If you don't have one yet you can follow the instruction in": [
-        ""
-      ],
-      "Send money": [
-        ""
-      ],
-      "to a %1$s wallet": [
-        ""
-      ],
-      "Withdraw digital money into your mobile wallet or browser extension": [
-        ""
-      ],
-      "operation ready": [
-        ""
-      ],
-      "to another bank account": [
-        "Trasferisci fondi a un altro conto di questa banca:"
-      ],
-      "Make a wire transfer to an account with known bank account number.": [
-        ""
-      ],
-      "Transfer details": [
-        "Effettua un bonifico"
-      ],
-      "This is a demo bank": [
-        ""
-      ],
-      "This part of the demo shows how a bank that supports Taler directly 
would work. In addition to using your own bank account, you can also see the 
transaction history of some %1$s.": [
-        ""
-      ],
-      "This part of the demo shows how a bank that supports Taler directly 
would work.": [
-        ""
-      ],
-      "Pending account delete operation": [
-        ""
-      ],
-      "Pending account update operation": [
-        ""
-      ],
-      "Pending password update operation": [
-        ""
-      ],
-      "Pending transaction operation": [
-        ""
-      ],
-      "Pending withdrawal operation": [
-        ""
-      ],
-      "Pending cashout operation": [
-        ""
-      ],
-      "You can complete or cancel the operation in": [
-        ""
-      ],
-      "Download bank stats": [
-        ""
-      ],
-      "Include hour metric": [
-        ""
-      ],
-      "Include day metric": [
-        ""
-      ],
-      "Include month metric": [
-        ""
-      ],
-      "Include year metric": [
-        ""
-      ],
-      "Include table header": [
-        ""
-      ],
-      "Add previous metric for compare": [
-        ""
-      ],
-      "Fail on first error": [
-        ""
-      ],
-      "Download": [
-        ""
-      ],
-      "downloading... %1$s": [
-        ""
-      ],
-      "Download completed": [
-        ""
-      ],
-      "click here to save the file in your computer": [
-        ""
-      ],
-      "Challenge not found.": [
-        ""
+      "Fail on first error": ["Fallar en el primer error"],
+      Download: ["Descargar"],
+      "downloading... %1$s": ["descargando... %1$s"],
+      "Download completed": ["Descarga completada"],
+      "click here to save the file in your computer": [
+        "click aquí para guardar el archivo en su computadora",
       ],
+      "Challenge not found.": ["Desafío no encontrado."],
       "This user is not authorized to complete this challenge.": [
-        ""
+        "Este usuario no está autorizado para completar este desafío.",
       ],
       "Too many attemps, try another code.": [
-        ""
+        "Demasiados intentos, intente otro código.",
       ],
       "The confirmation code is wrong, try again.": [
-        ""
-      ],
-      "The operation expired.": [
-        ""
-      ],
-      "The operation failed.": [
-        "Questo ritiro è stato annullato!"
+        "El código de confirmación es erroneo, intente otra vez.",
       ],
+      "The operation expired.": ["La operación expiró."],
+      "The operation failed.": ["La operación falló."],
       "The operation needs another confirmation to complete.": [
-        ""
-      ],
-      "Account delete": [
-        ""
-      ],
-      "Account update": [
-        ""
-      ],
-      "Password update": [
-        ""
-      ],
-      "Wire transfer": [
-        "Bonifico"
-      ],
-      "Withdrawal": [
-        "Prelevare"
-      ],
-      "Confirm the operation": [
-        "Conferma il ritiro"
-      ],
-      "Send again": [
-        ""
-      ],
-      "Send code": [
-        ""
-      ],
-      "Operation details": [
-        ""
-      ],
-      "Challenge details": [
-        ""
-      ],
-      "Sent at": [
-        ""
-      ],
-      "To phone": [
-        ""
-      ],
-      "To email": [
-        ""
-      ],
-      "Welcome to %1$s!": [
-        ""
-      ]
-    }
+        "La operación necesita otra confirmación para completar.",
+      ],
+      "Account delete": ["Eliminación de cuenta"],
+      "Account update": ["Actualización de cuenta"],
+      "Password update": ["Actualización de contraseña"],
+      "Wire transfer": ["Transferencia bancaria"],
+      Withdrawal: ["Extracción"],
+      "Confirm the operation": ["Confirmar la operación"],
+      "Send again": ["Enviar otra vez"],
+      "Send code": ["Enviar código"],
+      "Operation details": ["Detalles de operación"],
+      "Challenge details": ["Detalles del desafío"],
+      "Sent at": ["Enviado a"],
+      "To phone": ["Al teléfono"],
+      "To email": ["Al email"],
+      "Welcome to %1$s!": ["Bienvenido a %1$s!"],
+    },
   },
-  "domain": "messages",
-  "plural_forms": "nplurals=2; plural=n != 1;",
-  "lang": "it",
-  "completeness": 14
+  domain: "messages",
+  plural_forms: "nplurals=2; plural=n != 1;",
+  lang: "es",
+  completeness: 100,
 };
 
-strings['fr'] = {
-  "locale_data": {
-    "messages": {
+strings["en"] = {
+  locale_data: {
+    messages: {
       "": {
-        "domain": "messages",
-        "plural_forms": "nplurals=2; plural=n > 1;",
-        "lang": "fr"
+        domain: "messages",
+        plural_forms: "nplurals=2; plural=(n != 1);",
+        lang: "en",
       },
-      "Operation failed, please report": [
-        ""
-      ],
-      "Request timeout": [
-        ""
-      ],
-      "Request throttled": [
-        ""
-      ],
-      "Malformed response": [
-        ""
-      ],
-      "Network error": [
-        ""
-      ],
-      "Unexpected request error": [
-        ""
-      ],
-      "Unexpected error": [
-        ""
-      ],
-      "IBAN numbers usually have more that 4 digits": [
-        ""
-      ],
-      "IBAN numbers usually have less that 34 digits": [
-        ""
-      ],
-      "IBAN country code not found": [
-        ""
-      ],
-      "IBAN number is not valid, checksum is wrong": [
-        ""
-      ],
-      "Max withdrawal amount": [
-        ""
-      ],
-      "Show withdrawal confirmation": [
-        ""
-      ],
-      "Show demo description": [
-        ""
-      ],
-      "Show install wallet first": [
-        ""
-      ],
-      "Use fast withdrawal form": [
-        ""
-      ],
-      "Show debug info": [
-        ""
-      ],
-      "The reserve operation has been confirmed previously and can't be 
aborted": [
-        ""
-      ],
-      "The operation id is invalid.": [
-        ""
-      ],
-      "The operation was not found.": [
-        ""
-      ],
-      "If you have a Taler wallet installed in this device": [
-        ""
-      ],
-      "You will see the details of the operation in your wallet including the 
fees (if applies). If you still don't have one you can install it following 
instructions in": [
-        ""
-      ],
-      "this page": [
-        ""
-      ],
-      "Withdraw": [
-        ""
-      ],
-      "Or if you have the wallet in another device": [
-        ""
-      ],
-      "Scan the QR below to start the withdrawal.": [
-        ""
-      ],
-      "required": [
-        ""
-      ],
-      "IBAN should have just uppercased letters and numbers": [
-        ""
-      ],
-      "not valid": [
-        ""
-      ],
-      "should be greater than 0": [
-        ""
-      ],
-      "balance is not enough": [
-        ""
-      ],
-      "does not follow the pattern": [
-        ""
-      ],
-      "only \"IBAN\" target are supported": [
-        ""
-      ],
-      "use the \"amount\" parameter to specify the amount to be transferred": [
-        ""
-      ],
-      "the amount is not valid": [
-        ""
-      ],
-      "use the \"message\" parameter to specify a reference text for the 
transfer": [
-        ""
-      ],
-      "The request was invalid or the payto://-URI used unacceptable 
features.": [
-        ""
-      ],
-      "Not enough permission to complete the operation.": [
-        ""
-      ],
-      "The destination account \"%1$s\" was not found.": [
-        ""
-      ],
-      "The origin and the destination of the transfer can't be the same.": [
-        ""
-      ],
-      "Your balance is not enough.": [
-        ""
-      ],
-      "The origin account \"%1$s\" was not found.": [
-        ""
-      ],
-      "Using a form": [
-        ""
-      ],
-      "Import payto:// URI": [
-        ""
-      ],
-      "Recipient": [
-        ""
-      ],
-      "IBAN of the recipient's account": [
-        ""
-      ],
-      "Transfer subject": [
-        ""
-      ],
-      "subject": [
-        ""
-      ],
-      "some text to identify the transfer": [
-        ""
-      ],
-      "Amount": [
-        ""
-      ],
-      "amount to transfer": [
-        ""
-      ],
-      "payto URI:": [
-        ""
-      ],
-      "uniform resource identifier of the target account": [
-        ""
-      ],
-      "payto://iban/[receiver-iban]?message=[subject]&amount=[%1$s:X.Y]": [
-        ""
-      ],
-      "Cancel": [
-        ""
-      ],
-      "Send": [
-        ""
-      ],
-      "Missing username": [
-        ""
-      ],
-      "Missing password": [
-        ""
-      ],
-      "Wrong credentials for \"%1$s\"": [
-        ""
-      ],
-      "Account not found": [
-        ""
-      ],
-      "Username": [
-        ""
-      ],
-      "username of the account": [
-        ""
-      ],
-      "Password": [
-        ""
-      ],
-      "password of the account": [
-        ""
-      ],
-      "Check": [
-        ""
-      ],
-      "Log in": [
-        ""
-      ],
-      "Register": [
-        ""
-      ],
-      "Wire transfer completed!": [
-        ""
-      ],
-      "The withdrawal has been aborted previously and can't be confirmed": [
-        ""
-      ],
-      "The withdrawal operation can't be confirmed before a wallet accepted 
the transaction.": [
-        ""
-      ],
-      "Your balance is not enough for the operation.": [
-        ""
-      ],
-      "Confirm the withdrawal operation": [
-        ""
-      ],
-      "Wire transfer details": [
-        ""
-      ],
-      "Taler Exchange operator's account": [
-        ""
-      ],
-      "Taler Exchange operator's name": [
-        ""
-      ],
-      "Transfer": [
-        ""
-      ],
-      "Authentication required": [
-        ""
-      ],
-      "This operation was created with other username": [
-        ""
-      ],
-      "Operation aborted": [
-        ""
-      ],
-      "The wire transfer to the Taler Exchange operator's account was aborted, 
your balance was not affected.": [
-        ""
-      ],
-      "You can close this page now or continue to the account page.": [
-        ""
-      ],
-      "Continue": [
-        ""
-      ],
-      "Withdrawal confirmed": [
-        ""
-      ],
-      "The wire transfer to the Taler operator has been initiated. You will 
soon receive the requested amount in your Taler wallet.": [
-        ""
-      ],
-      "Done": [
-        ""
-      ],
-      "Operation canceled": [
-        ""
-      ],
-      "The operation is marked as 'selected' but some step in the withdrawal 
failed": [
-        ""
-      ],
-      "The account is selected but no withdrawal identification found.": [
-        ""
-      ],
-      "There is a withdrawal identification but no account has been selected 
or the selected account is invalid.": [
-        ""
-      ],
-      "No withdrawal ID found and no account has been selected or the selected 
account is invalid.": [
-        ""
-      ],
-      "Operation not found": [
-        ""
-      ],
-      "This operation is not known by the server. The operation id is wrong or 
the server deleted the operation information before reaching here.": [
-        ""
-      ],
-      "Cotinue to dashboard": [
-        ""
-      ],
-      "The Withdrawal URI is not valid": [
-        ""
-      ],
-      "the bank backend is not supported. supported version \"%1$s\", server 
version \"%2$s\"": [
-        ""
-      ],
-      "Internal error, please report.": [
-        ""
-      ],
-      "Preferences": [
-        ""
-      ],
-      "Welcome, %1$s": [
-        ""
-      ],
-      "Latest transactions": [
-        ""
-      ],
-      "Date": [
-        ""
-      ],
-      "Counterpart": [
-        ""
-      ],
-      "Subject": [
-        ""
-      ],
-      "sent": [
-        ""
-      ],
-      "received": [
-        ""
-      ],
-      "invalid value": [
-        ""
-      ],
-      "to": [
-        ""
-      ],
-      "from": [
-        ""
-      ],
-      "First page": [
-        ""
+      "Operation failed, please report": [""],
+      "Request timeout": [""],
+      "Request throttled": [""],
+      "Malformed response": [""],
+      "Network error": [""],
+      "Unexpected request error": [""],
+      "Unexpected error": [""],
+      "IBAN numbers usually have more that 4 digits": [""],
+      "IBAN numbers usually have less that 34 digits": [""],
+      "IBAN country code not found": [""],
+      "IBAN number is not valid, checksum is wrong": [""],
+      "Max withdrawal amount": ["Start withdrawal"],
+      "Show withdrawal confirmation": [""],
+      "Show demo description": [""],
+      "Show install wallet first": [""],
+      "Use fast withdrawal form": ["Start withdrawal"],
+      "Show debug info": [""],
+      "The reserve operation has been confirmed previously and can't be 
aborted":
+        [""],
+      "The operation id is invalid.": [""],
+      "The operation was not found.": [""],
+      "If you have a Taler wallet installed in this device": [""],
+      "You will see the details of the operation in your wallet including the 
fees (if applies). If you still don't have one you can install it following 
instructions in":
+        [""],
+      "this page": [""],
+      Withdraw: ["Confirm withdrawal"],
+      "Or if you have the wallet in another device": [""],
+      "Scan the QR below to start the withdrawal.": ["Close Taler withdrawal"],
+      required: [""],
+      "IBAN should have just uppercased letters and numbers": [""],
+      "not valid": [""],
+      "should be greater than 0": [""],
+      "balance is not enough": [""],
+      "does not follow the pattern": [""],
+      'only "IBAN" target are supported': [""],
+      'use the "amount" parameter to specify the amount to be transferred': [
+        "",
+      ],
+      "the amount is not valid": [""],
+      'use the "message" parameter to specify a reference text for the 
transfer':
+        [""],
+      "The request was invalid or the payto://-URI used unacceptable 
features.":
+        [""],
+      "Not enough permission to complete the operation.": [""],
+      'The destination account "%1$s" was not found.': [""],
+      "The origin and the destination of the transfer can't be the same.": 
[""],
+      "Your balance is not enough.": [""],
+      'The origin account "%1$s" was not found.': [""],
+      "Using a form": [""],
+      "Import payto:// URI": [""],
+      Recipient: [""],
+      "IBAN of the recipient's account": [""],
+      "Transfer subject": [""],
+      subject: [""],
+      "some text to identify the transfer": [""],
+      Amount: [""],
+      "amount to transfer": ["Amount to withdraw"],
+      "payto URI:": [""],
+      "uniform resource identifier of the target account": [""],
+      "payto://iban/[receiver-iban]?message=[subject]&amount=[%1$s:X.Y]": [""],
+      Cancel: [""],
+      Send: [""],
+      "Missing username": [""],
+      "Missing password": [""],
+      'Wrong credentials for "%1$s"': [""],
+      "Account not found": [""],
+      Username: [""],
+      "username of the account": [""],
+      Password: [""],
+      "password of the account": [""],
+      Check: [""],
+      "Log in": [""],
+      Register: [""],
+      "Wire transfer completed!": [""],
+      "The withdrawal has been aborted previously and can't be confirmed": 
[""],
+      "The withdrawal operation can't be confirmed before a wallet accepted 
the transaction.":
+        [""],
+      "Your balance is not enough for the operation.": [""],
+      "Confirm the withdrawal operation": ["Confirm withdrawal"],
+      "Wire transfer details": [""],
+      "Taler Exchange operator's account": [""],
+      "Taler Exchange operator's name": [""],
+      Transfer: [""],
+      "Authentication required": [""],
+      "This operation was created with other username": [""],
+      "Operation aborted": [""],
+      "The wire transfer to the Taler Exchange operator's account was aborted, 
your balance was not affected.":
+        [""],
+      "You can close this page now or continue to the account page.": [""],
+      Continue: [""],
+      "Withdrawal confirmed": [""],
+      "The wire transfer to the Taler operator has been initiated. You will 
soon receive the requested amount in your Taler wallet.":
+        [""],
+      Done: [""],
+      "Operation canceled": [""],
+      "The operation is marked as 'selected' but some step in the withdrawal 
failed":
+        [""],
+      "The account is selected but no withdrawal identification found.": [""],
+      "There is a withdrawal identification but no account has been selected 
or the selected account is invalid.":
+        [""],
+      "No withdrawal ID found and no account has been selected or the selected 
account is invalid.":
+        [""],
+      "Operation not found": [""],
+      "This operation is not known by the server. The operation id is wrong or 
the server deleted the operation information before reaching here.":
+        [""],
+      "Cotinue to dashboard": [""],
+      "The Withdrawal URI is not valid": [""],
+      'the bank backend is not supported. supported version "%1$s", server 
version "%2$s"':
+        [""],
+      "Internal error, please report.": [""],
+      Preferences: [""],
+      "Welcome, %1$s": [""],
+      "Latest transactions": [""],
+      Date: [""],
+      Counterpart: [""],
+      Subject: [""],
+      sent: [""],
+      received: [""],
+      "invalid value": [""],
+      to: [""],
+      from: [""],
+      "First page": [""],
+      Next: [""],
+      "History of public accounts": [""],
+      "Currently, the bank is not accepting new registrations!": [""],
+      "Missing name": [""],
+      "Use letters and numbers only, and start with a lowercase letter": [""],
+      "Passwords don't match": [""],
+      "Server replied with invalid phone or email.": [""],
+      "Registration is disabled because the bank ran out of bonus credit.": [
+        "",
+      ],
+      "No enough permission to create that account.": [""],
+      "That account id is already taken.": [""],
+      "That username is already taken.": [""],
+      "That username can't be used because is reserved.": [""],
+      "Only admin is allow to set debt limit.": [""],
+      "No information for the selected authentication channel.": [""],
+      "Authentication channel is not supported.": [""],
+      "Only admin can create accounts with second factor authentication.": 
[""],
+      "Account registration": [""],
+      "Repeat password": [""],
+      Name: [""],
+      "Create a random temporary user": [""],
+      "Make a wire transfer": [""],
+      "Wire transfer created!": [""],
+      Accounts: [""],
+      "A list of all business account in the bank.": [""],
+      "Create account": [""],
+      Balance: [""],
+      Actions: [""],
+      unknown: [""],
+      "change password": [""],
+      remove: [""],
+      "Select a section": [""],
+      "Last hour": [""],
+      "Last day": [""],
+      "Last month": [""],
+      "Last year": [""],
+      "Last Year": [""],
+      "Trading volume on %1$s compared to %2$s": [""],
+      Cashin: [""],
+      Cashout: [""],
+      Payin: [""],
+      Payout: [""],
+      "download stats as CSV": [""],
+      "Descreased by": [""],
+      "Increased by": [""],
+      "Unable to create a cashout": [""],
+      "The bank configuration does not support cashout operations.": [""],
+      invalid: [""],
+      "need to be higher due to fees": [""],
+      "the total transfer at destination will be zero": [""],
+      "Cashout created": [""],
+      "Duplicated request detected, check if the operation succeded or try 
again.":
+        [""],
+      "The conversion rate was incorrectly applied": [""],
+      "The account does not have sufficient funds": [""],
+      "Cashouts are not supported": [""],
+      "Missing cashout URI in the profile": [""],
+      "Sending the confirmation message failed, retry later or contact the 
administrator.":
+        [""],
+      "Convertion rate": [""],
+      Fee: [""],
+      "To account": [""],
+      "No cashout account": [""],
+      "Before doing a cashout you need to complete your profile": [""],
+      "Amount to send": ["Amount to withdraw"],
+      "Amount to receive": ["Amount to withdraw"],
+      "Total cost": [""],
+      "Balance left": [""],
+      "Before fee": [""],
+      "Total cashout transfer": [""],
+      "No cashout channel available": [""],
+      "Before doing a cashout the server need to provide an second channel to 
confirm the operation":
+        [""],
+      "Second factor authentication": [""],
+      Email: [""],
+      "add a email in your profile to enable this option": [""],
+      SMS: [""],
+      "add a phone number in your profile to enable this option": [""],
+      Details: [""],
+      Delete: [""],
+      Credentials: [""],
+      Cashouts: [""],
+      "it doesnt have the pattern of an IBAN number": [""],
+      "it doesnt have the pattern of an email": [""],
+      "should start with +": [""],
+      "phone number can't have other than numbers": [""],
+      "account identification in the bank": [""],
+      "name of the person owner the account": [""],
+      "Internal IBAN": [""],
+      "if empty a random account number will be assigned": [""],
+      "account identification for bank transfer": [""],
+      Phone: [""],
+      "Cashout IBAN": [""],
+      "account number where the money is going to be sent when doing cashouts":
+        [""],
+      "Max debt": [""],
+      "how much is user able to transfer after zero balance": [""],
+      "Is this a Taler Exchange?": [""],
+      "This server doesn't support second factor authentication.": [""],
+      "Enable second factor authentication": [""],
+      "Using email": [""],
+      "Using SMS": [""],
+      "Is this account public?": [""],
+      "public accounts have their balance publicly accesible": [""],
+      "Account updated": [""],
+      "The rights to change the account are not sufficient": [""],
+      "The username was not found": [""],
+      "You can't change the legal name, please contact the your account 
administrator.":
+        [""],
+      "You can't change the debt limit, please contact the your account 
administrator.":
+        [""],
+      "You can't change the cashout address, please contact the your account 
administrator.":
+        [""],
+      "You can't change the contact data, please contact the your account 
administrator.":
+        [""],
+      'Account "%1$s"': [""],
+      "Change details": [""],
+      Update: [""],
+      "password doesn't match": [""],
+      "Password changed": [""],
+      "Not authorized to change the password, maybe the session is invalid.": [
+        "",
       ],
-      "Next": [
-        ""
+      "You need to provide the old password. If you don't have it contact your 
account administrator.":
+        [""],
+      "Your current password doesn't match, can't change to a new password.": [
+        "",
+      ],
+      "Update password": [""],
+      "New password": [""],
+      "Type it again": [""],
+      "repeat the same password": [""],
+      "Current password": [""],
+      "your current password, for security": [""],
+      Change: [""],
+      "Can't delete the account": [""],
+      "The account can't be delete while still holding some balance. First 
make sure that the owner make a complete cashout.":
+        [""],
+      "Account removed": [""],
+      "No enough permission to delete the account.": [""],
+      "The username was not found.": [""],
+      "Can't delete a reserved username.": [""],
+      "Can't delete an account with balance different than zero.": [""],
+      "name doesn't match": [""],
+      "You are going to remove the account": [""],
+      "This step can't be undone.": [""],
+      'Deleting account "%1$s"': [""],
+      Verification: [""],
+      "enter the account name that is going to be deleted": [""],
+      'Account created with password "%1$s". The user must change the password 
on the next login.':
+        [""],
+      "Server replied that phone or email is invalid": [""],
+      "The rights to perform the operation are not sufficient": [""],
+      "Account username is already taken": [""],
+      "Account id is already taken": [""],
+      "Bank ran out of bonus credit.": [""],
+      "Account username can't be used because is reserved": [""],
+      "Can't create accounts": [""],
+      "Only system admin can create accounts.": [""],
+      "New business account": [""],
+      Create: [""],
+      "Cashout not supported.": [""],
+      "Account not found.": [""],
+      "Latest cashouts": [""],
+      Created: [""],
+      Confirmed: [""],
+      "Total debit": [""],
+      "Total credit": [""],
+      Status: [""],
+      never: [""],
+      "Cashout for account %1$s": [""],
+      "This cashout not found. Maybe already aborted.": [""],
+      "Cashout not found. It may be also mean that it was already aborted.": [
+        "",
+      ],
+      "Cashout was already confimed.": [""],
+      "Cashout operation is not supported.": [""],
+      "The cashout operation is already aborted.": [""],
+      "Missing destination account.": [""],
+      "Too many failed attempts.": [""],
+      "The code for this cashout is invalid.": [""],
+      "Cashout detail": [""],
+      Debited: [""],
+      Credited: [""],
+      "Enter the confirmation code": [""],
+      Abort: [""],
+      Confirm: [""],
+      "Unauthorized to make the operation, maybe the session has expired or 
the password changed.":
+        [""],
+      "The operation was rejected due to insufficient funds.": [""],
+      "Do not show this again": [""],
+      Close: ["Close"],
+      "On this device": [""],
+      'If you are using a web browser on desktop you should access your wallet 
with the GNU Taler WebExtension now or click the link if your WebExtension have 
the "Inject Taler support" option enabled.':
+        [""],
+      Start: [""],
+      "On a mobile phone": [""],
+      "Scan the QR code with your mobile device.": [""],
+      "There is an operation already": [""],
+      "Complete or cancel the operation in": ["Confirm withdrawal"],
+      "Server responded with an invalid  withdraw URI": [""],
+      "Withdraw URI: %1$s": ["Confirm withdrawal"],
+      "The operation was rejected due to insufficient funds": [""],
+      "Prepare your wallet": [""],
+      "After using your wallet you will need to confirm or cancel the 
operation on this site.":
+        [""],
+      "You need a GNU Taler Wallet": ["Top up Taler wallet"],
+      "If you don't have one yet you can follow the instruction in": [""],
+      "Send money": [""],
+      "to a %1$s wallet": [""],
+      "Withdraw digital money into your mobile wallet or browser extension": [
+        "",
       ],
+      "operation ready": [""],
+      "to another bank account": [""],
+      "Make a wire transfer to an account with known bank account number.": [
+        "",
+      ],
+      "Transfer details": ["Top up Taler wallet"],
+      "This is a demo bank": [""],
+      "This part of the demo shows how a bank that supports Taler directly 
would work. In addition to using your own bank account, you can also see the 
transaction history of some %1$s.":
+        [""],
+      "This part of the demo shows how a bank that supports Taler directly 
would work.":
+        [""],
+      "Pending account delete operation": [""],
+      "Pending account update operation": [""],
+      "Pending password update operation": [""],
+      "Pending transaction operation": [""],
+      "Pending withdrawal operation": [""],
+      "Pending cashout operation": [""],
+      "You can complete or cancel the operation in": [""],
+      "Download bank stats": [""],
+      "Include hour metric": [""],
+      "Include day metric": [""],
+      "Include month metric": [""],
+      "Include year metric": [""],
+      "Include table header": [""],
+      "Add previous metric for compare": [""],
+      "Fail on first error": [""],
+      Download: [""],
+      "downloading... %1$s": [""],
+      "Download completed": [""],
+      "click here to save the file in your computer": [""],
+      "Challenge not found.": [""],
+      "This user is not authorized to complete this challenge.": [""],
+      "Too many attemps, try another code.": [""],
+      "The confirmation code is wrong, try again.": [""],
+      "The operation expired.": [""],
+      "The operation failed.": [""],
+      "The operation needs another confirmation to complete.": [""],
+      "Account delete": [""],
+      "Account update": [""],
+      "Password update": [""],
+      "Wire transfer": [""],
+      Withdrawal: ["Confirm withdrawal"],
+      "Confirm the operation": ["Confirm withdrawal"],
+      "Send again": [""],
+      "Send code": [""],
+      "Operation details": [""],
+      "Challenge details": [""],
+      "Sent at": [""],
+      "To phone": [""],
+      "To email": [""],
+      "Welcome to %1$s!": [""],
+    },
+  },
+  domain: "messages",
+  plural_forms: "nplurals=2; plural=(n != 1);",
+  lang: "en",
+  completeness: 100,
+};
+
+strings["de"] = {
+  locale_data: {
+    messages: {
+      "": {
+        domain: "messages",
+        plural_forms: "nplurals=2; plural=n != 1;",
+        lang: "de",
+      },
+      "Operation failed, please report": [""],
+      "Request timeout": [""],
+      "Request throttled": [""],
+      "Malformed response": [""],
+      "Network error": [""],
+      "Unexpected request error": [""],
+      "Unexpected error": [""],
+      "IBAN numbers usually have more that 4 digits": [""],
+      "IBAN numbers usually have less that 34 digits": [""],
+      "IBAN country code not found": [""],
+      "IBAN number is not valid, checksum is wrong": [""],
+      "Max withdrawal amount": [""],
+      "Show withdrawal confirmation": [""],
+      "Show demo description": [""],
+      "Show install wallet first": [""],
+      "Use fast withdrawal form": [""],
+      "Show debug info": [""],
+      "The reserve operation has been confirmed previously and can't be 
aborted":
+        [""],
+      "The operation id is invalid.": [""],
+      "The operation was not found.": [""],
+      "If you have a Taler wallet installed in this device": [""],
+      "You will see the details of the operation in your wallet including the 
fees (if applies). If you still don't have one you can install it following 
instructions in":
+        [""],
+      "this page": [""],
+      Withdraw: [""],
+      "Or if you have the wallet in another device": [""],
+      "Scan the QR below to start the withdrawal.": [""],
+      required: [""],
+      "IBAN should have just uppercased letters and numbers": [""],
+      "not valid": [""],
+      "should be greater than 0": [""],
+      "balance is not enough": [""],
+      "does not follow the pattern": [""],
+      'only "IBAN" target are supported': [""],
+      'use the "amount" parameter to specify the amount to be transferred': [
+        "",
+      ],
+      "the amount is not valid": [""],
+      'use the "message" parameter to specify a reference text for the 
transfer':
+        [""],
+      "The request was invalid or the payto://-URI used unacceptable 
features.":
+        [""],
+      "Not enough permission to complete the operation.": [""],
+      'The destination account "%1$s" was not found.': [""],
+      "The origin and the destination of the transfer can't be the same.": 
[""],
+      "Your balance is not enough.": [""],
+      'The origin account "%1$s" was not found.': [""],
+      "Using a form": [""],
+      "Import payto:// URI": [""],
+      Recipient: [""],
+      "IBAN of the recipient's account": [""],
+      "Transfer subject": [""],
+      subject: ["Verwendungszweck"],
+      "some text to identify the transfer": [""],
+      Amount: ["Betrag"],
+      "amount to transfer": ["Betrag"],
+      "payto URI:": [""],
+      "uniform resource identifier of the target account": [""],
+      "payto://iban/[receiver-iban]?message=[subject]&amount=[%1$s:X.Y]": [""],
+      Cancel: [""],
+      Send: [""],
+      "Missing username": [""],
+      "Missing password": [""],
+      'Wrong credentials for "%1$s"': [""],
+      "Account not found": [""],
+      Username: [""],
+      "username of the account": [""],
+      Password: [""],
+      "password of the account": ["Buchungen auf öffentlich sichtbaren 
Konten"],
+      Check: [""],
+      "Log in": [""],
+      Register: [""],
+      "Wire transfer completed!": [""],
+      "The withdrawal has been aborted previously and can't be confirmed": 
[""],
+      "The withdrawal operation can't be confirmed before a wallet accepted 
the transaction.":
+        [""],
+      "Your balance is not enough for the operation.": [""],
+      "Confirm the withdrawal operation": ["Abhebung bestätigen"],
+      "Wire transfer details": [""],
+      "Taler Exchange operator's account": [""],
+      "Taler Exchange operator's name": [""],
+      Transfer: [""],
+      "Authentication required": [""],
+      "This operation was created with other username": [""],
+      "Operation aborted": [""],
+      "The wire transfer to the Taler Exchange operator's account was aborted, 
your balance was not affected.":
+        [""],
+      "You can close this page now or continue to the account page.": [""],
+      Continue: [""],
+      "Withdrawal confirmed": [""],
+      "The wire transfer to the Taler operator has been initiated. You will 
soon receive the requested amount in your Taler wallet.":
+        [""],
+      Done: [""],
+      "Operation canceled": [""],
+      "The operation is marked as 'selected' but some step in the withdrawal 
failed":
+        [""],
+      "The account is selected but no withdrawal identification found.": [""],
+      "There is a withdrawal identification but no account has been selected 
or the selected account is invalid.":
+        [""],
+      "No withdrawal ID found and no account has been selected or the selected 
account is invalid.":
+        [""],
+      "Operation not found": [""],
+      "This operation is not known by the server. The operation id is wrong or 
the server deleted the operation information before reaching here.":
+        [""],
+      "Cotinue to dashboard": [""],
+      "The Withdrawal URI is not valid": [""],
+      'the bank backend is not supported. supported version "%1$s", server 
version "%2$s"':
+        [""],
+      "Internal error, please report.": [""],
+      Preferences: [""],
+      "Welcome, %1$s": [""],
+      "Latest transactions": [""],
+      Date: ["Datum"],
+      Counterpart: ["Empfänger"],
+      Subject: ["Verwendungszweck"],
+      sent: [""],
+      received: [""],
+      "invalid value": [""],
+      to: [""],
+      from: [""],
+      "First page": [""],
+      Next: [""],
       "History of public accounts": [
-        ""
-      ],
-      "Currently, the bank is not accepting new registrations!": [
-        ""
-      ],
-      "Missing name": [
-        ""
-      ],
-      "Use letters and numbers only, and start with a lowercase letter": [
-        ""
-      ],
-      "Passwords don't match": [
-        ""
-      ],
-      "Server replied with invalid phone or email.": [
-        ""
+        "Buchungen auf öffentlich sichtbaren Konten",
       ],
+      "Currently, the bank is not accepting new registrations!": [""],
+      "Missing name": [""],
+      "Use letters and numbers only, and start with a lowercase letter": [""],
+      "Passwords don't match": [""],
+      "Server replied with invalid phone or email.": [""],
       "Registration is disabled because the bank ran out of bonus credit.": [
-        ""
-      ],
-      "No enough permission to create that account.": [
-        ""
-      ],
-      "That account id is already taken.": [
-        ""
-      ],
-      "That username is already taken.": [
-        ""
-      ],
-      "That username can't be used because is reserved.": [
-        ""
-      ],
-      "Only admin is allow to set debt limit.": [
-        ""
-      ],
-      "No information for the selected authentication channel.": [
-        ""
-      ],
-      "Authentication channel is not supported.": [
-        ""
-      ],
-      "Only admin can create accounts with second factor authentication.": [
-        ""
-      ],
-      "Account registration": [
-        ""
-      ],
-      "Repeat password": [
-        ""
-      ],
-      "Name": [
-        ""
-      ],
-      "Create a random temporary user": [
-        ""
-      ],
-      "Make a wire transfer": [
-        ""
-      ],
-      "Wire transfer created!": [
-        ""
-      ],
-      "Accounts": [
-        ""
+        "",
+      ],
+      "No enough permission to create that account.": [""],
+      "That account id is already taken.": [""],
+      "That username is already taken.": [""],
+      "That username can't be used because is reserved.": [""],
+      "Only admin is allow to set debt limit.": [""],
+      "No information for the selected authentication channel.": [""],
+      "Authentication channel is not supported.": [""],
+      "Only admin can create accounts with second factor authentication.": 
[""],
+      "Account registration": [""],
+      "Repeat password": [""],
+      Name: [""],
+      "Create a random temporary user": [""],
+      "Make a wire transfer": [""],
+      "Wire transfer created!": [""],
+      Accounts: ["Betrag"],
+      "A list of all business account in the bank.": [""],
+      "Create account": [""],
+      Balance: [""],
+      Actions: [""],
+      unknown: [""],
+      "change password": [""],
+      remove: [""],
+      "Select a section": [""],
+      "Last hour": [""],
+      "Last day": [""],
+      "Last month": [""],
+      "Last year": [""],
+      "Last Year": [""],
+      "Trading volume on %1$s compared to %2$s": [""],
+      Cashin: [""],
+      Cashout: [""],
+      Payin: [""],
+      Payout: [""],
+      "download stats as CSV": [""],
+      "Descreased by": [""],
+      "Increased by": [""],
+      "Unable to create a cashout": [""],
+      "The bank configuration does not support cashout operations.": [""],
+      invalid: [""],
+      "need to be higher due to fees": [""],
+      "the total transfer at destination will be zero": [""],
+      "Cashout created": [""],
+      "Duplicated request detected, check if the operation succeded or try 
again.":
+        [""],
+      "The conversion rate was incorrectly applied": [""],
+      "The account does not have sufficient funds": [""],
+      "Cashouts are not supported": [""],
+      "Missing cashout URI in the profile": [""],
+      "Sending the confirmation message failed, retry later or contact the 
administrator.":
+        [""],
+      "Convertion rate": [""],
+      Fee: [""],
+      "To account": [""],
+      "No cashout account": [""],
+      "Before doing a cashout you need to complete your profile": [""],
+      "Amount to send": ["Betrag"],
+      "Amount to receive": [""],
+      "Total cost": [""],
+      "Balance left": [""],
+      "Before fee": [""],
+      "Total cashout transfer": [""],
+      "No cashout channel available": [""],
+      "Before doing a cashout the server need to provide an second channel to 
confirm the operation":
+        [""],
+      "Second factor authentication": [""],
+      Email: [""],
+      "add a email in your profile to enable this option": [""],
+      SMS: [""],
+      "add a phone number in your profile to enable this option": [""],
+      Details: [""],
+      Delete: [""],
+      Credentials: [""],
+      Cashouts: [""],
+      "it doesnt have the pattern of an IBAN number": [""],
+      "it doesnt have the pattern of an email": [""],
+      "should start with +": [""],
+      "phone number can't have other than numbers": [""],
+      "account identification in the bank": [""],
+      "name of the person owner the account": [""],
+      "Internal IBAN": [""],
+      "if empty a random account number will be assigned": [""],
+      "account identification for bank transfer": [""],
+      Phone: [""],
+      "Cashout IBAN": [""],
+      "account number where the money is going to be sent when doing cashouts":
+        [""],
+      "Max debt": [""],
+      "how much is user able to transfer after zero balance": [""],
+      "Is this a Taler Exchange?": [""],
+      "This server doesn't support second factor authentication.": [""],
+      "Enable second factor authentication": [""],
+      "Using email": [""],
+      "Using SMS": [""],
+      "Is this account public?": [""],
+      "public accounts have their balance publicly accesible": [""],
+      "Account updated": [""],
+      "The rights to change the account are not sufficient": [""],
+      "The username was not found": [""],
+      "You can't change the legal name, please contact the your account 
administrator.":
+        [""],
+      "You can't change the debt limit, please contact the your account 
administrator.":
+        [""],
+      "You can't change the cashout address, please contact the your account 
administrator.":
+        [""],
+      "You can't change the contact data, please contact the your account 
administrator.":
+        [""],
+      'Account "%1$s"': [""],
+      "Change details": [""],
+      Update: [""],
+      "password doesn't match": [""],
+      "Password changed": [""],
+      "Not authorized to change the password, maybe the session is invalid.": [
+        "",
       ],
-      "A list of all business account in the bank.": [
-        ""
-      ],
-      "Create account": [
-        ""
-      ],
-      "Balance": [
-        ""
-      ],
-      "Actions": [
-        ""
-      ],
-      "unknown": [
-        ""
-      ],
-      "change password": [
-        ""
-      ],
-      "remove": [
-        ""
-      ],
-      "Select a section": [
-        ""
-      ],
-      "Last hour": [
-        ""
-      ],
-      "Last day": [
-        ""
-      ],
-      "Last month": [
-        ""
-      ],
-      "Last year": [
-        ""
-      ],
-      "Last Year": [
-        ""
-      ],
-      "Trading volume on %1$s compared to %2$s": [
-        ""
-      ],
-      "Cashin": [
-        ""
-      ],
-      "Cashout": [
-        ""
-      ],
-      "Payin": [
-        ""
-      ],
-      "Payout": [
-        ""
-      ],
-      "download stats as CSV": [
-        ""
-      ],
-      "Descreased by": [
-        ""
-      ],
-      "Increased by": [
-        ""
-      ],
-      "Unable to create a cashout": [
-        ""
-      ],
-      "The bank configuration does not support cashout operations.": [
-        ""
-      ],
-      "invalid": [
-        ""
-      ],
-      "need to be higher due to fees": [
-        ""
-      ],
-      "the total transfer at destination will be zero": [
-        ""
-      ],
-      "Cashout created": [
-        ""
-      ],
-      "Duplicated request detected, check if the operation succeded or try 
again.": [
-        ""
-      ],
-      "The conversion rate was incorrectly applied": [
-        ""
-      ],
-      "The account does not have sufficient funds": [
-        ""
-      ],
-      "Cashouts are not supported": [
-        ""
-      ],
-      "Missing cashout URI in the profile": [
-        ""
-      ],
-      "Sending the confirmation message failed, retry later or contact the 
administrator.": [
-        ""
-      ],
-      "Convertion rate": [
-        ""
-      ],
-      "Fee": [
-        ""
-      ],
-      "To account": [
-        ""
-      ],
-      "No cashout account": [
-        ""
-      ],
-      "Before doing a cashout you need to complete your profile": [
-        ""
-      ],
-      "Amount to send": [
-        ""
-      ],
-      "Amount to receive": [
-        ""
-      ],
-      "Total cost": [
-        ""
-      ],
-      "Balance left": [
-        ""
-      ],
-      "Before fee": [
-        ""
-      ],
-      "Total cashout transfer": [
-        ""
-      ],
-      "No cashout channel available": [
-        ""
-      ],
-      "Before doing a cashout the server need to provide an second channel to 
confirm the operation": [
-        ""
-      ],
-      "Second factor authentication": [
-        ""
-      ],
-      "Email": [
-        ""
-      ],
-      "add a email in your profile to enable this option": [
-        ""
-      ],
-      "SMS": [
-        ""
-      ],
-      "add a phone number in your profile to enable this option": [
-        ""
-      ],
-      "Details": [
-        ""
-      ],
-      "Delete": [
-        ""
-      ],
-      "Credentials": [
-        ""
-      ],
-      "Cashouts": [
-        ""
-      ],
-      "it doesnt have the pattern of an IBAN number": [
-        ""
-      ],
-      "it doesnt have the pattern of an email": [
-        ""
-      ],
-      "should start with +": [
-        ""
-      ],
-      "phone number can't have other than numbers": [
-        ""
-      ],
-      "account identification in the bank": [
-        ""
-      ],
-      "name of the person owner the account": [
-        ""
-      ],
-      "Internal IBAN": [
-        ""
-      ],
-      "if empty a random account number will be assigned": [
-        ""
-      ],
-      "account identification for bank transfer": [
-        ""
-      ],
-      "Phone": [
-        ""
-      ],
-      "Cashout IBAN": [
-        ""
-      ],
-      "account number where the money is going to be sent when doing 
cashouts": [
-        ""
-      ],
-      "Max debt": [
-        ""
-      ],
-      "how much is user able to transfer after zero balance": [
-        ""
-      ],
-      "Is this a Taler Exchange?": [
-        ""
-      ],
-      "This server doesn't support second factor authentication.": [
-        ""
-      ],
-      "Enable second factor authentication": [
-        ""
-      ],
-      "Using email": [
-        ""
-      ],
-      "Using SMS": [
-        ""
-      ],
-      "Is this account public?": [
-        ""
-      ],
-      "public accounts have their balance publicly accesible": [
-        ""
-      ],
-      "Account updated": [
-        ""
-      ],
-      "The rights to change the account are not sufficient": [
-        ""
-      ],
-      "The username was not found": [
-        ""
-      ],
-      "You can't change the legal name, please contact the your account 
administrator.": [
-        ""
-      ],
-      "You can't change the debt limit, please contact the your account 
administrator.": [
-        ""
-      ],
-      "You can't change the cashout address, please contact the your account 
administrator.": [
-        ""
-      ],
-      "You can't change the contact data, please contact the your account 
administrator.": [
-        ""
-      ],
-      "Account \"%1$s\"": [
-        ""
-      ],
-      "Change details": [
-        ""
-      ],
-      "Update": [
-        ""
-      ],
-      "password doesn't match": [
-        ""
-      ],
-      "Password changed": [
-        ""
-      ],
-      "Not authorized to change the password, maybe the session is invalid.": [
-        ""
-      ],
-      "You need to provide the old password. If you don't have it contact your 
account administrator.": [
-        ""
-      ],
-      "Your current password doesn't match, can't change to a new password.": [
-        ""
-      ],
-      "Update password": [
-        ""
-      ],
-      "New password": [
-        ""
-      ],
-      "Type it again": [
-        ""
-      ],
-      "repeat the same password": [
-        ""
-      ],
-      "Current password": [
-        ""
-      ],
-      "your current password, for security": [
-        ""
-      ],
-      "Change": [
-        ""
-      ],
-      "Can't delete the account": [
-        ""
-      ],
-      "The account can't be delete while still holding some balance. First 
make sure that the owner make a complete cashout.": [
-        ""
-      ],
-      "Account removed": [
-        ""
-      ],
-      "No enough permission to delete the account.": [
-        ""
-      ],
-      "The username was not found.": [
-        ""
-      ],
-      "Can't delete a reserved username.": [
-        ""
-      ],
-      "Can't delete an account with balance different than zero.": [
-        ""
-      ],
-      "name doesn't match": [
-        ""
-      ],
-      "You are going to remove the account": [
-        ""
-      ],
-      "This step can't be undone.": [
-        ""
-      ],
-      "Deleting account \"%1$s\"": [
-        ""
-      ],
-      "Verification": [
-        ""
-      ],
-      "enter the account name that is going to be deleted": [
-        ""
-      ],
-      "Account created with password \"%1$s\". The user must change the 
password on the next login.": [
-        ""
-      ],
-      "Server replied that phone or email is invalid": [
-        ""
-      ],
-      "The rights to perform the operation are not sufficient": [
-        ""
-      ],
-      "Account username is already taken": [
-        ""
-      ],
-      "Account id is already taken": [
-        ""
-      ],
-      "Bank ran out of bonus credit.": [
-        ""
-      ],
-      "Account username can't be used because is reserved": [
-        ""
-      ],
-      "Can't create accounts": [
-        ""
-      ],
-      "Only system admin can create accounts.": [
-        ""
-      ],
-      "New business account": [
-        ""
-      ],
-      "Create": [
-        ""
-      ],
-      "Cashout not supported.": [
-        ""
-      ],
-      "Account not found.": [
-        ""
-      ],
-      "Latest cashouts": [
-        ""
-      ],
-      "Created": [
-        ""
-      ],
-      "Confirmed": [
-        ""
-      ],
-      "Total debit": [
-        ""
-      ],
-      "Total credit": [
-        ""
-      ],
-      "Status": [
-        ""
-      ],
-      "never": [
-        ""
-      ],
-      "Cashout for account %1$s": [
-        ""
-      ],
-      "This cashout not found. Maybe already aborted.": [
-        ""
-      ],
-      "Cashout not found. It may be also mean that it was already aborted.": [
-        ""
-      ],
-      "Cashout was already confimed.": [
-        ""
-      ],
-      "Cashout operation is not supported.": [
-        ""
-      ],
-      "The cashout operation is already aborted.": [
-        ""
-      ],
-      "Missing destination account.": [
-        ""
-      ],
-      "Too many failed attempts.": [
-        ""
-      ],
-      "The code for this cashout is invalid.": [
-        ""
-      ],
-      "Cashout detail": [
-        ""
-      ],
-      "Debited": [
-        ""
-      ],
-      "Credited": [
-        ""
-      ],
-      "Enter the confirmation code": [
-        ""
-      ],
-      "Abort": [
-        ""
-      ],
-      "Confirm": [
-        ""
-      ],
-      "Unauthorized to make the operation, maybe the session has expired or 
the password changed.": [
-        ""
-      ],
-      "The operation was rejected due to insufficient funds.": [
-        ""
-      ],
-      "Do not show this again": [
-        ""
-      ],
-      "Close": [
-        ""
-      ],
-      "On this device": [
-        ""
-      ],
-      "If you are using a web browser on desktop you should access your wallet 
with the GNU Taler WebExtension now or click the link if your WebExtension have 
the \"Inject Taler support\" option enabled.": [
-        ""
-      ],
-      "Start": [
-        ""
-      ],
-      "On a mobile phone": [
-        ""
-      ],
-      "Scan the QR code with your mobile device.": [
-        ""
-      ],
-      "There is an operation already": [
-        ""
-      ],
-      "Complete or cancel the operation in": [
-        ""
-      ],
-      "Server responded with an invalid  withdraw URI": [
-        ""
-      ],
-      "Withdraw URI: %1$s": [
-        ""
-      ],
-      "The operation was rejected due to insufficient funds": [
-        ""
-      ],
-      "Prepare your wallet": [
-        ""
-      ],
-      "After using your wallet you will need to confirm or cancel the 
operation on this site.": [
-        ""
-      ],
-      "You need a GNU Taler Wallet": [
-        ""
-      ],
-      "If you don't have one yet you can follow the instruction in": [
-        ""
-      ],
-      "Send money": [
-        ""
-      ],
-      "to a %1$s wallet": [
-        ""
-      ],
-      "Withdraw digital money into your mobile wallet or browser extension": [
-        ""
-      ],
-      "operation ready": [
-        ""
-      ],
-      "to another bank account": [
-        ""
-      ],
-      "Make a wire transfer to an account with known bank account number.": [
-        ""
-      ],
-      "Transfer details": [
-        ""
-      ],
-      "This is a demo bank": [
-        ""
-      ],
-      "This part of the demo shows how a bank that supports Taler directly 
would work. In addition to using your own bank account, you can also see the 
transaction history of some %1$s.": [
-        ""
-      ],
-      "This part of the demo shows how a bank that supports Taler directly 
would work.": [
-        ""
-      ],
-      "Pending account delete operation": [
-        ""
-      ],
-      "Pending account update operation": [
-        ""
-      ],
-      "Pending password update operation": [
-        ""
-      ],
-      "Pending transaction operation": [
-        ""
-      ],
-      "Pending withdrawal operation": [
-        ""
-      ],
-      "Pending cashout operation": [
-        ""
-      ],
-      "You can complete or cancel the operation in": [
-        ""
-      ],
-      "Download bank stats": [
-        ""
-      ],
-      "Include hour metric": [
-        ""
-      ],
-      "Include day metric": [
-        ""
-      ],
-      "Include month metric": [
-        ""
-      ],
-      "Include year metric": [
-        ""
-      ],
-      "Include table header": [
-        ""
-      ],
-      "Add previous metric for compare": [
-        ""
-      ],
-      "Fail on first error": [
-        ""
-      ],
-      "Download": [
-        ""
-      ],
-      "downloading... %1$s": [
-        ""
-      ],
-      "Download completed": [
-        ""
-      ],
-      "click here to save the file in your computer": [
-        ""
-      ],
-      "Challenge not found.": [
-        ""
-      ],
-      "This user is not authorized to complete this challenge.": [
-        ""
-      ],
-      "Too many attemps, try another code.": [
-        ""
-      ],
-      "The confirmation code is wrong, try again.": [
-        ""
-      ],
-      "The operation expired.": [
-        ""
-      ],
-      "The operation failed.": [
-        ""
-      ],
-      "The operation needs another confirmation to complete.": [
-        ""
-      ],
-      "Account delete": [
-        ""
-      ],
-      "Account update": [
-        ""
-      ],
-      "Password update": [
-        ""
-      ],
-      "Wire transfer": [
-        ""
-      ],
-      "Withdrawal": [
-        ""
-      ],
-      "Confirm the operation": [
-        ""
-      ],
-      "Send again": [
-        ""
-      ],
-      "Send code": [
-        ""
-      ],
-      "Operation details": [
-        ""
-      ],
-      "Challenge details": [
-        ""
-      ],
-      "Sent at": [
-        ""
-      ],
-      "To phone": [
-        ""
-      ],
-      "To email": [
-        ""
-      ],
-      "Welcome to %1$s!": [
-        ""
-      ]
-    }
-  },
-  "domain": "messages",
-  "plural_forms": "nplurals=2; plural=n > 1;",
-  "lang": "fr",
-  "completeness": 0
-};
-
-strings['es'] = {
-  "locale_data": {
-    "messages": {
-      "": {
-        "domain": "messages",
-        "plural_forms": "nplurals=2; plural=n != 1;",
-        "lang": "es"
-      },
-      "Operation failed, please report": [
-        "La operaicón falló, por favor reportelo"
-      ],
-      "Request timeout": [
-        "La petición al servidor agoto su tiempo"
-      ],
-      "Request throttled": [
-        "La petición al servidor interrumpida"
-      ],
-      "Malformed response": [
-        "Respuesta malformada"
-      ],
-      "Network error": [
-        "Error de conexión"
-      ],
-      "Unexpected request error": [
-        "Error de pedido inesperado"
-      ],
-      "Unexpected error": [
-        "Error inesperado"
-      ],
-      "IBAN numbers usually have more that 4 digits": [
-        "Los números IBAN usualmente tienen mas de 4 digitos"
-      ],
-      "IBAN numbers usually have less that 34 digits": [
-        "Los números IBAN usualmente tienen menos de 34 digitos"
-      ],
-      "IBAN country code not found": [
-        "Código de pais de IBAN no encontrado"
-      ],
-      "IBAN number is not valid, checksum is wrong": [
-        "El número IBAN no es válido, falló la verificación"
-      ],
-      "Max withdrawal amount": [
-        "Monto máximo de extracción"
-      ],
-      "Show withdrawal confirmation": [
-        "Mostrar confirmación de extracción"
-      ],
-      "Show demo description": [
-        "Mostrar descripción de demo"
-      ],
-      "Show install wallet first": [
-        "Mostrar instalar la billetera primero"
-      ],
-      "Use fast withdrawal form": [
-        "Usar formulario de extracción rápida"
-      ],
-      "Show debug info": [
-        "Mostrar información de depuración"
-      ],
-      "The reserve operation has been confirmed previously and can't be 
aborted": [
-        "La operación en la reserva ya ha sido confirmada previamente y no 
puede ser abortada"
-      ],
-      "The operation id is invalid.": [
-        "El id de operación es invalido."
-      ],
-      "The operation was not found.": [
-        "La operación no se encontró."
-      ],
-      "If you have a Taler wallet installed in this device": [
-        "Si tienes una billetera Taler instalada en este dispositivo"
-      ],
-      "You will see the details of the operation in your wallet including the 
fees (if applies). If you still don't have one you can install it following 
instructions in": [
-        "Veras los detalles de la operación en tu billetera incluyendo 
comisiones (si aplicán). Si todavía no tienes una puedes instalarla siguiendo 
las instrucciones en"
-      ],
-      "this page": [
-        "esta página"
-      ],
-      "Withdraw": [
-        "Retirar"
-      ],
-      "Or if you have the wallet in another device": [
-        "O si tienes la billetera en otro dispositivo"
-      ],
-      "Scan the QR below to start the withdrawal.": [
-        "Escanea el QR debajo para comenzar la extracción."
-      ],
-      "required": [
-        "requerido"
-      ],
-      "IBAN should have just uppercased letters and numbers": [
-        "IBAN debería tener letras mayúsculas y números"
-      ],
-      "not valid": [
-        "no válido"
-      ],
-      "should be greater than 0": [
-        "Debería ser mas grande que 0"
-      ],
-      "balance is not enough": [
-        "el saldo no es suficiente"
-      ],
-      "does not follow the pattern": [
-        "no tiene un patrón valido"
-      ],
-      "only \"IBAN\" target are supported": [
-        "solo cuentas \"IBAN\" son soportadas"
-      ],
-      "use the \"amount\" parameter to specify the amount to be transferred": [
-        "usa el parámetro \"amount\" para indicar el monto a ser transferido"
-      ],
-      "the amount is not valid": [
-        "el monto no es válido"
-      ],
-      "use the \"message\" parameter to specify a reference text for the 
transfer": [
-        "usa el parámetro \"message\" para indicar un texto de referencia en 
la transferencia"
-      ],
-      "The request was invalid or the payto://-URI used unacceptable 
features.": [
-        "El pedido era inválido o el URI payto:// usado tiene características 
inaceptables."
-      ],
-      "Not enough permission to complete the operation.": [
-        "Sin permisos suficientes para completar la operación."
-      ],
-      "The destination account \"%1$s\" was not found.": [
-        "La cuenta de destino \"%1$s\" no fue encontrada."
-      ],
-      "The origin and the destination of the transfer can't be the same.": [
-        "El origen y destino de la transferencia no puede ser la misma."
-      ],
-      "Your balance is not enough.": [
-        "El saldo no es suficiente."
-      ],
-      "The origin account \"%1$s\" was not found.": [
-        "La cuenta origen \"%1$s\" no fue encontrada."
-      ],
-      "Using a form": [
-        "Usando un formulario"
-      ],
-      "Import payto:// URI": [
-        "Importando un URI payto://"
-      ],
-      "Recipient": [
-        "Destinatario"
-      ],
-      "IBAN of the recipient's account": [
-        "Numero IBAN de la cuenta destinataria"
-      ],
-      "Transfer subject": [
-        "Asunto de transferencia"
-      ],
-      "subject": [
-        "asunto"
-      ],
-      "some text to identify the transfer": [
-        "algún texto para identificar la transferencia"
-      ],
-      "Amount": [
-        "Monto"
-      ],
-      "amount to transfer": [
-        "monto a transferir"
-      ],
-      "payto URI:": [
-        "payto URI:"
-      ],
-      "uniform resource identifier of the target account": [
-        "identificador de recurso uniforme de la cuenta destino"
-      ],
-      "payto://iban/[receiver-iban]?message=[subject]&amount=[%1$s:X.Y]": [
-        "payto://iban/[iban-destinatario]?message=[asunto]&amount=[%1$s:X.Y]"
-      ],
-      "Cancel": [
-        "Cancelar"
-      ],
-      "Send": [
-        "Envíar"
-      ],
-      "Missing username": [
-        "Falta nombre de usuario"
-      ],
-      "Missing password": [
-        "Falta contraseña"
-      ],
-      "Wrong credentials for \"%1$s\"": [
-        "Credenciales incorrectas para \"%1$s\""
-      ],
-      "Account not found": [
-        "Cuenta no encontrada"
-      ],
-      "Username": [
-        "Nombre de usuario"
-      ],
-      "username of the account": [
-        "nombre de usuario de la cuenta"
-      ],
-      "Password": [
-        "Contraseña"
-      ],
-      "password of the account": [
-        "contraseña de la cuenta"
-      ],
-      "Check": [
-        "Verificar"
-      ],
-      "Log in": [
-        "Acceso"
-      ],
-      "Register": [
-        "Registrarse"
-      ],
-      "Wire transfer completed!": [
-        "Transferencia bancaria completada!"
-      ],
-      "The withdrawal has been aborted previously and can't be confirmed": [
-        "La extracción fue abortada anteriormente y no puede ser confirmada"
-      ],
-      "The withdrawal operation can't be confirmed before a wallet accepted 
the transaction.": [
-        "La operación de extracción no puede ser confirmada antes de que una 
billetera acepte la transaccion."
-      ],
-      "Your balance is not enough for the operation.": [
-        "El saldo no es suficiente para la operación."
-      ],
-      "Confirm the withdrawal operation": [
-        "Confirme la operación de extracción"
-      ],
-      "Wire transfer details": [
-        "Detalle de transferencia bancaria"
-      ],
-      "Taler Exchange operator's account": [
-        "Cuenta del operador del Taler Exchange"
-      ],
-      "Taler Exchange operator's name": [
-        "Nombre del operador del Taler Exchange"
-      ],
-      "Transfer": [
-        "Transferencia"
-      ],
-      "Authentication required": [
-        "Autenticación requerida"
-      ],
-      "This operation was created with other username": [
-        "Esta operación fue creada con otro usuario"
-      ],
-      "Operation aborted": [
-        "Operación abortada"
-      ],
-      "The wire transfer to the Taler Exchange operator's account was aborted, 
your balance was not affected.": [
-        "La transferencia bancaria a la cuenta del operador del Taler Exchange 
fue abortada, su saldo no fue afectado."
-      ],
-      "You can close this page now or continue to the account page.": [
-        "Ya puedes cerrar esta pagina or continuar a la página de estado de 
cuenta."
-      ],
-      "Continue": [
-        "Continuar"
-      ],
-      "Withdrawal confirmed": [
-        "La extracción fue confirmada"
-      ],
-      "The wire transfer to the Taler operator has been initiated. You will 
soon receive the requested amount in your Taler wallet.": [
-        "La transferencia bancaria al operador Taler fue iniciada. Pronto 
recibirás el monto pedido en tu billetera Taler."
-      ],
-      "Done": [
-        "Listo"
-      ],
-      "Operation canceled": [
-        "Operación cancelada"
-      ],
-      "The operation is marked as 'selected' but some step in the withdrawal 
failed": [
-        "La operación está marcada como 'seleccionada' pero algunos pasos en 
la extracción fallaron"
-      ],
-      "The account is selected but no withdrawal identification found.": [
-        "La cuenta está seleccionada pero no se encontró el identificador de 
extracción."
-      ],
-      "There is a withdrawal identification but no account has been selected 
or the selected account is invalid.": [
-        "Hay un identificador de extracción pero la cuenta no ha sido 
seleccionada o la selccionada es inválida."
-      ],
-      "No withdrawal ID found and no account has been selected or the selected 
account is invalid.": [
-        "No hay un identificador de extracción y ninguna cuenta a sido 
seleccionada o la seleccionada es inválida."
-      ],
-      "Operation not found": [
-        "Operación no encontrada"
-      ],
-      "This operation is not known by the server. The operation id is wrong or 
the server deleted the operation information before reaching here.": [
-        "Esta operación no es conocida por el servidor. El identificador de 
operación es incorrecto o el server borró la información de la operación antes 
de llegar hasta aquí."
-      ],
-      "Cotinue to dashboard": [
-        "Continuar al panel"
-      ],
-      "The Withdrawal URI is not valid": [
-        "El URI de estracción no es válido"
-      ],
-      "the bank backend is not supported. supported version \"%1$s\", server 
version \"%2$s\"": [
-        "El servidor de bank no esta spoportado. Version soportada \"%1$s\", 
version del server \"%2$s\""
-      ],
-      "Internal error, please report.": [
-        "Error interno, por favor reporte el error."
-      ],
-      "Preferences": [
-        "Preferencias"
-      ],
-      "Welcome, %1$s": [
-        "Bienvenido/a, %1$s"
-      ],
-      "Latest transactions": [
-        "Últimas transacciones"
-      ],
-      "Date": [
-        "Fecha"
-      ],
-      "Counterpart": [
-        "Contraparte"
-      ],
-      "Subject": [
-        "Asunto"
-      ],
-      "sent": [
-        "enviado"
-      ],
-      "received": [
-        "recibido"
-      ],
-      "invalid value": [
-        "valor inválido"
-      ],
-      "to": [
-        "hacia"
-      ],
-      "from": [
-        "desde"
-      ],
-      "First page": [
-        "Primera página"
-      ],
-      "Next": [
-        "Siguiente"
-      ],
-      "History of public accounts": [
-        "Historial de cuentas públicas"
-      ],
-      "Currently, the bank is not accepting new registrations!": [
-        "Actualmente, el banco no está aceptado nuevos registros!"
-      ],
-      "Missing name": [
-        "Falta nombre"
-      ],
-      "Use letters and numbers only, and start with a lowercase letter": [
-        "Solo use letras y números, y comience con una letra minúscula"
-      ],
-      "Passwords don't match": [
-        "La contraseña no coincide"
-      ],
-      "Server replied with invalid phone or email.": [
-        "El servidor repondio con teléfono o dirección de correo inválido."
-      ],
-      "Registration is disabled because the bank ran out of bonus credit.": [
-        "El registro está deshabilitado porque el banco se quedó sin crédito 
bonus."
-      ],
-      "No enough permission to create that account.": [
-        "Sin permisos suficientes para crear esa cuenta."
-      ],
-      "That account id is already taken.": [
-        "El identificador de cuenta ya está tomado."
-      ],
-      "That username is already taken.": [
-        "El nombre de usuario ya está tomado."
-      ],
-      "That username can't be used because is reserved.": [
-        "El nombre de usuario no puede ser usado porque esta reservado."
-      ],
-      "Only admin is allow to set debt limit.": [
-        "Solo el administrador tiene permitido cambiar el límite de deuda."
-      ],
-      "No information for the selected authentication channel.": [
-        "No hay información para el canal de autenticación seleccionado."
-      ],
-      "Authentication channel is not supported.": [
-        "Canal de autenticación no esta soportado."
-      ],
-      "Only admin can create accounts with second factor authentication.": [
-        "Solo el administrador puede crear cuentas con el segundo factor de 
autenticación."
-      ],
-      "Account registration": [
-        "Registro de cuenta"
-      ],
-      "Repeat password": [
-        "Repita la contraseña"
-      ],
-      "Name": [
-        "Nombre"
-      ],
-      "Create a random temporary user": [
-        "Crear un usuario aleatorio temporal"
-      ],
-      "Make a wire transfer": [
-        "Hacer una transferencia bancaria"
-      ],
-      "Wire transfer created!": [
-        "Transferencia bancaria creada!"
-      ],
-      "Accounts": [
-        "Cuentas"
-      ],
-      "A list of all business account in the bank.": [
-        "Una lista de todas las cuentas en el banco."
-      ],
-      "Create account": [
-        "Crear cuenta"
-      ],
-      "Balance": [
-        "Saldo"
-      ],
-      "Actions": [
-        "Acciones"
-      ],
-      "unknown": [
-        "desconocido"
-      ],
-      "change password": [
-        "cambiar contraseña"
-      ],
-      "remove": [
-        "elimiar"
-      ],
-      "Select a section": [
-        "Seleccione una sección"
-      ],
-      "Last hour": [
-        "Última hora"
-      ],
-      "Last day": [
-        "Último día"
-      ],
-      "Last month": [
-        "Último mes"
-      ],
-      "Last year": [
-        "Último año"
-      ],
-      "Last Year": [
-        "Último Año"
-      ],
-      "Trading volume on %1$s compared to %2$s": [
-        "Vólumen de comercio en %1$s comparado con %2$s"
-      ],
-      "Cashin": [
-        "Ingreso"
-      ],
-      "Cashout": [
-        "Egreso"
-      ],
-      "Payin": [
-        "Envios de dinero"
-      ],
-      "Payout": [
-        "Recibos de dinero"
-      ],
-      "download stats as CSV": [
-        "descargar estadísticas en CSV"
-      ],
-      "Descreased by": [
-        "Descendiente por"
-      ],
-      "Increased by": [
-        "Ascendente por"
-      ],
-      "Unable to create a cashout": [
-        "Imposible crear un egreso"
-      ],
-      "The bank configuration does not support cashout operations.": [
-        "La configuración del banco no soporta operaciones de egreso."
-      ],
-      "invalid": [
-        "inválido"
-      ],
-      "need to be higher due to fees": [
-        "necesita ser mayor debido a las comisiones"
-      ],
-      "the total transfer at destination will be zero": [
-        "el total de la transferencia en destino será cero"
-      ],
-      "Cashout created": [
-        "Egreso creado"
-      ],
-      "Duplicated request detected, check if the operation succeded or try 
again.": [
-        "Se detectó una petición duplicada, verifique si la operación tuvo 
éxito o intente otra vez."
-      ],
-      "The conversion rate was incorrectly applied": [
-        "La tasa de conversión se aplicó incorrectamente"
-      ],
-      "The account does not have sufficient funds": [
-        "La cuenta no tiene fondos suficientes"
-      ],
-      "Cashouts are not supported": [
-        "Egresos no están soportados"
-      ],
-      "Missing cashout URI in the profile": [
-        "Falta dirección de egreso en el perfíl"
-      ],
-      "Sending the confirmation message failed, retry later or contact the 
administrator.": [
-        "El envío del mensaje de confirmación falló, intente mas tarde o 
contacte al administrador."
-      ],
-      "Convertion rate": [
-        "Tasa de conversión"
-      ],
-      "Fee": [
-        "Comisión"
-      ],
-      "To account": [
-        "Hacia cuenta"
-      ],
-      "No cashout account": [
-        "No hay cuenta de egreso"
-      ],
-      "Before doing a cashout you need to complete your profile": [
-        "Antes de hacer un egreso necesita completar su perfíl"
-      ],
-      "Amount to send": [
-        "Monto a enviar"
-      ],
-      "Amount to receive": [
-        "Monto a recibir"
-      ],
-      "Total cost": [
-        "Costo total"
-      ],
-      "Balance left": [
-        "Saldo remanente"
-      ],
-      "Before fee": [
-        "Antes de comisión"
-      ],
-      "Total cashout transfer": [
-        "Total de egreso"
-      ],
-      "No cashout channel available": [
-        "No hay canal de egreso disponible"
-      ],
-      "Before doing a cashout the server need to provide an second channel to 
confirm the operation": [
-        "Antes de hacer un egreso el servidor necesita proveer un segundo 
canal para confirmar la operación"
-      ],
-      "Second factor authentication": [
-        "Segundo factor de autenticación"
-      ],
-      "Email": [
-        "Correo eletrónico"
-      ],
-      "add a email in your profile to enable this option": [
-        "agrege un correo en su perfíl para habilitar esta opción"
-      ],
-      "SMS": [
-        "SMS"
-      ],
-      "add a phone number in your profile to enable this option": [
-        "agregue un número de teléfono para habilitar esta opción"
-      ],
-      "Details": [
-        "Detalles"
-      ],
-      "Delete": [
-        "Borrar"
-      ],
-      "Credentials": [
-        "Credenciales"
-      ],
-      "Cashouts": [
-        "Egresos"
-      ],
-      "it doesnt have the pattern of an IBAN number": [
-        "no tiene el patrón de un número IBAN"
-      ],
-      "it doesnt have the pattern of an email": [
-        "no tiene el patrón de un correo electrónico"
-      ],
-      "should start with +": [
-        "debería comenzar con un +"
-      ],
-      "phone number can't have other than numbers": [
-        "número de teléfono no puede tener otra cosa que numeros"
-      ],
-      "account identification in the bank": [
-        "identificador de cuenta en el banco"
-      ],
-      "name of the person owner the account": [
-        "nombre de la persona dueña de la cuenta"
-      ],
-      "Internal IBAN": [
-        "IBAN interno"
-      ],
-      "if empty a random account number will be assigned": [
-        "si está vacío un número de cuenta aleatorio será asignado"
-      ],
-      "account identification for bank transfer": [
-        "identificador de cuenta para transferencia bancaria"
-      ],
-      "Phone": [
-        "Teléfono"
-      ],
-      "Cashout IBAN": [
-        "IBAN de egreso"
-      ],
-      "account number where the money is going to be sent when doing 
cashouts": [
-        "numero de cuenta donde el dinero será enviado cuando se ejecuten 
egresos"
-      ],
-      "Max debt": [
-        "Máxima deuda"
-      ],
-      "how much is user able to transfer after zero balance": [
-        "cuanto tiene habilitado a transferir despues de un saldo en cero"
-      ],
-      "Is this a Taler Exchange?": [
-        "Es un Taler Exchange?"
-      ],
-      "This server doesn't support second factor authentication.": [
-        "Este servidor no tiene soporte para segundo factor de autenticación."
-      ],
-      "Enable second factor authentication": [
-        "Hábilitar segundo factor de autenticación"
-      ],
-      "Using email": [
-        "Usando correo eletrónico"
-      ],
-      "Using SMS": [
-        "Usando SMS"
-      ],
-      "Is this account public?": [
-        "Es una cuenta pública?"
-      ],
-      "public accounts have their balance publicly accesible": [
-        "las cuentas públicas tienen su saldo accesible al público"
-      ],
-      "Account updated": [
-        "Cuenta actualizada"
-      ],
-      "The rights to change the account are not sufficient": [
-        "Los permisos para cambiar la cuenta no son suficientes"
-      ],
-      "The username was not found": [
-        "El nombre de usaurio no se encontró"
-      ],
-      "You can't change the legal name, please contact the your account 
administrator.": [
-        "No puede cambiar el nombre legal, por favor contacte el administrador 
de la cuenta."
-      ],
-      "You can't change the debt limit, please contact the your account 
administrator.": [
-        "No puede cambiar el límite de deuda, por favor contacte el 
administrador de la cuenta."
-      ],
-      "You can't change the cashout address, please contact the your account 
administrator.": [
-        "No puede cambiar la dirección de egreso, por favor contacte al 
administrador de la cuenta."
-      ],
-      "You can't change the contact data, please contact the your account 
administrator.": [
-        "No puede cambiar los datos de contacto, por favor contacte al 
administrador de la cuenta."
-      ],
-      "Account \"%1$s\"": [
-        "Cuenta \"%1$s\""
-      ],
-      "Change details": [
-        "Cambiar detalles"
-      ],
-      "Update": [
-        "Actualizar"
-      ],
-      "password doesn't match": [
-        "la contraseña no coincide"
-      ],
-      "Password changed": [
-        "La contraseña cambió"
-      ],
-      "Not authorized to change the password, maybe the session is invalid.": [
-        "No está autorizado a cambiar el password, quizá la sesión es 
invalida."
-      ],
-      "You need to provide the old password. If you don't have it contact your 
account administrator.": [
-        "Se necesita el password viejo para cambiar la contraseña. Si no lo 
tiene contacte a su administrador."
-      ],
-      "Your current password doesn't match, can't change to a new password.": [
-        "Su actual contraseña no coincide, no puede cambiar a una nueva 
contraseña."
-      ],
-      "Update password": [
-        "Actualizar contraseña"
-      ],
-      "New password": [
-        "Nueva contraseña"
-      ],
-      "Type it again": [
-        "Escribalo otra vez"
-      ],
-      "repeat the same password": [
-        "repita la misma contraseña"
-      ],
-      "Current password": [
-        "Contraseña actual"
-      ],
-      "your current password, for security": [
-        "su actual contraseña, por seguridad"
-      ],
-      "Change": [
-        "Cambiar"
-      ],
-      "Can't delete the account": [
-        "No se puede eliminar la cuenta"
-      ],
-      "The account can't be delete while still holding some balance. First 
make sure that the owner make a complete cashout.": [
-        "La cuenta no puede ser eliminada mientras tiene saldo. Primero 
aseguresé que el dueño haga un egreso completo."
-      ],
-      "Account removed": [
-        "Cuenta eliminada"
-      ],
-      "No enough permission to delete the account.": [
-        "No tiene permisos suficientes para eliminar la cuenta."
-      ],
-      "The username was not found.": [
-        "El nombr ede usuario no se encontró."
-      ],
-      "Can't delete a reserved username.": [
-        "No se puede eliminar un nombre de usuario reservado."
-      ],
-      "Can't delete an account with balance different than zero.": [
-        "No se puede eliminar una cuenta con saldo diferente a cero."
-      ],
-      "name doesn't match": [
-        "el nombre no coincide"
-      ],
-      "You are going to remove the account": [
-        "Está por eliminar la cuenta"
-      ],
-      "This step can't be undone.": [
-        "Este paso no puede ser deshecho."
-      ],
-      "Deleting account \"%1$s\"": [
-        "Borrando cuenta \"%1$s\""
-      ],
-      "Verification": [
-        "Verificación"
-      ],
-      "enter the account name that is going to be deleted": [
-        "ingrese el nombre de cuenta que será eliminado"
-      ],
-      "Account created with password \"%1$s\". The user must change the 
password on the next login.": [
-        "Cuenta creada con contraseña \"%1$s\". El usuario debe cambiar la 
contraseña en el siguiente ingreso."
-      ],
-      "Server replied that phone or email is invalid": [
-        "El servidor respondió que el teléfono o correo eletrónico es invalido"
-      ],
-      "The rights to perform the operation are not sufficient": [
-        "Los permisos para ejecutar la operación no son suficientes"
-      ],
-      "Account username is already taken": [
-        "El nombre del usuario ya está tomado"
-      ],
-      "Account id is already taken": [
-        "El id de cuenta ya está tomado"
-      ],
-      "Bank ran out of bonus credit.": [
-        "El banco no tiene mas crédito de bonus."
-      ],
-      "Account username can't be used because is reserved": [
-        "El nombre de usuario de la cuenta no puede userse porque está 
reservado"
-      ],
-      "Can't create accounts": [
-        "No puede crear cuentas"
-      ],
-      "Only system admin can create accounts.": [
-        "Solo los administradores del sistema pueden crear cuentas."
-      ],
-      "New business account": [
-        "Nueva cuenta"
-      ],
-      "Create": [
-        "Crear"
-      ],
-      "Cashout not supported.": [
-        "Egreso no soportado."
-      ],
-      "Account not found.": [
-        "Cuenta no encontrada."
-      ],
-      "Latest cashouts": [
-        "Últimos egresos"
-      ],
-      "Created": [
-        "Creado"
-      ],
-      "Confirmed": [
-        "Confirmado"
-      ],
-      "Total debit": [
-        "Débito total"
-      ],
-      "Total credit": [
-        "Crédito total"
-      ],
-      "Status": [
-        "Estado"
-      ],
-      "never": [
-        "nunca"
-      ],
-      "Cashout for account %1$s": [
-        "Egreso para cuenta %1$s"
-      ],
-      "This cashout not found. Maybe already aborted.": [
-        "Este egreso no se encontró. Quizá fue abortado."
-      ],
-      "Cashout not found. It may be also mean that it was already aborted.": [
-        "Egreso no econtrado. También puede significar que ya ha sido 
abortado."
-      ],
-      "Cashout was already confimed.": [
-        "Egreso ya fue confirmado."
-      ],
-      "Cashout operation is not supported.": [
-        "Operación de egreso no soportada."
-      ],
-      "The cashout operation is already aborted.": [
-        "La operación de egreso ya está abortada."
-      ],
-      "Missing destination account.": [
-        "Falta cuenta destino."
-      ],
-      "Too many failed attempts.": [
-        "Demasiados intentos fallidos."
-      ],
-      "The code for this cashout is invalid.": [
-        "El código para este egreso es invalido."
-      ],
-      "Cashout detail": [
-        "Detalles de egreso"
-      ],
-      "Debited": [
-        "Debitado"
-      ],
-      "Credited": [
-        "Acreditado"
-      ],
-      "Enter the confirmation code": [
-        "Ingresar el código de confirmación"
-      ],
-      "Abort": [
-        "Abortar"
-      ],
-      "Confirm": [
-        "Confirmar"
-      ],
-      "Unauthorized to make the operation, maybe the session has expired or 
the password changed.": [
-        "No autorizado para hacer la operación, quizá la sesión haya expirado 
or cambió la contraseña."
-      ],
-      "The operation was rejected due to insufficient funds.": [
-        "La operación fue rechazada debido a saldo insuficiente."
-      ],
-      "Do not show this again": [
-        "No mostrar otra vez"
-      ],
-      "Close": [
-        "Cerrar"
-      ],
-      "On this device": [
-        "En este dispositivo"
-      ],
-      "If you are using a web browser on desktop you should access your wallet 
with the GNU Taler WebExtension now or click the link if your WebExtension have 
the \"Inject Taler support\" option enabled.": [
-        "Si esta usando un explorador web de escritorio deberías acceder ahora 
a tu billletera con la GNU Taler WebExtension o hacer click en el link si tu 
extensión tiene la configuración \"Inyectar soporte para Taler\" habilitada."
-      ],
-      "Start": [
-        "Comenzar"
-      ],
-      "On a mobile phone": [
-        "En un dispotivo mobile"
-      ],
-      "Scan the QR code with your mobile device.": [
-        "Escanear el código QR con tu dispotivo móvil."
-      ],
-      "There is an operation already": [
-        "Ya hay una operación"
-      ],
-      "Complete or cancel the operation in": [
-        "Completa o cancela la operación en"
-      ],
-      "Server responded with an invalid  withdraw URI": [
-        "El servidor respondió con una URI de extracción inválida"
-      ],
-      "Withdraw URI: %1$s": [
-        "URI de extracción: %1$s"
-      ],
-      "The operation was rejected due to insufficient funds": [
-        "La operación fue rechazada debido a fundos insuficientes"
-      ],
-      "Prepare your wallet": [
-        "Prepare su billetera"
-      ],
-      "After using your wallet you will need to confirm or cancel the 
operation on this site.": [
-        "Despues de usar tu billetera necesitarás confirmar o cancelar la 
operación en este sitio."
-      ],
-      "You need a GNU Taler Wallet": [
-        "Necesitas una GNU Taler Wallet"
-      ],
-      "If you don't have one yet you can follow the instruction in": [
-        "Si no tienes una todavía puedes seguir las instrucciones en"
-      ],
-      "Send money": [
-        "Enviar dinero"
-      ],
-      "to a %1$s wallet": [
-        "a una billetera %1$s"
-      ],
-      "Withdraw digital money into your mobile wallet or browser extension": [
-        "Extraer dinero digital a tu billetera móvil o extesión web"
-      ],
-      "operation ready": [
-        "operación lista"
-      ],
-      "to another bank account": [
-        "a otra cuenta bancaria"
-      ],
-      "Make a wire transfer to an account with known bank account number.": [
-        "Hacer una transferencia bancaria a una cuenta con un número de cuenta 
conocido."
-      ],
-      "Transfer details": [
-        "Detalles de transferencia"
-      ],
-      "This is a demo bank": [
-        "Este es un banco de demostración"
-      ],
-      "This part of the demo shows how a bank that supports Taler directly 
would work. In addition to using your own bank account, you can also see the 
transaction history of some %1$s.": [
-        "Esta parte de la demostración muestra cómo funciona un banco que 
soporta Taler directamente. Además de usar tu propia cuenta de banco, también 
podrás ver el historial de transacciones de algunas %1$s."
-      ],
-      "This part of the demo shows how a bank that supports Taler directly 
would work.": [
-        "Esta parte de la demostración muetra como un banco que soporta Taler 
directamente funcionaría."
-      ],
-      "Pending account delete operation": [
-        "Operación pendiente de eliminación de cuenta"
-      ],
-      "Pending account update operation": [
-        "Operación pendiente de actualización de cuenta"
-      ],
-      "Pending password update operation": [
-        "Operación pendiente de actualización de password"
-      ],
-      "Pending transaction operation": [
-        "Operación pendiente de transacción"
-      ],
-      "Pending withdrawal operation": [
-        "Operación pendiente de extracción"
-      ],
-      "Pending cashout operation": [
-        "Operación pendiente de egreso"
-      ],
-      "You can complete or cancel the operation in": [
-        "Puedes completar o cancelar la operación en"
-      ],
-      "Download bank stats": [
-        "Descargar estadísticas del banco"
-      ],
-      "Include hour metric": [
-        "Incluir métrica horaria"
-      ],
-      "Include day metric": [
-        "Incluir métrica diaria"
-      ],
-      "Include month metric": [
-        "Incluir métrica mensual"
-      ],
-      "Include year metric": [
-        "Incluir métrica anual"
-      ],
-      "Include table header": [
-        "Incluir encabezado de tabla"
-      ],
-      "Add previous metric for compare": [
-        "Agregar métrica previa para comparar"
-      ],
-      "Fail on first error": [
-        "Fallar en el primer error"
-      ],
-      "Download": [
-        "Descargar"
-      ],
-      "downloading... %1$s": [
-        "descargando... %1$s"
-      ],
-      "Download completed": [
-        "Descarga completada"
-      ],
-      "click here to save the file in your computer": [
-        "click aquí para guardar el archivo en su computadora"
-      ],
-      "Challenge not found.": [
-        "Desafío no encontrado."
-      ],
-      "This user is not authorized to complete this challenge.": [
-        "Este usuario no está autorizado para completar este desafío."
-      ],
-      "Too many attemps, try another code.": [
-        "Demasiados intentos, intente otro código."
-      ],
-      "The confirmation code is wrong, try again.": [
-        "El código de confirmación es erroneo, intente otra vez."
-      ],
-      "The operation expired.": [
-        "La operación expiró."
-      ],
-      "The operation failed.": [
-        "La operación falló."
-      ],
-      "The operation needs another confirmation to complete.": [
-        "La operación necesita otra confirmación para completar."
-      ],
-      "Account delete": [
-        "Eliminación de cuenta"
-      ],
-      "Account update": [
-        "Actualización de cuenta"
-      ],
-      "Password update": [
-        "Actualización de contraseña"
-      ],
-      "Wire transfer": [
-        "Transferencia bancaria"
-      ],
-      "Withdrawal": [
-        "Extracción"
-      ],
-      "Confirm the operation": [
-        "Confirmar la operación"
-      ],
-      "Send again": [
-        "Enviar otra vez"
-      ],
-      "Send code": [
-        "Enviar código"
-      ],
-      "Operation details": [
-        "Detalles de operación"
-      ],
-      "Challenge details": [
-        "Detalles del desafío"
-      ],
-      "Sent at": [
-        "Enviado a"
-      ],
-      "To phone": [
-        "Al teléfono"
-      ],
-      "To email": [
-        "Al email"
-      ],
-      "Welcome to %1$s!": [
-        "Bienvenido a %1$s!"
-      ]
-    }
-  },
-  "domain": "messages",
-  "plural_forms": "nplurals=2; plural=n != 1;",
-  "lang": "es",
-  "completeness": 100
-};
-
-strings['en'] = {
-  "locale_data": {
-    "messages": {
-      "": {
-        "domain": "messages",
-        "plural_forms": "nplurals=2; plural=(n != 1);",
-        "lang": "en"
-      },
-      "Operation failed, please report": [
-        ""
-      ],
-      "Request timeout": [
-        ""
-      ],
-      "Request throttled": [
-        ""
-      ],
-      "Malformed response": [
-        ""
-      ],
-      "Network error": [
-        ""
-      ],
-      "Unexpected request error": [
-        ""
-      ],
-      "Unexpected error": [
-        ""
-      ],
-      "IBAN numbers usually have more that 4 digits": [
-        ""
-      ],
-      "IBAN numbers usually have less that 34 digits": [
-        ""
-      ],
-      "IBAN country code not found": [
-        ""
-      ],
-      "IBAN number is not valid, checksum is wrong": [
-        ""
-      ],
-      "Max withdrawal amount": [
-        "Start withdrawal"
-      ],
-      "Show withdrawal confirmation": [
-        ""
-      ],
-      "Show demo description": [
-        ""
-      ],
-      "Show install wallet first": [
-        ""
-      ],
-      "Use fast withdrawal form": [
-        "Start withdrawal"
-      ],
-      "Show debug info": [
-        ""
-      ],
-      "The reserve operation has been confirmed previously and can't be 
aborted": [
-        ""
-      ],
-      "The operation id is invalid.": [
-        ""
-      ],
-      "The operation was not found.": [
-        ""
-      ],
-      "If you have a Taler wallet installed in this device": [
-        ""
-      ],
-      "You will see the details of the operation in your wallet including the 
fees (if applies). If you still don't have one you can install it following 
instructions in": [
-        ""
-      ],
-      "this page": [
-        ""
-      ],
-      "Withdraw": [
-        "Confirm withdrawal"
-      ],
-      "Or if you have the wallet in another device": [
-        ""
-      ],
-      "Scan the QR below to start the withdrawal.": [
-        "Close Taler withdrawal"
-      ],
-      "required": [
-        ""
-      ],
-      "IBAN should have just uppercased letters and numbers": [
-        ""
-      ],
-      "not valid": [
-        ""
-      ],
-      "should be greater than 0": [
-        ""
-      ],
-      "balance is not enough": [
-        ""
-      ],
-      "does not follow the pattern": [
-        ""
-      ],
-      "only \"IBAN\" target are supported": [
-        ""
-      ],
-      "use the \"amount\" parameter to specify the amount to be transferred": [
-        ""
-      ],
-      "the amount is not valid": [
-        ""
-      ],
-      "use the \"message\" parameter to specify a reference text for the 
transfer": [
-        ""
-      ],
-      "The request was invalid or the payto://-URI used unacceptable 
features.": [
-        ""
-      ],
-      "Not enough permission to complete the operation.": [
-        ""
-      ],
-      "The destination account \"%1$s\" was not found.": [
-        ""
-      ],
-      "The origin and the destination of the transfer can't be the same.": [
-        ""
-      ],
-      "Your balance is not enough.": [
-        ""
-      ],
-      "The origin account \"%1$s\" was not found.": [
-        ""
-      ],
-      "Using a form": [
-        ""
-      ],
-      "Import payto:// URI": [
-        ""
-      ],
-      "Recipient": [
-        ""
-      ],
-      "IBAN of the recipient's account": [
-        ""
-      ],
-      "Transfer subject": [
-        ""
-      ],
-      "subject": [
-        ""
-      ],
-      "some text to identify the transfer": [
-        ""
-      ],
-      "Amount": [
-        ""
-      ],
-      "amount to transfer": [
-        "Amount to withdraw"
-      ],
-      "payto URI:": [
-        ""
-      ],
-      "uniform resource identifier of the target account": [
-        ""
-      ],
-      "payto://iban/[receiver-iban]?message=[subject]&amount=[%1$s:X.Y]": [
-        ""
-      ],
-      "Cancel": [
-        ""
-      ],
-      "Send": [
-        ""
-      ],
-      "Missing username": [
-        ""
-      ],
-      "Missing password": [
-        ""
-      ],
-      "Wrong credentials for \"%1$s\"": [
-        ""
-      ],
-      "Account not found": [
-        ""
-      ],
-      "Username": [
-        ""
-      ],
-      "username of the account": [
-        ""
-      ],
-      "Password": [
-        ""
-      ],
-      "password of the account": [
-        ""
-      ],
-      "Check": [
-        ""
-      ],
-      "Log in": [
-        ""
-      ],
-      "Register": [
-        ""
-      ],
-      "Wire transfer completed!": [
-        ""
-      ],
-      "The withdrawal has been aborted previously and can't be confirmed": [
-        ""
-      ],
-      "The withdrawal operation can't be confirmed before a wallet accepted 
the transaction.": [
-        ""
-      ],
-      "Your balance is not enough for the operation.": [
-        ""
-      ],
-      "Confirm the withdrawal operation": [
-        "Confirm withdrawal"
-      ],
-      "Wire transfer details": [
-        ""
-      ],
-      "Taler Exchange operator's account": [
-        ""
-      ],
-      "Taler Exchange operator's name": [
-        ""
-      ],
-      "Transfer": [
-        ""
-      ],
-      "Authentication required": [
-        ""
-      ],
-      "This operation was created with other username": [
-        ""
-      ],
-      "Operation aborted": [
-        ""
-      ],
-      "The wire transfer to the Taler Exchange operator's account was aborted, 
your balance was not affected.": [
-        ""
-      ],
-      "You can close this page now or continue to the account page.": [
-        ""
-      ],
-      "Continue": [
-        ""
-      ],
-      "Withdrawal confirmed": [
-        ""
-      ],
-      "The wire transfer to the Taler operator has been initiated. You will 
soon receive the requested amount in your Taler wallet.": [
-        ""
-      ],
-      "Done": [
-        ""
-      ],
-      "Operation canceled": [
-        ""
-      ],
-      "The operation is marked as 'selected' but some step in the withdrawal 
failed": [
-        ""
-      ],
-      "The account is selected but no withdrawal identification found.": [
-        ""
-      ],
-      "There is a withdrawal identification but no account has been selected 
or the selected account is invalid.": [
-        ""
-      ],
-      "No withdrawal ID found and no account has been selected or the selected 
account is invalid.": [
-        ""
-      ],
-      "Operation not found": [
-        ""
-      ],
-      "This operation is not known by the server. The operation id is wrong or 
the server deleted the operation information before reaching here.": [
-        ""
-      ],
-      "Cotinue to dashboard": [
-        ""
-      ],
-      "The Withdrawal URI is not valid": [
-        ""
-      ],
-      "the bank backend is not supported. supported version \"%1$s\", server 
version \"%2$s\"": [
-        ""
-      ],
-      "Internal error, please report.": [
-        ""
-      ],
-      "Preferences": [
-        ""
-      ],
-      "Welcome, %1$s": [
-        ""
-      ],
-      "Latest transactions": [
-        ""
-      ],
-      "Date": [
-        ""
-      ],
-      "Counterpart": [
-        ""
-      ],
-      "Subject": [
-        ""
-      ],
-      "sent": [
-        ""
-      ],
-      "received": [
-        ""
-      ],
-      "invalid value": [
-        ""
-      ],
-      "to": [
-        ""
-      ],
-      "from": [
-        ""
-      ],
-      "First page": [
-        ""
-      ],
-      "Next": [
-        ""
-      ],
-      "History of public accounts": [
-        ""
-      ],
-      "Currently, the bank is not accepting new registrations!": [
-        ""
-      ],
-      "Missing name": [
-        ""
-      ],
-      "Use letters and numbers only, and start with a lowercase letter": [
-        ""
-      ],
-      "Passwords don't match": [
-        ""
-      ],
-      "Server replied with invalid phone or email.": [
-        ""
-      ],
-      "Registration is disabled because the bank ran out of bonus credit.": [
-        ""
-      ],
-      "No enough permission to create that account.": [
-        ""
-      ],
-      "That account id is already taken.": [
-        ""
-      ],
-      "That username is already taken.": [
-        ""
-      ],
-      "That username can't be used because is reserved.": [
-        ""
-      ],
-      "Only admin is allow to set debt limit.": [
-        ""
-      ],
-      "No information for the selected authentication channel.": [
-        ""
-      ],
-      "Authentication channel is not supported.": [
-        ""
-      ],
-      "Only admin can create accounts with second factor authentication.": [
-        ""
-      ],
-      "Account registration": [
-        ""
-      ],
-      "Repeat password": [
-        ""
-      ],
-      "Name": [
-        ""
-      ],
-      "Create a random temporary user": [
-        ""
-      ],
-      "Make a wire transfer": [
-        ""
-      ],
-      "Wire transfer created!": [
-        ""
-      ],
-      "Accounts": [
-        ""
-      ],
-      "A list of all business account in the bank.": [
-        ""
-      ],
-      "Create account": [
-        ""
-      ],
-      "Balance": [
-        ""
-      ],
-      "Actions": [
-        ""
-      ],
-      "unknown": [
-        ""
-      ],
-      "change password": [
-        ""
-      ],
-      "remove": [
-        ""
-      ],
-      "Select a section": [
-        ""
-      ],
-      "Last hour": [
-        ""
-      ],
-      "Last day": [
-        ""
-      ],
-      "Last month": [
-        ""
-      ],
-      "Last year": [
-        ""
-      ],
-      "Last Year": [
-        ""
-      ],
-      "Trading volume on %1$s compared to %2$s": [
-        ""
-      ],
-      "Cashin": [
-        ""
-      ],
-      "Cashout": [
-        ""
-      ],
-      "Payin": [
-        ""
-      ],
-      "Payout": [
-        ""
-      ],
-      "download stats as CSV": [
-        ""
-      ],
-      "Descreased by": [
-        ""
-      ],
-      "Increased by": [
-        ""
-      ],
-      "Unable to create a cashout": [
-        ""
-      ],
-      "The bank configuration does not support cashout operations.": [
-        ""
-      ],
-      "invalid": [
-        ""
-      ],
-      "need to be higher due to fees": [
-        ""
-      ],
-      "the total transfer at destination will be zero": [
-        ""
-      ],
-      "Cashout created": [
-        ""
-      ],
-      "Duplicated request detected, check if the operation succeded or try 
again.": [
-        ""
-      ],
-      "The conversion rate was incorrectly applied": [
-        ""
-      ],
-      "The account does not have sufficient funds": [
-        ""
-      ],
-      "Cashouts are not supported": [
-        ""
-      ],
-      "Missing cashout URI in the profile": [
-        ""
-      ],
-      "Sending the confirmation message failed, retry later or contact the 
administrator.": [
-        ""
-      ],
-      "Convertion rate": [
-        ""
-      ],
-      "Fee": [
-        ""
-      ],
-      "To account": [
-        ""
-      ],
-      "No cashout account": [
-        ""
-      ],
-      "Before doing a cashout you need to complete your profile": [
-        ""
-      ],
-      "Amount to send": [
-        "Amount to withdraw"
-      ],
-      "Amount to receive": [
-        "Amount to withdraw"
-      ],
-      "Total cost": [
-        ""
-      ],
-      "Balance left": [
-        ""
-      ],
-      "Before fee": [
-        ""
-      ],
-      "Total cashout transfer": [
-        ""
-      ],
-      "No cashout channel available": [
-        ""
-      ],
-      "Before doing a cashout the server need to provide an second channel to 
confirm the operation": [
-        ""
-      ],
-      "Second factor authentication": [
-        ""
-      ],
-      "Email": [
-        ""
-      ],
-      "add a email in your profile to enable this option": [
-        ""
-      ],
-      "SMS": [
-        ""
-      ],
-      "add a phone number in your profile to enable this option": [
-        ""
-      ],
-      "Details": [
-        ""
-      ],
-      "Delete": [
-        ""
-      ],
-      "Credentials": [
-        ""
-      ],
-      "Cashouts": [
-        ""
-      ],
-      "it doesnt have the pattern of an IBAN number": [
-        ""
-      ],
-      "it doesnt have the pattern of an email": [
-        ""
-      ],
-      "should start with +": [
-        ""
-      ],
-      "phone number can't have other than numbers": [
-        ""
-      ],
-      "account identification in the bank": [
-        ""
-      ],
-      "name of the person owner the account": [
-        ""
-      ],
-      "Internal IBAN": [
-        ""
-      ],
-      "if empty a random account number will be assigned": [
-        ""
-      ],
-      "account identification for bank transfer": [
-        ""
-      ],
-      "Phone": [
-        ""
-      ],
-      "Cashout IBAN": [
-        ""
-      ],
-      "account number where the money is going to be sent when doing 
cashouts": [
-        ""
-      ],
-      "Max debt": [
-        ""
-      ],
-      "how much is user able to transfer after zero balance": [
-        ""
-      ],
-      "Is this a Taler Exchange?": [
-        ""
-      ],
-      "This server doesn't support second factor authentication.": [
-        ""
-      ],
-      "Enable second factor authentication": [
-        ""
-      ],
-      "Using email": [
-        ""
-      ],
-      "Using SMS": [
-        ""
-      ],
-      "Is this account public?": [
-        ""
-      ],
-      "public accounts have their balance publicly accesible": [
-        ""
-      ],
-      "Account updated": [
-        ""
-      ],
-      "The rights to change the account are not sufficient": [
-        ""
-      ],
-      "The username was not found": [
-        ""
-      ],
-      "You can't change the legal name, please contact the your account 
administrator.": [
-        ""
-      ],
-      "You can't change the debt limit, please contact the your account 
administrator.": [
-        ""
-      ],
-      "You can't change the cashout address, please contact the your account 
administrator.": [
-        ""
-      ],
-      "You can't change the contact data, please contact the your account 
administrator.": [
-        ""
-      ],
-      "Account \"%1$s\"": [
-        ""
-      ],
-      "Change details": [
-        ""
-      ],
-      "Update": [
-        ""
-      ],
-      "password doesn't match": [
-        ""
-      ],
-      "Password changed": [
-        ""
-      ],
-      "Not authorized to change the password, maybe the session is invalid.": [
-        ""
-      ],
-      "You need to provide the old password. If you don't have it contact your 
account administrator.": [
-        ""
-      ],
-      "Your current password doesn't match, can't change to a new password.": [
-        ""
-      ],
-      "Update password": [
-        ""
-      ],
-      "New password": [
-        ""
-      ],
-      "Type it again": [
-        ""
-      ],
-      "repeat the same password": [
-        ""
-      ],
-      "Current password": [
-        ""
-      ],
-      "your current password, for security": [
-        ""
-      ],
-      "Change": [
-        ""
-      ],
-      "Can't delete the account": [
-        ""
-      ],
-      "The account can't be delete while still holding some balance. First 
make sure that the owner make a complete cashout.": [
-        ""
-      ],
-      "Account removed": [
-        ""
-      ],
-      "No enough permission to delete the account.": [
-        ""
-      ],
-      "The username was not found.": [
-        ""
-      ],
-      "Can't delete a reserved username.": [
-        ""
-      ],
-      "Can't delete an account with balance different than zero.": [
-        ""
-      ],
-      "name doesn't match": [
-        ""
-      ],
-      "You are going to remove the account": [
-        ""
-      ],
-      "This step can't be undone.": [
-        ""
-      ],
-      "Deleting account \"%1$s\"": [
-        ""
-      ],
-      "Verification": [
-        ""
-      ],
-      "enter the account name that is going to be deleted": [
-        ""
-      ],
-      "Account created with password \"%1$s\". The user must change the 
password on the next login.": [
-        ""
-      ],
-      "Server replied that phone or email is invalid": [
-        ""
-      ],
-      "The rights to perform the operation are not sufficient": [
-        ""
-      ],
-      "Account username is already taken": [
-        ""
-      ],
-      "Account id is already taken": [
-        ""
-      ],
-      "Bank ran out of bonus credit.": [
-        ""
-      ],
-      "Account username can't be used because is reserved": [
-        ""
-      ],
-      "Can't create accounts": [
-        ""
-      ],
-      "Only system admin can create accounts.": [
-        ""
-      ],
-      "New business account": [
-        ""
-      ],
-      "Create": [
-        ""
-      ],
-      "Cashout not supported.": [
-        ""
-      ],
-      "Account not found.": [
-        ""
-      ],
-      "Latest cashouts": [
-        ""
-      ],
-      "Created": [
-        ""
-      ],
-      "Confirmed": [
-        ""
-      ],
-      "Total debit": [
-        ""
-      ],
-      "Total credit": [
-        ""
-      ],
-      "Status": [
-        ""
-      ],
-      "never": [
-        ""
-      ],
-      "Cashout for account %1$s": [
-        ""
-      ],
-      "This cashout not found. Maybe already aborted.": [
-        ""
-      ],
-      "Cashout not found. It may be also mean that it was already aborted.": [
-        ""
-      ],
-      "Cashout was already confimed.": [
-        ""
-      ],
-      "Cashout operation is not supported.": [
-        ""
-      ],
-      "The cashout operation is already aborted.": [
-        ""
-      ],
-      "Missing destination account.": [
-        ""
-      ],
-      "Too many failed attempts.": [
-        ""
-      ],
-      "The code for this cashout is invalid.": [
-        ""
-      ],
-      "Cashout detail": [
-        ""
-      ],
-      "Debited": [
-        ""
-      ],
-      "Credited": [
-        ""
-      ],
-      "Enter the confirmation code": [
-        ""
-      ],
-      "Abort": [
-        ""
-      ],
-      "Confirm": [
-        ""
-      ],
-      "Unauthorized to make the operation, maybe the session has expired or 
the password changed.": [
-        ""
-      ],
-      "The operation was rejected due to insufficient funds.": [
-        ""
-      ],
-      "Do not show this again": [
-        ""
-      ],
-      "Close": [
-        "Close"
-      ],
-      "On this device": [
-        ""
-      ],
-      "If you are using a web browser on desktop you should access your wallet 
with the GNU Taler WebExtension now or click the link if your WebExtension have 
the \"Inject Taler support\" option enabled.": [
-        ""
-      ],
-      "Start": [
-        ""
-      ],
-      "On a mobile phone": [
-        ""
-      ],
-      "Scan the QR code with your mobile device.": [
-        ""
-      ],
-      "There is an operation already": [
-        ""
-      ],
-      "Complete or cancel the operation in": [
-        "Confirm withdrawal"
-      ],
-      "Server responded with an invalid  withdraw URI": [
-        ""
-      ],
-      "Withdraw URI: %1$s": [
-        "Confirm withdrawal"
-      ],
-      "The operation was rejected due to insufficient funds": [
-        ""
-      ],
-      "Prepare your wallet": [
-        ""
-      ],
-      "After using your wallet you will need to confirm or cancel the 
operation on this site.": [
-        ""
-      ],
-      "You need a GNU Taler Wallet": [
-        "Top up Taler wallet"
-      ],
-      "If you don't have one yet you can follow the instruction in": [
-        ""
-      ],
-      "Send money": [
-        ""
-      ],
-      "to a %1$s wallet": [
-        ""
-      ],
-      "Withdraw digital money into your mobile wallet or browser extension": [
-        ""
-      ],
-      "operation ready": [
-        ""
-      ],
-      "to another bank account": [
-        ""
-      ],
-      "Make a wire transfer to an account with known bank account number.": [
-        ""
-      ],
-      "Transfer details": [
-        "Top up Taler wallet"
-      ],
-      "This is a demo bank": [
-        ""
-      ],
-      "This part of the demo shows how a bank that supports Taler directly 
would work. In addition to using your own bank account, you can also see the 
transaction history of some %1$s.": [
-        ""
-      ],
-      "This part of the demo shows how a bank that supports Taler directly 
would work.": [
-        ""
-      ],
-      "Pending account delete operation": [
-        ""
-      ],
-      "Pending account update operation": [
-        ""
-      ],
-      "Pending password update operation": [
-        ""
-      ],
-      "Pending transaction operation": [
-        ""
-      ],
-      "Pending withdrawal operation": [
-        ""
-      ],
-      "Pending cashout operation": [
-        ""
-      ],
-      "You can complete or cancel the operation in": [
-        ""
-      ],
-      "Download bank stats": [
-        ""
-      ],
-      "Include hour metric": [
-        ""
-      ],
-      "Include day metric": [
-        ""
-      ],
-      "Include month metric": [
-        ""
-      ],
-      "Include year metric": [
-        ""
-      ],
-      "Include table header": [
-        ""
-      ],
-      "Add previous metric for compare": [
-        ""
-      ],
-      "Fail on first error": [
-        ""
-      ],
-      "Download": [
-        ""
-      ],
-      "downloading... %1$s": [
-        ""
-      ],
-      "Download completed": [
-        ""
-      ],
-      "click here to save the file in your computer": [
-        ""
-      ],
-      "Challenge not found.": [
-        ""
-      ],
-      "This user is not authorized to complete this challenge.": [
-        ""
-      ],
-      "Too many attemps, try another code.": [
-        ""
-      ],
-      "The confirmation code is wrong, try again.": [
-        ""
-      ],
-      "The operation expired.": [
-        ""
-      ],
-      "The operation failed.": [
-        ""
-      ],
-      "The operation needs another confirmation to complete.": [
-        ""
-      ],
-      "Account delete": [
-        ""
-      ],
-      "Account update": [
-        ""
-      ],
-      "Password update": [
-        ""
-      ],
-      "Wire transfer": [
-        ""
-      ],
-      "Withdrawal": [
-        "Confirm withdrawal"
-      ],
-      "Confirm the operation": [
-        "Confirm withdrawal"
-      ],
-      "Send again": [
-        ""
-      ],
-      "Send code": [
-        ""
-      ],
-      "Operation details": [
-        ""
-      ],
-      "Challenge details": [
-        ""
-      ],
-      "Sent at": [
-        ""
-      ],
-      "To phone": [
-        ""
-      ],
-      "To email": [
-        ""
-      ],
-      "Welcome to %1$s!": [
-        ""
-      ]
-    }
-  },
-  "domain": "messages",
-  "plural_forms": "nplurals=2; plural=(n != 1);",
-  "lang": "en",
-  "completeness": 100
-};
-
-strings['de'] = {
-  "locale_data": {
-    "messages": {
-      "": {
-        "domain": "messages",
-        "plural_forms": "nplurals=2; plural=n != 1;",
-        "lang": "de"
-      },
-      "Operation failed, please report": [
-        ""
-      ],
-      "Request timeout": [
-        ""
-      ],
-      "Request throttled": [
-        ""
-      ],
-      "Malformed response": [
-        ""
-      ],
-      "Network error": [
-        ""
-      ],
-      "Unexpected request error": [
-        ""
-      ],
-      "Unexpected error": [
-        ""
-      ],
-      "IBAN numbers usually have more that 4 digits": [
-        ""
-      ],
-      "IBAN numbers usually have less that 34 digits": [
-        ""
-      ],
-      "IBAN country code not found": [
-        ""
-      ],
-      "IBAN number is not valid, checksum is wrong": [
-        ""
-      ],
-      "Max withdrawal amount": [
-        ""
-      ],
-      "Show withdrawal confirmation": [
-        ""
-      ],
-      "Show demo description": [
-        ""
-      ],
-      "Show install wallet first": [
-        ""
-      ],
-      "Use fast withdrawal form": [
-        ""
-      ],
-      "Show debug info": [
-        ""
-      ],
-      "The reserve operation has been confirmed previously and can't be 
aborted": [
-        ""
-      ],
-      "The operation id is invalid.": [
-        ""
-      ],
-      "The operation was not found.": [
-        ""
-      ],
-      "If you have a Taler wallet installed in this device": [
-        ""
-      ],
-      "You will see the details of the operation in your wallet including the 
fees (if applies). If you still don't have one you can install it following 
instructions in": [
-        ""
-      ],
-      "this page": [
-        ""
-      ],
-      "Withdraw": [
-        ""
-      ],
-      "Or if you have the wallet in another device": [
-        ""
-      ],
-      "Scan the QR below to start the withdrawal.": [
-        ""
-      ],
-      "required": [
-        ""
-      ],
-      "IBAN should have just uppercased letters and numbers": [
-        ""
-      ],
-      "not valid": [
-        ""
-      ],
-      "should be greater than 0": [
-        ""
-      ],
-      "balance is not enough": [
-        ""
-      ],
-      "does not follow the pattern": [
-        ""
-      ],
-      "only \"IBAN\" target are supported": [
-        ""
-      ],
-      "use the \"amount\" parameter to specify the amount to be transferred": [
-        ""
-      ],
-      "the amount is not valid": [
-        ""
-      ],
-      "use the \"message\" parameter to specify a reference text for the 
transfer": [
-        ""
-      ],
-      "The request was invalid or the payto://-URI used unacceptable 
features.": [
-        ""
-      ],
-      "Not enough permission to complete the operation.": [
-        ""
-      ],
-      "The destination account \"%1$s\" was not found.": [
-        ""
-      ],
-      "The origin and the destination of the transfer can't be the same.": [
-        ""
-      ],
-      "Your balance is not enough.": [
-        ""
-      ],
-      "The origin account \"%1$s\" was not found.": [
-        ""
-      ],
-      "Using a form": [
-        ""
-      ],
-      "Import payto:// URI": [
-        ""
-      ],
-      "Recipient": [
-        ""
-      ],
-      "IBAN of the recipient's account": [
-        ""
-      ],
-      "Transfer subject": [
-        ""
-      ],
-      "subject": [
-        "Verwendungszweck"
-      ],
-      "some text to identify the transfer": [
-        ""
-      ],
-      "Amount": [
-        "Betrag"
-      ],
-      "amount to transfer": [
-        "Betrag"
-      ],
-      "payto URI:": [
-        ""
-      ],
-      "uniform resource identifier of the target account": [
-        ""
-      ],
-      "payto://iban/[receiver-iban]?message=[subject]&amount=[%1$s:X.Y]": [
-        ""
-      ],
-      "Cancel": [
-        ""
-      ],
-      "Send": [
-        ""
-      ],
-      "Missing username": [
-        ""
-      ],
-      "Missing password": [
-        ""
-      ],
-      "Wrong credentials for \"%1$s\"": [
-        ""
-      ],
-      "Account not found": [
-        ""
-      ],
-      "Username": [
-        ""
-      ],
-      "username of the account": [
-        ""
-      ],
-      "Password": [
-        ""
-      ],
-      "password of the account": [
-        "Buchungen auf öffentlich sichtbaren Konten"
-      ],
-      "Check": [
-        ""
-      ],
-      "Log in": [
-        ""
-      ],
-      "Register": [
-        ""
-      ],
-      "Wire transfer completed!": [
-        ""
-      ],
-      "The withdrawal has been aborted previously and can't be confirmed": [
-        ""
-      ],
-      "The withdrawal operation can't be confirmed before a wallet accepted 
the transaction.": [
-        ""
-      ],
-      "Your balance is not enough for the operation.": [
-        ""
-      ],
-      "Confirm the withdrawal operation": [
-        "Abhebung bestätigen"
-      ],
-      "Wire transfer details": [
-        ""
-      ],
-      "Taler Exchange operator's account": [
-        ""
-      ],
-      "Taler Exchange operator's name": [
-        ""
-      ],
-      "Transfer": [
-        ""
-      ],
-      "Authentication required": [
-        ""
-      ],
-      "This operation was created with other username": [
-        ""
-      ],
-      "Operation aborted": [
-        ""
-      ],
-      "The wire transfer to the Taler Exchange operator's account was aborted, 
your balance was not affected.": [
-        ""
-      ],
-      "You can close this page now or continue to the account page.": [
-        ""
-      ],
-      "Continue": [
-        ""
-      ],
-      "Withdrawal confirmed": [
-        ""
-      ],
-      "The wire transfer to the Taler operator has been initiated. You will 
soon receive the requested amount in your Taler wallet.": [
-        ""
-      ],
-      "Done": [
-        ""
-      ],
-      "Operation canceled": [
-        ""
-      ],
-      "The operation is marked as 'selected' but some step in the withdrawal 
failed": [
-        ""
-      ],
-      "The account is selected but no withdrawal identification found.": [
-        ""
-      ],
-      "There is a withdrawal identification but no account has been selected 
or the selected account is invalid.": [
-        ""
-      ],
-      "No withdrawal ID found and no account has been selected or the selected 
account is invalid.": [
-        ""
-      ],
-      "Operation not found": [
-        ""
-      ],
-      "This operation is not known by the server. The operation id is wrong or 
the server deleted the operation information before reaching here.": [
-        ""
-      ],
-      "Cotinue to dashboard": [
-        ""
-      ],
-      "The Withdrawal URI is not valid": [
-        ""
-      ],
-      "the bank backend is not supported. supported version \"%1$s\", server 
version \"%2$s\"": [
-        ""
-      ],
-      "Internal error, please report.": [
-        ""
-      ],
-      "Preferences": [
-        ""
-      ],
-      "Welcome, %1$s": [
-        ""
-      ],
-      "Latest transactions": [
-        ""
-      ],
-      "Date": [
-        "Datum"
-      ],
-      "Counterpart": [
-        "Empfänger"
-      ],
-      "Subject": [
-        "Verwendungszweck"
-      ],
-      "sent": [
-        ""
-      ],
-      "received": [
-        ""
-      ],
-      "invalid value": [
-        ""
-      ],
-      "to": [
-        ""
-      ],
-      "from": [
-        ""
-      ],
-      "First page": [
-        ""
-      ],
-      "Next": [
-        ""
-      ],
-      "History of public accounts": [
-        "Buchungen auf öffentlich sichtbaren Konten"
-      ],
-      "Currently, the bank is not accepting new registrations!": [
-        ""
-      ],
-      "Missing name": [
-        ""
-      ],
-      "Use letters and numbers only, and start with a lowercase letter": [
-        ""
-      ],
-      "Passwords don't match": [
-        ""
-      ],
-      "Server replied with invalid phone or email.": [
-        ""
-      ],
-      "Registration is disabled because the bank ran out of bonus credit.": [
-        ""
-      ],
-      "No enough permission to create that account.": [
-        ""
-      ],
-      "That account id is already taken.": [
-        ""
-      ],
-      "That username is already taken.": [
-        ""
-      ],
-      "That username can't be used because is reserved.": [
-        ""
-      ],
-      "Only admin is allow to set debt limit.": [
-        ""
-      ],
-      "No information for the selected authentication channel.": [
-        ""
-      ],
-      "Authentication channel is not supported.": [
-        ""
-      ],
-      "Only admin can create accounts with second factor authentication.": [
-        ""
-      ],
-      "Account registration": [
-        ""
-      ],
-      "Repeat password": [
-        ""
-      ],
-      "Name": [
-        ""
-      ],
-      "Create a random temporary user": [
-        ""
-      ],
-      "Make a wire transfer": [
-        ""
-      ],
-      "Wire transfer created!": [
-        ""
-      ],
-      "Accounts": [
-        "Betrag"
-      ],
-      "A list of all business account in the bank.": [
-        ""
-      ],
-      "Create account": [
-        ""
-      ],
-      "Balance": [
-        ""
-      ],
-      "Actions": [
-        ""
-      ],
-      "unknown": [
-        ""
-      ],
-      "change password": [
-        ""
-      ],
-      "remove": [
-        ""
-      ],
-      "Select a section": [
-        ""
-      ],
-      "Last hour": [
-        ""
-      ],
-      "Last day": [
-        ""
-      ],
-      "Last month": [
-        ""
-      ],
-      "Last year": [
-        ""
-      ],
-      "Last Year": [
-        ""
-      ],
-      "Trading volume on %1$s compared to %2$s": [
-        ""
-      ],
-      "Cashin": [
-        ""
-      ],
-      "Cashout": [
-        ""
-      ],
-      "Payin": [
-        ""
-      ],
-      "Payout": [
-        ""
-      ],
-      "download stats as CSV": [
-        ""
-      ],
-      "Descreased by": [
-        ""
-      ],
-      "Increased by": [
-        ""
-      ],
-      "Unable to create a cashout": [
-        ""
-      ],
-      "The bank configuration does not support cashout operations.": [
-        ""
-      ],
-      "invalid": [
-        ""
-      ],
-      "need to be higher due to fees": [
-        ""
-      ],
-      "the total transfer at destination will be zero": [
-        ""
-      ],
-      "Cashout created": [
-        ""
-      ],
-      "Duplicated request detected, check if the operation succeded or try 
again.": [
-        ""
-      ],
-      "The conversion rate was incorrectly applied": [
-        ""
-      ],
-      "The account does not have sufficient funds": [
-        ""
-      ],
-      "Cashouts are not supported": [
-        ""
-      ],
-      "Missing cashout URI in the profile": [
-        ""
-      ],
-      "Sending the confirmation message failed, retry later or contact the 
administrator.": [
-        ""
-      ],
-      "Convertion rate": [
-        ""
-      ],
-      "Fee": [
-        ""
-      ],
-      "To account": [
-        ""
-      ],
-      "No cashout account": [
-        ""
-      ],
-      "Before doing a cashout you need to complete your profile": [
-        ""
-      ],
-      "Amount to send": [
-        "Betrag"
-      ],
-      "Amount to receive": [
-        ""
-      ],
-      "Total cost": [
-        ""
-      ],
-      "Balance left": [
-        ""
-      ],
-      "Before fee": [
-        ""
-      ],
-      "Total cashout transfer": [
-        ""
-      ],
-      "No cashout channel available": [
-        ""
-      ],
-      "Before doing a cashout the server need to provide an second channel to 
confirm the operation": [
-        ""
-      ],
-      "Second factor authentication": [
-        ""
-      ],
-      "Email": [
-        ""
-      ],
-      "add a email in your profile to enable this option": [
-        ""
-      ],
-      "SMS": [
-        ""
-      ],
-      "add a phone number in your profile to enable this option": [
-        ""
-      ],
-      "Details": [
-        ""
-      ],
-      "Delete": [
-        ""
-      ],
-      "Credentials": [
-        ""
-      ],
-      "Cashouts": [
-        ""
-      ],
-      "it doesnt have the pattern of an IBAN number": [
-        ""
-      ],
-      "it doesnt have the pattern of an email": [
-        ""
-      ],
-      "should start with +": [
-        ""
-      ],
-      "phone number can't have other than numbers": [
-        ""
-      ],
-      "account identification in the bank": [
-        ""
-      ],
-      "name of the person owner the account": [
-        ""
-      ],
-      "Internal IBAN": [
-        ""
-      ],
-      "if empty a random account number will be assigned": [
-        ""
-      ],
-      "account identification for bank transfer": [
-        ""
-      ],
-      "Phone": [
-        ""
-      ],
-      "Cashout IBAN": [
-        ""
-      ],
-      "account number where the money is going to be sent when doing 
cashouts": [
-        ""
-      ],
-      "Max debt": [
-        ""
-      ],
-      "how much is user able to transfer after zero balance": [
-        ""
-      ],
-      "Is this a Taler Exchange?": [
-        ""
-      ],
-      "This server doesn't support second factor authentication.": [
-        ""
-      ],
-      "Enable second factor authentication": [
-        ""
-      ],
-      "Using email": [
-        ""
-      ],
-      "Using SMS": [
-        ""
-      ],
-      "Is this account public?": [
-        ""
-      ],
-      "public accounts have their balance publicly accesible": [
-        ""
-      ],
-      "Account updated": [
-        ""
-      ],
-      "The rights to change the account are not sufficient": [
-        ""
-      ],
-      "The username was not found": [
-        ""
-      ],
-      "You can't change the legal name, please contact the your account 
administrator.": [
-        ""
-      ],
-      "You can't change the debt limit, please contact the your account 
administrator.": [
-        ""
-      ],
-      "You can't change the cashout address, please contact the your account 
administrator.": [
-        ""
-      ],
-      "You can't change the contact data, please contact the your account 
administrator.": [
-        ""
-      ],
-      "Account \"%1$s\"": [
-        ""
-      ],
-      "Change details": [
-        ""
-      ],
-      "Update": [
-        ""
-      ],
-      "password doesn't match": [
-        ""
-      ],
-      "Password changed": [
-        ""
-      ],
-      "Not authorized to change the password, maybe the session is invalid.": [
-        ""
-      ],
-      "You need to provide the old password. If you don't have it contact your 
account administrator.": [
-        ""
-      ],
-      "Your current password doesn't match, can't change to a new password.": [
-        ""
-      ],
-      "Update password": [
-        ""
-      ],
-      "New password": [
-        ""
-      ],
-      "Type it again": [
-        ""
-      ],
-      "repeat the same password": [
-        ""
-      ],
-      "Current password": [
-        ""
-      ],
-      "your current password, for security": [
-        ""
-      ],
-      "Change": [
-        ""
-      ],
-      "Can't delete the account": [
-        ""
-      ],
-      "The account can't be delete while still holding some balance. First 
make sure that the owner make a complete cashout.": [
-        ""
-      ],
-      "Account removed": [
-        ""
-      ],
-      "No enough permission to delete the account.": [
-        ""
-      ],
-      "The username was not found.": [
-        ""
-      ],
-      "Can't delete a reserved username.": [
-        ""
-      ],
-      "Can't delete an account with balance different than zero.": [
-        ""
-      ],
-      "name doesn't match": [
-        ""
-      ],
-      "You are going to remove the account": [
-        ""
-      ],
-      "This step can't be undone.": [
-        ""
-      ],
-      "Deleting account \"%1$s\"": [
-        ""
-      ],
-      "Verification": [
-        ""
-      ],
-      "enter the account name that is going to be deleted": [
-        ""
-      ],
-      "Account created with password \"%1$s\". The user must change the 
password on the next login.": [
-        ""
-      ],
-      "Server replied that phone or email is invalid": [
-        ""
-      ],
-      "The rights to perform the operation are not sufficient": [
-        ""
-      ],
-      "Account username is already taken": [
-        ""
-      ],
-      "Account id is already taken": [
-        ""
-      ],
-      "Bank ran out of bonus credit.": [
-        ""
-      ],
-      "Account username can't be used because is reserved": [
-        ""
-      ],
-      "Can't create accounts": [
-        ""
-      ],
-      "Only system admin can create accounts.": [
-        ""
-      ],
-      "New business account": [
-        ""
-      ],
-      "Create": [
-        ""
-      ],
-      "Cashout not supported.": [
-        ""
-      ],
-      "Account not found.": [
-        ""
-      ],
-      "Latest cashouts": [
-        ""
-      ],
-      "Created": [
-        ""
-      ],
-      "Confirmed": [
-        "Bestätigen"
-      ],
-      "Total debit": [
-        ""
-      ],
-      "Total credit": [
-        ""
-      ],
-      "Status": [
-        ""
-      ],
-      "never": [
-        ""
-      ],
-      "Cashout for account %1$s": [
-        ""
-      ],
-      "This cashout not found. Maybe already aborted.": [
-        ""
-      ],
-      "Cashout not found. It may be also mean that it was already aborted.": [
-        ""
-      ],
-      "Cashout was already confimed.": [
-        ""
-      ],
-      "Cashout operation is not supported.": [
-        ""
-      ],
-      "The cashout operation is already aborted.": [
-        ""
-      ],
-      "Missing destination account.": [
-        ""
-      ],
-      "Too many failed attempts.": [
-        ""
-      ],
-      "The code for this cashout is invalid.": [
-        ""
-      ],
-      "Cashout detail": [
-        ""
-      ],
-      "Debited": [
-        ""
-      ],
-      "Credited": [
-        ""
-      ],
-      "Enter the confirmation code": [
-        ""
-      ],
-      "Abort": [
-        ""
-      ],
-      "Confirm": [
-        "Bestätigen"
-      ],
-      "Unauthorized to make the operation, maybe the session has expired or 
the password changed.": [
-        ""
-      ],
-      "The operation was rejected due to insufficient funds.": [
-        ""
-      ],
-      "Do not show this again": [
-        ""
-      ],
-      "Close": [
-        ""
-      ],
-      "On this device": [
-        ""
-      ],
-      "If you are using a web browser on desktop you should access your wallet 
with the GNU Taler WebExtension now or click the link if your WebExtension have 
the \"Inject Taler support\" option enabled.": [
-        ""
-      ],
-      "Start": [
-        ""
-      ],
-      "On a mobile phone": [
-        ""
-      ],
-      "Scan the QR code with your mobile device.": [
-        ""
-      ],
-      "There is an operation already": [
-        ""
-      ],
-      "Complete or cancel the operation in": [
-        "Abhebung bestätigen"
-      ],
-      "Server responded with an invalid  withdraw URI": [
-        ""
-      ],
-      "Withdraw URI: %1$s": [
-        ""
-      ],
-      "The operation was rejected due to insufficient funds": [
-        ""
-      ],
-      "Prepare your wallet": [
-        ""
-      ],
-      "After using your wallet you will need to confirm or cancel the 
operation on this site.": [
-        ""
-      ],
-      "You need a GNU Taler Wallet": [
-        ""
-      ],
-      "If you don't have one yet you can follow the instruction in": [
-        ""
-      ],
-      "Send money": [
-        ""
-      ],
-      "to a %1$s wallet": [
-        ""
-      ],
-      "Withdraw digital money into your mobile wallet or browser extension": [
-        ""
-      ],
-      "operation ready": [
-        ""
-      ],
-      "to another bank account": [
-        ""
+      "You need to provide the old password. If you don't have it contact your 
account administrator.":
+        [""],
+      "Your current password doesn't match, can't change to a new password.": [
+        "",
+      ],
+      "Update password": [""],
+      "New password": [""],
+      "Type it again": [""],
+      "repeat the same password": [""],
+      "Current password": [""],
+      "your current password, for security": [""],
+      Change: [""],
+      "Can't delete the account": [""],
+      "The account can't be delete while still holding some balance. First 
make sure that the owner make a complete cashout.":
+        [""],
+      "Account removed": [""],
+      "No enough permission to delete the account.": [""],
+      "The username was not found.": [""],
+      "Can't delete a reserved username.": [""],
+      "Can't delete an account with balance different than zero.": [""],
+      "name doesn't match": [""],
+      "You are going to remove the account": [""],
+      "This step can't be undone.": [""],
+      'Deleting account "%1$s"': [""],
+      Verification: [""],
+      "enter the account name that is going to be deleted": [""],
+      'Account created with password "%1$s". The user must change the password 
on the next login.':
+        [""],
+      "Server replied that phone or email is invalid": [""],
+      "The rights to perform the operation are not sufficient": [""],
+      "Account username is already taken": [""],
+      "Account id is already taken": [""],
+      "Bank ran out of bonus credit.": [""],
+      "Account username can't be used because is reserved": [""],
+      "Can't create accounts": [""],
+      "Only system admin can create accounts.": [""],
+      "New business account": [""],
+      Create: [""],
+      "Cashout not supported.": [""],
+      "Account not found.": [""],
+      "Latest cashouts": [""],
+      Created: [""],
+      Confirmed: ["Bestätigen"],
+      "Total debit": [""],
+      "Total credit": [""],
+      Status: [""],
+      never: [""],
+      "Cashout for account %1$s": [""],
+      "This cashout not found. Maybe already aborted.": [""],
+      "Cashout not found. It may be also mean that it was already aborted.": [
+        "",
+      ],
+      "Cashout was already confimed.": [""],
+      "Cashout operation is not supported.": [""],
+      "The cashout operation is already aborted.": [""],
+      "Missing destination account.": [""],
+      "Too many failed attempts.": [""],
+      "The code for this cashout is invalid.": [""],
+      "Cashout detail": [""],
+      Debited: [""],
+      Credited: [""],
+      "Enter the confirmation code": [""],
+      Abort: [""],
+      Confirm: ["Bestätigen"],
+      "Unauthorized to make the operation, maybe the session has expired or 
the password changed.":
+        [""],
+      "The operation was rejected due to insufficient funds.": [""],
+      "Do not show this again": [""],
+      Close: [""],
+      "On this device": [""],
+      'If you are using a web browser on desktop you should access your wallet 
with the GNU Taler WebExtension now or click the link if your WebExtension have 
the "Inject Taler support" option enabled.':
+        [""],
+      Start: [""],
+      "On a mobile phone": [""],
+      "Scan the QR code with your mobile device.": [""],
+      "There is an operation already": [""],
+      "Complete or cancel the operation in": ["Abhebung bestätigen"],
+      "Server responded with an invalid  withdraw URI": [""],
+      "Withdraw URI: %1$s": [""],
+      "The operation was rejected due to insufficient funds": [""],
+      "Prepare your wallet": [""],
+      "After using your wallet you will need to confirm or cancel the 
operation on this site.":
+        [""],
+      "You need a GNU Taler Wallet": [""],
+      "If you don't have one yet you can follow the instruction in": [""],
+      "Send money": [""],
+      "to a %1$s wallet": [""],
+      "Withdraw digital money into your mobile wallet or browser extension": [
+        "",
       ],
+      "operation ready": [""],
+      "to another bank account": [""],
       "Make a wire transfer to an account with known bank account number.": [
-        ""
-      ],
-      "Transfer details": [
-        ""
-      ],
-      "This is a demo bank": [
-        ""
-      ],
-      "This part of the demo shows how a bank that supports Taler directly 
would work. In addition to using your own bank account, you can also see the 
transaction history of some %1$s.": [
-        ""
-      ],
-      "This part of the demo shows how a bank that supports Taler directly 
would work.": [
-        ""
-      ],
-      "Pending account delete operation": [
-        ""
-      ],
-      "Pending account update operation": [
-        ""
-      ],
-      "Pending password update operation": [
-        ""
-      ],
-      "Pending transaction operation": [
-        ""
-      ],
-      "Pending withdrawal operation": [
-        ""
-      ],
-      "Pending cashout operation": [
-        ""
-      ],
-      "You can complete or cancel the operation in": [
-        ""
-      ],
-      "Download bank stats": [
-        ""
-      ],
-      "Include hour metric": [
-        ""
-      ],
-      "Include day metric": [
-        ""
-      ],
-      "Include month metric": [
-        ""
-      ],
-      "Include year metric": [
-        ""
-      ],
-      "Include table header": [
-        ""
-      ],
-      "Add previous metric for compare": [
-        ""
-      ],
-      "Fail on first error": [
-        ""
-      ],
-      "Download": [
-        ""
-      ],
-      "downloading... %1$s": [
-        ""
-      ],
-      "Download completed": [
-        ""
-      ],
-      "click here to save the file in your computer": [
-        ""
-      ],
-      "Challenge not found.": [
-        ""
-      ],
-      "This user is not authorized to complete this challenge.": [
-        ""
-      ],
-      "Too many attemps, try another code.": [
-        ""
-      ],
-      "The confirmation code is wrong, try again.": [
-        ""
-      ],
-      "The operation expired.": [
-        ""
-      ],
-      "The operation failed.": [
-        ""
-      ],
-      "The operation needs another confirmation to complete.": [
-        ""
-      ],
-      "Account delete": [
-        ""
-      ],
-      "Account update": [
-        ""
-      ],
-      "Password update": [
-        ""
-      ],
-      "Wire transfer": [
-        ""
-      ],
-      "Withdrawal": [
-        "Abhebung bestätigen"
-      ],
-      "Confirm the operation": [
-        "Abhebung bestätigen"
-      ],
-      "Send again": [
-        ""
-      ],
-      "Send code": [
-        ""
-      ],
-      "Operation details": [
-        ""
-      ],
-      "Challenge details": [
-        ""
-      ],
-      "Sent at": [
-        ""
-      ],
-      "To phone": [
-        ""
-      ],
-      "To email": [
-        ""
-      ],
-      "Welcome to %1$s!": [
-        ""
-      ]
-    }
+        "",
+      ],
+      "Transfer details": [""],
+      "This is a demo bank": [""],
+      "This part of the demo shows how a bank that supports Taler directly 
would work. In addition to using your own bank account, you can also see the 
transaction history of some %1$s.":
+        [""],
+      "This part of the demo shows how a bank that supports Taler directly 
would work.":
+        [""],
+      "Pending account delete operation": [""],
+      "Pending account update operation": [""],
+      "Pending password update operation": [""],
+      "Pending transaction operation": [""],
+      "Pending withdrawal operation": [""],
+      "Pending cashout operation": [""],
+      "You can complete or cancel the operation in": [""],
+      "Download bank stats": [""],
+      "Include hour metric": [""],
+      "Include day metric": [""],
+      "Include month metric": [""],
+      "Include year metric": [""],
+      "Include table header": [""],
+      "Add previous metric for compare": [""],
+      "Fail on first error": [""],
+      Download: [""],
+      "downloading... %1$s": [""],
+      "Download completed": [""],
+      "click here to save the file in your computer": [""],
+      "Challenge not found.": [""],
+      "This user is not authorized to complete this challenge.": [""],
+      "Too many attemps, try another code.": [""],
+      "The confirmation code is wrong, try again.": [""],
+      "The operation expired.": [""],
+      "The operation failed.": [""],
+      "The operation needs another confirmation to complete.": [""],
+      "Account delete": [""],
+      "Account update": [""],
+      "Password update": [""],
+      "Wire transfer": [""],
+      Withdrawal: ["Abhebung bestätigen"],
+      "Confirm the operation": ["Abhebung bestätigen"],
+      "Send again": [""],
+      "Send code": [""],
+      "Operation details": [""],
+      "Challenge details": [""],
+      "Sent at": [""],
+      "To phone": [""],
+      "To email": [""],
+      "Welcome to %1$s!": [""],
+    },
   },
-  "domain": "messages",
-  "plural_forms": "nplurals=2; plural=n != 1;",
-  "lang": "de",
-  "completeness": 4
+  domain: "messages",
+  plural_forms: "nplurals=2; plural=n != 1;",
+  lang: "de",
+  completeness: 4,
 };
-
diff --git a/packages/demobank-ui/src/index.html 
b/packages/demobank-ui/src/index.html
index 3cc7f7fd2..720b678a3 100644
--- a/packages/demobank-ui/src/index.html
+++ b/packages/demobank-ui/src/index.html
@@ -15,27 +15,27 @@
 
  @author Sebastian Javier Marchano
 -->
-<!DOCTYPE html>
+<!doctype html>
 <html lang="en" class="h-full bg-gray-100">
+  <head>
+    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width,initial-scale=1" />
+    <meta name="taler-support" content="uri" />
+    <meta name="mobile-web-app-capable" content="yes" />
+    <meta name="apple-mobile-web-app-capable" content="yes" />
+    <link
+      rel="icon"
+      
href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/
 [...]
+    />
+    <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" />
+    <title>Bank</title>
+    <!-- Entry point for the bank SPA. -->
+    <script type="module" src="index.js"></script>
+    <link rel="stylesheet" href="index.css" />
+  </head>
 
-<head>
-  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
-  <meta charset="utf-8" />
-  <meta name="viewport" content="width=device-width,initial-scale=1" />
-  <meta name="taler-support" content="uri">
-  <meta name="mobile-web-app-capable" content="yes" />
-  <meta name="apple-mobile-web-app-capable" content="yes" />
-  <link rel="icon"
-    
href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/Qn
 [...]
-  <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" />
-  <title>Bank</title>
-  <!-- Entry point for the bank SPA. -->
-  <script type="module" src="index.js"></script>
-  <link rel="stylesheet" href="index.css" />
-</head>
-
-<body class="h-full">
-  <div id="app"></div>
-</body>
-
-</html>
\ No newline at end of file
+  <body class="h-full">
+    <div id="app"></div>
+  </body>
+</html>
diff --git a/packages/demobank-ui/src/index.tsx 
b/packages/demobank-ui/src/index.tsx
index b7d69fd2d..5f00f1b68 100644
--- a/packages/demobank-ui/src/index.tsx
+++ b/packages/demobank-ui/src/index.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -16,8 +16,12 @@
 
 import App from "./components/app.js";
 import { h, render } from "preact";
-import "./scss/main.css"
+import "./scss/main.css";
 
 const app = document.getElementById("app");
 
-render(<App />, app as any);
+if (app) {
+  render(<App />, app);
+} else {
+  console.error("HTML element with id 'app' not found.");
+}
diff --git a/packages/demobank-ui/src/pages.ts 
b/packages/demobank-ui/src/pages.ts
deleted file mode 100644
index cf003fde5..000000000
--- a/packages/demobank-ui/src/pages.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { WithdrawalOperationPage } from "./pages/WithdrawalOperationPage.js";
-import { PageEntry, pageDefinition } from "./route.js";
-
-// const operationById: PageEntry<{ operationId: string }> = {
-//   url: pageDefinition("#/operation/:operationId"),
-//   view: WithdrawalOperationPage,
-// };
-
-
-// const home: PageEntry = {
-//   url: "#/",
-//   view: Home,
-// };
-// const cases: PageEntry = {
-//   url: "#/cases",
-//   view: Cases,
-// };
-
-// const newFormEntry: PageEntry<{ account?: string; type?: string }> = {
-//   url: pageDefinition("#/account/:account/new/:type?"),
-//   view: NewFormEntry,
-// };
-
-// const settings: PageEntry = {
-//   url: "#/settings",
-//   view: Settings,
-// };
-// const officer: PageEntry = {
-//   url: "#/officer",
-//   view: Officer,
-// };
-// const welcome: PageEntry<{ asd?: string; name?: string }> = {
-//   url: pageDefinition("#/welcome/:name?"),
-//   view: Welcome,
-// };
-// const form: PageEntry<{ number?: string }> = {
-//   url: pageDefinition("#/form/:number?"),
-//   view: AntiMoneyLaunderingForm,
-// };
-
-export const Pages = {
-  // operationById,
-
-};
diff --git a/packages/demobank-ui/src/pages/AccountPage/index.ts 
b/packages/demobank-ui/src/pages/AccountPage/index.ts
index cfe184612..31a8a9e34 100644
--- a/packages/demobank-ui/src/pages/AccountPage/index.ts
+++ b/packages/demobank-ui/src/pages/AccountPage/index.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -14,20 +14,36 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { AbsoluteTime, AmountJson, TalerCorebankApi, TalerError } from 
"@gnu-taler/taler-util";
+import {
+  AbsoluteTime,
+  AmountJson,
+  TalerCorebankApi,
+  TalerError,
+} from "@gnu-taler/taler-util";
 import { Loading, utils } from "@gnu-taler/web-util/browser";
 import { ErrorLoadingWithDebug } from 
"../../components/ErrorLoadingWithDebug.js";
 import { LoginForm } from "../LoginForm.js";
 import { useComponentState } from "./state.js";
 import { InvalidIbanView, ReadyView } from "./views.js";
+import { RouteDefinition } from "../../route.js";
 
 export interface Props {
   account: string;
   onAuthorizationRequired: () => void;
-  goToConfirmOperation: (id: string) => void;
+  onOperationCreated: (wopid: string) => void;
+  onClose: () => void;
+  tab: "charge-wallet" | "wire-transfer" | undefined;
+  routeClose: RouteDefinition<Record<string, never>>;
+  routeChargeWallet: RouteDefinition<Record<string, never>>;
+  routeWireTransfer: RouteDefinition<Record<string, never>>;
 }
 
-export type State = State.Loading | State.LoadingError | State.Ready | 
State.InvalidIban | State.UserNotFound;
+export type State =
+  | State.Loading
+  | State.LoadingError
+  | State.Ready
+  | State.InvalidIban
+  | State.UserNotFound;
 
 export namespace State {
   export interface Loading {
@@ -47,21 +63,26 @@ export namespace State {
   export interface Ready extends BaseInfo {
     status: "ready";
     error: undefined;
-    account: string,
-    limit: AmountJson,
+    account: string;
+    tab: "charge-wallet" | "wire-transfer" | undefined;
+    limit: AmountJson;
     onAuthorizationRequired: () => void;
-    goToConfirmOperation: (id: string) => void;
+    onOperationCreated: (wopid: string) => void;
+    onClose: () => void;
+    routeClose: RouteDefinition<Record<string, never>>;
+    routeChargeWallet: RouteDefinition<Record<string, never>>;
+    routeWireTransfer: RouteDefinition<Record<string, never>>;
   }
 
   export interface InvalidIban {
-    status: "invalid-iban",
+    status: "invalid-iban";
     error: TalerCorebankApi.AccountData;
   }
 
   export interface UserNotFound {
-    status: "login",
+    status: "login";
     reason: "not-found" | "forbidden";
-    onRegister?: () => void;
+    routeRegister?: RouteDefinition<Record<string, never>>;
   }
 }
 
@@ -75,7 +96,7 @@ export interface Transaction {
 
 const viewMapping: utils.StateViewMap<State> = {
   loading: Loading,
-  "login": LoginForm,
+  login: LoginForm,
   "invalid-iban": InvalidIbanView,
   "loading-error": ErrorLoadingWithDebug,
   ready: ReadyView,
diff --git a/packages/demobank-ui/src/pages/AccountPage/state.ts 
b/packages/demobank-ui/src/pages/AccountPage/state.ts
index 38b4d9f36..a07ea37d3 100644
--- a/packages/demobank-ui/src/pages/AccountPage/state.ts
+++ b/packages/demobank-ui/src/pages/AccountPage/state.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -14,15 +14,27 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { Amounts, HttpStatusCode, TalerError, parsePaytoUri } from 
"@gnu-taler/taler-util";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import {
+  Amounts,
+  HttpStatusCode,
+  TalerError,
+  assertUnreachable,
+  parsePaytoUri,
+} from "@gnu-taler/taler-util";
 import { useAccountDetails } from "../../hooks/access.js";
-import { assertUnreachable } from "../WithdrawalOperationPage.js";
 import { Props, State } from "./index.js";
 
-export function useComponentState({ account, goToConfirmOperation, 
onAuthorizationRequired }: Props): State {
+export function useComponentState({
+  account,
+  tab,
+  routeChargeWallet,
+  routeWireTransfer,
+  onOperationCreated,
+  onClose,
+  routeClose,
+  onAuthorizationRequired,
+}: Props): State {
   const result = useAccountDetails(account);
-  const { i18n } = useTranslationContext();
 
   if (!result) {
     return {
@@ -40,16 +52,18 @@ export function useComponentState({ account, 
goToConfirmOperation, onAuthorizati
 
   if (result.type === "fail") {
     switch (result.case) {
-      case HttpStatusCode.Unauthorized: return {
-        status: "login",
-        reason: "forbidden"
-      }
-      case HttpStatusCode.NotFound: return {
-        status: "login",
-        reason: "not-found",
-      }
+      case HttpStatusCode.Unauthorized:
+        return {
+          status: "login",
+          reason: "forbidden",
+        };
+      case HttpStatusCode.NotFound:
+        return {
+          status: "login",
+          reason: "not-found",
+        };
       default: {
-        assertUnreachable(result)
+        assertUnreachable(result);
       }
     }
   }
@@ -61,10 +75,14 @@ export function useComponentState({ account, 
goToConfirmOperation, onAuthorizati
   const debitThreshold = Amounts.parseOrThrow(data.debit_threshold);
   const payto = parsePaytoUri(data.payto_uri);
 
-  if (!payto || !payto.isKnown || (payto.targetType !== "iban" && 
payto.targetType !== "x-taler-bank")) {
+  if (
+    !payto ||
+    !payto.isKnown ||
+    (payto.targetType !== "iban" && payto.targetType !== "x-taler-bank")
+  ) {
     return {
       status: "invalid-iban",
-      error: data
+      error: data,
     };
   }
 
@@ -73,12 +91,16 @@ export function useComponentState({ account, 
goToConfirmOperation, onAuthorizati
     ? Amounts.sub(debitThreshold, balance).amount
     : Amounts.add(balance, debitThreshold).amount;
 
-
   return {
     status: "ready",
-    goToConfirmOperation,
+    onOperationCreated,
     error: undefined,
+    tab,
     onAuthorizationRequired,
+    onClose,
+    routeClose,
+    routeChargeWallet,
+    routeWireTransfer,
     account,
     limit,
   };
diff --git a/packages/demobank-ui/src/pages/AccountPage/stories.tsx 
b/packages/demobank-ui/src/pages/AccountPage/stories.tsx
index f3828a5d6..fe09a4f89 100644
--- a/packages/demobank-ui/src/pages/AccountPage/stories.tsx
+++ b/packages/demobank-ui/src/pages/AccountPage/stories.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
diff --git a/packages/demobank-ui/src/pages/AccountPage/test.ts 
b/packages/demobank-ui/src/pages/AccountPage/test.ts
index 588b84c35..14c8be948 100644
--- a/packages/demobank-ui/src/pages/AccountPage/test.ts
+++ b/packages/demobank-ui/src/pages/AccountPage/test.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -19,14 +19,13 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-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 * 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";
 
 describe("Account states", () => {
-  it("should do some tests", async () => {
-  });
+  it("should do some tests", async () => {});
 });
diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx 
b/packages/demobank-ui/src/pages/AccountPage/views.tsx
index a90cb9812..9baefe96c 100644
--- a/packages/demobank-ui/src/pages/AccountPage/views.tsx
+++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -14,15 +14,15 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { TalerError, TranslatedString } from "@gnu-taler/taler-util";
+import { TranslatedString } from "@gnu-taler/taler-util";
 import { Attention, useTranslationContext } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { Transactions } from "../../components/Transactions/index.js";
 import { useBankState } from "../../hooks/bank-state.js";
-import { useOnePendingCashouts } from "../../hooks/circuit.js";
 import { usePreferences } from "../../hooks/preferences.js";
 import { PaymentOptions } from "../PaymentOptions.js";
 import { State } from "./index.js";
+import { privatePages } from "../../Routing.js";
 
 export function InvalidIbanView({ error }: State.InvalidIban) {
   return (
@@ -30,29 +30,34 @@ export function InvalidIbanView({ error }: 
State.InvalidIban) {
   );
 }
 
-const IS_PUBLIC_ACCOUNT_ENABLED = false
+const IS_PUBLIC_ACCOUNT_ENABLED = false;
 
 function ShowDemoInfo(): VNode {
   const { i18n } = useTranslationContext();
   const [settings, updateSettings] = usePreferences();
-  if (!settings.showDemoDescription) return <Fragment />
-  return <Attention title={i18n.str`This is a demo bank`} onClose={() => {
-    updateSettings("showDemoDescription", false);
-  }}>
-    {IS_PUBLIC_ACCOUNT_ENABLED ? (
-      <i18n.Translate>
-        This part of the demo shows how a bank that supports Taler
-        directly would work. In addition to using your own bank
-        account, you can also see the transaction history of some{" "}
-        <a href="/public-accounts">Public Accounts</a>.
-      </i18n.Translate>
-    ) : (
-      <i18n.Translate>
-        This part of the demo shows how a bank that supports Taler
-        directly would work.
-      </i18n.Translate>
-    )}
-  </Attention>
+  if (!settings.showDemoDescription) return <Fragment />;
+  return (
+    <Attention
+      title={i18n.str`This is a demo bank`}
+      onClose={() => {
+        updateSettings("showDemoDescription", false);
+      }}
+    >
+      {IS_PUBLIC_ACCOUNT_ENABLED ? (
+        <i18n.Translate>
+          This part of the demo shows how a bank that supports Taler directly
+          would work. In addition to using your own bank account, you can also
+          see the transaction history of some{" "}
+          <a href={privatePages.publicAccountList.url({})}>Public Accounts</a>.
+        </i18n.Translate>
+      ) : (
+        <i18n.Translate>
+          This part of the demo shows how a bank that supports Taler directly
+          would work.
+        </i18n.Translate>
+      )}
+    </Attention>
+  );
 }
 
 function ShowPedingOperation(): VNode {
@@ -61,30 +66,67 @@ function ShowPedingOperation(): VNode {
   if (!bankState.currentChallenge) return <Fragment />;
   const title = ((op): TranslatedString => {
     switch (op) {
-      case "delete-account": return i18n.str`Pending account delete operation`
-      case "update-account": return i18n.str`Pending account update operation`
-      case "update-password": return i18n.str`Pending password update 
operation`
-      case "create-transaction": return i18n.str`Pending transaction operation`
-      case "confirm-withdrawal": return i18n.str`Pending withdrawal operation`
-      case "create-cashout": return i18n.str`Pending cashout operation`
+      case "delete-account":
+        return i18n.str`Pending account delete operation`;
+      case "update-account":
+        return i18n.str`Pending account update operation`;
+      case "update-password":
+        return i18n.str`Pending password update operation`;
+      case "create-transaction":
+        return i18n.str`Pending transaction operation`;
+      case "confirm-withdrawal":
+        return i18n.str`Pending withdrawal operation`;
+      case "create-cashout":
+        return i18n.str`Pending cashout operation`;
     }
-  })(bankState.currentChallenge.operation)
-  return <Attention title={title} type="warning" onClose={() => { 
updateBankState("currentChallenge", undefined); }}>
-    <i18n.Translate>
-      You can complete or cancel the operation in</i18n.Translate> <a 
class="font-semibold text-yellow-700 hover:text-yellow-600" href={`#/2fa`}>
-      <i18n.Translate>this page</i18n.Translate>
-    </a>
-  </Attention>
+  })(bankState.currentChallenge.operation);
+  return (
+    <Attention
+      title={title}
+      type="warning"
+      onClose={() => {
+        updateBankState("currentChallenge", undefined);
+      }}
+    >
+      <i18n.Translate>
+        You can complete or cancel the operation in
+      </i18n.Translate>{" "}
+      <a
+        class="font-semibold text-yellow-700 hover:text-yellow-600"
+        href={`#/2fa`}
+      >
+        <i18n.Translate>this page</i18n.Translate>
+      </a>
+    </Attention>
+  );
 }
 
-export function ReadyView({ account, limit, goToConfirmOperation, 
onAuthorizationRequired }: State.Ready): VNode<{}> {
-
-  return <Fragment>
-    <ShowPedingOperation />
-    <ShowDemoInfo />
-    <PaymentOptions limit={limit} goToConfirmOperation={goToConfirmOperation} 
onAuthorizationRequired={onAuthorizationRequired} />
-    <Transactions account={account} />
-  </Fragment>;
+export function ReadyView({
+  tab,
+  account,
+  routeChargeWallet,
+  routeWireTransfer,
+  limit,
+  onClose,
+  routeClose,
+  onOperationCreated,
+  onAuthorizationRequired,
+}: State.Ready): VNode {
+  return (
+    <Fragment>
+      <ShowPedingOperation />
+      <ShowDemoInfo />
+      <PaymentOptions
+        tab={tab}
+        routeChargeWallet={routeChargeWallet}
+        routeWireTransfer={routeWireTransfer}
+        limit={limit}
+        routeClose={routeClose}
+        onClose={onClose}
+        onOperationCreated={onOperationCreated}
+        onAuthorizationRequired={onAuthorizationRequired}
+      />
+      <Transactions account={account} />
+    </Fragment>
+  );
 }
-
-
diff --git a/packages/demobank-ui/src/pages/BankFrame.stories.tsx 
b/packages/demobank-ui/src/pages/BankFrame.stories.tsx
index a64885489..c874ac4ca 100644
--- a/packages/demobank-ui/src/pages/BankFrame.stories.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.stories.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx 
b/packages/demobank-ui/src/pages/BankFrame.tsx
index 6ec553425..a106f370d 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -15,143 +15,195 @@
  */
 
 import { Amounts, TalerError, TranslatedString } from "@gnu-taler/taler-util";
-import { Footer, GlobalNotificationsBanner, Header, Loading, notifyError, 
notifyException, useTranslationContext } from "@gnu-taler/web-util/browser";
-import { ComponentChildren, Fragment, VNode, h } from "preact";
-import { useEffect, useErrorBoundary, useState } from "preact/hooks";
+import {
+  Footer,
+  GlobalNotificationsBanner,
+  Header,
+  Loading,
+  notifyError,
+  notifyException,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
+import { ComponentChildren, VNode, h } from "preact";
+import { useEffect, useErrorBoundary } from "preact/hooks";
+import { privatePages } from "../Routing.js";
+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 { getAllBooleanPreferences, getLabelForPreferences, usePreferences } 
from "../hooks/preferences.js";
-import { RenderAmount } from "./PaytoWireTransferForm.js";
-import { useSettingsContext } from "../context/settings.js";
-import { useBankCoreApiContext } from "../context/config.js";
 import { useBankState } from "../hooks/bank-state.js";
+import {
+  getAllBooleanPreferences,
+  getLabelForPreferences,
+  usePreferences,
+} from "../hooks/preferences.js";
+import { RenderAmount } from "./PaytoWireTransferForm.js";
 
 const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : 
undefined;
 const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined;
 
-
 export function BankFrame({
   children,
   account,
 }: {
-  account?: string,
+  account?: string;
   children: ComponentChildren;
 }): VNode {
   const { i18n } = useTranslationContext();
   const backend = useBackendState();
   const settings = useSettingsContext();
   const [preferences, updatePreferences] = usePreferences();
-  const [, , resetBankState] = useBankState()
+  const [, , resetBankState] = useBankState();
 
   const [error, resetError] = useErrorBoundary();
 
   useEffect(() => {
     if (error) {
-      const desc = (error instanceof Error ? error.stack : String(error)) as 
TranslatedString
       if (error instanceof Error) {
-        console.log("Internal error, please report", error)
-        notifyException(i18n.str`Internal error, please report.`, error)
+        console.log("Internal error, please report", error);
+        notifyException(i18n.str`Internal error, please report.`, error);
       } else {
-        console.log("Internal error, please report", error)
-        notifyError(i18n.str`Internal error, please report.`, String(error) as 
TranslatedString)
+        console.log("Internal error, please report", error);
+        notifyError(
+          i18n.str`Internal error, please report.`,
+          String(error) as TranslatedString,
+        );
       }
-      resetError()
+      resetError();
     }
-  }, [error])
-
-  return (<div class="min-h-full flex flex-col m-0 bg-slate-200" 
style="min-height: 100vh;">
-
-    <div class="bg-indigo-600 pb-32">
-      <Header
-        title="Bank"
-        iconLinkURL={settings.iconLinkURL ?? "#"}
-        onLogout={backend.state.status !== "loggedIn" ? undefined : () => {
-          backend.logOut()
-          resetBankState();
-        }}
-        sites={!settings.topNavSites ? [] : 
Object.entries(settings.topNavSites)}
-        supportedLangs={["en", "es", "de"]}
-      >
-        <li>
-          <div class="text-xs font-semibold leading-6 text-gray-400">
-            <i18n.Translate>Preferences</i18n.Translate>
-          </div>
-          <ul role="list" class="space-y-1">
-            {getAllBooleanPreferences().map(set => {
-              const isOn: boolean = !!preferences[set]
-              return <li class="mt-2 pl-2">
-                <div class="flex items-center justify-between">
-                  <span class="flex flex-grow flex-col">
-                    <span class="text-sm text-black font-medium leading-6 " 
id="availability-label">
-                      {getLabelForPreferences(set, i18n)}
-                    </span>
-                  </span>
-                  <button type="button" data-enabled={isOn} 
class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 
w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent 
transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 
focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" 
aria-labelledby="availability-label" aria-describedby="availability-description"
-
-                    onClick={() => { updatePreferences(set, !isOn); }}>
-                    <span aria-hidden="true" data-enabled={isOn} 
class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none 
inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition 
duration-200 ease-in-out"></span>
-                  </button>
-                </div>
-              </li>
-            })}
-          </ul>
-        </li>
-      </Header>
-    </div >
-
-    <GlobalNotificationsBanner />
-
-    <main class="-mt-32 flex-1">
-      {account &&
-        <header class="py-5 bg-indigo-600   ">
-          <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
-            <h1 class=" flex flex-wrap items-center justify-between 
sm:flex-nowrap">
-              <span class="text-2xl font-bold tracking-tight 
text-white"><WelcomeAccount account={account} /></span>
-              <span class="text-2xl font-bold tracking-tight 
text-white"><AccountBalance account={account} /></span>
-            </h1>
-          </div>
-        </header>
-      }
-
-      <div class="mx-auto max-w-7xl px-4 pb-12 sm:px-6 lg:px-8">
-        <div class="rounded-lg bg-white px-5 py-6 shadow sm:px-6">
-          {children}
-        </div>
+  }, [error]);
+
+  return (
+    <div
+      class="min-h-full flex flex-col m-0 bg-slate-200"
+      style="min-height: 100vh;"
+    >
+      <div class="bg-indigo-600 pb-32">
+        <Header
+          title="Bank"
+          iconLinkURL={settings.iconLinkURL ?? "#"}
+          onLogout={
+            backend.state.status !== "loggedIn"
+              ? undefined
+              : () => {
+                  backend.logOut();
+                  resetBankState();
+                }
+          }
+          sites={
+            !settings.topNavSites ? [] : Object.entries(settings.topNavSites)
+          }
+          supportedLangs={["en", "es", "de"]}
+        >
+          <li>
+            <div class="text-xs font-semibold leading-6 text-gray-400">
+              <i18n.Translate>Preferences</i18n.Translate>
+            </div>
+            <ul role="list" class="space-y-1">
+              {getAllBooleanPreferences().map((set) => {
+                const isOn: boolean = !!preferences[set];
+                return (
+                  <li key={set} class="mt-2 pl-2">
+                    <div class="flex items-center justify-between">
+                      <span class="flex flex-grow flex-col">
+                        <span
+                          class="text-sm text-black font-medium leading-6 "
+                          id="availability-label"
+                        >
+                          {getLabelForPreferences(set, i18n)}
+                        </span>
+                      </span>
+                      <button
+                        type="button"
+                        data-enabled={isOn}
+                        class="bg-indigo-600 data-[enabled=false]:bg-gray-200 
relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full 
border-2 border-transparent transition-colors duration-200 ease-in-out 
focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
+                        role="switch"
+                        aria-checked="false"
+                        aria-labelledby="availability-label"
+                        aria-describedby="availability-description"
+                        onClick={() => {
+                          updatePreferences(set, !isOn);
+                        }}
+                      >
+                        <span
+                          aria-hidden="true"
+                          data-enabled={isOn}
+                          class="translate-x-5 
data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 
transform rounded-full bg-white shadow ring-0 transition duration-200 
ease-in-out"
+                        ></span>
+                      </button>
+                    </div>
+                  </li>
+                );
+              })}
+            </ul>
+          </li>
+        </Header>
       </div>
-    </main>
-
-    <Footer
-      testingUrlKey="corebank-api-base-url"
-      GIT_HASH={GIT_HASH}
-      VERSION={VERSION}
-    />
-
-  </div >
 
+      <GlobalNotificationsBanner />
+
+      <main class="-mt-32 flex-1">
+        {account && (
+          <header class="py-5 bg-indigo-600   ">
+            <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
+              <h1 class=" flex flex-wrap items-center justify-between 
sm:flex-nowrap">
+                <span class="text-2xl font-bold tracking-tight text-white">
+                  <WelcomeAccount account={account} />
+                </span>
+                <span class="text-2xl font-bold tracking-tight text-white">
+                  <AccountBalance account={account} />
+                </span>
+              </h1>
+            </div>
+          </header>
+        )}
+
+        <div class="mx-auto max-w-7xl px-4 pb-12 sm:px-6 lg:px-8">
+          <div class="rounded-lg bg-white px-5 py-6 shadow sm:px-6">
+            {children}
+          </div>
+        </div>
+      </main>
+
+      <Footer
+        testingUrlKey="corebank-api-base-url"
+        GIT_HASH={GIT_HASH}
+        VERSION={VERSION}
+      />
+    </div>
   );
 }
 
 function WelcomeAccount({ account: accountName }: { account: string }): VNode {
   const { i18n } = useTranslationContext();
-  return <a href="#/my-profile" class="underline underline-offset-2">
-    <i18n.Translate>Welcome,  <span 
class="whitespace-nowrap">{accountName}</span></i18n.Translate>
-  </a>
+  return (
+    <a
+      href={privatePages.myAccountDetails.url({})}
+      class="underline underline-offset-2"
+    >
+      <i18n.Translate>
+        Welcome, <span class="whitespace-nowrap">{accountName}</span>
+      </i18n.Translate>
+    </a>
+  );
 }
 
 function AccountBalance({ account }: { account: string }): VNode {
   const result = useAccountDetails(account);
   const { config } = useBankCoreApiContext();
   if (!result) {
-    return <Loading />
+    return <Loading />;
   }
   if (result instanceof TalerError) {
-    return <div />
+    return <div />;
   }
-  if (result.type === "fail") return <div />
+  if (result.type === "fail") return <div />;
 
-  return <RenderAmount
-    value={Amounts.parseOrThrow(result.body.balance.amount)}
-    negative={result.body.balance.credit_debit_indicator === "debit"}
-    spec={config.currency_specification}
-  />
+  return (
+    <RenderAmount
+      value={Amounts.parseOrThrow(result.body.balance.amount)}
+      negative={result.body.balance.credit_debit_indicator === "debit"}
+      spec={config.currency_specification}
+    />
+  );
 }
diff --git a/packages/demobank-ui/src/pages/DownloadStats.tsx 
b/packages/demobank-ui/src/pages/DownloadStats.tsx
index 596539e7e..a98c573ae 100644
--- a/packages/demobank-ui/src/pages/DownloadStats.tsx
+++ b/packages/demobank-ui/src/pages/DownloadStats.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -14,18 +14,28 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { AccessToken, AmountString, Logger, TalerCoreBankHttpClient, 
TalerCorebankApi, TalerError } from "@gnu-taler/taler-util";
-import { Attention, LocalNotificationBanner, useLocalNotification, 
useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Fragment, VNode, h } from "preact";
+import {
+  AccessToken,
+  AmountString,
+  TalerCoreBankHttpClient,
+  TalerCorebankApi,
+  TalerError,
+} from "@gnu-taler/taler-util";
+import {
+  Attention,
+  LocalNotificationBanner,
+  useLocalNotification,
+  useTranslationContext,
+} 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 { RouteDefinition } from "../route.js";
 import { getTimeframesForDate } from "./admin/AdminHome.js";
 
-const logger = new Logger("PublicHistoriesPage");
-
-interface Props { 
-  onCancel: () => void;
+interface Props {
+  routeCancel: RouteDefinition<Record<string, never>>;
 }
 
 type Options = {
@@ -36,16 +46,19 @@ type Options = {
   compareWithPrevious: boolean;
   endOnFirstFail: boolean;
   includeHeader: boolean;
-}
+};
 
-/** 
+/**
  * Show histories of public accounts.
  */
-export function DownloadStats({ onCancel }: Props): VNode {
+export function DownloadStats({ routeCancel }: Props): VNode {
   const { i18n } = useTranslationContext();
 
   const { state: credentials } = useBackendState();
-  const creds = credentials.status !== "loggedIn" || 
!credentials.isUserAdministrator ? undefined : credentials
+  const creds =
+    credentials.status !== "loggedIn" || !credentials.isUserAdministrator
+      ? undefined
+      : credentials;
   const { api } = useBankCoreApiContext();
 
   const [options, setOptions] = useState<Options>({
@@ -56,19 +69,18 @@ export function DownloadStats({ onCancel }: Props): VNode {
     includeHeader: true,
     monthMetric: true,
     yearMetric: true,
-  })
-  const [lastStep, setLastStep] = useState<{ step: number, total: number }>()
-  const [downloaded, setDownloaded] = useState<string>()
-  const referenceDates = [new Date()]
-  const [notification, notify, handleError] = useLocalNotification()
+  });
+  const [lastStep, setLastStep] = useState<{ step: number; total: number }>();
+  const [downloaded, setDownloaded] = useState<string>();
+  const referenceDates = [new Date()];
+  const [notification, , handleError] = useLocalNotification();
 
   if (!creds) {
-    return <div>only admin can download stats</div>
+    return <div>only admin can download stats</div>;
   }
 
   return (
     <div>
-
       <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 
bg-gray-100 my-4 px-4 pb-4 rounded-lg">
         <LocalNotificationBanner notification={notification} />
 
@@ -78,13 +90,12 @@ export function DownloadStats({ onCancel }: Props): VNode {
           </h2>
         </div>
 
-
         <form
           class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl 
md:col-span-2"
           autoCapitalize="none"
           autoCorrect="off"
-          onSubmit={e => {
-            e.preventDefault()
+          onSubmit={(e) => {
+            e.preventDefault();
           }}
         >
           <div class="px-4 py-6 sm:p-8">
@@ -92,223 +103,393 @@ export function DownloadStats({ onCancel }: Props): 
VNode {
               <div class="sm:col-span-5">
                 <div class="flex items-center justify-between">
                   <span class="flex flex-grow flex-col">
-                    <span class="text-sm text-black font-medium leading-6 " 
id="availability-label">
+                    <span
+                      class="text-sm text-black font-medium leading-6 "
+                      id="availability-label"
+                    >
                       <i18n.Translate>Include hour metric</i18n.Translate>
                     </span>
                   </span>
-                  <button type="button" data-enabled={options.hourMetric} 
class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 
w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent 
transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 
focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" 
aria-labelledby="availability-label" aria-describedby="availability-description"
-
-                    onClick={() => { setOptions({ ...options, hourMetric: 
!options.hourMetric }) }}>
-                    <span aria-hidden="true" data-enabled={options.hourMetric} 
class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none 
inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition 
duration-200 ease-in-out"></span>
+                  <button
+                    type="button"
+                    data-enabled={options.hourMetric}
+                    class="bg-indigo-600 data-[enabled=false]:bg-gray-200 
relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full 
border-2 border-transparent transition-colors duration-200 ease-in-out 
focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
+                    role="switch"
+                    aria-checked="false"
+                    aria-labelledby="availability-label"
+                    aria-describedby="availability-description"
+                    onClick={() => {
+                      setOptions({
+                        ...options,
+                        hourMetric: !options.hourMetric,
+                      });
+                    }}
+                  >
+                    <span
+                      aria-hidden="true"
+                      data-enabled={options.hourMetric}
+                      class="translate-x-5 data-[enabled=false]:translate-x-0 
pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow 
ring-0 transition duration-200 ease-in-out"
+                    ></span>
                   </button>
                 </div>
               </div>
               <div class="sm:col-span-5">
                 <div class="flex items-center justify-between">
                   <span class="flex flex-grow flex-col">
-                    <span class="text-sm text-black font-medium leading-6 " 
id="availability-label">
+                    <span
+                      class="text-sm text-black font-medium leading-6 "
+                      id="availability-label"
+                    >
                       <i18n.Translate>Include day metric</i18n.Translate>
                     </span>
                   </span>
-                  <button type="button" data-enabled={!!options.dayMetric} 
class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 
w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent 
transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 
focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" 
aria-labelledby="availability-label" aria-describedby="availability-description"
-
-                    onClick={() => { setOptions({ ...options, dayMetric: 
!options.dayMetric }) }}>
-                    <span aria-hidden="true" data-enabled={options.dayMetric} 
class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none 
inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition 
duration-200 ease-in-out"></span>
+                  <button
+                    type="button"
+                    data-enabled={!!options.dayMetric}
+                    class="bg-indigo-600 data-[enabled=false]:bg-gray-200 
relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full 
border-2 border-transparent transition-colors duration-200 ease-in-out 
focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
+                    role="switch"
+                    aria-checked="false"
+                    aria-labelledby="availability-label"
+                    aria-describedby="availability-description"
+                    onClick={() => {
+                      setOptions({ ...options, dayMetric: !options.dayMetric 
});
+                    }}
+                  >
+                    <span
+                      aria-hidden="true"
+                      data-enabled={options.dayMetric}
+                      class="translate-x-5 data-[enabled=false]:translate-x-0 
pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow 
ring-0 transition duration-200 ease-in-out"
+                    ></span>
                   </button>
                 </div>
               </div>
               <div class="sm:col-span-5">
                 <div class="flex items-center justify-between">
                   <span class="flex flex-grow flex-col">
-                    <span class="text-sm text-black font-medium leading-6 " 
id="availability-label">
+                    <span
+                      class="text-sm text-black font-medium leading-6 "
+                      id="availability-label"
+                    >
                       <i18n.Translate>Include month metric</i18n.Translate>
                     </span>
                   </span>
-                  <button type="button" data-enabled={!!options.monthMetric} 
class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 
w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent 
transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 
focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" 
aria-labelledby="availability-label" aria-describedby="availability-description"
-
-                    onClick={() => { setOptions({ ...options, monthMetric: 
!options.monthMetric }) }}>
-                    <span aria-hidden="true" 
data-enabled={options.monthMetric} class="translate-x-5 
data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 
transform rounded-full bg-white shadow ring-0 transition duration-200 
ease-in-out"></span>
+                  <button
+                    type="button"
+                    data-enabled={!!options.monthMetric}
+                    class="bg-indigo-600 data-[enabled=false]:bg-gray-200 
relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full 
border-2 border-transparent transition-colors duration-200 ease-in-out 
focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
+                    role="switch"
+                    aria-checked="false"
+                    aria-labelledby="availability-label"
+                    aria-describedby="availability-description"
+                    onClick={() => {
+                      setOptions({
+                        ...options,
+                        monthMetric: !options.monthMetric,
+                      });
+                    }}
+                  >
+                    <span
+                      aria-hidden="true"
+                      data-enabled={options.monthMetric}
+                      class="translate-x-5 data-[enabled=false]:translate-x-0 
pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow 
ring-0 transition duration-200 ease-in-out"
+                    ></span>
                   </button>
                 </div>
               </div>
               <div class="sm:col-span-5">
                 <div class="flex items-center justify-between">
                   <span class="flex flex-grow flex-col">
-                    <span class="text-sm text-black font-medium leading-6 " 
id="availability-label">
+                    <span
+                      class="text-sm text-black font-medium leading-6 "
+                      id="availability-label"
+                    >
                       <i18n.Translate>Include year metric</i18n.Translate>
                     </span>
                   </span>
-                  <button type="button" data-enabled={!!options.yearMetric} 
class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 
w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent 
transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 
focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" 
aria-labelledby="availability-label" aria-describedby="availability-description"
-
-                    onClick={() => { setOptions({ ...options, yearMetric: 
!options.yearMetric }) }}>
-                    <span aria-hidden="true" data-enabled={options.yearMetric} 
class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none 
inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition 
duration-200 ease-in-out"></span>
+                  <button
+                    type="button"
+                    data-enabled={!!options.yearMetric}
+                    class="bg-indigo-600 data-[enabled=false]:bg-gray-200 
relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full 
border-2 border-transparent transition-colors duration-200 ease-in-out 
focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
+                    role="switch"
+                    aria-checked="false"
+                    aria-labelledby="availability-label"
+                    aria-describedby="availability-description"
+                    onClick={() => {
+                      setOptions({
+                        ...options,
+                        yearMetric: !options.yearMetric,
+                      });
+                    }}
+                  >
+                    <span
+                      aria-hidden="true"
+                      data-enabled={options.yearMetric}
+                      class="translate-x-5 data-[enabled=false]:translate-x-0 
pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow 
ring-0 transition duration-200 ease-in-out"
+                    ></span>
                   </button>
                 </div>
               </div>
               <div class="sm:col-span-5">
                 <div class="flex items-center justify-between">
                   <span class="flex flex-grow flex-col">
-                    <span class="text-sm text-black font-medium leading-6 " 
id="availability-label">
+                    <span
+                      class="text-sm text-black font-medium leading-6 "
+                      id="availability-label"
+                    >
                       <i18n.Translate>Include table header</i18n.Translate>
                     </span>
                   </span>
-                  <button type="button" data-enabled={!!options.includeHeader} 
class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 
w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent 
transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 
focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" 
aria-labelledby="availability-label" aria-describedby="availability-description"
-
-                    onClick={() => { setOptions({ ...options, includeHeader: 
!options.includeHeader }) }}>
-                    <span aria-hidden="true" 
data-enabled={options.includeHeader} class="translate-x-5 
data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 
transform rounded-full bg-white shadow ring-0 transition duration-200 
ease-in-out"></span>
+                  <button
+                    type="button"
+                    data-enabled={!!options.includeHeader}
+                    class="bg-indigo-600 data-[enabled=false]:bg-gray-200 
relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full 
border-2 border-transparent transition-colors duration-200 ease-in-out 
focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
+                    role="switch"
+                    aria-checked="false"
+                    aria-labelledby="availability-label"
+                    aria-describedby="availability-description"
+                    onClick={() => {
+                      setOptions({
+                        ...options,
+                        includeHeader: !options.includeHeader,
+                      });
+                    }}
+                  >
+                    <span
+                      aria-hidden="true"
+                      data-enabled={options.includeHeader}
+                      class="translate-x-5 data-[enabled=false]:translate-x-0 
pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow 
ring-0 transition duration-200 ease-in-out"
+                    ></span>
                   </button>
                 </div>
               </div>
               <div class="sm:col-span-5">
                 <div class="flex items-center justify-between">
                   <span class="flex flex-grow flex-col">
-                    <span class="text-sm text-black font-medium leading-6 " 
id="availability-label">
-                      <i18n.Translate>Add previous metric for 
compare</i18n.Translate>
+                    <span
+                      class="text-sm text-black font-medium leading-6 "
+                      id="availability-label"
+                    >
+                      <i18n.Translate>
+                        Add previous metric for compare
+                      </i18n.Translate>
                     </span>
                   </span>
-                  <button type="button" 
data-enabled={!!options.compareWithPrevious} class="bg-indigo-600 
data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 
cursor-pointer rounded-full border-2 border-transparent transition-colors 
duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 
focus:ring-offset-2" role="switch" aria-checked="false" 
aria-labelledby="availability-label" aria-describedby="availability-description"
-
-                    onClick={() => { setOptions({ ...options, 
compareWithPrevious: !options.compareWithPrevious }) }}>
-                    <span aria-hidden="true" 
data-enabled={options.compareWithPrevious} class="translate-x-5 
data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 
transform rounded-full bg-white shadow ring-0 transition duration-200 
ease-in-out"></span>
+                  <button
+                    type="button"
+                    data-enabled={!!options.compareWithPrevious}
+                    class="bg-indigo-600 data-[enabled=false]:bg-gray-200 
relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full 
border-2 border-transparent transition-colors duration-200 ease-in-out 
focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
+                    role="switch"
+                    aria-checked="false"
+                    aria-labelledby="availability-label"
+                    aria-describedby="availability-description"
+                    onClick={() => {
+                      setOptions({
+                        ...options,
+                        compareWithPrevious: !options.compareWithPrevious,
+                      });
+                    }}
+                  >
+                    <span
+                      aria-hidden="true"
+                      data-enabled={options.compareWithPrevious}
+                      class="translate-x-5 data-[enabled=false]:translate-x-0 
pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow 
ring-0 transition duration-200 ease-in-out"
+                    ></span>
                   </button>
                 </div>
               </div>
               <div class="sm:col-span-5">
                 <div class="flex items-center justify-between">
                   <span class="flex flex-grow flex-col">
-                    <span class="text-sm text-black font-medium leading-6 " 
id="availability-label">
+                    <span
+                      class="text-sm text-black font-medium leading-6 "
+                      id="availability-label"
+                    >
                       <i18n.Translate>Fail on first error</i18n.Translate>
                     </span>
                   </span>
-                  <button type="button" 
data-enabled={!!options.endOnFirstFail} class="bg-indigo-600 
data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 
cursor-pointer rounded-full border-2 border-transparent transition-colors 
duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 
focus:ring-offset-2" role="switch" aria-checked="false" 
aria-labelledby="availability-label" aria-describedby="availability-description"
-
-                    onClick={() => { setOptions({ ...options, endOnFirstFail: 
!options.endOnFirstFail }) }}>
-                    <span aria-hidden="true" 
data-enabled={options.endOnFirstFail} class="translate-x-5 
data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 
transform rounded-full bg-white shadow ring-0 transition duration-200 
ease-in-out"></span>
+                  <button
+                    type="button"
+                    data-enabled={!!options.endOnFirstFail}
+                    class="bg-indigo-600 data-[enabled=false]:bg-gray-200 
relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full 
border-2 border-transparent transition-colors duration-200 ease-in-out 
focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
+                    role="switch"
+                    aria-checked="false"
+                    aria-labelledby="availability-label"
+                    aria-describedby="availability-description"
+                    onClick={() => {
+                      setOptions({
+                        ...options,
+                        endOnFirstFail: !options.endOnFirstFail,
+                      });
+                    }}
+                  >
+                    <span
+                      aria-hidden="true"
+                      data-enabled={options.endOnFirstFail}
+                      class="translate-x-5 data-[enabled=false]:translate-x-0 
pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow 
ring-0 transition duration-200 ease-in-out"
+                    ></span>
                   </button>
                 </div>
               </div>
             </div>
           </div>
 
-
-
           <div class="flex items-center justify-between gap-x-6 border-t 
border-gray-900/10 px-4 py-4 sm:px-8">
-            <button type="button" class="text-sm font-semibold leading-6 
text-gray-900"
-              onClick={onCancel}
+            <a
+              href={routeCancel.url({})}
+              class="text-sm font-semibold leading-6 text-gray-900"
             >
               <i18n.Translate>Cancel</i18n.Translate>
-            </button>
-            <button type="submit"
+            </a>
+            <button
+              type="submit"
               class="disabled:opacity-50 disabled:cursor-default 
cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold 
text-white shadow-sm hover:bg-indigo-500 focus-visible:outline 
focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
               disabled={lastStep !== undefined}
               onClick={async () => {
-                setDownloaded(undefined)
+                setDownloaded(undefined);
                 await handleError(async () => {
-                  const csv = await fetchAllStatus(api, creds.token, options, 
referenceDates, (step, total) => {
-                    setLastStep({ step, total })
-                  })
-                  setDownloaded(csv)
-                })
-                setLastStep(undefined)
+                  const csv = await fetchAllStatus(
+                    api,
+                    creds.token,
+                    options,
+                    referenceDates,
+                    (step, total) => {
+                      setLastStep({ step, total });
+                    },
+                  );
+                  setDownloaded(csv);
+                });
+                setLastStep(undefined);
               }}
             >
               <i18n.Translate>Download</i18n.Translate>
             </button>
           </div>
         </form>
-
       </div>
-      {!lastStep || lastStep.step === lastStep.total ? <div class="h-5 mb-5"/> 
: <div>
-        <div class="relative mb-5 h-5 rounded-full bg-gray-200">
-          <div class="h-full animate-pulse rounded-full bg-blue-500" style={{
-            width: `${Math.round((((lastStep.step / lastStep.total))* 100) )}%`
-          }}>
-            <span class="absolute inset-0 flex items-center justify-center 
text-xs font-semibold text-white">
-              <i18n.Translate>downloading... {Math.round((((lastStep.step / 
lastStep.total))* 100) )}</i18n.Translate>
-            </span>
+      {!lastStep || lastStep.step === lastStep.total ? (
+        <div class="h-5 mb-5" />
+      ) : (
+        <div>
+          <div class="relative mb-5 h-5 rounded-full bg-gray-200">
+            <div
+              class="h-full animate-pulse rounded-full bg-blue-500"
+              style={{
+                width: `${Math.round((lastStep.step / lastStep.total) * 
100)}%`,
+              }}
+            >
+              <span class="absolute inset-0 flex items-center justify-center 
text-xs font-semibold text-white">
+                <i18n.Translate>
+                  downloading...{" "}
+                  {Math.round((lastStep.step / lastStep.total) * 100)}
+                </i18n.Translate>
+              </span>
+            </div>
           </div>
         </div>
-      </div>}
-      {!downloaded ? <div class="h-5 mb-5"/> :
-        <a href={"data:text/plain;charset=utf-8," + 
encodeURIComponent(downloaded)} download={"bank-stats.csv"}>
+      )}
+      {!downloaded ? (
+        <div class="h-5 mb-5" />
+      ) : (
+        <a
+          href={
+            "data:text/plain;charset=utf-8," + encodeURIComponent(downloaded)
+          }
+          download={"bank-stats.csv"}
+        >
           <Attention title={i18n.str`Download completed`}>
-            <i18n.Translate>click here to save the file in your 
computer</i18n.Translate>
+            <i18n.Translate>
+              click here to save the file in your computer
+            </i18n.Translate>
           </Attention>
         </a>
-      }
+      )}
     </div>
   );
 }
 
-
-async function fetchAllStatus(api: TalerCoreBankHttpClient, token: 
AccessToken, options: Options, references: Date[], progres: (current: number, 
total: number) => void): Promise<string> {
+async function fetchAllStatus(
+  api: TalerCoreBankHttpClient,
+  token: AccessToken,
+  options: Options,
+  references: Date[],
+  progres: (current: number, total: number) => void,
+): Promise<string> {
   const allMetrics: TalerCorebankApi.MonitorTimeframeParam[] = [];
   if (options.hourMetric) {
-    allMetrics.push(TalerCorebankApi.MonitorTimeframeParam.hour)
+    allMetrics.push(TalerCorebankApi.MonitorTimeframeParam.hour);
   }
   if (options.dayMetric) {
-    allMetrics.push(TalerCorebankApi.MonitorTimeframeParam.day)
+    allMetrics.push(TalerCorebankApi.MonitorTimeframeParam.day);
   }
   if (options.monthMetric) {
-    allMetrics.push(TalerCorebankApi.MonitorTimeframeParam.month)
+    allMetrics.push(TalerCorebankApi.MonitorTimeframeParam.month);
   }
   if (options.yearMetric) {
-    allMetrics.push(TalerCorebankApi.MonitorTimeframeParam.year)
+    allMetrics.push(TalerCorebankApi.MonitorTimeframeParam.year);
   }
 
   /**
    * conver request into frames
    */
-  const allFrames = allMetrics.flatMap(timeframe => references.map(reference 
=> ({
-    reference,
-    timeframe,
-    moment: getTimeframesForDate(reference, timeframe)
-  }))
-  )
-  const total = allFrames.length
+  const allFrames = allMetrics.flatMap((timeframe) =>
+    references.map((reference) => ({
+      reference,
+      timeframe,
+      moment: getTimeframesForDate(reference, timeframe),
+    })),
+  );
+  const total = allFrames.length;
 
   /**
    * call API for info
    */
-  const allInfo = await allFrames.reduce(async (prev, frame, index) => {
-    const accumulatedMap = await prev
-    progres(index, total)
-    // await delay()
-    const previous = options.compareWithPrevious ? (await 
api.getMonitor(token, {
-      timeframe: frame.timeframe,
-      which: frame.moment.previous
-    })) : undefined
-
-    if (previous && previous.type === "fail" && options.endOnFirstFail) {
-      throw TalerError.fromUncheckedDetail(previous.detail)
-    }
+  const allInfo = await allFrames.reduce(
+    async (prev, frame, index) => {
+      const accumulatedMap = await prev;
+      progres(index, total);
+      // await delay()
+      const previous = options.compareWithPrevious
+        ? await api.getMonitor(token, {
+            timeframe: frame.timeframe,
+            which: frame.moment.previous,
+          })
+        : undefined;
+
+      if (previous && previous.type === "fail" && options.endOnFirstFail) {
+        throw TalerError.fromUncheckedDetail(previous.detail);
+      }
 
-    const current = await api.getMonitor(token, {
-      timeframe: frame.timeframe,
-      which: frame.moment.current
-    })
+      const current = await api.getMonitor(token, {
+        timeframe: frame.timeframe,
+        which: frame.moment.current,
+      });
 
-    if (current.type === "fail" && options.endOnFirstFail) {
-      throw TalerError.fromUncheckedDetail(current.detail)
-    }
+      if (current.type === "fail" && options.endOnFirstFail) {
+        throw TalerError.fromUncheckedDetail(current.detail);
+      }
 
-    const metricName = 
TalerCorebankApi.MonitorTimeframeParam[allMetrics[index]]
-    accumulatedMap[metricName] = {
-      reference: frame.reference,
-      current: current.type !== "ok" ? undefined : current.body,
-      previous: !previous || previous.type !== "ok" ? undefined : 
previous.body,
-    }
-    return accumulatedMap
-  }, Promise.resolve({} as Record<string, Data>))
-  progres(total, total)
+      const metricName =
+        TalerCorebankApi.MonitorTimeframeParam[allMetrics[index]];
+      accumulatedMap[metricName] = {
+        reference: frame.reference,
+        current: current.type !== "ok" ? undefined : current.body,
+        previous:
+          !previous || previous.type !== "ok" ? undefined : previous.body,
+      };
+      return accumulatedMap;
+    },
+    Promise.resolve({} as Record<string, Data>),
+  );
+  progres(total, total);
 
   /**
    * conver into table format
-   * 
+   *
    */
   const table: Array<string[]> = [];
   if (options.includeHeader) {
-    table.push(["date",
+    table.push([
+      "date",
       "metric",
       "reference",
       "talerInCount",
@@ -320,7 +501,8 @@ async function fetchAllStatus(api: TalerCoreBankHttpClient, 
token: AccessToken,
       "cashinRegionalVolume",
       "cashoutCount",
       "cashoutFiatVolume",
-      "cashoutRegionalVolume",])
+      "cashoutRegionalVolume",
+    ]);
   }
   Object.entries(allInfo).forEach(([name, data]) => {
     if (data.current) {
@@ -328,9 +510,9 @@ async function fetchAllStatus(api: TalerCoreBankHttpClient, 
token: AccessToken,
         date: data.reference.getTime(),
         metric: name,
         reference: "current",
-        ...dataToRow(data.current)
-      }
-      table.push((Object.values(row) as string[]))
+        ...dataToRow(data.current),
+      };
+      table.push(Object.values(row) as string[]);
     }
 
     if (data.previous) {
@@ -338,20 +520,20 @@ async function fetchAllStatus(api: 
TalerCoreBankHttpClient, token: AccessToken,
         date: data.reference.getTime(),
         metric: name,
         reference: "previous",
-        ...dataToRow(data.previous)
-      }
-      table.push((Object.values(row) as string[]))
+        ...dataToRow(data.previous),
+      };
+      table.push(Object.values(row) as string[]);
     }
-  })
+  });
 
   const csv = table.reduce((acc, row) => {
-    return acc + row.join(",") + "\n"
-  }, "")
+    return acc + row.join(",") + "\n";
+  }, "");
 
-  return csv
+  return csv;
 }
 
-type JustData = Omit<Omit<Omit<TableRow, "metric">, "date">, "reference">
+type JustData = Omit<Omit<Omit<TableRow, "metric">, "date">, "reference">;
 function dataToRow(info: TalerCorebankApi.MonitorResponse): JustData {
   return {
     talerInCount: info.talerInCount,
@@ -359,23 +541,28 @@ function dataToRow(info: 
TalerCorebankApi.MonitorResponse): JustData {
     talerOutCount: info.talerOutCount,
     talerOutVolume: info.talerOutVolume,
     cashinCount: info.type === "no-conversions" ? undefined : info.cashinCount,
-    cashinFiatVolume: info.type === "no-conversions" ? undefined : 
info.cashinFiatVolume,
-    cashinRegionalVolume: info.type === "no-conversions" ? undefined : 
info.cashinRegionalVolume,
-    cashoutCount: info.type === "no-conversions" ? undefined : 
info.cashoutCount,
-    cashoutFiatVolume: info.type === "no-conversions" ? undefined : 
info.cashoutFiatVolume,
-    cashoutRegionalVolume: info.type === "no-conversions" ? undefined : 
info.cashoutRegionalVolume,
-  }
+    cashinFiatVolume:
+      info.type === "no-conversions" ? undefined : info.cashinFiatVolume,
+    cashinRegionalVolume:
+      info.type === "no-conversions" ? undefined : info.cashinRegionalVolume,
+    cashoutCount:
+      info.type === "no-conversions" ? undefined : info.cashoutCount,
+    cashoutFiatVolume:
+      info.type === "no-conversions" ? undefined : info.cashoutFiatVolume,
+    cashoutRegionalVolume:
+      info.type === "no-conversions" ? undefined : info.cashoutRegionalVolume,
+  };
 }
 
 type Data = {
-  reference: Date,
+  reference: Date;
   previous: TalerCorebankApi.MonitorResponse | undefined;
   current: TalerCorebankApi.MonitorResponse | undefined;
-}
+};
 type TableRow = {
-  date: number,
-  metric: string,
-  reference: "current" | "previous",
+  date: number;
+  metric: string;
+  reference: "current" | "previous";
   cashinCount?: number;
   cashinRegionalVolume?: AmountString;
   cashinFiatVolume?: AmountString;
@@ -386,11 +573,4 @@ type TableRow = {
   talerInVolume: AmountString;
   talerOutCount: number;
   talerOutVolume: AmountString;
-}
-async function delay() {
-  return new Promise(res => {
-    setTimeout(( )=> {
-      res(null)
-    }, 500)
-  })
-}
\ No newline at end of file
+};
diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx 
b/packages/demobank-ui/src/pages/LoginForm.tsx
index 7ec490c19..7e5631cfb 100644
--- a/packages/demobank-ui/src/pages/LoginForm.tsx
+++ b/packages/demobank-ui/src/pages/LoginForm.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -14,29 +14,48 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
-import { LocalNotificationBanner, ShowInputErrorLabel, useLocalNotification, 
useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Fragment, VNode, h } from "preact";
+import {
+  HttpStatusCode,
+  TranslatedString,
+  assertUnreachable,
+} from "@gnu-taler/taler-util";
+import {
+  LocalNotificationBanner,
+  ShowInputErrorLabel,
+  useLocalNotification,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
+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 { undefinedIfEmpty } from "../utils.js";
 import { doAutoFocus } from "./PaytoWireTransferForm.js";
-import { assertUnreachable } from "./WithdrawalOperationPage.js";
-
+import { RouteDefinition } from "../route.js";
 
 /**
  * Collect and submit login data.
  */
-export function LoginForm({ currentUser, fixedUser, onRegister }: { 
fixedUser?: boolean, currentUser?: string, onRegister?: () => void }): VNode {
+export function LoginForm({
+  currentUser,
+  fixedUser,
+  routeRegister,
+}: {
+  fixedUser?: boolean;
+  currentUser?: string;
+  routeRegister?: RouteDefinition<Record<string, never>>;
+}): VNode {
   const backend = useBackendState();
 
-  const sessionUser = backend.state.status !== "loggedOut" ? 
backend.state.username : undefined
-  const [username, setUsername] = useState<string | undefined>(currentUser ?? 
sessionUser);
+  const sessionUser =
+    backend.state.status !== "loggedOut" ? backend.state.username : undefined;
+  const [username, setUsername] = useState<string | undefined>(
+    currentUser ?? sessionUser,
+  );
   const [password, setPassword] = useState<string | undefined>();
   const { i18n } = useTranslationContext();
   const { api } = useBankCoreApiContext();
-  const [notification, notify, handleError] = useLocalNotification()
+  const [notification, notify, handleError] = useLocalNotification();
   const { config } = useBankCoreApiContext();
 
   const ref = useRef<HTMLInputElement>(null);
@@ -44,63 +63,71 @@ export function LoginForm({ currentUser, fixedUser, 
onRegister }: { fixedUser?:
     ref.current?.focus();
   }, []);
 
-  const [busy, setBusy] = useState<Record<string, undefined>>()
+  const [busy, setBusy] = useState<Record<string, undefined>>();
 
-  const errors = undefinedIfEmpty({
-    username: !username
-      ? i18n.str`Missing username`
-      // : !USERNAME_REGEX.test(username)
-      //   ? i18n.str`Use letters and numbers only, and start with a lowercase 
letter`
-      : undefined,
-    password: !password ? i18n.str`Missing password` : undefined,
-  }) ?? busy;
+  const errors =
+    undefinedIfEmpty({
+      username: !username
+        ? i18n.str`Missing username`
+        : // : !USERNAME_REGEX.test(username)
+          //   ? i18n.str`Use letters and numbers only, and start with a 
lowercase letter`
+          undefined,
+      password: !password ? i18n.str`Missing password` : undefined,
+    }) ?? busy;
 
   async function doLogout() {
-    backend.logOut()
+    backend.logOut();
   }
 
   async function doLogin() {
     if (!username || !password) return;
-    setBusy({})
+    setBusy({});
     await handleError(async () => {
-      const resp = await 
api.getAuthenticationAPI(username).createAccessToken(password, {
-        // scope: "readwrite" as "write", //FIX: different than merchant
-        scope: "readwrite",
-        duration: {
-          d_us: "forever" //FIX: should return shortest
-          // d_us: 60 * 60 * 24 * 7 * 1000 * 1000
-        },
-        refreshable: true,
-      })
+      const resp = await api
+        .getAuthenticationAPI(username)
+        .createAccessToken(password, {
+          // scope: "readwrite" as "write", // FIX: different than merchant
+          scope: "readwrite",
+          duration: {
+            d_us: "forever", // FIX: should return shortest
+            // d_us: 60 * 60 * 24 * 7 * 1000 * 1000
+          },
+          refreshable: true,
+        });
       if (resp.type === "ok") {
         backend.logIn({ username, token: resp.body.access_token });
       } else {
         switch (resp.case) {
-          case HttpStatusCode.Unauthorized: return notify({
-            type: "error",
-            title: i18n.str`Wrong credentials for "${username}"`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case HttpStatusCode.NotFound: return notify({
-            type: "error",
-            title: i18n.str`Account not found`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          default: assertUnreachable(resp)
+          case HttpStatusCode.Unauthorized:
+            return notify({
+              type: "error",
+              title: i18n.str`Wrong credentials for "${username}"`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case HttpStatusCode.NotFound:
+            return notify({
+              type: "error",
+              title: i18n.str`Account not found`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          default:
+            assertUnreachable(resp);
         }
       }
-    })
+    });
     setPassword(undefined);
-    setBusy(undefined)
+    setBusy(undefined);
   }
 
   return (
     <div class="flex min-h-full flex-col justify-center ">
       <LocalNotificationBanner notification={notification} />
       <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
-        <form class="space-y-6" noValidate
+        <form
+          class="space-y-6"
+          noValidate
           onSubmit={(e) => {
             e.preventDefault();
           }}
@@ -108,7 +135,10 @@ export function LoginForm({ currentUser, fixedUser, 
onRegister }: { fixedUser?:
           autoCorrect="off"
         >
           <div>
-            <label for="username" class="block text-sm font-medium leading-6 
text-gray-900">
+            <label
+              for="username"
+              class="block text-sm font-medium leading-6 text-gray-900"
+            >
               <i18n.Translate>Username</i18n.Translate>
             </label>
             <div class="mt-2">
@@ -138,7 +168,10 @@ export function LoginForm({ currentUser, fixedUser, 
onRegister }: { fixedUser?:
 
           <div>
             <div class="flex items-center justify-between">
-              <label for="password" class="block text-sm font-medium leading-6 
text-gray-900">
+              <label
+                for="password"
+                class="block text-sm font-medium leading-6 text-gray-900"
+              >
                 <i18n.Translate>Password</i18n.Translate>
               </label>
             </div>
@@ -165,54 +198,58 @@ export function LoginForm({ currentUser, fixedUser, 
onRegister }: { fixedUser?:
             </div>
           </div>
 
-          {backend.state.status !== "loggedOut" ? <div class="flex 
justify-between">
-            <button type="submit"
-              class="rounded-md bg-white-600 px-3 py-1.5 text-sm font-semibold 
leading-6 text-black shadow-sm hover:bg-gray-100 focus-visible:outline 
focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-gray-600"
-              onClick={(e) => {
-                e.preventDefault()
-                doLogout()
-              }}
-            >
-              <i18n.Translate>Cancel</i18n.Translate>
-            </button>
-
-            <button type="submit"
-              class="rounded-md bg-indigo-600 disabled:bg-gray-300 px-3 py-1.5 
text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
-              disabled={!!errors}
-              onClick={async (e) => {
-                e.preventDefault()
-                doLogin()
-              }}
-            >
-              <i18n.Translate>Check</i18n.Translate>
-            </button>
-          </div> : <div>
-            <button type="submit"
-              class="flex w-full justify-center rounded-md bg-indigo-600 
disabled:bg-gray-300 px-3 py-1.5 text-sm font-semibold leading-6 text-white 
shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
-              disabled={!!errors}
-              onClick={(e) => {
-                e.preventDefault()
-                doLogin()
-              }}
-            >
-              <i18n.Translate>Log in</i18n.Translate>
-            </button>
-          </div>}
+          {backend.state.status !== "loggedOut" ? (
+            <div class="flex justify-between">
+              <button
+                type="submit"
+                class="rounded-md bg-white-600 px-3 py-1.5 text-sm 
font-semibold leading-6 text-black shadow-sm hover:bg-gray-100 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-gray-600"
+                onClick={(e) => {
+                  e.preventDefault();
+                  doLogout();
+                }}
+              >
+                <i18n.Translate>Cancel</i18n.Translate>
+              </button>
+
+              <button
+                type="submit"
+                class="rounded-md bg-indigo-600 disabled:bg-gray-300 px-3 
py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
+                disabled={!!errors}
+                onClick={async (e) => {
+                  e.preventDefault();
+                  doLogin();
+                }}
+              >
+                <i18n.Translate>Check</i18n.Translate>
+              </button>
+            </div>
+          ) : (
+            <div>
+              <button
+                type="submit"
+                class="flex w-full justify-center rounded-md bg-indigo-600 
disabled:bg-gray-300 px-3 py-1.5 text-sm font-semibold leading-6 text-white 
shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
+                disabled={!!errors}
+                onClick={(e) => {
+                  e.preventDefault();
+                  doLogin();
+                }}
+              >
+                <i18n.Translate>Log in</i18n.Translate>
+              </button>
+            </div>
+          )}
         </form>
 
-        {config.allow_registrations && onRegister &&
+        {config.allow_registrations && routeRegister && (
           <p class="mt-10 text-center text-sm text-gray-500 border-t">
-            <button type="submit"
+            <a
+              href={routeRegister.url({})}
               class="flex mt-4 rounded-md bg-blue-600 px-3 py-1.5 text-sm 
font-semibold leading-6 text-white shadow-sm hover:bg-blue-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-blue-600"
-              onClick={(e) => {
-                e.preventDefault()
-                onRegister()
-              }}
             >
               <i18n.Translate>Register</i18n.Translate>
-            </button>
+            </a>
           </p>
-        }
+        )}
       </div>
     </div>
   );
diff --git a/packages/demobank-ui/src/pages/OperationState/index.ts 
b/packages/demobank-ui/src/pages/OperationState/index.ts
index 53d07e44b..20cb1760f 100644
--- a/packages/demobank-ui/src/pages/OperationState/index.ts
+++ b/packages/demobank-ui/src/pages/OperationState/index.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -14,28 +14,46 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { AbsoluteTime, AmountJson, TalerCoreBankErrorsByMethod, TalerError, 
WithdrawUriResult } from "@gnu-taler/taler-util";
+import {
+  AbsoluteTime,
+  AmountJson,
+  TalerCoreBankErrorsByMethod,
+  TalerError,
+  WithdrawUriResult,
+} from "@gnu-taler/taler-util";
 import { Loading, utils } from "@gnu-taler/web-util/browser";
 import { ErrorLoadingWithDebug } from 
"../../components/ErrorLoadingWithDebug.js";
 import { useComponentState } from "./state.js";
-import { AbortedView, ConfirmedView, FailedView, InvalidPaytoView, 
InvalidReserveView, InvalidWithdrawalView, NeedConfirmationView, ReadyView } 
from "./views.js";
+import {
+  AbortedView,
+  ConfirmedView,
+  FailedView,
+  InvalidPaytoView,
+  InvalidReserveView,
+  InvalidWithdrawalView,
+  NeedConfirmationView,
+  ReadyView,
+} from "./views.js";
+import { RouteDefinition } from "../../route.js";
 
 export interface Props {
   currency: string;
-  onAuthorizationRequired: () => void,
-  onClose: () => void;
+  onAuthorizationRequired: () => void;
+  routeClose: RouteDefinition<Record<string, never>>;
+  onAbort: () => void;
 }
 
-export type State = State.Loading |
-  State.LoadingError |
-  State.Ready |
-  State.Failed |
-  State.Aborted |
-  State.Confirmed |
-  State.InvalidPayto |
-  State.InvalidWithdrawal |
-  State.InvalidReserve |
-  State.NeedConfirmation;
+export type State =
+  | State.Loading
+  | State.LoadingError
+  | State.Ready
+  | State.Failed
+  | State.Aborted
+  | State.Confirmed
+  | State.InvalidPayto
+  | State.InvalidWithdrawal
+  | State.InvalidReserve
+  | State.NeedConfirmation;
 
 export namespace State {
   export interface Loading {
@@ -59,48 +77,55 @@ export namespace State {
   export interface Ready {
     status: "ready";
     error: undefined;
-    uri: WithdrawUriResult,
-    onClose: () => Promise<TalerCoreBankErrorsByMethod<"abortWithdrawalById"> 
| undefined>;
+    uri: WithdrawUriResult;
+    onAbort: () => Promise<
+      TalerCoreBankErrorsByMethod<"abortWithdrawalById"> | undefined
+    >;
+    routeClose: RouteDefinition<Record<string, never>>;
   }
 
   export interface InvalidPayto {
-    status: "invalid-payto",
+    status: "invalid-payto";
     error: undefined;
     payto: string | undefined;
-    onClose: () => void;
   }
   export interface InvalidWithdrawal {
-    status: "invalid-withdrawal",
+    status: "invalid-withdrawal";
     error: undefined;
-    onClose: () => void;
-    uri: string,
+    uri: string;
   }
   export interface InvalidReserve {
-    status: "invalid-reserve",
+    status: "invalid-reserve";
     error: undefined;
-    onClose: () => void;
     reserve: string | undefined;
   }
   export interface NeedConfirmation {
-    status: "need-confirmation",
-    onAuthorizationRequired: () => void,
-    account: string,
-    onAbort: undefined | (() => 
Promise<TalerCoreBankErrorsByMethod<"abortWithdrawalById"> | undefined>);
-    onConfirm: undefined | (() => 
Promise<TalerCoreBankErrorsByMethod<"confirmWithdrawalById"> | undefined>);
+    status: "need-confirmation";
+    onAuthorizationRequired: () => void;
+    account: string;
+    onAbort:
+      | undefined
+      | (() => Promise<
+          TalerCoreBankErrorsByMethod<"abortWithdrawalById"> | undefined
+        >);
+    onConfirm:
+      | undefined
+      | (() => Promise<
+          TalerCoreBankErrorsByMethod<"confirmWithdrawalById"> | undefined
+        >);
     error: undefined;
-    id: string,
+    id: string;
   }
   export interface Aborted {
-    status: "aborted",
+    status: "aborted";
     error: undefined;
-    onClose: () => void;
+    routeClose: RouteDefinition<Record<string, never>>;
   }
   export interface Confirmed {
-    status: "confirmed",
+    status: "confirmed";
     error: undefined;
-    onClose: () => void;
+    routeClose: RouteDefinition<Record<string, never>>;
   }
-
 }
 
 export interface Transaction {
@@ -113,13 +138,13 @@ export interface Transaction {
 
 const viewMapping: utils.StateViewMap<State> = {
   loading: Loading,
-  "failed": FailedView,
+  failed: FailedView,
   "invalid-payto": InvalidPaytoView,
   "invalid-withdrawal": InvalidWithdrawalView,
   "invalid-reserve": InvalidReserveView,
   "need-confirmation": NeedConfirmationView,
-  "aborted": AbortedView,
-  "confirmed": ConfirmedView,
+  aborted: AbortedView,
+  confirmed: ConfirmedView,
   "loading-error": ErrorLoadingWithDebug,
   ready: ReadyView,
 };
diff --git a/packages/demobank-ui/src/pages/OperationState/state.ts 
b/packages/demobank-ui/src/pages/OperationState/state.ts
index fbf43867f..20d66bbb1 100644
--- a/packages/demobank-ui/src/pages/OperationState/state.ts
+++ b/packages/demobank-ui/src/pages/OperationState/state.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -14,7 +14,16 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { Amounts, HttpStatusCode, TalerCoreBankErrorsByMethod, TalerError, 
parsePaytoUri, parseWithdrawUri, stringifyWithdrawUri } from 
"@gnu-taler/taler-util";
+import {
+  Amounts,
+  HttpStatusCode,
+  TalerCoreBankErrorsByMethod,
+  TalerError,
+  assertUnreachable,
+  parsePaytoUri,
+  parseWithdrawUri,
+  stringifyWithdrawUri,
+} from "@gnu-taler/taler-util";
 import { utils } from "@gnu-taler/web-util/browser";
 import { useEffect, useState } from "preact/hooks";
 import { mutate } from "swr";
@@ -23,73 +32,80 @@ import { useWithdrawalDetails } from 
"../../hooks/access.js";
 import { useBackendState } from "../../hooks/backend.js";
 import { useBankState } from "../../hooks/bank-state.js";
 import { usePreferences } from "../../hooks/preferences.js";
-import { assertUnreachable } from "../WithdrawalOperationPage.js";
 import { Props, State } from "./index.js";
 
-export function useComponentState({ currency, onClose, 
onAuthorizationRequired, }: Props): utils.RecursiveState<State> {
-  const [settings] = usePreferences()
+export function useComponentState({
+  currency,
+  routeClose,
+  onAbort,
+  onAuthorizationRequired,
+}: Props): utils.RecursiveState<State> {
+  const [settings] = usePreferences();
   const [bankState, updateBankState] = useBankState();
-  const { state: credentials } = useBackendState()
-  const creds = credentials.status !== "loggedIn" ? undefined : credentials
-  const { api } = useBankCoreApiContext()
+  const { state: credentials } = useBackendState();
+  const creds = credentials.status !== "loggedIn" ? undefined : credentials;
+  const { api } = useBankCoreApiContext();
 
-  const [failure, setFailure] = 
useState<TalerCoreBankErrorsByMethod<"createWithdrawal"> | undefined>()
-  const amount = settings.maxWithdrawalAmount
+  const [failure, setFailure] = useState<
+    TalerCoreBankErrorsByMethod<"createWithdrawal"> | undefined
+  >();
+  const amount = settings.maxWithdrawalAmount;
 
   async function doSilentStart() {
-    //FIXME: if amount is not enough use balance
-    const parsedAmount = Amounts.parseOrThrow(`${currency}:${amount}`)
+    // FIXME: if amount is not enough use balance
+    const parsedAmount = Amounts.parseOrThrow(`${currency}:${amount}`);
     if (!creds) return;
     const resp = await api.createWithdrawal(creds, {
       amount: Amounts.stringify(parsedAmount),
     });
     if (resp.type === "fail") {
-      setFailure(resp)
+      setFailure(resp);
       return;
     }
-    updateBankState("currentWithdrawalOperationId", resp.body.withdrawal_id)
-
+    updateBankState("currentWithdrawalOperationId", resp.body.withdrawal_id);
   }
 
-  const withdrawalOperationId = bankState.currentWithdrawalOperationId
+  const withdrawalOperationId = bankState.currentWithdrawalOperationId;
   useEffect(() => {
     if (withdrawalOperationId === undefined) {
-      doSilentStart()
+      doSilentStart();
     }
-  }, [settings.fastWithdrawal, amount])
+  }, [settings.fastWithdrawal, amount]);
 
   if (failure) {
     return {
       status: "failed",
-      error: failure
-    }
+      error: failure,
+    };
   }
 
   if (!withdrawalOperationId) {
     return {
       status: "loading",
-      error: undefined
-    }
+      error: undefined,
+    };
   }
 
-  const wid = withdrawalOperationId
+  const wid = withdrawalOperationId;
 
   async function doAbort() {
     if (!creds) return;
     const resp = await api.abortWithdrawalById(creds, wid);
     if (resp.type === "ok") {
-      updateBankState("currentWithdrawalOperationId", undefined)
-      onClose();
+      // updateBankState("currentWithdrawalOperationId", undefined)
+      onAbort();
     } else {
       return resp;
     }
   }
 
-  async function doConfirm(): 
Promise<TalerCoreBankErrorsByMethod<"confirmWithdrawalById"> | undefined> {
+  async function doConfirm(): Promise<
+    TalerCoreBankErrorsByMethod<"confirmWithdrawalById"> | undefined
+  > {
     if (!creds) return;
     const resp = await api.confirmWithdrawalById(creds, wid);
     if (resp.type === "ok") {
-      mutate(() => true)//clean withdrawal state
+      mutate(() => true); //clean withdrawal state
     } else {
       return resp;
     }
@@ -105,30 +121,29 @@ export function useComponentState({ currency, onClose, 
onAuthorizationRequired,
       status: "invalid-withdrawal",
       error: undefined,
       uri,
-      onClose,
-    }
+    };
   }
 
   return (): utils.RecursiveState<State> => {
     const result = useWithdrawalDetails(withdrawalOperationId);
-    const shouldCreateNewOperation = result && !(result instanceof TalerError)
+    const shouldCreateNewOperation = result && !(result instanceof TalerError);
 
     useEffect(() => {
       if (shouldCreateNewOperation) {
-        doSilentStart()
+        doSilentStart();
       }
-    }, [])
+    }, []);
     if (!result) {
       return {
         status: "loading",
-        error: undefined
-      }
+        error: undefined,
+      };
     }
     if (result instanceof TalerError) {
       return {
         status: "loading-error",
-        error: result
-      }
+        error: result,
+      };
     }
 
     if (result.type === "fail") {
@@ -138,13 +153,11 @@ export function useComponentState({ currency, onClose, 
onAuthorizationRequired,
           return {
             status: "aborted",
             error: undefined,
-            onClose: async () => {
-              updateBankState("currentWithdrawalOperationId", undefined)
-              onClose()
-            },
-          }
+            routeClose,
+          };
         }
-        default: assertUnreachable(result)
+        default:
+          assertUnreachable(result);
       }
     }
 
@@ -153,26 +166,20 @@ export function useComponentState({ currency, onClose, 
onAuthorizationRequired,
       return {
         status: "aborted",
         error: undefined,
-        onClose: async () => {
-          updateBankState("currentWithdrawalOperationId", undefined)
-          onClose()
-        },
-      }
+        routeClose,
+      };
     }
 
     if (data.status === "confirmed") {
       if (!settings.showWithdrawalSuccess) {
-        updateBankState("currentWithdrawalOperationId", undefined)
-        onClose()
+        updateBankState("currentWithdrawalOperationId", undefined);
+        // onClose()
       }
       return {
         status: "confirmed",
         error: undefined,
-        onClose: async () => {
-          updateBankState("currentWithdrawalOperationId", undefined)
-          onClose()
-        },
-      }
+        routeClose,
+      };
     }
 
     if (data.status === "pending") {
@@ -180,11 +187,14 @@ export function useComponentState({ currency, onClose, 
onAuthorizationRequired,
         status: "ready",
         error: undefined,
         uri: parsedUri,
-        onClose: !creds ? (async () => {
-          onClose();
-          return undefined
-        }) : doAbort,
-      }
+        routeClose,
+        onAbort: !creds
+          ? async () => {
+              onAbort();
+              return undefined;
+            }
+          : doAbort,
+      };
     }
 
     if (!data.selected_reserve_pub) {
@@ -192,19 +202,19 @@ export function useComponentState({ currency, onClose, 
onAuthorizationRequired,
         status: "invalid-reserve",
         error: undefined,
         reserve: data.selected_reserve_pub,
-        onClose,
-      }
+      };
     }
 
-    const account = !data.selected_exchange_account ? undefined : 
parsePaytoUri(data.selected_exchange_account)
+    const account = !data.selected_exchange_account
+      ? undefined
+      : parsePaytoUri(data.selected_exchange_account);
 
     if (!account) {
       return {
         status: "invalid-payto",
         error: undefined,
         payto: data.selected_exchange_account,
-        onClose,
-      }
+      };
     }
 
     return {
@@ -214,8 +224,7 @@ export function useComponentState({ currency, onClose, 
onAuthorizationRequired,
       account: data.username,
       id: withdrawalOperationId,
       onAbort: !creds ? undefined : doAbort,
-      onConfirm: !creds ? undefined : doConfirm
-    }
-  }
-
+      onConfirm: !creds ? undefined : doConfirm,
+    };
+  };
 }
diff --git a/packages/demobank-ui/src/pages/OperationState/stories.tsx 
b/packages/demobank-ui/src/pages/OperationState/stories.tsx
index 03917a8fb..82253b82c 100644
--- a/packages/demobank-ui/src/pages/OperationState/stories.tsx
+++ b/packages/demobank-ui/src/pages/OperationState/stories.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
diff --git a/packages/demobank-ui/src/pages/OperationState/test.ts 
b/packages/demobank-ui/src/pages/OperationState/test.ts
index f4d6cf4b2..d47cb64a2 100644
--- a/packages/demobank-ui/src/pages/OperationState/test.ts
+++ b/packages/demobank-ui/src/pages/OperationState/test.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -19,14 +19,13 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-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 * 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";
 
 describe("Withdrawal operation states", () => {
-  it("should do some tests", async () => {
-  });
+  it("should do some tests", async () => {});
 });
diff --git a/packages/demobank-ui/src/pages/OperationState/views.tsx 
b/packages/demobank-ui/src/pages/OperationState/views.tsx
index 05d53bb05..ac3724eb8 100644
--- a/packages/demobank-ui/src/pages/OperationState/views.tsx
+++ b/packages/demobank-ui/src/pages/OperationState/views.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -14,121 +14,143 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { AbsoluteTime, HttpStatusCode, TalerErrorCode, TranslatedString, 
stringifyWithdrawUri } from "@gnu-taler/taler-util";
-import { Attention, LocalNotificationBanner, notifyInfo, useLocalNotification, 
useTranslationContext } from "@gnu-taler/web-util/browser";
+import {
+  AbsoluteTime,
+  HttpStatusCode,
+  TalerErrorCode,
+  TranslatedString,
+  assertUnreachable,
+  stringifyWithdrawUri,
+} from "@gnu-taler/taler-util";
+import {
+  Attention,
+  LocalNotificationBanner,
+  notifyInfo,
+  useLocalNotification,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useEffect } from "preact/hooks";
 import { QR } from "../../components/QR.js";
 import { useBankState } from "../../hooks/bank-state.js";
 import { usePreferences } from "../../hooks/preferences.js";
 import { ShouldBeSameUser } from "../WithdrawalConfirmationQuestion.js";
-import { assertUnreachable } from "../WithdrawalOperationPage.js";
 import { State } from "./index.js";
 
-export function InvalidPaytoView({ payto, onClose }: State.InvalidPayto) {
-  return (
-    <div>Payto from server is not valid &quot;{payto}&quot;</div>
-  );
+export function InvalidPaytoView({ payto }: State.InvalidPayto) {
+  return <div>Payto from server is not valid &quot;{payto}&quot;</div>;
 }
-export function InvalidWithdrawalView({ uri, onClose }: 
State.InvalidWithdrawal) {
-  return (
-    <div>Withdrawal uri from server is not valid &quot;{uri}&quot;</div>
-  );
+export function InvalidWithdrawalView({ uri }: State.InvalidWithdrawal) {
+  return <div>Withdrawal uri from server is not valid &quot;{uri}&quot;</div>;
 }
-export function InvalidReserveView({ reserve, onClose }: State.InvalidReserve) 
{
-  return (
-    <div>Reserve from server is not valid &quot;{reserve}&quot;</div>
-  );
+export function InvalidReserveView({ reserve }: State.InvalidReserve) {
+  return <div>Reserve from server is not valid &quot;{reserve}&quot;</div>;
 }
 
-export function NeedConfirmationView({ error, onAbort: doAbort, onConfirm: 
doConfirm, account, id, onAuthorizationRequired, }: State.NeedConfirmation) {
-  const { i18n } = useTranslationContext()
-  const [settings] = usePreferences()
-  const [notification, notify, errorHandler] = useLocalNotification()
-  const [, updateBankState] = useBankState()
+export function NeedConfirmationView({
+  onAbort: doAbort,
+  onConfirm: doConfirm,
+  account,
+  id,
+  onAuthorizationRequired,
+}: State.NeedConfirmation) {
+  const { i18n } = useTranslationContext();
+  const [settings] = usePreferences();
+  const [notification, notify, errorHandler] = useLocalNotification();
+  const [, updateBankState] = useBankState();
 
   async function onCancel() {
     errorHandler(async () => {
       if (!doAbort) return;
-      const resp = await doAbort()
+      const resp = await doAbort();
       if (!resp) return;
       switch (resp.case) {
-        case HttpStatusCode.Conflict: return notify({
-          type: "error",
-          title: i18n.str`The reserve operation has been confirmed previously 
and can't be aborted`,
-          description: resp.detail.hint as TranslatedString,
-          debug: resp.detail,
-        })
-        case HttpStatusCode.BadRequest: return notify({
-          type: "error",
-          title: i18n.str`The operation id is invalid.`,
-          description: resp.detail.hint as TranslatedString,
-          debug: resp.detail,
-        });
-        case HttpStatusCode.NotFound: return notify({
-          type: "error",
-          title: i18n.str`The operation was not found.`,
-          description: resp.detail.hint as TranslatedString,
-          debug: resp.detail,
-        });
-        default: assertUnreachable(resp)
+        case HttpStatusCode.Conflict:
+          return notify({
+            type: "error",
+            title: i18n.str`The reserve operation has been confirmed 
previously and can't be aborted`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          });
+        case HttpStatusCode.BadRequest:
+          return notify({
+            type: "error",
+            title: i18n.str`The operation id is invalid.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          });
+        case HttpStatusCode.NotFound:
+          return notify({
+            type: "error",
+            title: i18n.str`The operation was not found.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          });
+        default:
+          assertUnreachable(resp);
       }
-    })
+    });
   }
 
   async function onConfirm() {
     errorHandler(async () => {
       if (!doConfirm) return;
-      const resp = await doConfirm()
+      const resp = await doConfirm();
       if (!resp) {
         if (!settings.showWithdrawalSuccess) {
-          notifyInfo(i18n.str`Wire transfer completed!`)
+          notifyInfo(i18n.str`Wire transfer completed!`);
         }
-        return
+        return;
       }
       switch (resp.case) {
-        case TalerErrorCode.BANK_CONFIRM_ABORT_CONFLICT: return notify({
-          type: "error",
-          title: i18n.str`The withdrawal has been aborted previously and can't 
be confirmed`,
-          description: resp.detail.hint as TranslatedString,
-          debug: resp.detail,
-        })
-        case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: return notify({
-          type: "error",
-          title: i18n.str`The withdrawal operation can't be confirmed before a 
wallet accepted the transaction.`,
-          description: resp.detail.hint as TranslatedString,
-          debug: resp.detail,
-        })
-        case HttpStatusCode.BadRequest: return notify({
-          type: "error",
-          title: i18n.str`The operation id is invalid.`,
-          description: resp.detail.hint as TranslatedString,
-          debug: resp.detail,
-        });
-        case HttpStatusCode.NotFound: return notify({
-          type: "error",
-          title: i18n.str`The operation was not found.`,
-          description: resp.detail.hint as TranslatedString,
-          debug: resp.detail,
-        });
-        case TalerErrorCode.BANK_UNALLOWED_DEBIT: return notify({
-          type: "error",
-          title: i18n.str`Your balance is not enough.`,
-          description: resp.detail.hint as TranslatedString,
-          debug: resp.detail,
-        });
+        case TalerErrorCode.BANK_CONFIRM_ABORT_CONFLICT:
+          return notify({
+            type: "error",
+            title: i18n.str`The withdrawal has been aborted previously and 
can't be confirmed`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          });
+        case TalerErrorCode.BANK_CONFIRM_INCOMPLETE:
+          return notify({
+            type: "error",
+            title: i18n.str`The withdrawal operation can't be confirmed before 
a wallet accepted the transaction.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          });
+        case HttpStatusCode.BadRequest:
+          return notify({
+            type: "error",
+            title: i18n.str`The operation id is invalid.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          });
+        case HttpStatusCode.NotFound:
+          return notify({
+            type: "error",
+            title: i18n.str`The operation was not found.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          });
+        case TalerErrorCode.BANK_UNALLOWED_DEBIT:
+          return notify({
+            type: "error",
+            title: i18n.str`Your balance is not enough.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          });
         case HttpStatusCode.Accepted: {
           updateBankState("currentChallenge", {
             operation: "confirm-withdrawal",
             id: String(resp.body.challenge_id),
             sent: AbsoluteTime.never(),
             request: id,
-          })
-          return onAuthorizationRequired()
+          });
+          return onAuthorizationRequired();
         }
-        default: assertUnreachable(resp)
+        default:
+          assertUnreachable(resp);
       }
-    })
+    });
   }
 
   return (
@@ -144,23 +166,27 @@ export function NeedConfirmationView({ error, onAbort: 
doAbort, onConfirm: doCon
               class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl 
md:col-span-2"
               autoCapitalize="none"
               autoCorrect="off"
-              onSubmit={e => {
-                e.preventDefault()
+              onSubmit={(e) => {
+                e.preventDefault();
               }}
             >
               <div class="flex items-center justify-between gap-x-6 border-t 
border-gray-900/10 px-4 py-4 sm:px-8">
-                <button type="button" class="text-sm font-semibold leading-6 
text-gray-900"
+                <button
+                  type="button"
+                  class="text-sm font-semibold leading-6 text-gray-900"
                   onClick={(e) => {
-                    e.preventDefault()
-                    onCancel()
+                    e.preventDefault();
+                    onCancel();
                   }}
                 >
-                  <i18n.Translate>Cancel</i18n.Translate></button>
-                <button type="submit"
+                  <i18n.Translate>Cancel</i18n.Translate>
+                </button>
+                <button
+                  type="submit"
                   class="disabled:opacity-50 disabled:cursor-default 
cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold 
text-white shadow-sm hover:bg-indigo-500 focus-visible:outline 
focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
                   onClick={(e) => {
-                    e.preventDefault()
-                    onConfirm()
+                    e.preventDefault();
+                    onConfirm();
                   }}
                 >
                   <i18n.Translate>Transfer</i18n.Translate>
@@ -171,61 +197,81 @@ export function NeedConfirmationView({ error, onAbort: 
doAbort, onConfirm: doCon
         </div>
       </div>
     </div>
-
   );
 }
 export function FailedView({ error }: State.Failed) {
   const { i18n } = useTranslationContext();
   switch (error.case) {
-    case HttpStatusCode.Unauthorized: return <Attention type="danger"
-      title={i18n.str`Unauthorized to make the operation, maybe the session 
has expired or the password changed.`}>
-      <div class="mt-2 text-sm text-red-700">
-        {error.detail.hint}
-      </div>
-    </Attention>
-    case HttpStatusCode.Conflict: return <Attention type="danger"
-      title={i18n.str`The operation was rejected due to insufficient funds.`}>
-      <div class="mt-2 text-sm text-red-700">
-        {error.detail.hint}
-      </div>
-    </Attention>
-    case HttpStatusCode.NotFound: return <Attention type="danger"
-      title={i18n.str`The operation was rejected due to insufficient funds.`}>
-      <div class="mt-2 text-sm text-red-700">
-        {error.detail.hint}
-      </div>
-    </Attention>
-    default: assertUnreachable(error)
+    case HttpStatusCode.Unauthorized:
+      return (
+        <Attention
+          type="danger"
+          title={i18n.str`Unauthorized to make the operation, maybe the 
session has expired or the password changed.`}
+        >
+          <div class="mt-2 text-sm text-red-700">{error.detail.hint}</div>
+        </Attention>
+      );
+    case HttpStatusCode.Conflict:
+      return (
+        <Attention
+          type="danger"
+          title={i18n.str`The operation was rejected due to insufficient 
funds.`}
+        >
+          <div class="mt-2 text-sm text-red-700">{error.detail.hint}</div>
+        </Attention>
+      );
+    case HttpStatusCode.NotFound:
+      return (
+        <Attention
+          type="danger"
+          title={i18n.str`The operation was rejected due to insufficient 
funds.`}
+        >
+          <div class="mt-2 text-sm text-red-700">{error.detail.hint}</div>
+        </Attention>
+      );
+    default:
+      assertUnreachable(error);
   }
 }
 
-export function AbortedView({ error, onClose }: State.Aborted) {
-  return (
-    <div>aborted</div>
-  );
+export function AbortedView() {
+  return <div>aborted</div>;
 }
 
-export function ConfirmedView({ error, onClose }: State.Confirmed) {
+export function ConfirmedView({ routeClose }: State.Confirmed) {
   const { i18n } = useTranslationContext();
-  const [settings, updateSettings] = usePreferences()
+  const [settings, updateSettings] = usePreferences();
   return (
     <Fragment>
-
       <div class="relative ml-auto mr-auto transform overflow-hidden 
rounded-lg bg-white p-4 text-left shadow-xl transition-all ">
-
         <div class="mx-auto flex h-12 w-12 items-center justify-center 
rounded-full bg-green-100">
-          <svg class="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" 
stroke-width="1.5" stroke="currentColor" aria-hidden="true">
-            <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 
12.75l6 6 9-13.5" />
+          <svg
+            class="h-6 w-6 text-green-600"
+            fill="none"
+            viewBox="0 0 24 24"
+            stroke-width="1.5"
+            stroke="currentColor"
+            aria-hidden="true"
+          >
+            <path
+              stroke-linecap="round"
+              stroke-linejoin="round"
+              d="M4.5 12.75l6 6 9-13.5"
+            />
           </svg>
         </div>
         <div class="mt-3 text-center sm:mt-5">
-          <h3 class="text-base font-semibold leading-6 text-gray-900" 
id="modal-title">
+          <h3
+            class="text-base font-semibold leading-6 text-gray-900"
+            id="modal-title"
+          >
             <i18n.Translate>Withdrawal confirmed</i18n.Translate>
           </h3>
           <div class="mt-2">
             <p class="text-sm text-gray-500">
               <i18n.Translate>
-                The wire transfer to the Taler operator has been initiated. 
You will soon receive the requested amount in your Taler wallet.
+                The wire transfer to the Taler operator has been initiated. You
+                will soon receive the requested amount in your Taler wallet.
               </i18n.Translate>
             </p>
           </div>
@@ -234,132 +280,165 @@ export function ConfirmedView({ error, onClose }: 
State.Confirmed) {
       <div class="mt-4">
         <div class="flex items-center justify-between">
           <span class="flex flex-grow flex-col">
-            <span class="text-sm text-black font-medium leading-6 " 
id="availability-label">
+            <span
+              class="text-sm text-black font-medium leading-6 "
+              id="availability-label"
+            >
               <i18n.Translate>Do not show this again</i18n.Translate>
             </span>
           </span>
-          <button type="button" data-enabled={!settings.showWithdrawalSuccess} 
class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 
w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent 
transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 
focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" 
aria-labelledby="availability-label" aria-describedby="availability-description"
+          <button
+            type="button"
+            data-enabled={!settings.showWithdrawalSuccess}
+            class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative 
inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 
border-transparent transition-colors duration-200 ease-in-out 
focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
+            role="switch"
+            aria-checked="false"
+            aria-labelledby="availability-label"
+            aria-describedby="availability-description"
             onClick={() => {
-              updateSettings("showWithdrawalSuccess", 
!settings.showWithdrawalSuccess);
-            }}>
-            <span aria-hidden="true" 
data-enabled={!settings.showWithdrawalSuccess} class="translate-x-5 
data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 
transform rounded-full bg-white shadow ring-0 transition duration-200 
ease-in-out"></span>
+              updateSettings(
+                "showWithdrawalSuccess",
+                !settings.showWithdrawalSuccess,
+              );
+            }}
+          >
+            <span
+              aria-hidden="true"
+              data-enabled={!settings.showWithdrawalSuccess}
+              class="translate-x-5 data-[enabled=false]:translate-x-0 
pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow 
ring-0 transition duration-200 ease-in-out"
+            ></span>
           </button>
         </div>
       </div>
       <div class="mt-5 sm:mt-6">
-        <button type="button"
+        <a
+          href={routeClose.url({})}
+          type="button"
           class="inline-flex w-full justify-center rounded-md bg-indigo-600 
px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
-          onClick={async (e) => {
-            e.preventDefault();
-            onClose()
-          }}>
+        >
           <i18n.Translate>Close</i18n.Translate>
-        </button>
+        </a>
       </div>
     </Fragment>
-
   );
 }
 
-export function ReadyView({ uri, onClose: doClose }: State.Ready): VNode<{}> {
+export function ReadyView({
+  uri,
+  onAbort: doAbort,
+}: State.Ready): VNode<Record<string, never>> {
   const { i18n } = useTranslationContext();
-  const [notification, notify, errorHandler] = useLocalNotification()
+  const [notification, notify, errorHandler] = useLocalNotification();
 
   const talerWithdrawUri = stringifyWithdrawUri(uri);
   useEffect(() => {
-    //Taler Wallet WebExtension is listening to headers response and tab 
updates.
-    //In the SPA there is no header response with the Taler URI so
-    //this hack manually triggers the tab update after the QR is in the DOM.
+    // Taler Wallet WebExtension is listening to headers response and tab 
updates.
+    // In the SPA there is no header response with the Taler URI so
+    // this hack manually triggers the tab update after the QR is in the DOM.
     // WebExtension will be using
     // 
https://developer.chrome.com/docs/extensions/reference/tabs/#event-onUpdated
     document.title = `${document.title} ${uri.withdrawalOperationId}`;
-    const meta = document.createElement("meta")
-    meta.setAttribute("name", "taler-uri")
-    meta.setAttribute("content", talerWithdrawUri)
-    document.head.insertBefore(meta, document.head.children.length ? 
document.head.children[0] : null)
+    const meta = document.createElement("meta");
+    meta.setAttribute("name", "taler-uri");
+    meta.setAttribute("content", talerWithdrawUri);
+    document.head.insertBefore(
+      meta,
+      document.head.children.length ? document.head.children[0] : null,
+    );
   }, []);
 
-  async function onClose() {
+  async function onAbort() {
     errorHandler(async () => {
-      const hasError = await doClose()
+      const hasError = await doAbort();
       if (!hasError) return;
       switch (hasError.case) {
-        case HttpStatusCode.Conflict: return notify({
-          type: "error",
-          title: i18n.str`The reserve operation has been confirmed previously 
and can't be aborted`,
-          description: hasError.detail.hint as TranslatedString,
-          debug: hasError.detail,
-        })
-        case HttpStatusCode.BadRequest: return notify({
-          type: "error",
-          title: i18n.str`The operation id is invalid.`,
-          description: hasError.detail.hint as TranslatedString,
-          debug: hasError.detail,
-        });
-        case HttpStatusCode.NotFound: return notify({
-          type: "error",
-          title: i18n.str`The operation was not found.`,
-          description: hasError.detail.hint as TranslatedString,
-          debug: hasError.detail,
-        });
-        default: assertUnreachable(hasError)
+        case HttpStatusCode.Conflict:
+          return notify({
+            type: "error",
+            title: i18n.str`The reserve operation has been confirmed 
previously and can't be aborted`,
+            description: hasError.detail.hint as TranslatedString,
+            debug: hasError.detail,
+          });
+        case HttpStatusCode.BadRequest:
+          return notify({
+            type: "error",
+            title: i18n.str`The operation id is invalid.`,
+            description: hasError.detail.hint as TranslatedString,
+            debug: hasError.detail,
+          });
+        case HttpStatusCode.NotFound:
+          return notify({
+            type: "error",
+            title: i18n.str`The operation was not found.`,
+            description: hasError.detail.hint as TranslatedString,
+            debug: hasError.detail,
+          });
+        default:
+          assertUnreachable(hasError);
       }
-    })
+    });
   }
 
-  return <Fragment>
-    <LocalNotificationBanner notification={notification} />
+  return (
+    <Fragment>
+      <LocalNotificationBanner notification={notification} />
 
-    <div class="flex justify-end mt-4">
-      <button type="button"
-        class="inline-flex items-center rounded-md bg-red-600 px-3 py-2 
text-sm font-semibold text-white shadow-sm hover:bg-red-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-red-500"
-        onClick={() => {
-          onClose()
-        }}
-      >
-        Cancel
-      </button>
-    </div>
+      <div class="flex justify-end mt-4">
+        <button
+          type="button"
+          class="inline-flex items-center rounded-md bg-red-600 px-3 py-2 
text-sm font-semibold text-white shadow-sm hover:bg-red-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-red-500"
+          onClick={onAbort}
+        >
+          <i18n.Translate>Cancel</i18n.Translate>
+        </button>
+      </div>
 
-    <div class="bg-white shadow sm:rounded-lg mt-4">
-      <div class="p-4">
-        <h3 class="text-base font-semibold leading-6 text-gray-900">
-          <i18n.Translate>On this device</i18n.Translate>
-        </h3>
-        <div class="mt-2 sm:flex sm:items-start sm:justify-between">
-          <div class="max-w-xl text-sm text-gray-500">
-            <p>
-              <i18n.Translate>If you are using a web browser on desktop you 
should access your wallet with the GNU Taler WebExtension now or click the link 
if your WebExtension have the "Inject Taler support" option 
enabled.</i18n.Translate>
-            </p>
-          </div>
-          <div class="mt-5 sm:ml-6 sm:mt-0 sm:flex sm:flex-shrink-0 
sm:items-center">
-            <a href={talerWithdrawUri}
-              class="inline-flex items-center  disabled:opacity-50 
disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 
text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
-            >
-              <i18n.Translate>Start</i18n.Translate>
-            </a>
+      <div class="bg-white shadow sm:rounded-lg mt-4">
+        <div class="p-4">
+          <h3 class="text-base font-semibold leading-6 text-gray-900">
+            <i18n.Translate>On this device</i18n.Translate>
+          </h3>
+          <div class="mt-2 sm:flex sm:items-start sm:justify-between">
+            <div class="max-w-xl text-sm text-gray-500">
+              <p>
+                <i18n.Translate>
+                  If you are using a web browser on desktop you should access
+                  your wallet with the GNU Taler WebExtension now or click the
+                  link if your WebExtension have the "Inject Taler support"
+                  option enabled.
+                </i18n.Translate>
+              </p>
+            </div>
+            <div class="mt-5 sm:ml-6 sm:mt-0 sm:flex sm:flex-shrink-0 
sm:items-center">
+              <a
+                href={talerWithdrawUri}
+                class="inline-flex items-center  disabled:opacity-50 
disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 
text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
+              >
+                <i18n.Translate>Start</i18n.Translate>
+              </a>
+            </div>
           </div>
         </div>
       </div>
-    </div>
-    <div class="bg-white shadow sm:rounded-lg mt-2">
-      <div class="p-4">
-        <h3 class="text-base font-semibold leading-6 text-gray-900">
-          <i18n.Translate>On a mobile phone</i18n.Translate>
-        </h3>
-        <div class="mt-2 sm:flex sm:items-start sm:justify-between">
-          <div class="max-w-xl text-sm text-gray-500">
-            <p>
-              <i18n.Translate>Scan the QR code with your mobile 
device.</i18n.Translate>
-            </p>
+      <div class="bg-white shadow sm:rounded-lg mt-2">
+        <div class="p-4">
+          <h3 class="text-base font-semibold leading-6 text-gray-900">
+            <i18n.Translate>On a mobile phone</i18n.Translate>
+          </h3>
+          <div class="mt-2 sm:flex sm:items-start sm:justify-between">
+            <div class="max-w-xl text-sm text-gray-500">
+              <p>
+                <i18n.Translate>
+                  Scan the QR code with your mobile device.
+                </i18n.Translate>
+              </p>
+            </div>
+          </div>
+          <div class="mt-2 max-w-md ml-auto mr-auto">
+            <QR text={talerWithdrawUri} />
           </div>
-        </div>
-        <div class="mt-2 max-w-md ml-auto mr-auto">
-          <QR text={talerWithdrawUri} />
         </div>
       </div>
-    </div>
-
-  </Fragment>
-
+    </Fragment>
+  );
 }
diff --git a/packages/demobank-ui/src/pages/PaymentOptions.stories.tsx 
b/packages/demobank-ui/src/pages/PaymentOptions.stories.tsx
index 23046dd2e..78af886a8 100644
--- a/packages/demobank-ui/src/pages/PaymentOptions.stories.tsx
+++ b/packages/demobank-ui/src/pages/PaymentOptions.stories.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx 
b/packages/demobank-ui/src/pages/PaymentOptions.tsx
index 2a7374cab..53086d4cc 100644
--- a/packages/demobank-ui/src/pages/PaymentOptions.tsx
+++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -15,30 +15,41 @@
  */
 
 import { AmountJson } from "@gnu-taler/taler-util";
-import { notifyInfo, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { VNode, h } from "preact";
-import { useState } from "preact/hooks";
 import { useBankState } from "../hooks/bank-state.js";
 import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js";
 import { WalletWithdrawForm } from "./WalletWithdrawForm.js";
+import { RouteDefinition } from "../route.js";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
 
 /**
  * Let the user choose a payment option,
  * then specify the details trigger the action.
  */
-export function PaymentOptions({ limit, goToConfirmOperation, 
onAuthorizationRequired }: {
-  limit: AmountJson,
-  onAuthorizationRequired: () => void,
-  goToConfirmOperation: (id: string) => void,
+export function PaymentOptions({
+  routeClose,
+  routeChargeWallet,
+  routeWireTransfer,
+  tab,
+  limit,
+  onOperationCreated,
+  onClose,
+  onAuthorizationRequired,
+}: {
+  limit: AmountJson;
+  tab: "charge-wallet" | "wire-transfer" | undefined;
+  onAuthorizationRequired: () => void;
+  onOperationCreated: (wopid: string) => void;
+  onClose: () => void;
+  routeClose: RouteDefinition<Record<string, never>>;
+  routeChargeWallet: RouteDefinition<Record<string, never>>;
+  routeWireTransfer: RouteDefinition<Record<string, never>>;
 }): VNode {
   const { i18n } = useTranslationContext();
   const [bankState] = useBankState();
 
-  const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | 
undefined>();
-
   return (
     <div class="mt-4">
-
       <fieldset>
         <legend class="px-4 text-base font-semibold leading-6 text-gray-900">
           <i18n.Translate>Send money</i18n.Translate>
@@ -46,65 +57,112 @@ export function PaymentOptions({ limit, 
goToConfirmOperation, onAuthorizationReq
 
         <div class="px-4 mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-2 
sm:gap-x-4">
           {/* <!-- Active: "border-indigo-600 ring-2 ring-indigo-600", Not 
Active: "border-gray-300" --> */}
-          <label class={"relative flex cursor-pointer rounded-lg border 
bg-white p-4 shadow-sm focus:outline-none" + (tab === "charge-wallet" ? 
"border-indigo-600 ring-2 ring-indigo-600" : "border-gray-300")}>
-            <input type="radio" name="project-type" value="Newsletter" 
class="sr-only" aria-labelledby="project-type-0-label" 
aria-describedby="project-type-0-description-0 project-type-0-description-1" 
onClick={() => {
-              setTab("charge-wallet")
-            }}
-            />
-            <div class="flex flex-col">
-              <span class="flex">
-                <div class="text-4xl mr-4 my-auto">&#x1F4B5;</div>
-                <span class="grow self-center text-lg text-gray-900 
align-middle text-center">
-                  <i18n.Translate>to a <b>Taler</b> wallet</i18n.Translate>
-                </span>
-                <svg class="self-center flex-none h-5 w-5 text-indigo-600" 
style={{ visibility: tab === "charge-wallet" ? "visible" : "hidden" }} 
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
-                  <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 
16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 
1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
-                </svg>
-              </span>
-              <div class="mt-1 flex items-center text-sm text-gray-500">
-                <i18n.Translate>Withdraw digital money into your mobile wallet 
or browser extension</i18n.Translate>
-              </div>
-              {!!bankState.currentWithdrawalOperationId &&
-                <span class="flex items-center gap-x-1.5 w-fit rounded-md 
bg-green-100 px-2 py-1 text-xs font-medium text-green-700 whitespace-pre">
-                  <svg class="h-1.5 w-1.5 fill-green-500" viewBox="0 0 6 6" 
aria-hidden="true">
-                    <circle cx="3" cy="3" r="3" />
+          <a href={routeChargeWallet.url({})}>
+            <label
+              class={
+                "relative flex cursor-pointer rounded-lg border bg-white p-4 
shadow-sm focus:outline-none" +
+                (tab === "charge-wallet"
+                  ? "border-indigo-600 ring-2 ring-indigo-600"
+                  : "border-gray-300")
+              }
+            >
+              <div class="flex flex-col">
+                <span class="flex">
+                  <div class="text-4xl mr-4 my-auto">&#x1F4B5;</div>
+                  <span class="grow self-center text-lg text-gray-900 
align-middle text-center">
+                    <i18n.Translate>
+                      to a <b>Taler</b> wallet
+                    </i18n.Translate>
+                  </span>
+                  <svg
+                    class="self-center flex-none h-5 w-5 text-indigo-600"
+                    style={{
+                      visibility:
+                        tab === "charge-wallet" ? "visible" : "hidden",
+                    }}
+                    viewBox="0 0 20 20"
+                    fill="currentColor"
+                    aria-hidden="true"
+                  >
+                    <path
+                      fill-rule="evenodd"
+                      d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 
0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 
001.137-.089l4-5.5z"
+                      clip-rule="evenodd"
+                    />
                   </svg>
-                  <i18n.Translate>operation ready</i18n.Translate>
                 </span>
-              }
-            </div>
-          </label>
-
+                <div class="mt-1 flex items-center text-sm text-gray-500">
+                  <i18n.Translate>
+                    Withdraw digital money into your mobile wallet or browser
+                    extension
+                  </i18n.Translate>
+                </div>
+                {!!bankState.currentWithdrawalOperationId && (
+                  <span class="flex items-center gap-x-1.5 w-fit rounded-md 
bg-green-100 px-2 py-1 text-xs font-medium text-green-700 whitespace-pre">
+                    <svg
+                      class="h-1.5 w-1.5 fill-green-500"
+                      viewBox="0 0 6 6"
+                      aria-hidden="true"
+                    >
+                      <circle cx="3" cy="3" r="3" />
+                    </svg>
+                    <i18n.Translate>operation ready</i18n.Translate>
+                  </span>
+                )}
+              </div>
+            </label>
+          </a>
 
-          <label class={"relative flex cursor-pointer rounded-lg border 
bg-white p-4 shadow-sm focus:outline-none" + (tab === "wire-transfer" ? 
"border-indigo-600 ring-2 ring-indigo-600" : "border-gray-300")}>
-            <input type="radio" name="project-type" value="Existing Customers" 
class="sr-only" aria-labelledby="project-type-1-label" 
aria-describedby="project-type-1-description-0 project-type-1-description-1" 
onClick={() => {
-              setTab("wire-transfer")
-            }} />
-            <div class="flex flex-col">
-              <span class="flex">
-                <div class="text-4xl mr-4 my-auto">&#x2194;</div>
-                <span class="grow self-center text-lg font-medium 
text-gray-900 align-middle text-center">
-                  <i18n.Translate>to another bank account</i18n.Translate>
+          <a href={routeWireTransfer.url({})}>
+            <label
+              class={
+                "relative flex cursor-pointer rounded-lg border bg-white p-4 
shadow-sm focus:outline-none" +
+                (tab === "wire-transfer"
+                  ? "border-indigo-600 ring-2 ring-indigo-600"
+                  : "border-gray-300")
+              }
+            >
+              <div class="flex flex-col">
+                <span class="flex">
+                  <div class="text-4xl mr-4 my-auto">&#x2194;</div>
+                  <span class="grow self-center text-lg font-medium 
text-gray-900 align-middle text-center">
+                    <i18n.Translate>to another bank account</i18n.Translate>
+                  </span>
+                  <svg
+                    class="self-center flex-none h-5 w-5 text-indigo-600"
+                    style={{
+                      visibility:
+                        tab === "wire-transfer" ? "visible" : "hidden",
+                    }}
+                    viewBox="0 0 20 20"
+                    fill="currentColor"
+                    aria-hidden="true"
+                  >
+                    <path
+                      fill-rule="evenodd"
+                      d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 
0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 
001.137-.089l4-5.5z"
+                      clip-rule="evenodd"
+                    />
+                  </svg>
                 </span>
-                <svg class="self-center flex-none h-5 w-5 text-indigo-600" 
style={{ visibility: tab === "wire-transfer" ? "visible" : "hidden" }} 
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
-                  <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 
16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 
1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
-                </svg>
-              </span>
-              <div class="mt-1 flex items-center text-sm text-gray-500">
-                <i18n.Translate>Make a wire transfer to an account with known 
bank account number.</i18n.Translate>
+                <div class="mt-1 flex items-center text-sm text-gray-500">
+                  <i18n.Translate>
+                    Make a wire transfer to an account with known bank account
+                    number.
+                  </i18n.Translate>
+                </div>
               </div>
-            </div>
-          </label>
+            </label>
+          </a>
         </div>
         {tab === "charge-wallet" && (
           <WalletWithdrawForm
             focus
             limit={limit}
             onAuthorizationRequired={onAuthorizationRequired}
-            goToConfirmOperation={goToConfirmOperation}
-            onCancel={() => {
-              setTab(undefined)
-            }}
+            onOperationCreated={onOperationCreated}
+            onOperationAborted={onClose}
+            routeCancel={routeClose}
           />
         )}
         {tab === "wire-transfer" && (
@@ -113,17 +171,11 @@ export function PaymentOptions({ limit, 
goToConfirmOperation, onAuthorizationReq
             title={i18n.str`Transfer details`}
             limit={limit}
             onAuthorizationRequired={onAuthorizationRequired}
-            onSuccess={() => {
-              notifyInfo(i18n.str`Wire transfer created!`);
-              setTab(undefined)
-            }}
-            onCancel={() => {
-              setTab(undefined)
-            }}
+            onSuccess={onClose}
+            routeCancel={routeClose}
           />
         )}
-
       </fieldset>
     </div>
-  )
+  );
 }
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.stories.tsx 
b/packages/demobank-ui/src/pages/PaytoWireTransferForm.stories.tsx
index 81f2d82f5..61cfb5629 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.stories.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.stories.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx 
b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index d60b06dc3..2259929e7 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -17,68 +17,62 @@
 import {
   AbsoluteTime,
   AmountJson,
-  AmountLike,
   AmountString,
   Amounts,
   CurrencySpecification,
   FRAC_SEPARATOR,
   HttpStatusCode,
-  Logger,
   PaytoString,
   TalerErrorCode,
   TranslatedString,
+  assertUnreachable,
   buildPayto,
   parsePaytoUri,
-  stringifyPaytoUri
+  stringifyPaytoUri,
 } from "@gnu-taler/taler-util";
 import {
+  LocalNotificationBanner,
+  ShowInputErrorLabel,
+  notifyInfo,
   useLocalNotification,
-  useTranslationContext
+  useTranslationContext,
 } from "@gnu-taler/web-util/browser";
-import { Fragment, Ref, VNode, h } from "preact";
+import { Ref, VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { mutate } from "swr";
-import { ShowInputErrorLabel } from "@gnu-taler/web-util/browser";
 import { useBankCoreApiContext } from "../context/config.js";
 import { useBackendState } from "../hooks/backend.js";
-import {
-  undefinedIfEmpty,
-  validateIBAN,
-  withRuntimeErrorHandling
-} from "../utils.js";
-import { assertUnreachable } from "./WithdrawalOperationPage.js";
-import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
 import { useBankState } from "../hooks/bank-state.js";
-
-const logger = new Logger("PaytoWireTransferForm");
+import { RouteDefinition } from "../route.js";
+import { undefinedIfEmpty, validateIBAN } from "../utils.js";
 
 export function PaytoWireTransferForm({
   focus,
   title,
   toAccount,
   onSuccess,
-  onCancel,
+  routeCancel,
   onAuthorizationRequired,
   limit,
 }: {
-  title: TranslatedString,
+  title: TranslatedString;
   focus?: boolean;
-  toAccount?: string,
+  toAccount?: string;
   onSuccess: () => void;
   onAuthorizationRequired: () => void;
-  onCancel: (() => void) | undefined;
+  routeCancel?: RouteDefinition<Record<string, never>>;
   limit: AmountJson;
 }): VNode {
   const [isRawPayto, setIsRawPayto] = useState(false);
-  const { state: credentials } = useBackendState()
+  const { state: credentials } = useBackendState();
   const { api } = useBankCoreApiContext();
 
-  const sendingToFixedAccount = toAccount !== undefined
-  //FIXME: support other destination that just IBAN
+  const sendingToFixedAccount = toAccount !== undefined;
+  // FIXME: support other destination that just IBAN
   const [iban, setIban] = useState<string | undefined>(toAccount);
   const [subject, setSubject] = useState<string | undefined>();
   const [amount, setAmount] = useState<string | undefined>();
-  const [, updateBankState] = useBankState()
+  const [, updateBankState] = useBankState();
 
   const [rawPaytoInput, rawPaytoInputSetter] = useState<string | undefined>(
     undefined,
@@ -89,7 +83,7 @@ export function PaytoWireTransferForm({
   const trimmedAmountStr = amount?.trim();
   const parsedAmount = Amounts.parse(`${limit.currency}:${trimmedAmountStr}`);
   const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/;
-  const [notification, notify, handleError] = useLocalNotification()
+  const [notification, notify, handleError] = useLocalNotification();
 
   const errorsWire = undefinedIfEmpty({
     iban: !iban
@@ -135,18 +129,18 @@ export function PaytoWireTransferForm({
 
     if (credentials.status !== "loggedIn") return;
     if (rawPaytoInput) {
-      const p = parsePaytoUri(rawPaytoInput)
+      const p = parsePaytoUri(rawPaytoInput);
       if (!p) return;
-      sendingAmount = p.params.amount as AmountString
-      delete p.params.amount
-      //if this payto is valid then it already have message
-      payto_uri = stringifyPaytoUri(p)
+      sendingAmount = p.params.amount as AmountString;
+      delete p.params.amount;
+      // if this payto is valid then it already have message
+      payto_uri = stringifyPaytoUri(p);
     } else {
       if (!iban || !subject) return;
       const ibanPayto = buildPayto("iban", iban, undefined);
       ibanPayto.params.message = encodeURIComponent(subject);
       payto_uri = stringifyPaytoUri(ibanPayto);
-      sendingAmount = `${limit.currency}:${trimmedAmountStr}` as AmountString
+      sendingAmount = `${limit.currency}:${trimmedAmountStr}` as AmountString;
     }
     const puri = payto_uri;
     const sAmount = sendingAmount;
@@ -155,288 +149,348 @@ export function PaytoWireTransferForm({
       const request = {
         payto_uri: puri,
         amount: sAmount,
-      }
+      };
       const resp = await api.createTransaction(credentials, request);
-      mutate(() => true)
+      mutate(() => true);
       if (resp.type === "fail") {
         switch (resp.case) {
-          case HttpStatusCode.BadRequest: return notify({
-            type: "error",
-            title: i18n.str`The request was invalid or the payto://-URI used 
unacceptable features.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case HttpStatusCode.Unauthorized: return notify({
-            type: "error",
-            title: i18n.str`Not enough permission to complete the operation.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case TalerErrorCode.BANK_UNKNOWN_CREDITOR: return notify({
-            type: "error",
-            title: i18n.str`The destination account "${puri}" was not found.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case TalerErrorCode.BANK_SAME_ACCOUNT: return notify({
-            type: "error",
-            title: i18n.str`The origin and the destination of the transfer 
can't be the same.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return notify({
-            type: "error",
-            title: i18n.str`Your balance is not enough.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case HttpStatusCode.NotFound: return notify({
-            type: "error",
-            title: i18n.str`The origin account "${puri}" was not found.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
+          case HttpStatusCode.BadRequest:
+            return notify({
+              type: "error",
+              title: i18n.str`The request was invalid or the payto://-URI used 
unacceptable features.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case HttpStatusCode.Unauthorized:
+            return notify({
+              type: "error",
+              title: i18n.str`Not enough permission to complete the 
operation.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_UNKNOWN_CREDITOR:
+            return notify({
+              type: "error",
+              title: i18n.str`The destination account "${puri}" was not 
found.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_SAME_ACCOUNT:
+            return notify({
+              type: "error",
+              title: i18n.str`The origin and the destination of the transfer 
can't be the same.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_UNALLOWED_DEBIT:
+            return notify({
+              type: "error",
+              title: i18n.str`Your balance is not enough.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case HttpStatusCode.NotFound:
+            return notify({
+              type: "error",
+              title: i18n.str`The origin account "${puri}" was not found.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
           case HttpStatusCode.Accepted: {
             updateBankState("currentChallenge", {
               operation: "create-transaction",
               id: String(resp.body.challenge_id),
               sent: AbsoluteTime.never(),
               request,
-            })
-            return onAuthorizationRequired()
+            });
+            return onAuthorizationRequired();
           }
-          default: assertUnreachable(resp)
+          default:
+            assertUnreachable(resp);
         }
       }
+      notifyInfo(i18n.str`Wire transfer created!`);
       onSuccess();
       setAmount(undefined);
       setIban(undefined);
       setSubject(undefined);
-      rawPaytoInputSetter(undefined)
-    })
+      rawPaytoInputSetter(undefined);
+    });
   }
 
-  return (<div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 
bg-gray-100 my-4 px-4 pb-4 rounded-lg">
-    {/**
-     * FIXME: Scan a qr code
-     */}
-    <div class="">
-      <h2 class="text-base font-semibold leading-7 text-gray-900">
-        {title}
-      </h2>
-      <div>
-        <div class="px-2 mt-2 grid grid-cols-1 gap-y-4 sm:gap-x-4">
-          <label class={"relative flex cursor-pointer rounded-lg border 
bg-white p-4 shadow-sm focus:outline-none" + (!isRawPayto ? "border-indigo-600 
ring-2 ring-indigo-600" : "border-gray-300")}>
-            <input type="radio" name="project-type" value="Newsletter" 
class="sr-only" aria-labelledby="project-type-0-label" 
aria-describedby="project-type-0-description-0 project-type-0-description-1" 
onChange={() => {
-              if (parsed && parsed.isKnown && parsed.targetType === "iban") {
-                setIban(parsed.iban)
-                const amountStr = parsed.params["amount"]
-                if (amountStr) {
-                  const amount = Amounts.parse(parsed.params["amount"])
-                  if (amount) {
-                    setAmount(Amounts.stringifyValue(amount))
-                  }
-                }
-                const subject = parsed.params["message"]
-                if (subject) {
-                  setSubject(subject)
-                }
+  return (
+    <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 
bg-gray-100 my-4 px-4 pb-4 rounded-lg">
+      {/**
+       * FIXME: Scan a qr code
+       */}
+      <div class="">
+        <h2 class="text-base font-semibold leading-7 
text-gray-900">{title}</h2>
+        <div>
+          <div class="px-2 mt-2 grid grid-cols-1 gap-y-4 sm:gap-x-4">
+            <label
+              class={
+                "relative flex cursor-pointer rounded-lg border bg-white p-4 
shadow-sm focus:outline-none" +
+                (!isRawPayto
+                  ? "border-indigo-600 ring-2 ring-indigo-600"
+                  : "border-gray-300")
               }
-              setIsRawPayto(false)
-            }} />
-            <span class="flex flex-1">
-              <span class="flex flex-col">
-                <span class="block text-sm  font-medium text-gray-900">
-                  <i18n.Translate>Using a form</i18n.Translate>
-                </span>
-              </span>
-            </span>
-          </label>
-
-          {sendingToFixedAccount ? undefined :
-            <label class={"relative flex cursor-pointer rounded-lg border 
bg-white p-4 shadow-sm focus:outline-none" + (isRawPayto ? "border-indigo-600 
ring-2 ring-indigo-600" : "border-gray-300")}>
-              <input type="radio" name="project-type" value="Existing 
Customers" class="sr-only" aria-labelledby="project-type-1-label" 
aria-describedby="project-type-1-description-0 project-type-1-description-1" 
onChange={() => {
-                if (iban) {
-                  const payto = buildPayto("iban", iban, undefined)
-                  if (parsedAmount) {
-                    payto.params["amount"] = Amounts.stringify(parsedAmount)
+            >
+              <input
+                type="radio"
+                name="project-type"
+                value="Newsletter"
+                class="sr-only"
+                aria-labelledby="project-type-0-label"
+                aria-describedby="project-type-0-description-0 
project-type-0-description-1"
+                onChange={() => {
+                  if (
+                    parsed &&
+                    parsed.isKnown &&
+                    parsed.targetType === "iban"
+                  ) {
+                    setIban(parsed.iban);
+                    const amountStr = parsed.params["amount"];
+                    if (amountStr) {
+                      const amount = Amounts.parse(parsed.params["amount"]);
+                      if (amount) {
+                        setAmount(Amounts.stringifyValue(amount));
+                      }
+                    }
+                    const subject = parsed.params["message"];
+                    if (subject) {
+                      setSubject(subject);
+                    }
                   }
-                  if (subject) {
-                    payto.params["message"] = subject
-                  }
-                  rawPaytoInputSetter(stringifyPaytoUri(payto))
-                }
-                setIsRawPayto(true)
-              }} />
+                  setIsRawPayto(false);
+                }}
+              />
               <span class="flex flex-1">
                 <span class="flex flex-col">
-                  <span class="block text-sm font-medium text-gray-900">
-                    <i18n.Translate>Import payto:// URI</i18n.Translate>
+                  <span class="block text-sm  font-medium text-gray-900">
+                    <i18n.Translate>Using a form</i18n.Translate>
                   </span>
                 </span>
               </span>
             </label>
-          }
-        </div>
-      </div>
-    </div>
 
-    <form
-      class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-md 
sm:rounded-xl md:col-span-2 w-fit mx-auto"
-      autoCapitalize="none"
-      autoCorrect="off"
-      onSubmit={e => {
-        e.preventDefault()
-      }}
-    >
-      <div class="p-4 sm:p-8">
-        {!isRawPayto ?
-          <div class="grid max-w-xs grid-cols-1 gap-x-6 gap-y-8 
sm:grid-cols-6">
-            <div class="sm:col-span-5">
-              <label for="iban" class="block text-sm font-medium leading-6 
text-gray-900">{i18n.str`Recipient`}</label>
-              <div class="mt-2">
+            {sendingToFixedAccount ? undefined : (
+              <label
+                class={
+                  "relative flex cursor-pointer rounded-lg border bg-white p-4 
shadow-sm focus:outline-none" +
+                  (isRawPayto
+                    ? "border-indigo-600 ring-2 ring-indigo-600"
+                    : "border-gray-300")
+                }
+              >
                 <input
-                  ref={focus ? doAutoFocus : undefined}
-                  type="text"
-                  class="block w-full disabled:bg-gray-200 rounded-md border-0 
py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 
sm:text-sm sm:leading-6"
-                  name="iban"
-                  id="iban"
-                  disabled={sendingToFixedAccount}
-                  value={iban ?? ""}
-                  placeholder="CC0123456789"
-                  autocomplete="off"
-                  required
-                  pattern={ibanRegex}
-                  onInput={(e): void => {
-                    setIban(e.currentTarget.value.toUpperCase());
+                  type="radio"
+                  name="project-type"
+                  value="Existing Customers"
+                  class="sr-only"
+                  aria-labelledby="project-type-1-label"
+                  aria-describedby="project-type-1-description-0 
project-type-1-description-1"
+                  onChange={() => {
+                    if (iban) {
+                      const payto = buildPayto("iban", iban, undefined);
+                      if (parsedAmount) {
+                        payto.params["amount"] =
+                          Amounts.stringify(parsedAmount);
+                      }
+                      if (subject) {
+                        payto.params["message"] = subject;
+                      }
+                      rawPaytoInputSetter(stringifyPaytoUri(payto));
+                    }
+                    setIsRawPayto(true);
                   }}
                 />
-                <ShowInputErrorLabel
-                  message={errorsWire?.iban}
-                  isDirty={iban !== undefined}
-                />
-              </div>
-              <p class="mt-2 text-sm text-gray-500" >
-                <i18n.Translate>IBAN of the recipient's 
account</i18n.Translate>
-              </p>
-            </div>
+                <span class="flex flex-1">
+                  <span class="flex flex-col">
+                    <span class="block text-sm font-medium text-gray-900">
+                      <i18n.Translate>Import payto:// URI</i18n.Translate>
+                    </span>
+                  </span>
+                </span>
+              </label>
+            )}
+          </div>
+        </div>
+      </div>
 
-            <div class="sm:col-span-5">
-              <label for="subject" class="block text-sm font-medium leading-6 
text-gray-900">{i18n.str`Transfer subject`}</label>
-              <div class="mt-2">
-                <input
-                  type="text"
-                  class="block w-full rounded-md border-0 py-1.5 text-gray-900 
shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 
focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
-                  name="subject"
-                  id="subject"
-                  autocomplete="off"
-                  placeholder={i18n.str`subject`}
-                  value={subject ?? ""}
-                  required
-                  onInput={(e): void => {
-                    setSubject(e.currentTarget.value);
-                  }}
-                />
-                <ShowInputErrorLabel
-                  message={errorsWire?.subject}
-                  isDirty={subject !== undefined}
-                />
+      <form
+        class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-md 
sm:rounded-xl md:col-span-2 w-fit mx-auto"
+        autoCapitalize="none"
+        autoCorrect="off"
+        onSubmit={(e) => {
+          e.preventDefault();
+        }}
+      >
+        <div class="p-4 sm:p-8">
+          {!isRawPayto ? (
+            <div class="grid max-w-xs grid-cols-1 gap-x-6 gap-y-8 
sm:grid-cols-6">
+              <div class="sm:col-span-5">
+                <label
+                  for="iban"
+                  class="block text-sm font-medium leading-6 text-gray-900"
+                >{i18n.str`Recipient`}</label>
+                <div class="mt-2">
+                  <input
+                    ref={focus ? doAutoFocus : undefined}
+                    type="text"
+                    class="block w-full disabled:bg-gray-200 rounded-md 
border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 
sm:text-sm sm:leading-6"
+                    name="iban"
+                    id="iban"
+                    disabled={sendingToFixedAccount}
+                    value={iban ?? ""}
+                    placeholder="CC0123456789"
+                    autocomplete="off"
+                    required
+                    pattern={ibanRegex}
+                    onInput={(e): void => {
+                      setIban(e.currentTarget.value.toUpperCase());
+                    }}
+                  />
+                  <ShowInputErrorLabel
+                    message={errorsWire?.iban}
+                    isDirty={iban !== undefined}
+                  />
+                </div>
+                <p class="mt-2 text-sm text-gray-500">
+                  <i18n.Translate>
+                    IBAN of the recipient's account
+                  </i18n.Translate>
+                </p>
               </div>
-              <p class="mt-2 text-sm text-gray-500" >
-                <i18n.Translate>some text to identify the 
transfer</i18n.Translate>
-              </p>
-            </div>
 
-            <div class="sm:col-span-5">
-              <label for="amount" class="block text-sm font-medium leading-6 
text-gray-900">{i18n.str`Amount`}</label>
-              <InputAmount
-                name="amount"
-                left
-                currency={limit.currency}
-                value={trimmedAmountStr}
-                onChange={(d) => {
-                  setAmount(d)
-                }}
-              />
-              <ShowInputErrorLabel
-                message={errorsWire?.amount}
-                isDirty={trimmedAmountStr !== undefined}
-              />
-              <p class="mt-2 text-sm text-gray-500" >
-                <i18n.Translate>amount to transfer</i18n.Translate>
-              </p>
-            </div>
+              <div class="sm:col-span-5">
+                <label
+                  for="subject"
+                  class="block text-sm font-medium leading-6 text-gray-900"
+                >{i18n.str`Transfer subject`}</label>
+                <div class="mt-2">
+                  <input
+                    type="text"
+                    class="block w-full rounded-md border-0 py-1.5 
text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 
sm:text-sm sm:leading-6"
+                    name="subject"
+                    id="subject"
+                    autocomplete="off"
+                    placeholder={i18n.str`subject`}
+                    value={subject ?? ""}
+                    required
+                    onInput={(e): void => {
+                      setSubject(e.currentTarget.value);
+                    }}
+                  />
+                  <ShowInputErrorLabel
+                    message={errorsWire?.subject}
+                    isDirty={subject !== undefined}
+                  />
+                </div>
+                <p class="mt-2 text-sm text-gray-500">
+                  <i18n.Translate>
+                    some text to identify the transfer
+                  </i18n.Translate>
+                </p>
+              </div>
 
-          </div> :
-          <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 
sm:grid-cols-6 w-full">
-            <div class="sm:col-span-6">
-              <label for="address" class="block text-sm font-medium leading-6 
text-gray-900">{i18n.str`payto URI:`}</label>
-              <div class="mt-2">
-                <textarea
-                  ref={focus ? doAutoFocus : undefined}
-                  name="address"
-                  id="address"
-                  type="textarea"
-                  rows={5}
-                  class="block overflow-hidden w-44 sm:w-96 rounded-md 
border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 
sm:text-sm sm:leading-6"
-                  value={rawPaytoInput ?? ""}
-                  required
-                  title={i18n.str`uniform resource identifier of the target 
account`}
-                  
placeholder={i18n.str`payto://iban/[receiver-iban]?message=[subject]&amount=[${limit.currency}:X.Y]`}
-                  onInput={(e): void => {
-                    rawPaytoInputSetter(e.currentTarget.value);
+              <div class="sm:col-span-5">
+                <label
+                  for="amount"
+                  class="block text-sm font-medium leading-6 text-gray-900"
+                >{i18n.str`Amount`}</label>
+                <InputAmount
+                  name="amount"
+                  left
+                  currency={limit.currency}
+                  value={trimmedAmountStr}
+                  onChange={(d) => {
+                    setAmount(d);
                   }}
                 />
                 <ShowInputErrorLabel
-                  message={errorsPayto?.rawPaytoInput}
-                  isDirty={rawPaytoInput !== undefined}
+                  message={errorsWire?.amount}
+                  isDirty={trimmedAmountStr !== undefined}
                 />
+                <p class="mt-2 text-sm text-gray-500">
+                  <i18n.Translate>amount to transfer</i18n.Translate>
+                </p>
               </div>
             </div>
-          </div>
-        }
-      </div>
-      <div class="flex items-center justify-between gap-x-6 border-t 
border-gray-900/10 px-4 py-4 sm:px-8">
-        {onCancel ?
-          <button type="button" class="text-sm font-semibold leading-6 
text-gray-900"
-            onClick={onCancel}
+          ) : (
+            <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 
sm:grid-cols-6 w-full">
+              <div class="sm:col-span-6">
+                <label
+                  for="address"
+                  class="block text-sm font-medium leading-6 text-gray-900"
+                >{i18n.str`payto URI:`}</label>
+                <div class="mt-2">
+                  <textarea
+                    ref={focus ? doAutoFocus : undefined}
+                    name="address"
+                    id="address"
+                    type="textarea"
+                    rows={5}
+                    class="block overflow-hidden w-44 sm:w-96 rounded-md 
border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 
sm:text-sm sm:leading-6"
+                    value={rawPaytoInput ?? ""}
+                    required
+                    title={i18n.str`uniform resource identifier of the target 
account`}
+                    
placeholder={i18n.str`payto://iban/[receiver-iban]?message=[subject]&amount=[${limit.currency}:X.Y]`}
+                    onInput={(e): void => {
+                      rawPaytoInputSetter(e.currentTarget.value);
+                    }}
+                  />
+                  <ShowInputErrorLabel
+                    message={errorsPayto?.rawPaytoInput}
+                    isDirty={rawPaytoInput !== undefined}
+                  />
+                </div>
+              </div>
+            </div>
+          )}
+        </div>
+        <div class="flex items-center justify-between gap-x-6 border-t 
border-gray-900/10 px-4 py-4 sm:px-8">
+          {routeCancel ? (
+            <a
+              href={routeCancel.url({})}
+              class="text-sm font-semibold leading-6 text-gray-900"
+            >
+              <i18n.Translate>Cancel</i18n.Translate>
+            </a>
+          ) : (
+            <div />
+          )}
+          <button
+            type="submit"
+            class="disabled:opacity-50 disabled:cursor-default cursor-pointer 
rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
+            disabled={isRawPayto ? !!errorsPayto : !!errorsWire}
+            onClick={(e) => {
+              e.preventDefault();
+              doSend();
+            }}
           >
-            <i18n.Translate>Cancel</i18n.Translate>
+            <i18n.Translate>Send</i18n.Translate>
           </button>
-          : <div />
-        }
-        <button type="submit"
-          class="disabled:opacity-50 disabled:cursor-default cursor-pointer 
rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
-          disabled={isRawPayto ? !!errorsPayto : !!errorsWire}
-          onClick={(e) => {
-            e.preventDefault()
-            doSend()
-          }}
-        >
-          <i18n.Translate>Send</i18n.Translate>
-        </button>
-      </div>
-      <LocalNotificationBanner notification={notification} />
-    </form>
-  </div >
-  )
-
+        </div>
+        <LocalNotificationBanner notification={notification} />
+      </form>
+    </div>
+  );
 }
 
 /**
  * Show the element when the load ended
- * @param element 
+ * @param element
  */
 export function doAutoFocus(element: HTMLElement | null) {
   if (element) {
     setTimeout(() => {
-      element.focus({ preventScroll: true })
+      element.focus({ preventScroll: true });
       element.scrollIntoView({
         behavior: "smooth",
         block: "center",
         inline: "center",
-      })
-    }, 100)
+      });
+    }, 100);
   }
 }
 
@@ -452,26 +506,25 @@ export function InputAmount(
     error?: string;
     currency: string;
     name: string;
-    left?: boolean | undefined,
+    left?: boolean | undefined;
     value: string | undefined;
     onChange?: (s: string) => void;
   },
   ref: Ref<HTMLInputElement>,
 ): VNode {
-  const { config } = useBankCoreApiContext()
+  const { config } = useBankCoreApiContext();
   return (
     <div class="mt-2">
       <div class="flex rounded-md shadow-sm border-0 ring-1 ring-inset 
ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600">
-        <div
-          class="pointer-events-none inset-y-0 flex items-center px-3"
-        >
+        <div class="pointer-events-none inset-y-0 flex items-center px-3">
           <span class="text-gray-500 sm:text-sm">{currency}</span>
         </div>
         <input
           type="number"
           data-left={left}
           class="disabled:bg-gray-200 text-right rounded-md rounded-l-none 
data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900  
placeholder:text-gray-400  sm:text-sm sm:leading-6"
-          placeholder="0.00" aria-describedby="price-currency"
+          placeholder="0.00"
+          aria-describedby="price-currency"
           ref={ref}
           name={name}
           id={name}
@@ -480,10 +533,19 @@ export function InputAmount(
           disabled={!onChange}
           onInput={(e) => {
             if (!onChange) return;
-            const l = e.currentTarget.value.length
-            const sep_pos = e.currentTarget.value.indexOf(FRAC_SEPARATOR)
-            if (sep_pos !== -1 && l - sep_pos - 1 > 
config.currency_specification.num_fractional_input_digits) {
-              e.currentTarget.value = e.currentTarget.value.substring(0, 
sep_pos + config.currency_specification.num_fractional_input_digits + 1)
+            const l = e.currentTarget.value.length;
+            const sep_pos = e.currentTarget.value.indexOf(FRAC_SEPARATOR);
+            if (
+              sep_pos !== -1 &&
+              l - sep_pos - 1 >
+                config.currency_specification.num_fractional_input_digits
+            ) {
+              e.currentTarget.value = e.currentTarget.value.substring(
+                0,
+                sep_pos +
+                  config.currency_specification.num_fractional_input_digits +
+                  1,
+              );
             }
             onChange(e.currentTarget.value);
           }}
@@ -494,13 +556,34 @@ export function InputAmount(
   );
 }
 
-export function RenderAmount({ value, spec, negative, withColor, hideSmall }: 
{ spec: CurrencySpecification; value: AmountJson, hideSmall?: boolean, 
negative?: boolean, withColor?: boolean }): VNode {
-  const neg = !!negative //convert to true or false
+export function RenderAmount({
+  value,
+  spec,
+  negative,
+  withColor,
+  hideSmall,
+}: {
+  spec: CurrencySpecification;
+  value: AmountJson;
+  hideSmall?: boolean;
+  negative?: boolean;
+  withColor?: boolean;
+}): VNode {
+  const neg = !!negative; // convert to true or false
 
-  const { currency, normal, small } = Amounts.stringifyValueWithSpec(value, 
spec)
+  const { currency, normal, small } = Amounts.stringifyValueWithSpec(
+    value,
+    spec,
+  );
 
-  return <span data-negative={withColor ? neg : undefined} 
class="whitespace-nowrap data-[negative=false]:text-green-600 
data-[negative=true]:text-red-600">
-    {negative ? "- " : undefined}
-    {currency} {normal} {!hideSmall && small && <sup 
class="-ml-1">{small}</sup>}
-  </span>
-}
\ No newline at end of file
+  return (
+    <span
+      data-negative={withColor ? neg : undefined}
+      class="whitespace-nowrap data-[negative=false]:text-green-600 
data-[negative=true]:text-red-600"
+    >
+      {negative ? "- " : undefined}
+      {currency} {normal}{" "}
+      {!hideSmall && small && <sup class="-ml-1">{small}</sup>}
+    </span>
+  );
+}
diff --git a/packages/demobank-ui/src/pages/ProfileNavigation.tsx 
b/packages/demobank-ui/src/pages/ProfileNavigation.tsx
index bd9883b1b..a6615d578 100644
--- a/packages/demobank-ui/src/pages/ProfileNavigation.tsx
+++ b/packages/demobank-ui/src/pages/ProfileNavigation.tsx
@@ -1,72 +1,158 @@
+/*
+ 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 { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Fragment, VNode, h } from "preact";
+import { VNode, h } from "preact";
+import { privatePages } from "../Routing.js";
 import { useBankCoreApiContext } from "../context/config.js";
-import { assertUnreachable } from "./WithdrawalOperationPage.js";
 import { useBackendState } from "../hooks/backend.js";
+import { assertUnreachable } from "@gnu-taler/taler-util";
 
-export function ProfileNavigation({ current }: { current: "details" | "delete" 
| "credentials" | "cashouts" }): VNode {
-  const { i18n } = useTranslationContext()
-  const { config } = useBankCoreApiContext()
+export function ProfileNavigation({
+  current,
+}: {
+  current: "details" | "delete" | "credentials" | "cashouts";
+}): VNode {
+  const { i18n } = useTranslationContext();
+  const { config } = useBankCoreApiContext();
   const { state: credentials } = useBackendState();
-  const nonAdminUser = credentials.status !== "loggedIn" ? false : 
!credentials.isUserAdministrator
-  return <div>
-    <div class="sm:hidden">
-      <label for="tabs" class="sr-only"><i18n.Translate>Select a 
section</i18n.Translate></label>
-      <select id="tabs" name="tabs" class="block w-full rounded-md 
border-gray-300 focus:border-indigo-500 focus:ring-indigo-500" onChange={(e) => 
{
-        const op = e.currentTarget.value as typeof current
-        switch (op) {
-          case "details": {
-            window.location.href = "#/my-profile";
-            return;
-          }
-          case "delete": {
-            window.location.href = "#/delete-my-account";
-            return;
-          }
-          case "credentials": {
-            window.location.href = "#/my-password";
-            return;
-          }
-          case "cashouts": {
-            window.location.href = "#/my-cashouts";
-            return;
-          }
-          default: assertUnreachable(op)
-        }
-      }}>
-        <option value="details" selected={current == 
"details"}><i18n.Translate>Details</i18n.Translate></option>
-        {!config.allow_deletions ? undefined :
-          <option value="delete" selected={current == 
"delete"}><i18n.Translate>Delete</i18n.Translate></option>
-        }
-        <option value="credentials" selected={current == 
"credentials"}><i18n.Translate>Credentials</i18n.Translate></option>
-        {config.allow_conversion ?
-          <option value="cashouts" selected={current == 
"cashouts"}><i18n.Translate>Cashouts</i18n.Translate></option>
-          : undefined}
-      </select>
-    </div>
-    <div class="hidden sm:block">
-      <nav class="isolate flex divide-x divide-gray-200 rounded-lg shadow" 
aria-label="Tabs">
-        <a href="#/my-profile" data-selected={current == "details"} 
class="rounded-l-lg text-gray-500 hover:text-gray-700 
data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 
overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium 
hover:bg-gray-50 focus:z-10" >
-          <span><i18n.Translate>Details</i18n.Translate></span>
-          <span aria-hidden="true" data-selected={current == "details"} 
class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 
bottom-0 h-0.5"></span>
-        </a>
-        {!config.allow_deletions ? undefined :
-          <a href="#/delete-my-account" data-selected={current == "delete"} 
aria-current="page" class="             text-gray-500 hover:text-gray-700 
data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 
overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium 
hover:bg-gray-50 focus:z-10">
-            <span><i18n.Translate>Delete</i18n.Translate></span>
-            <span aria-hidden="true" data-selected={current == "delete"} 
class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 
bottom-0 h-0.5"></span>
+  const nonAdminUser =
+    credentials.status !== "loggedIn"
+      ? false
+      : !credentials.isUserAdministrator;
+  return (
+    <div>
+      <div class="sm:hidden">
+        <label for="tabs" class="sr-only">
+          <i18n.Translate>Select a section</i18n.Translate>
+        </label>
+        <select
+          id="tabs"
+          name="tabs"
+          class="block w-full rounded-md border-gray-300 
focus:border-indigo-500 focus:ring-indigo-500"
+          onChange={(e) => {
+            const op = e.currentTarget.value as typeof current;
+            switch (op) {
+              case "details": {
+                window.location.href = privatePages.myAccountDetails.url({});
+                return;
+              }
+              case "delete": {
+                window.location.href = privatePages.myAccountDelete.url({});
+                return;
+              }
+              case "credentials": {
+                window.location.href = privatePages.myAccountPassword.url({});
+                return;
+              }
+              case "cashouts": {
+                window.location.href = privatePages.myAccountCashouts.url({});
+                return;
+              }
+              default:
+                assertUnreachable(op);
+            }
+          }}
+        >
+          <option value="details" selected={current == "details"}>
+            <i18n.Translate>Details</i18n.Translate>
+          </option>
+          {!config.allow_deletions ? undefined : (
+            <option value="delete" selected={current == "delete"}>
+              <i18n.Translate>Delete</i18n.Translate>
+            </option>
+          )}
+          <option value="credentials" selected={current == "credentials"}>
+            <i18n.Translate>Credentials</i18n.Translate>
+          </option>
+          {config.allow_conversion ? (
+            <option value="cashouts" selected={current == "cashouts"}>
+              <i18n.Translate>Cashouts</i18n.Translate>
+            </option>
+          ) : undefined}
+        </select>
+      </div>
+      <div class="hidden sm:block">
+        <nav
+          class="isolate flex divide-x divide-gray-200 rounded-lg shadow"
+          aria-label="Tabs"
+        >
+          <a
+            href={privatePages.myAccountDetails.url({})}
+            data-selected={current == "details"}
+            class="rounded-l-lg text-gray-500 hover:text-gray-700 
data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 
overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium 
hover:bg-gray-50 focus:z-10"
+          >
+            <span>
+              <i18n.Translate>Details</i18n.Translate>
+            </span>
+            <span
+              aria-hidden="true"
+              data-selected={current == "details"}
+              class="bg-transparent data-[selected=true]:bg-indigo-500 
absolute inset-x-0 bottom-0 h-0.5"
+            ></span>
           </a>
-        }
-        <a href="#/my-password" data-selected={current == "credentials"} 
aria-current="page" class="             text-gray-500 hover:text-gray-700 
data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 
overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium 
hover:bg-gray-50 focus:z-10">
-          <span><i18n.Translate>Credentials</i18n.Translate></span>
-          <span aria-hidden="true" data-selected={current == "credentials"} 
class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 
bottom-0 h-0.5"></span>
-        </a>
-        {config.allow_conversion && nonAdminUser ?
-          <a href="#/my-cashouts" data-selected={current == "cashouts"} 
class="rounded-r-lg text-gray-500 hover:text-gray-700 
data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 
overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium 
hover:bg-gray-50 focus:z-10">
-            <span><i18n.Translate>Cashouts</i18n.Translate></span>
-            <span aria-hidden="true" data-selected={current == "cashouts"} 
class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 
bottom-0 h-0.5"></span>
+          {!config.allow_deletions ? undefined : (
+            <a
+              href={privatePages.myAccountDelete.url({})}
+              data-selected={current == "delete"}
+              aria-current="page"
+              class="             text-gray-500 hover:text-gray-700 
data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 
overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium 
hover:bg-gray-50 focus:z-10"
+            >
+              <span>
+                <i18n.Translate>Delete</i18n.Translate>
+              </span>
+              <span
+                aria-hidden="true"
+                data-selected={current == "delete"}
+                class="bg-transparent data-[selected=true]:bg-indigo-500 
absolute inset-x-0 bottom-0 h-0.5"
+              ></span>
+            </a>
+          )}
+          <a
+            href={privatePages.myAccountPassword.url({})}
+            data-selected={current == "credentials"}
+            aria-current="page"
+            class="             text-gray-500 hover:text-gray-700 
data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 
overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium 
hover:bg-gray-50 focus:z-10"
+          >
+            <span>
+              <i18n.Translate>Credentials</i18n.Translate>
+            </span>
+            <span
+              aria-hidden="true"
+              data-selected={current == "credentials"}
+              class="bg-transparent data-[selected=true]:bg-indigo-500 
absolute inset-x-0 bottom-0 h-0.5"
+            ></span>
           </a>
-          : undefined}
-      </nav>
+          {config.allow_conversion && nonAdminUser ? (
+            <a
+              href={privatePages.myAccountCashouts.url({})}
+              data-selected={current == "cashouts"}
+              class="rounded-r-lg text-gray-500 hover:text-gray-700 
data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 
overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium 
hover:bg-gray-50 focus:z-10"
+            >
+              <span>
+                <i18n.Translate>Cashouts</i18n.Translate>
+              </span>
+              <span
+                aria-hidden="true"
+                data-selected={current == "cashouts"}
+                class="bg-transparent data-[selected=true]:bg-indigo-500 
absolute inset-x-0 bottom-0 h-0.5"
+              ></span>
+            </a>
+          ) : undefined}
+        </nav>
+      </div>
     </div>
-  </div>
-}
\ No newline at end of file
+  );
+}
diff --git a/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx 
b/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx
index 08503fb9b..0f951b1a8 100644
--- a/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx
+++ b/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -14,37 +14,35 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { Logger, TalerError } from "@gnu-taler/taler-util";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { TalerError } from "@gnu-taler/taler-util";
+import { Loading, useTranslationContext } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
-import { Loading } from "@gnu-taler/web-util/browser";
 import { Transactions } from "../components/Transactions/index.js";
 import { usePublicAccounts } from "../hooks/access.js";
 
-const logger = new Logger("PublicHistoriesPage");
-
-interface Props { }
-
-/** 
+/**
  * Show histories of public accounts.
  */
-export function PublicHistoriesPage({ }: Props): VNode {
+export function PublicHistoriesPage(): VNode {
   const { i18n } = useTranslationContext();
 
-  //TODO: implemented filter by account name
+  // TODO: implemented filter by account name
   const result = usePublicAccounts(undefined);
-  const firstAccount = result && !(result instanceof TalerError) && 
result.data.public_accounts.length > 0
-    ? result.data.public_accounts[0].username
-    : undefined;
+  const firstAccount =
+    result &&
+    !(result instanceof TalerError) &&
+    result.data.public_accounts.length > 0
+      ? result.data.public_accounts[0].username
+      : undefined;
 
   const [showAccount, setShowAccount] = useState(firstAccount);
 
   if (!result) {
-    return <Loading />
+    return <Loading />;
   }
   if (result instanceof TalerError) {
-    return <Loading />
+    return <Loading />;
   }
 
   const { data } = result;
@@ -54,7 +52,6 @@ export function PublicHistoriesPage({ }: Props): VNode {
 
   // Ask story of all the public accounts.
   for (const account of data.public_accounts) {
-    logger.trace("Asking transactions for", account.username);
     const isSelected = account.username == showAccount;
     accountsBar.push(
       <li
@@ -89,9 +86,6 @@ export function PublicHistoriesPage({ }: Props): VNode {
               <p>No public transactions found.</p>
             )}
             <br />
-            <a href="/account" class="pure-button">
-              Go back
-            </a>
           </div>
         </article>
       </section>
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.stories.tsx 
b/packages/demobank-ui/src/pages/QrCodeSection.stories.tsx
index 95ac4bc33..d53d2e7b4 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.stories.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.stories.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx 
b/packages/demobank-ui/src/pages/QrCodeSection.tsx
index 36c8d2d37..f21134aa1 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -15,22 +15,21 @@
  */
 
 import {
+  assertUnreachable,
   HttpStatusCode,
   stringifyWithdrawUri,
   TranslatedString,
-  WithdrawUriResult
+  WithdrawUriResult,
 } from "@gnu-taler/taler-util";
 import {
+  LocalNotificationBanner,
   useLocalNotification,
-  useTranslationContext
+  useTranslationContext,
 } from "@gnu-taler/web-util/browser";
 import { Fragment, h, VNode } from "preact";
 import { useEffect } from "preact/hooks";
 import { QR } from "../components/QR.js";
 import { useBankCoreApiContext } from "../context/config.js";
-import { withRuntimeErrorHandling } from "../utils.js";
-import { assertUnreachable } from "./WithdrawalOperationPage.js";
-import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
 import { useBackendState } from "../hooks/backend.js";
 
 export function QrCodeSection({
@@ -43,54 +42,63 @@ export function QrCodeSection({
   const { i18n } = useTranslationContext();
   const talerWithdrawUri = stringifyWithdrawUri(withdrawUri);
   const { state: credentials } = useBackendState();
-  const creds = credentials.status !== "loggedIn" ? undefined : credentials
+  const creds = credentials.status !== "loggedIn" ? undefined : credentials;
 
   useEffect(() => {
-    //Taler Wallet WebExtension is listening to headers response and tab 
updates.
-    //In the SPA there is no header response with the Taler URI so
-    //this hack manually triggers the tab update after the QR is in the DOM.
+    // Taler Wallet WebExtension is listening to headers response and tab 
updates.
+    // In the SPA there is no header response with the Taler URI so
+    // this hack manually triggers the tab update after the QR is in the DOM.
     // WebExtension will be using
     // 
https://developer.chrome.com/docs/extensions/reference/tabs/#event-onUpdated
     document.title = `${document.title} ${withdrawUri.withdrawalOperationId}`;
-    const meta = document.createElement("meta")
-    meta.setAttribute("name", "taler-uri")
-    meta.setAttribute("content", talerWithdrawUri)
-    document.head.insertBefore(meta, document.head.children.length ? 
document.head.children[0] : null)
+    const meta = document.createElement("meta");
+    meta.setAttribute("name", "taler-uri");
+    meta.setAttribute("content", talerWithdrawUri);
+    document.head.insertBefore(
+      meta,
+      document.head.children.length ? document.head.children[0] : null,
+    );
   }, []);
-  const [notification, notify, handleError] = useLocalNotification()
+  const [notification, notify, handleError] = useLocalNotification();
 
-  const { api } = useBankCoreApiContext()
+  const { api } = useBankCoreApiContext();
 
   async function doAbort() {
     await handleError(async () => {
       if (!creds) return;
-      const resp = await api.abortWithdrawalById(creds, 
withdrawUri.withdrawalOperationId);
+      const resp = await api.abortWithdrawalById(
+        creds,
+        withdrawUri.withdrawalOperationId,
+      );
       if (resp.type === "ok") {
         onAborted();
       } else {
         switch (resp.case) {
-          case HttpStatusCode.Conflict: return notify({
-            type: "error",
-            title: i18n.str`The reserve operation has been confirmed 
previously and can't be aborted`
-          })
-          case HttpStatusCode.BadRequest: return notify({
-            type: "error",
-            title: i18n.str`The operation id is invalid.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case HttpStatusCode.NotFound: return notify({
-            type: "error",
-            title: i18n.str`The operation was not found.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
+          case HttpStatusCode.Conflict:
+            return notify({
+              type: "error",
+              title: i18n.str`The reserve operation has been confirmed 
previously and can't be aborted`,
+            });
+          case HttpStatusCode.BadRequest:
+            return notify({
+              type: "error",
+              title: i18n.str`The operation id is invalid.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case HttpStatusCode.NotFound:
+            return notify({
+              type: "error",
+              title: i18n.str`The operation was not found.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
           default: {
-            assertUnreachable(resp)
+            assertUnreachable(resp);
           }
         }
       }
-    })
+    });
   }
 
   return (
@@ -99,22 +107,37 @@ export function QrCodeSection({
       <div class="bg-white shadow-xl sm:rounded-lg">
         <div class="px-4 py-5 sm:p-6">
           <h3 class="text-base font-semibold leading-6 text-gray-900">
-            <i18n.Translate>If you have a Taler wallet installed in this 
device</i18n.Translate>
+            <i18n.Translate>
+              If you have a Taler wallet installed in this device
+            </i18n.Translate>
           </h3>
           <div class="mt-4 mb-4 text-sm text-gray-500">
-            <p><i18n.Translate>
-              You will see the details of the operation in your wallet 
including the fees (if applies).
-              If you still don't have one you can install it following 
instructions in</i18n.Translate> <a class="font-semibold text-gray-500 
hover:text-gray-400" 
href="https://taler.net/en/wallet.html";><i18n.Translate>this 
page</i18n.Translate></a>.</p>
+            <p>
+              <i18n.Translate>
+                You will see the details of the operation in your wallet
+                including the fees (if applies). If you still don't have one 
you
+                can install it following instructions in
+              </i18n.Translate>{" "}
+              <a
+                class="font-semibold text-gray-500 hover:text-gray-400"
+                href="https://taler.net/en/wallet.html";
+              >
+                <i18n.Translate>this page</i18n.Translate>
+              </a>
+              .
+            </p>
           </div>
           <div class="flex items-center justify-between gap-x-6 border-t 
border-gray-900/10 pt-2 mt-2 ">
-            <button type="button"
+            <button
+              type="button"
               // class="disabled:opacity-50 disabled:cursor-default 
cursor-pointer rounded-md  px-3 py-2 text-sm font-semibold text-black shadow-sm 
hover:bg-red-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-red-600"
               class="text-sm font-semibold leading-6 text-gray-900"
               onClick={doAbort}
             >
-              Cancel
+              <i18n.Translate>Cancel</i18n.Translate>
             </button>
-            <a href={talerWithdrawUri}
+            <a
+              href={talerWithdrawUri}
               class="inline-flex items-center  disabled:opacity-50 
disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 
text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
             >
               <i18n.Translate>Withdraw</i18n.Translate>
@@ -126,28 +149,30 @@ export function QrCodeSection({
       <div class="bg-white shadow-xl sm:rounded-lg mt-8">
         <div class="px-4 py-5 sm:p-6">
           <h3 class="text-base font-semibold leading-6 text-gray-900">
-            <i18n.Translate>Or if you have the wallet in another 
device</i18n.Translate>
+            <i18n.Translate>
+              Or if you have the wallet in another device
+            </i18n.Translate>
           </h3>
           <div class="mt-4 max-w-xl text-sm text-gray-500">
-            <i18n.Translate>Scan the QR below to start the 
withdrawal.</i18n.Translate>
+            <i18n.Translate>
+              Scan the QR below to start the withdrawal.
+            </i18n.Translate>
           </div>
           <div class="mt-2 max-w-md ml-auto mr-auto">
             <QR text={talerWithdrawUri} />
           </div>
         </div>
         <div class="flex items-center justify-center gap-x-6 border-t 
border-gray-900/10 px-4 py-4 sm:px-8">
-          <button type="button"
+          <button
+            type="button"
             // class="disabled:opacity-50 disabled:cursor-default 
cursor-pointer rounded-md  px-3 py-2 text-sm font-semibold text-black shadow-sm 
hover:bg-red-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-red-600"
             class="text-sm font-semibold leading-6 text-gray-900"
             onClick={doAbort}
           >
-            Cancel
+            <i18n.Translate>Cancel</i18n.Translate>
           </button>
         </div>
       </div>
-
     </Fragment>
   );
 }
-
-
diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx 
b/packages/demobank-ui/src/pages/RegistrationPage.tsx
index e948a5dad..931a9b700 100644
--- a/packages/demobank-ui/src/pages/RegistrationPage.tsx
+++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -13,29 +13,33 @@
  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 { AccessToken, HttpStatusCode, Logger, TalerErrorCode, TranslatedString 
} from "@gnu-taler/taler-util";
+import {
+  AccessToken,
+  HttpStatusCode,
+  TalerErrorCode,
+  TranslatedString,
+  assertUnreachable,
+} from "@gnu-taler/taler-util";
 import {
   LocalNotificationBanner,
   ShowInputErrorLabel,
   useLocalNotification,
-  useTranslationContext
+  useTranslationContext,
 } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { useBankCoreApiContext } from "../context/config.js";
-import { useBackendState } from "../hooks/backend.js";
+import { useSettingsContext } from "../context/settings.js";
+import { RouteDefinition } from "../route.js";
 import { undefinedIfEmpty } from "../utils.js";
 import { getRandomPassword, getRandomUsername } from "./rnd.js";
-import { useSettingsContext } from "../context/settings.js";
-
-const logger = new Logger("RegistrationPage");
 
 export function RegistrationPage({
-  onComplete,
-  onCancel
+  onRegistrationSuccesful,
+  routeCancel,
 }: {
-  onComplete: () => void;
-  onCancel: () => void;
+  onRegistrationSuccesful: (user: string, password: string) => void;
+  routeCancel: RouteDefinition<Record<string, never>>;
 }): VNode {
   const { i18n } = useTranslationContext();
   const { config } = useBankCoreApiContext();
@@ -44,50 +48,58 @@ export function RegistrationPage({
       <p>{i18n.str`Currently, the bank is not accepting new 
registrations!`}</p>
     );
   }
-  return <RegistrationForm onComplete={onComplete} onCancel={onCancel} />;
+  return (
+    <RegistrationForm
+      onRegistrationSuccesful={onRegistrationSuccesful}
+      routeCancel={routeCancel}
+    />
+  );
 }
 
 export const USERNAME_REGEX = /^[a-z][a-zA-Z0-9-]*$/;
 export const PHONE_REGEX = /^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$/;
-export const EMAIL_REGEX = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/;
+export const EMAIL_REGEX = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/;
 
-/** 
+/**
  * Collect and submit registration data.
  */
-function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, 
onCancel: () => void }): VNode {
-  const backend = useBackendState();
+function RegistrationForm({
+  onRegistrationSuccesful,
+  routeCancel,
+}: {
+  onRegistrationSuccesful: (user: string, password: string) => void;
+  routeCancel: RouteDefinition<Record<string, never>>;
+}): VNode {
   const [username, setUsername] = useState<string | undefined>();
   const [name, setName] = useState<string | undefined>();
   const [password, setPassword] = useState<string | undefined>();
-  const [phone, setPhone] = useState<string | undefined>();
-  const [email, setEmail] = useState<string | undefined>();
+  // const [phone, setPhone] = useState<string | undefined>();
+  // const [email, setEmail] = useState<string | undefined>();
   const [repeatPassword, setRepeatPassword] = useState<string | undefined>();
-  const [notification, notify, handleError] = useLocalNotification()
+  const [notification, notify, handleError] = useLocalNotification();
   const settings = useSettingsContext();
 
-  const { api } = useBankCoreApiContext()
+  const { api } = useBankCoreApiContext();
   // const { register } = useTestingAPI();
   const { i18n } = useTranslationContext();
 
   const errors = undefinedIfEmpty({
-    name: !name
-      ? i18n.str`Missing name`
-      : undefined,
+    name: !name ? i18n.str`Missing name` : undefined,
     username: !username
       ? i18n.str`Missing username`
       : !USERNAME_REGEX.test(username)
         ? i18n.str`Use letters and numbers only, and start with a lowercase 
letter`
         : undefined,
-    phone: !phone
-      ? undefined
-      : !PHONE_REGEX.test(phone)
-        ? i18n.str`Use letters and numbers only, and start with a lowercase 
letter`
-        : undefined,
-    email: !email
-      ? undefined
-      : !EMAIL_REGEX.test(email)
-        ? i18n.str`Use letters and numbers only, and start with a lowercase 
letter`
-        : undefined,
+    // phone: !phone
+    //   ? undefined
+    //   : !PHONE_REGEX.test(phone)
+    //     ? i18n.str`Use letters and numbers only, and start with a lowercase 
letter`
+    //     : undefined,
+    // email: !email
+    //   ? undefined
+    //   : !EMAIL_REGEX.test(email)
+    //     ? i18n.str`Use letters and numbers only, and start with a lowercase 
letter`
+    //     : undefined,
     password: !password ? i18n.str`Missing password` : undefined,
     repeatPassword: !repeatPassword
       ? i18n.str`Missing password`
@@ -96,106 +108,97 @@ function RegistrationForm({ onComplete, onCancel }: { 
onComplete: () => void, on
         : undefined,
   });
 
-  async function doRegistrationAndLogin(name: string, username: string, 
password: string, onComplete: () => void) {
+  async function doRegistrationAndLogin(
+    name: string,
+    username: string,
+    password: string,
+    onComplete: () => void,
+  ) {
     await handleError(async () => {
-      createAccount: {
-        const resp = await api.createAccount("" as AccessToken, { name, 
username, password });
-        if (resp.type === "fail") {
-          switch (resp.case) {
-            case HttpStatusCode.BadRequest: return notify({
+      const resp = await api.createAccount("" as AccessToken, {
+        name,
+        username,
+        password,
+      });
+      if (resp.type === "ok") {
+        onComplete();
+      } else {
+        switch (resp.case) {
+          case HttpStatusCode.BadRequest:
+            return notify({
               type: "error",
               title: i18n.str`Server replied with invalid phone or email.`,
               description: resp.detail.hint as TranslatedString,
               debug: resp.detail,
-            })
-            case TalerErrorCode.BANK_UNALLOWED_DEBIT: return notify({
+            });
+          case TalerErrorCode.BANK_UNALLOWED_DEBIT:
+            return notify({
               type: "error",
               title: i18n.str`Registration is disabled because the bank ran 
out of bonus credit.`,
               description: resp.detail.hint as TranslatedString,
               debug: resp.detail,
-            })
-            case HttpStatusCode.Unauthorized: return notify({
+            });
+          case HttpStatusCode.Unauthorized:
+            return notify({
               type: "error",
               title: i18n.str`No enough permission to create that account.`,
               description: resp.detail.hint as TranslatedString,
               debug: resp.detail,
-            })
-            case TalerErrorCode.BANK_REGISTER_PAYTO_URI_REUSE: return notify({
+            });
+          case TalerErrorCode.BANK_REGISTER_PAYTO_URI_REUSE:
+            return notify({
               type: "error",
               title: i18n.str`That account id is already taken.`,
               description: resp.detail.hint as TranslatedString,
               debug: resp.detail,
-            })
-            case TalerErrorCode.BANK_REGISTER_USERNAME_REUSE: return notify({
+            });
+          case TalerErrorCode.BANK_REGISTER_USERNAME_REUSE:
+            return notify({
               type: "error",
               title: i18n.str`That username is already taken.`,
               description: resp.detail.hint as TranslatedString,
               debug: resp.detail,
-            })
-            case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT: return 
notify({
+            });
+          case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT:
+            return notify({
               type: "error",
               title: i18n.str`That username can't be used because is 
reserved.`,
               description: resp.detail.hint as TranslatedString,
               debug: resp.detail,
-            })
-            case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT: return 
notify({
+            });
+          case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT:
+            return notify({
               type: "error",
               title: i18n.str`Only admin is allow to set debt limit.`,
               description: resp.detail.hint as TranslatedString,
               debug: resp.detail,
-            })
-            case TalerErrorCode.BANK_MISSING_TAN_INFO: return notify({
+            });
+          case TalerErrorCode.BANK_MISSING_TAN_INFO:
+            return notify({
               type: "error",
               title: i18n.str`No information for the selected authentication 
channel.`,
               description: resp.detail.hint as TranslatedString,
               debug: resp.detail,
-            })
-            case TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED: return notify({
+            });
+          case TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED:
+            return notify({
               type: "error",
               title: i18n.str`Authentication channel is not supported.`,
               description: resp.detail.hint as TranslatedString,
               debug: resp.detail,
-            })
-            case TalerErrorCode.BANK_NON_ADMIN_SET_TAN_CHANNEL: return notify({
+            });
+          case TalerErrorCode.BANK_NON_ADMIN_SET_TAN_CHANNEL:
+            return notify({
               type: "error",
               title: i18n.str`Only admin can create accounts with second 
factor authentication.`,
               description: resp.detail.hint as TranslatedString,
               debug: resp.detail,
-            })
-            default: assertUnreachable(resp)
-          }
-        }
-      }
-      login: {
-        const resp = await 
api.getAuthenticationAPI(username).createAccessToken(password, {
-          scope: "readwrite",
-          duration: { d_us: "forever" },
-          refreshable: true,
-        })
-
-        if (resp.type === "ok") {
-          backend.logIn({ username, token: resp.body.access_token });
-        } else {
-          switch (resp.case) {
-            case HttpStatusCode.Unauthorized: return notify({
-              type: "error",
-              title: i18n.str`Wrong credentials for "${username}"`,
-              description: resp.detail.hint as TranslatedString,
-              debug: resp.detail,
-            })
-            case HttpStatusCode.NotFound: return notify({
-              type: "error",
-              title: i18n.str`Account not found`,
-              description: resp.detail.hint as TranslatedString,
-              debug: resp.detail,
-            })
-            default: assertUnreachable(resp)
-          }
+            });
+          default:
+            assertUnreachable(resp);
         }
-
       }
-      onComplete()
-    })
+    });
   }
 
   async function doRegistrationStep() {
@@ -204,18 +207,21 @@ function RegistrationForm({ onComplete, onCancel }: { 
onComplete: () => void, on
       setUsername(undefined);
       setPassword(undefined);
       setRepeatPassword(undefined);
-      onComplete();
-    })
+      onRegistrationSuccesful(username, password);
+    });
   }
 
-  async function doRandomRegistration(tries: number = 3) {
+  async function doRandomRegistration() {
     const user = getRandomUsername();
 
-    const pass = settings.simplePasswordForRandomAccounts ? "123" : 
getRandomPassword();
-    const username = `_${user.first}-${user.second}_`
-    const name = `${user.first}, ${user.second}`
-    await doRegistrationAndLogin(name, username, pass, onComplete)
-
+    const password = settings.simplePasswordForRandomAccounts
+      ? "123"
+      : getRandomPassword();
+    const username = `_${user.first}-${user.second}_`;
+    const name = `${user.first}, ${user.second}`;
+    await doRegistrationAndLogin(name, username, password, () => {
+      onRegistrationSuccesful(username, password);
+    });
   }
 
   return (
@@ -228,7 +234,9 @@ function RegistrationForm({ onComplete, onCancel }: { 
onComplete: () => void, on
         </div>
 
         <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
-          <form class="space-y-6" noValidate
+          <form
+            class="space-y-6"
+            noValidate
             onSubmit={(e) => {
               e.preventDefault();
             }}
@@ -236,7 +244,10 @@ function RegistrationForm({ onComplete, onCancel }: { 
onComplete: () => void, on
             autoCorrect="off"
           >
             <div>
-              <label for="username" class="block text-sm font-medium leading-6 
text-gray-900">
+              <label
+                for="username"
+                class="block text-sm font-medium leading-6 text-gray-900"
+              >
                 <i18n.Translate>Username</i18n.Translate>
                 <b style={{ color: "red" }}> *</b>
               </label>
@@ -265,7 +276,10 @@ function RegistrationForm({ onComplete, onCancel }: { 
onComplete: () => void, on
 
             <div>
               <div class="flex items-center justify-between">
-                <label for="password" class="block text-sm font-medium 
leading-6 text-gray-900">
+                <label
+                  for="password"
+                  class="block text-sm font-medium leading-6 text-gray-900"
+                >
                   <i18n.Translate>Password</i18n.Translate>
                   <b style={{ color: "red" }}> *</b>
                 </label>
@@ -294,7 +308,10 @@ function RegistrationForm({ onComplete, onCancel }: { 
onComplete: () => void, on
 
             <div>
               <div class="flex items-center justify-between">
-                <label for="register-repeat" class="block text-sm font-medium 
leading-6 text-gray-900">
+                <label
+                  for="register-repeat"
+                  class="block text-sm font-medium leading-6 text-gray-900"
+                >
                   <i18n.Translate>Repeat password</i18n.Translate>
                   <b style={{ color: "red" }}> *</b>
                 </label>
@@ -323,7 +340,10 @@ function RegistrationForm({ onComplete, onCancel }: { 
onComplete: () => void, on
 
             <div>
               <div class="flex items-center justify-between">
-                <label for="name" class="block text-sm font-medium leading-6 
text-gray-900">
+                <label
+                  for="name"
+                  class="block text-sm font-medium leading-6 text-gray-900"
+                >
                   <i18n.Translate>Name</i18n.Translate>
                   <b style={{ color: "red" }}> *</b>
                 </label>
@@ -403,50 +423,43 @@ function RegistrationForm({ onComplete, onCancel }: { 
onComplete: () => void, on
             </div> */}
 
             <div class="flex w-full justify-between">
-              <button type="submit"
+              <a
+                href={routeCancel.url({})}
                 class="ring-1 ring-gray-600 rounded-md bg-white 
disabled:bg-gray-300 px-3 py-1.5 text-sm font-semibold leading-6 text-black 
shadow-sm hover:bg-white-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2"
-                onClick={(e) => {
-                  e.preventDefault()
-                  onCancel()
-                }}
               >
                 <i18n.Translate>Cancel</i18n.Translate>
-              </button>
-              <button type="submit"
+              </a>
+              <button
+                type="submit"
                 class=" rounded-md bg-indigo-600 disabled:bg-gray-300 px-3 
py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
                 disabled={!!errors}
                 onClick={async (e) => {
-                  e.preventDefault()
+                  e.preventDefault();
 
-                  doRegistrationStep()
+                  doRegistrationStep();
                 }}
               >
                 <i18n.Translate>Register</i18n.Translate>
               </button>
             </div>
-
           </form>
 
-          {settings.allowRandomAccountCreation &&
+          {settings.allowRandomAccountCreation && (
             <p class="mt-10 text-center text-sm text-gray-500 border-t">
-              <button type="submit"
+              <button
+                type="submit"
                 class="flex mt-4 w-full justify-center rounded-md bg-green-600 
px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm 
hover:bg-green-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-green-600"
                 onClick={(e) => {
-                  e.preventDefault()
-                  doRandomRegistration()
+                  e.preventDefault();
+                  doRandomRegistration();
                 }}
               >
                 <i18n.Translate>Create a random temporary user</i18n.Translate>
               </button>
             </p>
-          }
+          )}
         </div>
       </div>
-
     </Fragment>
   );
 }
-
-export function assertUnreachable(x: never): never {
-  throw new Error("Didn't expect to get here");
-}
diff --git a/packages/demobank-ui/src/pages/SolveChallengePage.tsx 
b/packages/demobank-ui/src/pages/SolveChallengePage.tsx
index d0ac5ab75..6d2d6512e 100644
--- a/packages/demobank-ui/src/pages/SolveChallengePage.tsx
+++ b/packages/demobank-ui/src/pages/SolveChallengePage.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -18,13 +18,12 @@ import {
   AbsoluteTime,
   Amounts,
   HttpStatusCode,
-  Logger,
   TalerCorebankApi,
   TalerError,
   TalerErrorCode,
   TranslatedString,
   assertUnreachable,
-  parsePaytoUri
+  parsePaytoUri,
 } from "@gnu-taler/taler-util";
 import {
   Attention,
@@ -32,7 +31,7 @@ import {
   LocalNotificationBanner,
   ShowInputErrorLabel,
   useLocalNotification,
-  useTranslationContext
+  useTranslationContext,
 } from "@gnu-taler/web-util/browser";
 import { format } from "date-fns";
 import { Fragment, VNode, h } from "preact";
@@ -43,40 +42,41 @@ import { useWithdrawalDetails } from "../hooks/access.js";
 import { useBackendState } from "../hooks/backend.js";
 import { ChallengeInProgess, useBankState } from "../hooks/bank-state.js";
 import { useConversionInfo } from "../hooks/circuit.js";
+import { RouteDefinition } from "../route.js";
 import { undefinedIfEmpty } from "../utils.js";
 import { RenderAmount } from "./PaytoWireTransferForm.js";
 import { OperationNotFound } from "./WithdrawalQRCode.js";
 
-const logger = new Logger("SolveChallenge");
-
 export function SolveChallengePage({
-  onContinue,
+  onChallengeCompleted,
+  routeClose,
 }: {
-  onContinue: () => void;
+  onChallengeCompleted: () => void;
+  routeClose: RouteDefinition<Record<string, never>>;
 }): VNode {
-  const { api } = useBankCoreApiContext()
+  const { api } = useBankCoreApiContext();
   const { i18n } = useTranslationContext();
   const [bankState, updateBankState] = useBankState();
   const [code, setCode] = useState<string | undefined>(undefined);
-  const [notification, notify, handleError] = useLocalNotification()
+  const [notification, notify, handleError] = useLocalNotification();
   const { state } = useBackendState();
-  const creds = state.status !== "loggedIn" ? undefined : state
+  const creds = state.status !== "loggedIn" ? undefined : state;
 
   if (!bankState.currentChallenge) {
-    return <div>
-      <span>no challenge to solve  </span>
-      <button type="button"
-        class="inline-flex items-center rounded-md bg-red-600 px-3 py-2 
text-sm font-semibold text-white shadow-sm hover:bg-red-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-red-500"
-        onClick={() => {
-          onContinue()
-        }}
-      >
-        <i18n.Translate>Continue</i18n.Translate>
-      </button>
-    </div>
+    return (
+      <div>
+        <span>no challenge to solve </span>
+        <a
+          href={routeClose.url({})}
+          class="inline-flex items-center rounded-md bg-red-600 px-3 py-2 
text-sm font-semibold text-white shadow-sm hover:bg-red-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-red-500"
+        >
+          <i18n.Translate>Continue</i18n.Translate>
+        </a>
+      </div>
+    );
   }
 
-  const ch = bankState.currentChallenge
+  const ch = bankState.currentChallenge;
   const errors = undefinedIfEmpty({
     code: !code ? i18n.str`required` : undefined,
   });
@@ -86,34 +86,38 @@ export function SolveChallengePage({
     await handleError(async () => {
       const resp = await api.sendChallenge(creds, ch.id);
       if (resp.type === "ok") {
-        const newCh = structuredClone(ch)
-        newCh.sent = AbsoluteTime.now()
-        newCh.info = resp.body
-        updateBankState("currentChallenge", newCh)
+        const newCh = structuredClone(ch);
+        newCh.sent = AbsoluteTime.now();
+        newCh.info = resp.body;
+        updateBankState("currentChallenge", newCh);
       } else {
         switch (resp.case) {
-          case HttpStatusCode.NotFound: return notify({
-            type: "error",
-            title: i18n.str`Cashout not found. It may be also mean that it was 
already aborted.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case HttpStatusCode.Unauthorized: return notify({
-            type: "error",
-            title: i18n.str`Cashout not found. It may be also mean that it was 
already aborted.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case TalerErrorCode.BANK_TAN_CHANNEL_SCRIPT_FAILED: return notify({
-            type: "error",
-            title: i18n.str`Cashout not found. It may be also mean that it was 
already aborted.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          default: assertUnreachable(resp)
+          case HttpStatusCode.NotFound:
+            return notify({
+              type: "error",
+              title: i18n.str`Cashout not found. It may be also mean that it 
was already aborted.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case HttpStatusCode.Unauthorized:
+            return notify({
+              type: "error",
+              title: i18n.str`Cashout not found. It may be also mean that it 
was already aborted.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_TAN_CHANNEL_SCRIPT_FAILED:
+            return notify({
+              type: "error",
+              title: i18n.str`Cashout not found. It may be also mean that it 
was already aborted.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          default:
+            assertUnreachable(resp);
         }
       }
-    })
+    });
   }
 
   async function completeChallenge() {
@@ -121,55 +125,68 @@ export function SolveChallengePage({
     await handleError(async () => {
       {
         const resp = await api.confirmChallenge(creds, ch.id, {
-          tan: code
+          tan: code,
         });
         if (resp.type === "fail") {
-          setCode("")
+          setCode("");
           switch (resp.case) {
-            case HttpStatusCode.NotFound: return notify({
-              type: "error",
-              title: i18n.str`Challenge not found.`,
-              description: resp.detail.hint as TranslatedString,
-              debug: resp.detail,
-            })
-            case HttpStatusCode.Unauthorized: return notify({
-              type: "error",
-              title: i18n.str`This user is not authorized to complete this 
challenge.`,
-              description: resp.detail.hint as TranslatedString,
-              debug: resp.detail,
-            })
-            case HttpStatusCode.TooManyRequests: return notify({
-              type: "error",
-              title: i18n.str`Too many attemps, try another code.`,
-              description: resp.detail.hint as TranslatedString,
-              debug: resp.detail,
-            })
-            case TalerErrorCode.BANK_TAN_CHALLENGE_FAILED: return notify({
-              type: "error",
-              title: i18n.str`The confirmation code is wrong, try again.`,
-              description: resp.detail.hint as TranslatedString,
-              debug: resp.detail,
-            })
-            case TalerErrorCode.BANK_TAN_CHALLENGE_EXPIRED: return notify({
-              type: "error",
-              title: i18n.str`The operation expired.`,
-              description: resp.detail.hint as TranslatedString,
-              debug: resp.detail,
-            })
-            default: assertUnreachable(resp)
+            case HttpStatusCode.NotFound:
+              return notify({
+                type: "error",
+                title: i18n.str`Challenge not found.`,
+                description: resp.detail.hint as TranslatedString,
+                debug: resp.detail,
+              });
+            case HttpStatusCode.Unauthorized:
+              return notify({
+                type: "error",
+                title: i18n.str`This user is not authorized to complete this 
challenge.`,
+                description: resp.detail.hint as TranslatedString,
+                debug: resp.detail,
+              });
+            case HttpStatusCode.TooManyRequests:
+              return notify({
+                type: "error",
+                title: i18n.str`Too many attemps, try another code.`,
+                description: resp.detail.hint as TranslatedString,
+                debug: resp.detail,
+              });
+            case TalerErrorCode.BANK_TAN_CHALLENGE_FAILED:
+              return notify({
+                type: "error",
+                title: i18n.str`The confirmation code is wrong, try again.`,
+                description: resp.detail.hint as TranslatedString,
+                debug: resp.detail,
+              });
+            case TalerErrorCode.BANK_TAN_CHALLENGE_EXPIRED:
+              return notify({
+                type: "error",
+                title: i18n.str`The operation expired.`,
+                description: resp.detail.hint as TranslatedString,
+                debug: resp.detail,
+              });
+            default:
+              assertUnreachable(resp);
           }
         }
       }
       {
         const resp = await (async (ch: ChallengeInProgess) => {
           switch (ch.operation) {
-            case "delete-account": return await api.deleteAccount(creds, ch.id)
-            case "update-account": return await api.updateAccount(creds, 
ch.request, ch.id)
-            case "update-password": return await api.updatePassword(creds, 
ch.request, ch.id)
-            case "create-transaction": return await 
api.createTransaction(creds, ch.request, ch.id)
-            case "confirm-withdrawal": return await 
api.confirmWithdrawalById(creds, ch.request, ch.id)
-            case "create-cashout": return await api.createCashout(creds, 
ch.request, ch.id)
-            default: assertUnreachable(ch)
+            case "delete-account":
+              return await api.deleteAccount(creds, ch.id);
+            case "update-account":
+              return await api.updateAccount(creds, ch.request, ch.id);
+            case "update-password":
+              return await api.updatePassword(creds, ch.request, ch.id);
+            case "create-transaction":
+              return await api.createTransaction(creds, ch.request, ch.id);
+            case "confirm-withdrawal":
+              return await api.confirmWithdrawalById(creds, ch.request, ch.id);
+            case "create-cashout":
+              return await api.createCashout(creds, ch.request, ch.id);
+            default:
+              assertUnreachable(ch);
           }
         })(ch);
 
@@ -180,36 +197,43 @@ export function SolveChallengePage({
               title: i18n.str`The operation failed.`,
               description: resp.detail.hint as TranslatedString,
               debug: resp.detail,
-            })
+            });
           }
-          // another challenge required
+          // another challenge required, save the request and the ID
+          // @ts-expect-error no need to check the type of request, since it 
will be the same as the previous request
           updateBankState("currentChallenge", {
             operation: ch.operation,
             id: String(resp.body.challenge_id),
             sent: AbsoluteTime.never(),
-            request: ch.request as any,
-          })
+            request: ch.request,
+          });
           return notify({
             type: "info",
             title: i18n.str`The operation needs another confirmation to 
complete.`,
-          })
+          });
         }
-        updateBankState("currentChallenge", undefined)
-        return onContinue()
+        updateBankState("currentChallenge", undefined);
+        return onChallengeCompleted();
       }
-    })
+    });
   }
 
   const subtitle = ((op): TranslatedString => {
     switch (op) {
-      case "delete-account": return i18n.str`Account delete`
-      case "update-account": return i18n.str`Account update`
-      case "update-password": return i18n.str`Password update`
-      case "create-transaction": return i18n.str`Wire transfer`
-      case "confirm-withdrawal": return i18n.str`Withdrawal`
-      case "create-cashout": return i18n.str`Cashout`
+      case "delete-account":
+        return i18n.str`Account delete`;
+      case "update-account":
+        return i18n.str`Account update`;
+      case "update-password":
+        return i18n.str`Password update`;
+      case "create-transaction":
+        return i18n.str`Wire transfer`;
+      case "confirm-withdrawal":
+        return i18n.str`Withdrawal`;
+      case "create-cashout":
+        return i18n.str`Cashout`;
     }
-  })(ch.operation)
+  })(ch.operation);
 
   return (
     <Fragment>
@@ -217,25 +241,29 @@ export function SolveChallengePage({
       <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 
bg-gray-100 my-4 px-4 pb-4 rounded-lg">
         <div class="px-4 sm:px-0">
           <h2 class="text-base font-semibold leading-7 text-gray-900">
-            <span class="text-sm text-black font-semibold leading-6 " 
id="availability-label">
+            <span
+              class="text-sm text-black font-semibold leading-6 "
+              id="availability-label"
+            >
               <i18n.Translate>Confirm the operation</i18n.Translate>
             </span>
           </h2>
-          <span>
-            {subtitle}
-          </span>
+          <span>{subtitle}</span>
         </div>
 
         <div class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl 
md:col-span-2">
-          <ChallengeDetails challenge={bankState.currentChallenge} 
onStart={startChallenge} />
-          {ch.info &&
+          <ChallengeDetails
+            challenge={bankState.currentChallenge}
+            onStart={startChallenge}
+          />
+          {ch.info && (
             <div class="mt-3 text-sm leading-6">
               <form
                 class="bg-white shadow-sm ring-1 ring-gray-900/5"
                 autoCapitalize="none"
                 autoCorrect="off"
-                onSubmit={e => {
-                  e.preventDefault()
+                onSubmit={(e) => {
+                  e.preventDefault();
                 }}
               >
                 <div class="px-4 py-6 sm:p-8">
@@ -252,314 +280,408 @@ export function SolveChallengePage({
                         class="block w-full rounded-md border-0 py-1.5 
text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 
sm:text-sm sm:leading-6"
                         value={code ?? ""}
                         required
-
                         name="answer"
                         id="answer"
                         autocomplete="off"
                         onChange={(e): void => {
-                          setCode(e.currentTarget.value)
+                          setCode(e.currentTarget.value);
                         }}
                       />
                     </div>
-                    <ShowInputErrorLabel message={errors?.code} isDirty={code 
!== undefined} />
+                    <ShowInputErrorLabel
+                      message={errors?.code}
+                      isDirty={code !== undefined}
+                    />
                   </div>
                 </div>
                 <div class="flex items-center justify-between gap-x-6 border-t 
border-gray-900/10 px-4 py-4 sm:px-8">
-                  <button type="button"
+                  <a
+                    href={routeClose.url({})}
                     class="inline-flex items-center rounded-md bg-red-600 px-3 
py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-red-500"
-                    onClick={() => {
-                      updateBankState("currentChallenge", undefined)
-                      onContinue()
-                    }}
                   >
                     <i18n.Translate>Cancel</i18n.Translate>
-                  </button>
-                  <button type="submit"
+                  </a>
+                  <button
+                    type="submit"
                     class="disabled:opacity-50 disabled:cursor-default 
cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold 
text-white shadow-sm hover:bg-indigo-500 focus-visible:outline 
focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
                     disabled={!!errors}
                     onClick={(e) => {
-                      completeChallenge()
+                      completeChallenge();
+                      e.preventDefault();
                     }}
                   >
                     <i18n.Translate>Confirm</i18n.Translate>
                   </button>
                 </div>
               </form>
-
-              {/* <ShouldBeSameUser username={details.username}> */}
-              {/* </ShouldBeSameUser> */}
             </div>
-          }
+          )}
         </div>
       </div>
     </Fragment>
-
   );
 }
 
-function ChallengeDetails({ challenge, onStart }: { challenge: 
ChallengeInProgess, onStart: () => void }): VNode {
+function ChallengeDetails({
+  challenge,
+  onStart,
+}: {
+  challenge: ChallengeInProgess;
+  onStart: () => void;
+}): VNode {
   const { i18n, dateLocale } = useTranslationContext();
   const { config } = useBankCoreApiContext();
 
-  return <div class="px-4 mt-4 ">
-    <div class="w-full">
-      <div class="flex justify-center">
-
-        {challenge.info ?
-          <button type="submit"
-            class="disabled:opacity-50 disabled:cursor-default cursor-pointer 
rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
-            onClick={(e) => {
-              onStart()
-            }}
-          >
-            <i18n.Translate>Send again</i18n.Translate>
-          </button>
-          :
-          <button type="submit"
-            class="disabled:opacity-50 disabled:cursor-default cursor-pointer 
rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
-            onClick={(e) => {
-              onStart()
-            }}
-          >
-            <i18n.Translate>Send code</i18n.Translate>
-          </button>
-        }
-      </div>
-      <div class="mt-6 border-t border-gray-100">
-        <h2 class="text-base font-semibold leading-7 text-gray-900">
-          <span class="text-sm text-black font-semibold leading-6 " 
id="availability-label">
-            <i18n.Translate>Operation details</i18n.Translate>
-          </span>
-        </h2>
-        <dl class="divide-y divide-gray-100">
-          {((): VNode => {
-            switch (challenge.operation) {
-              case "delete-account": return <div class="px-4 py-2 sm:grid 
sm:grid-cols-3 sm:gap-4 sm:px-0">
-                <dt class="text-sm font-medium leading-6 
text-gray-900">Account</dt>
-                <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 
sm:mt-0">{challenge.request}</dd>
-              </div>
-              case "create-transaction": {
-                const payto = parsePaytoUri(challenge.request.payto_uri)!
-                return <Fragment>
-                  {challenge.request.amount &&
-                    <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
-                      <dt class="text-sm font-medium leading-6 
text-gray-900">Amount</dt>
-                      <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">
-                        <RenderAmount 
value={Amounts.parseOrThrow(challenge.request.amount)} 
spec={config.currency_specification} />
-                      </dd>
-                    </div>
-                  }
-                  {payto.isKnown && payto.targetType === "iban" &&
-                    <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
-                      <dt class="text-sm font-medium leading-6 
text-gray-900">To account</dt>
-                      <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">
-                        {payto.iban}
-                      </dd>
-                    </div>
-                  }
-                </Fragment>
-              }
-              case "confirm-withdrawal": return <ShowWithdrawalDetails 
id={challenge.request} />
-              case "create-cashout": {
-                return <ShowCashoutDetails request={challenge.request} />
-              }
-              case "update-account": {
-                return <Fragment>
-                  {challenge.request.cashout_payto_uri !== undefined &&
-                    <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
-                      <dt class="text-sm font-medium leading-6 
text-gray-900">Cashout account</dt>
-                      <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">
-                        {challenge.request.cashout_payto_uri}
-                      </dd>
-                    </div>
-                  }
-                  {challenge.request.contact_data?.email !== undefined &&
-                    <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
-                      <dt class="text-sm font-medium leading-6 
text-gray-900">Email</dt>
-                      <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">
-                        {challenge.request.contact_data?.email}
-                      </dd>
-                    </div>
-                  }
-                  {challenge.request.contact_data?.phone !== undefined &&
-                    <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
-                      <dt class="text-sm font-medium leading-6 
text-gray-900">Phone</dt>
-                      <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">
-                        {challenge.request.contact_data?.phone}
-                      </dd>
-                    </div>
-                  }
-                  {challenge.request.debit_threshold !== undefined &&
-                    <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
-                      <dt class="text-sm font-medium leading-6 
text-gray-900">Debit threshold</dt>
-                      <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">
-                        <RenderAmount 
value={Amounts.parseOrThrow(challenge.request.debit_threshold)} 
spec={config.currency_specification} />
-                      </dd>
-                    </div>
-                  }
-                  {challenge.request.is_public !== undefined &&
-                    <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
-                      <dt class="text-sm font-medium leading-6 
text-gray-900">Is this account public?</dt>
-                      <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">
-                        {challenge.request.is_public ? "enable" : "disable"}
-                      </dd>
-                    </div>
-                  }
-                  {challenge.request.name !== undefined &&
-                    <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
-                      <dt class="text-sm font-medium leading-6 
text-gray-900">Name</dt>
-                      <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">
-                        {challenge.request.name}
-                      </dd>
-                    </div>
-                  }
-                  {challenge.request.tan_channel !== undefined &&
+  return (
+    <div class="px-4 mt-4 ">
+      <div class="w-full">
+        <div class="flex justify-center">
+          {challenge.info ? (
+            <button
+              type="submit"
+              class="disabled:opacity-50 disabled:cursor-default 
cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold 
text-white shadow-sm hover:bg-indigo-500 focus-visible:outline 
focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
+              onClick={(e) => {
+                onStart();
+                e.preventDefault();
+              }}
+            >
+              <i18n.Translate>Send again</i18n.Translate>
+            </button>
+          ) : (
+            <button
+              type="submit"
+              class="disabled:opacity-50 disabled:cursor-default 
cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold 
text-white shadow-sm hover:bg-indigo-500 focus-visible:outline 
focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
+              onClick={(e) => {
+                onStart();
+                e.preventDefault();
+              }}
+            >
+              <i18n.Translate>Send code</i18n.Translate>
+            </button>
+          )}
+        </div>
+        <div class="mt-6 border-t border-gray-100">
+          <h2 class="text-base font-semibold leading-7 text-gray-900">
+            <span
+              class="text-sm text-black font-semibold leading-6 "
+              id="availability-label"
+            >
+              <i18n.Translate>Operation details</i18n.Translate>
+            </span>
+          </h2>
+          <dl class="divide-y divide-gray-100">
+            {((): VNode => {
+              switch (challenge.operation) {
+                case "delete-account":
+                  return (
                     <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
-                      <dt class="text-sm font-medium leading-6 
text-gray-900">Authentication channel</dt>
+                      <dt class="text-sm font-medium leading-6 text-gray-900">
+                        Account
+                      </dt>
                       <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">
-                        {challenge.request.tan_channel}
+                        {challenge.request}
                       </dd>
                     </div>
-                  }
-                </Fragment>
+                  );
+                case "create-transaction": {
+                  const payto = parsePaytoUri(challenge.request.payto_uri)!;
+                  return (
+                    <Fragment>
+                      {challenge.request.amount && (
+                        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
+                          <dt class="text-sm font-medium leading-6 
text-gray-900">
+                            Amount
+                          </dt>
+                          <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">
+                            <RenderAmount
+                              value={Amounts.parseOrThrow(
+                                challenge.request.amount,
+                              )}
+                              spec={config.currency_specification}
+                            />
+                          </dd>
+                        </div>
+                      )}
+                      {payto.isKnown && payto.targetType === "iban" && (
+                        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
+                          <dt class="text-sm font-medium leading-6 
text-gray-900">
+                            To account
+                          </dt>
+                          <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">
+                            {payto.iban}
+                          </dd>
+                        </div>
+                      )}
+                    </Fragment>
+                  );
+                }
+                case "confirm-withdrawal":
+                  return <ShowWithdrawalDetails id={challenge.request} />;
+                case "create-cashout": {
+                  return <ShowCashoutDetails request={challenge.request} />;
+                }
+                case "update-account": {
+                  return (
+                    <Fragment>
+                      {challenge.request.cashout_payto_uri !== undefined && (
+                        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
+                          <dt class="text-sm font-medium leading-6 
text-gray-900">
+                            Cashout account
+                          </dt>
+                          <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">
+                            {challenge.request.cashout_payto_uri}
+                          </dd>
+                        </div>
+                      )}
+                      {challenge.request.contact_data?.email !== undefined && (
+                        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
+                          <dt class="text-sm font-medium leading-6 
text-gray-900">
+                            Email
+                          </dt>
+                          <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">
+                            {challenge.request.contact_data?.email}
+                          </dd>
+                        </div>
+                      )}
+                      {challenge.request.contact_data?.phone !== undefined && (
+                        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
+                          <dt class="text-sm font-medium leading-6 
text-gray-900">
+                            Phone
+                          </dt>
+                          <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">
+                            {challenge.request.contact_data?.phone}
+                          </dd>
+                        </div>
+                      )}
+                      {challenge.request.debit_threshold !== undefined && (
+                        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
+                          <dt class="text-sm font-medium leading-6 
text-gray-900">
+                            Debit threshold
+                          </dt>
+                          <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">
+                            <RenderAmount
+                              value={Amounts.parseOrThrow(
+                                challenge.request.debit_threshold,
+                              )}
+                              spec={config.currency_specification}
+                            />
+                          </dd>
+                        </div>
+                      )}
+                      {challenge.request.is_public !== undefined && (
+                        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
+                          <dt class="text-sm font-medium leading-6 
text-gray-900">
+                            Is this account public?
+                          </dt>
+                          <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">
+                            {challenge.request.is_public ? "enable" : 
"disable"}
+                          </dd>
+                        </div>
+                      )}
+                      {challenge.request.name !== undefined && (
+                        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
+                          <dt class="text-sm font-medium leading-6 
text-gray-900">
+                            Name
+                          </dt>
+                          <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">
+                            {challenge.request.name}
+                          </dd>
+                        </div>
+                      )}
+                      {challenge.request.tan_channel !== undefined && (
+                        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
+                          <dt class="text-sm font-medium leading-6 
text-gray-900">
+                            Authentication channel
+                          </dt>
+                          <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">
+                            {challenge.request.tan_channel}
+                          </dd>
+                        </div>
+                      )}
+                    </Fragment>
+                  );
+                }
+                case "update-password": {
+                  return (
+                    <Fragment>
+                      <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
+                        <dt class="text-sm font-medium leading-6 
text-gray-900">
+                          New password
+                        </dt>
+                        <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">
+                          {challenge.request.new_password}
+                        </dd>
+                      </div>
+                    </Fragment>
+                  );
+                }
+                default:
+                  assertUnreachable(challenge);
               }
-              case "update-password": {
-                return <Fragment>
-                  <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
-                    <dt class="text-sm font-medium leading-6 
text-gray-900">New password</dt>
-                    <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">
-                      {challenge.request.new_password}
-                    </dd>
-                  </div>
-                </Fragment>
-              }
-              default: assertUnreachable(challenge)
-            }
-          })()}
+            })()}
 
-          {challenge.info &&
-            <h2 class="text-base font-semibold leading-7 text-gray-900">
-              <span class="text-sm text-black font-semibold leading-6 " 
id="availability-label">
-                <i18n.Translate>Challenge details</i18n.Translate>
-              </span>
-            </h2>
-          }
-          {challenge.sent.t_ms !== "never" &&
-            <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
-              <dt class="text-sm font-medium leading-6 
text-gray-900"><i18n.Translate>Sent at</i18n.Translate></dt>
-              <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 
sm:mt-0">
-                {format(challenge.sent.t_ms, "dd/MM/yyyy HH:mm:ss", { locale: 
dateLocale })}
-              </dd>
-            </div>
-          }
-          {challenge.info &&
-            <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
-              <dt class="text-sm font-medium leading-6 text-gray-900">
-                {((ch: TalerCorebankApi.TanChannel): VNode => {
-                  switch (ch) {
-                    case TalerCorebankApi.TanChannel.SMS: return 
<i18n.Translate>To phone</i18n.Translate>
-                    case TalerCorebankApi.TanChannel.EMAIL: return 
<i18n.Translate>To email</i18n.Translate>
-                    default: assertUnreachable(ch)
-                  }
-                })(challenge.info.tan_channel)}
-              </dt>
-              <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 
sm:mt-0">
-                {challenge.info.tan_info}
-              </dd>
-            </div>
-          }
-
-        </dl>
+            {challenge.info && (
+              <h2 class="text-base font-semibold leading-7 text-gray-900">
+                <span
+                  class="text-sm text-black font-semibold leading-6 "
+                  id="availability-label"
+                >
+                  <i18n.Translate>Challenge details</i18n.Translate>
+                </span>
+              </h2>
+            )}
+            {challenge.sent.t_ms !== "never" && (
+              <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
+                <dt class="text-sm font-medium leading-6 text-gray-900">
+                  <i18n.Translate>Sent at</i18n.Translate>
+                </dt>
+                <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 
sm:mt-0">
+                  {format(challenge.sent.t_ms, "dd/MM/yyyy HH:mm:ss", {
+                    locale: dateLocale,
+                  })}
+                </dd>
+              </div>
+            )}
+            {challenge.info && (
+              <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
+                <dt class="text-sm font-medium leading-6 text-gray-900">
+                  {((ch: TalerCorebankApi.TanChannel): VNode => {
+                    switch (ch) {
+                      case TalerCorebankApi.TanChannel.SMS:
+                        return <i18n.Translate>To phone</i18n.Translate>;
+                      case TalerCorebankApi.TanChannel.EMAIL:
+                        return <i18n.Translate>To email</i18n.Translate>;
+                      default:
+                        assertUnreachable(ch);
+                    }
+                  })(challenge.info.tan_channel)}
+                </dt>
+                <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 
sm:mt-0">
+                  {challenge.info.tan_info}
+                </dd>
+              </div>
+            )}
+          </dl>
+        </div>
       </div>
     </div>
-  </div>
+  );
 }
 
 function ShowWithdrawalDetails({ id }: { id: string }): VNode {
-  const { i18n } = useTranslationContext();
-  const details = useWithdrawalDetails(id)
+  const details = useWithdrawalDetails(id);
   const { config } = useBankCoreApiContext();
   if (!details) {
-    return <Loading />
+    return <Loading />;
   }
   if (details instanceof TalerError) {
-    return <ErrorLoadingWithDebug error={details} />
+    return <ErrorLoadingWithDebug error={details} />;
   }
   if (details.type === "fail") {
     switch (details.case) {
       case HttpStatusCode.BadRequest:
-      case HttpStatusCode.NotFound: return <OperationNotFound 
onClose={undefined} />
-      default: assertUnreachable(details)
+      case HttpStatusCode.NotFound:
+        return <OperationNotFound routeClose={undefined} />;
+      default:
+        assertUnreachable(details);
     }
   }
 
-  return <Fragment>
-    <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
-      <dt class="text-sm font-medium leading-6 text-gray-900">Amount</dt>
-      <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
-        <RenderAmount value={Amounts.parseOrThrow(details.body.amount)} 
spec={config.currency_specification} />
-      </dd>
-    </div>
-    {details.body.selected_reserve_pub !== undefined &&
-      <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
-        <dt class="text-sm font-medium leading-6 text-gray-900">Withdraw 
id</dt>
-        <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0" 
title={details.body.selected_reserve_pub}>
-          {details.body.selected_reserve_pub.substring(0, 16)}...
-        </dd>
-      </div>
-    }
-    {details.body.selected_exchange_account !== undefined &&
+  return (
+    <Fragment>
       <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
-        <dt class="text-sm font-medium leading-6 text-gray-900">To account</dt>
+        <dt class="text-sm font-medium leading-6 text-gray-900">Amount</dt>
         <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
-          {details.body.selected_exchange_account}
+          <RenderAmount
+            value={Amounts.parseOrThrow(details.body.amount)}
+            spec={config.currency_specification}
+          />
         </dd>
       </div>
-    }
-  </Fragment>
+      {details.body.selected_reserve_pub !== undefined && (
+        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
+          <dt class="text-sm font-medium leading-6 text-gray-900">
+            Withdraw id
+          </dt>
+          <dd
+            class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0"
+            title={details.body.selected_reserve_pub}
+          >
+            {details.body.selected_reserve_pub.substring(0, 16)}...
+          </dd>
+        </div>
+      )}
+      {details.body.selected_exchange_account !== undefined && (
+        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
+          <dt class="text-sm font-medium leading-6 text-gray-900">
+            To account
+          </dt>
+          <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 
sm:mt-0">
+            {details.body.selected_exchange_account}
+          </dd>
+        </div>
+      )}
+    </Fragment>
+  );
 }
 
-function ShowCashoutDetails({ request }: { request: 
TalerCorebankApi.CashoutRequest }): VNode {
+function ShowCashoutDetails({
+  request,
+}: {
+  request: TalerCorebankApi.CashoutRequest;
+}): VNode {
   const { i18n } = useTranslationContext();
   const info = useConversionInfo();
   if (!info) {
-    return <Loading />
+    return <Loading />;
   }
 
   if (info instanceof TalerError) {
-    return <ErrorLoadingWithDebug error={info} />
+    return <ErrorLoadingWithDebug error={info} />;
   }
   if (info.type === "fail") {
     switch (info.case) {
       case HttpStatusCode.NotImplemented: {
-        return <Attention type="danger" title={i18n.str`Cashout not 
implemented`}>
-        </Attention>;
+        return (
+          <Attention
+            type="danger"
+            title={i18n.str`Cashout not implemented`}
+          ></Attention>
+        );
       }
-      default: assertUnreachable(info.case)
+      default:
+        assertUnreachable(info.case);
     }
   }
 
-
-  return <Fragment>
-    {request.subject !== undefined &&
+  return (
+    <Fragment>
+      {request.subject !== undefined && (
+        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
+          <dt class="text-sm font-medium leading-6 text-gray-900">Subject</dt>
+          <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 
sm:mt-0">
+            {request.subject}
+          </dd>
+        </div>
+      )}
       <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
-        <dt class="text-sm font-medium leading-6 text-gray-900">Subject</dt>
+        <dt class="text-sm font-medium leading-6 text-gray-900">Debit</dt>
         <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
-          {request.subject}
+          <RenderAmount
+            value={Amounts.parseOrThrow(request.amount_credit)}
+            spec={info.body.regional_currency_specification}
+          />
         </dd>
       </div>
-    }
-    <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
-      <dt class="text-sm font-medium leading-6 text-gray-900">Debit</dt>
-      <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
-        <RenderAmount value={Amounts.parseOrThrow(request.amount_credit)} 
spec={info.body.regional_currency_specification} />
-      </dd>
-    </div>
-    <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
-      <dt class="text-sm font-medium leading-6 text-gray-900">Credit</dt>
-      <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
-        <RenderAmount value={Amounts.parseOrThrow(request.amount_credit)} 
spec={info.body.fiat_currency_specification} />
-      </dd>
-    </div>
-  </Fragment>
-}
\ No newline at end of file
+      <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
+        <dt class="text-sm font-medium leading-6 text-gray-900">Credit</dt>
+        <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
+          <RenderAmount
+            value={Amounts.parseOrThrow(request.amount_credit)}
+            spec={info.body.fiat_currency_specification}
+          />
+        </dd>
+      </div>
+    </Fragment>
+  );
+}
diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx 
b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
index 8fe9ea36f..1e48b818a 100644
--- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
+++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -18,59 +18,73 @@ import {
   AmountJson,
   Amounts,
   HttpStatusCode,
-  Logger,
   TranslatedString,
-  parseWithdrawUri
+  assertUnreachable,
+  parseWithdrawUri,
 } from "@gnu-taler/taler-util";
 import {
+  Attention,
+  LocalNotificationBanner,
   notifyError,
   useLocalNotification,
-  useTranslationContext
+  useTranslationContext,
 } from "@gnu-taler/web-util/browser";
-import { Fragment, VNode, h } from "preact";
+import { VNode, h } from "preact";
 import { forwardRef } from "preact/compat";
 import { useState } from "preact/hooks";
-import { Attention } from "@gnu-taler/web-util/browser";
+import { privatePages } from "../Routing.js";
 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 { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
+import { RouteDefinition } from "../route.js";
+import { undefinedIfEmpty } from "../utils.js";
 import { OperationState } from "./OperationState/index.js";
 import { InputAmount, doAutoFocus } from "./PaytoWireTransferForm.js";
-import { assertUnreachable } from "./WithdrawalOperationPage.js";
-import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
-import { useBankState } from "../hooks/bank-state.js";
 
-const logger = new Logger("WalletWithdrawForm");
 const RefAmount = forwardRef(InputAmount);
 
-
-function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
+function OldWithdrawalForm({
+  onOperationCreated,
+  limit,
+  routeCancel,
+  focus,
+}: {
   limit: AmountJson;
   focus?: boolean;
-  goToConfirmOperation: (operationId: string) => void;
-  onCancel: () => void;
+  onOperationCreated: (wopid: string) => void;
+  routeCancel: RouteDefinition<Record<string, never>>;
 }): VNode {
   const { i18n } = useTranslationContext();
-  const [settings] = usePreferences()
+  const [settings] = usePreferences();
   const [bankState, updateBankState] = useBankState();
 
   const { state: credentials } = useBackendState();
-  const creds = credentials.status !== "loggedIn" ? undefined : credentials
+  const creds = credentials.status !== "loggedIn" ? undefined : credentials;
 
-  const { api } = useBankCoreApiContext()
-  const [amountStr, setAmountStr] = useState<string | 
undefined>(`${settings.maxWithdrawalAmount}`);
-  const [notification, notify, handleError] = useLocalNotification()
-
-  if (!!bankState.currentWithdrawalOperationId) {
-    return <Attention type="warning" title={i18n.str`There is an operation 
already`}>
-      <span ref={focus ? doAutoFocus : undefined} />
-      <i18n.Translate>
-        Complete or cancel the operation in</i18n.Translate> <a 
class="font-semibold text-yellow-700 hover:text-yellow-600" 
href={`#/operation/${bankState.currentWithdrawalOperationId}`}>
-        <i18n.Translate>this page</i18n.Translate>
-      </a>
+  const { api } = useBankCoreApiContext();
+  const [amountStr, setAmountStr] = useState<string | undefined>(
+    `${settings.maxWithdrawalAmount}`,
+  );
+  const [notification, notify, handleError] = useLocalNotification();
 
-    </Attention>
+  if (bankState.currentWithdrawalOperationId) {
+    return (
+      <Attention type="warning" title={i18n.str`There is an operation 
already`}>
+        <span ref={focus ? doAutoFocus : undefined} />
+        <i18n.Translate>
+          Complete or cancel the operation in
+        </i18n.Translate>{" "}
+        <a
+          class="font-semibold text-yellow-700 hover:text-yellow-600"
+          href={privatePages.operationDetails.url({
+            wopid: bankState.currentWithdrawalOperationId,
+          })}
+        >
+          <i18n.Translate>this page</i18n.Translate>
+        </a>
+      </Attention>
+    );
   }
 
   const trimmedAmountStr = amountStr?.trim();
@@ -101,10 +115,14 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, 
onCancel, focus }: {
         if (!uri) {
           return notifyError(
             i18n.str`Server responded with an invalid  withdraw URI`,
-            i18n.str`Withdraw URI: ${resp.body.taler_withdraw_uri}`);
+            i18n.str`Withdraw URI: ${resp.body.taler_withdraw_uri}`,
+          );
         } else {
-          updateBankState("currentWithdrawalOperationId", 
uri.withdrawalOperationId)
-          goToConfirmOperation(uri.withdrawalOperationId);
+          updateBankState(
+            "currentWithdrawalOperationId",
+            uri.withdrawalOperationId,
+          );
+          onOperationCreated(uri.withdrawalOperationId);
         }
       } else {
         switch (resp.case) {
@@ -114,7 +132,7 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, 
onCancel, focus }: {
               title: i18n.str`The operation was rejected due to insufficient 
funds`,
               description: resp.detail.hint as TranslatedString,
               debug: resp.detail,
-            })
+            });
             break;
           }
           case HttpStatusCode.Unauthorized: {
@@ -123,7 +141,7 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, 
onCancel, focus }: {
               title: i18n.str`The operation was rejected due to insufficient 
funds`,
               description: resp.detail.hint as TranslatedString,
               debug: resp.detail,
-            })
+            });
             break;
           }
           case HttpStatusCode.NotFound: {
@@ -132,159 +150,184 @@ function OldWithdrawalForm({ goToConfirmOperation, 
limit, onCancel, focus }: {
               title: i18n.str`Account not found`,
               description: resp.detail.hint as TranslatedString,
               debug: resp.detail,
-            })
+            });
             break;
           }
-          default: assertUnreachable(resp)
+          default:
+            assertUnreachable(resp);
         }
       }
-    })
+    });
   }
 
-  return <form
-    class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl 
md:col-span-2 mt-4"
-    autoCapitalize="none"
-    autoCorrect="off"
-    onSubmit={e => {
-      e.preventDefault()
-    }}
-  >
-    <LocalNotificationBanner notification={notification} />
+  return (
+    <form
+      class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl 
md:col-span-2 mt-4"
+      autoCapitalize="none"
+      autoCorrect="off"
+      onSubmit={(e) => {
+        e.preventDefault();
+      }}
+    >
+      <LocalNotificationBanner notification={notification} />
 
-    <div class="px-4 py-6 ">
-      <div class="grid max-w-xs grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
-        <div class="sm:col-span-5">
-          <label for="withdraw-amount">{i18n.str`Amount`}</label>
-          <RefAmount
-            currency={limit.currency}
-            value={amountStr}
-            name="withdraw-amount"
-            onChange={(v) => {
-              setAmountStr(v);
-            }}
-            error={errors?.amount}
-            ref={focus ? doAutoFocus : undefined}
-          />
+      <div class="px-4 py-6 ">
+        <div class="grid max-w-xs grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
+          <div class="sm:col-span-5">
+            <label for="withdraw-amount">{i18n.str`Amount`}</label>
+            <RefAmount
+              currency={limit.currency}
+              value={amountStr}
+              name="withdraw-amount"
+              onChange={(v) => {
+                setAmountStr(v);
+              }}
+              error={errors?.amount}
+              ref={focus ? doAutoFocus : undefined}
+            />
+          </div>
         </div>
-      </div>
-      <div class="mt-4">
-        <div class="sm:inline">
-
-          <button type="button"
-            class="               inline-flex px-6 py-4 text-sm items-center 
rounded-l-md bg-white text-gray-900 ring-1 ring-inset ring-gray-300 
hover:bg-gray-50 focus:z-10"
-            onClick={(e) => {
-              e.preventDefault();
-              setAmountStr("50.00")
-            }}
-          >
-            50.00
-          </button>
-          <button type="button"
-            class=" -ml-px -mr-px inline-flex px-6 py-4 text-sm items-center 
rounded-r-md sm:rounded-none             bg-white text-gray-900 ring-1 
ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
-            onClick={(e) => {
-              e.preventDefault();
-              setAmountStr("25.00")
-            }}
-          >
-
-            25.00
-          </button>
-        </div>
-        <div class="mt-4 sm:inline">
-          <button type="button"
-            class=" -ml-px -mr-px inline-flex px-6 py-4 text-sm items-center 
rounded-l-md sm:rounded-none             bg-white text-gray-900 ring-1 
ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
-            onClick={(e) => {
-              e.preventDefault();
-              setAmountStr("10.00")
-            }}
-          >
-            10.00
-          </button>
-          <button type="button"
-            class="               inline-flex px-6 py-4 text-sm items-center 
rounded-r-md bg-white  text-gray-900 ring-1 ring-inset ring-gray-300 
hover:bg-gray-50 focus:z-10"
-            onClick={(e) => {
-              e.preventDefault();
-              setAmountStr("5.00")
-            }}
-          >
-            5.00
-          </button>
+        <div class="mt-4">
+          <div class="sm:inline">
+            <button
+              type="button"
+              class="               inline-flex px-6 py-4 text-sm items-center 
rounded-l-md bg-white text-gray-900 ring-1 ring-inset ring-gray-300 
hover:bg-gray-50 focus:z-10"
+              onClick={(e) => {
+                e.preventDefault();
+                setAmountStr("50.00");
+              }}
+            >
+              50.00
+            </button>
+            <button
+              type="button"
+              class=" -ml-px -mr-px inline-flex px-6 py-4 text-sm items-center 
rounded-r-md sm:rounded-none             bg-white text-gray-900 ring-1 
ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
+              onClick={(e) => {
+                e.preventDefault();
+                setAmountStr("25.00");
+              }}
+            >
+              25.00
+            </button>
+          </div>
+          <div class="mt-4 sm:inline">
+            <button
+              type="button"
+              class=" -ml-px -mr-px inline-flex px-6 py-4 text-sm items-center 
rounded-l-md sm:rounded-none             bg-white text-gray-900 ring-1 
ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
+              onClick={(e) => {
+                e.preventDefault();
+                setAmountStr("10.00");
+              }}
+            >
+              10.00
+            </button>
+            <button
+              type="button"
+              class="               inline-flex px-6 py-4 text-sm items-center 
rounded-r-md bg-white  text-gray-900 ring-1 ring-inset ring-gray-300 
hover:bg-gray-50 focus:z-10"
+              onClick={(e) => {
+                e.preventDefault();
+                setAmountStr("5.00");
+              }}
+            >
+              5.00
+            </button>
+          </div>
         </div>
       </div>
-    </div>
-    <div class="flex items-center justify-between gap-x-6 border-t 
border-gray-900/10 px-4 py-4 sm:px-8">
-      <button type="button" class="text-sm font-semibold leading-6 
text-gray-900"
-        onClick={onCancel}
-      >
-        <i18n.Translate>Cancel</i18n.Translate></button>
-      <button type="submit"
-        class="disabled:opacity-50 disabled:cursor-default cursor-pointer 
rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
-        // disabled={isRawPayto ? !!errorsPayto : !!errorsWire}
-        onClick={(e) => {
-          e.preventDefault()
-          doStart()
-        }}
-      >
-        <i18n.Translate>Continue</i18n.Translate>
-      </button>
-    </div>
-
-  </form>
+      <div class="flex items-center justify-between gap-x-6 border-t 
border-gray-900/10 px-4 py-4 sm:px-8">
+        <a
+          href={routeCancel.url({})}
+          class="text-sm font-semibold leading-6 text-gray-900"
+        >
+          <i18n.Translate>Cancel</i18n.Translate>
+        </a>
+        <button
+          type="submit"
+          class="disabled:opacity-50 disabled:cursor-default cursor-pointer 
rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
+          // disabled={isRawPayto ? !!errorsPayto : !!errorsWire}
+          onClick={(e) => {
+            e.preventDefault();
+            doStart();
+          }}
+        >
+          <i18n.Translate>Continue</i18n.Translate>
+        </button>
+      </div>
+    </form>
+  );
 }
 
-
 export function WalletWithdrawForm({
   focus,
   limit,
-  onCancel,
+  routeCancel,
   onAuthorizationRequired,
-  goToConfirmOperation,
+  onOperationCreated,
+  onOperationAborted,
 }: {
   limit: AmountJson;
   focus?: boolean;
-  onAuthorizationRequired: () => void,
-  goToConfirmOperation: (operationId: string) => void;
-  onCancel: () => void;
+  onAuthorizationRequired: () => void;
+  onOperationCreated: (wopid: string) => void;
+  onOperationAborted: () => void;
+  routeCancel: RouteDefinition<Record<string, never>>;
 }): VNode {
   const { i18n } = useTranslationContext();
-  const [settings, updateSettings] = usePreferences()
+  const [settings, updateSettings] = usePreferences();
 
-  return (<div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 
bg-gray-100 my-4 px-4 pb-4 rounded-lg">
-    <div class="px-4 sm:px-0">
-      <h2 class="text-base font-semibold leading-7 
text-gray-900"><i18n.Translate>Prepare your wallet</i18n.Translate></h2>
-      <p class="mt-1 text-sm text-gray-500">
-        <i18n.Translate>After using your wallet you will need to confirm or 
cancel the operation on this site.</i18n.Translate>
-      </p>
-    </div>
-
-    <div class="col-span-2">
-      {settings.showInstallWallet &&
-        <Attention title={i18n.str`You need a GNU Taler Wallet`} onClose={() 
=> {
-          updateSettings("showInstallWallet", false);
-        }}>
+  return (
+    <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 
bg-gray-100 my-4 px-4 pb-4 rounded-lg">
+      <div class="px-4 sm:px-0">
+        <h2 class="text-base font-semibold leading-7 text-gray-900">
+          <i18n.Translate>Prepare your wallet</i18n.Translate>
+        </h2>
+        <p class="mt-1 text-sm text-gray-500">
           <i18n.Translate>
-            If you don't have one yet you can follow the instruction 
in</i18n.Translate> <a target="_blank" rel="noreferrer noopener" 
class="font-semibold text-blue-700 hover:text-blue-600" 
href="https://taler.net/en/wallet.html";>
-            <i18n.Translate>this page</i18n.Translate>
-          </a>
-        </Attention>
-      }
+            After using your wallet you will need to confirm or cancel the
+            operation on this site.
+          </i18n.Translate>
+        </p>
+      </div>
 
-      {!settings.fastWithdrawal ?
-        <OldWithdrawalForm
-          focus={focus}
-          limit={limit}
-          onCancel={onCancel}
-          goToConfirmOperation={goToConfirmOperation}
-        />
-        :
-        <OperationState
-          currency={limit.currency}
-          onAuthorizationRequired={onAuthorizationRequired}
-          onClose={onCancel}
-        />
-      }
+      <div class="col-span-2">
+        {settings.showInstallWallet && (
+          <Attention
+            title={i18n.str`You need a GNU Taler Wallet`}
+            onClose={() => {
+              updateSettings("showInstallWallet", false);
+            }}
+          >
+            <i18n.Translate>
+              If you don't have one yet you can follow the instruction in
+            </i18n.Translate>{" "}
+            <a
+              target="_blank"
+              rel="noreferrer noopener"
+              class="font-semibold text-blue-700 hover:text-blue-600"
+              href="https://taler.net/en/wallet.html";
+            >
+              <i18n.Translate>this page</i18n.Translate>
+            </a>
+          </Attention>
+        )}
+
+        {!settings.fastWithdrawal ? (
+          <OldWithdrawalForm
+            focus={focus}
+            limit={limit}
+            routeCancel={routeCancel}
+            onOperationCreated={onOperationCreated}
+          />
+        ) : (
+          <OperationState
+            currency={limit.currency}
+            onAuthorizationRequired={onAuthorizationRequired}
+            routeClose={routeCancel}
+            onAbort={onOperationAborted}
+            // route={routeCancel}
+          />
+        )}
+      </div>
     </div>
-  </div>
   );
 }
-
diff --git a/packages/demobank-ui/src/pages/WireTransfer.tsx 
b/packages/demobank-ui/src/pages/WireTransfer.tsx
index 25d43a832..190afd66e 100644
--- a/packages/demobank-ui/src/pages/WireTransfer.tsx
+++ b/packages/demobank-ui/src/pages/WireTransfer.tsx
@@ -1,18 +1,47 @@
-import { Amounts, HttpStatusCode, TalerError } from "@gnu-taler/taler-util";
-import { Loading, notifyInfo, useTranslationContext } from 
"@gnu-taler/web-util/browser";
+/*
+ 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 {
+  Amounts,
+  HttpStatusCode,
+  TalerError,
+  assertUnreachable,
+} from "@gnu-taler/taler-util";
+import {
+  Loading,
+  notifyInfo,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js";
 import { useAccountDetails } from "../hooks/access.js";
 import { useBackendState } from "../hooks/backend.js";
 import { LoginForm } from "./LoginForm.js";
 import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js";
-import { assertUnreachable } from "./WithdrawalOperationPage.js";
+import { RouteDefinition } from "../route.js";
 
-export function WireTransfer({ toAccount, onAuthorizationRequired, onCancel, 
onSuccess }: {
+export function WireTransfer({
+  toAccount,
+  onAuthorizationRequired,
+  routeCancel,
+  onSuccess,
+}: {
   onSuccess?: () => void;
-  toAccount?: string,
-  onCancel?: () => void,
-  onAuthorizationRequired: () => void,
+  toAccount?: string;
+  routeCancel?: RouteDefinition<Record<string, never>>;
+  onAuthorizationRequired: () => void;
 }): VNode {
   const { i18n } = useTranslationContext();
   const r = useBackendState();
@@ -20,16 +49,19 @@ export function WireTransfer({ toAccount, 
onAuthorizationRequired, onCancel, onS
   const result = useAccountDetails(account);
 
   if (!result) {
-    return <Loading />
+    return <Loading />;
   }
   if (result instanceof TalerError) {
-    return <ErrorLoadingWithDebug error={result} />
+    return <ErrorLoadingWithDebug error={result} />;
   }
   if (result.type === "fail") {
     switch (result.case) {
-      case HttpStatusCode.Unauthorized: return <LoginForm 
currentUser={account} />
-      case HttpStatusCode.NotFound: return <LoginForm currentUser={account} />
-      default: assertUnreachable(result)
+      case HttpStatusCode.Unauthorized:
+        return <LoginForm currentUser={account} />;
+      case HttpStatusCode.NotFound:
+        return <LoginForm currentUser={account} />;
+      default:
+        assertUnreachable(result);
     }
   }
   const { body: data } = result;
@@ -50,9 +82,9 @@ export function WireTransfer({ toAccount, 
onAuthorizationRequired, onCancel, onS
       onAuthorizationRequired={onAuthorizationRequired}
       onSuccess={() => {
         notifyInfo(i18n.str`Wire transfer created!`);
-        if (onSuccess) onSuccess()
+        if (onSuccess) onSuccess();
       }}
-      onCancel={onCancel}
+      routeCancel={routeCancel}
     />
   );
 }
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx 
b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index 3d1239857..66c27ef4c 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -18,20 +18,20 @@ import {
   AbsoluteTime,
   AmountJson,
   HttpStatusCode,
-  Logger,
   PaytoUri,
   PaytoUriIBAN,
   PaytoUriTalerBank,
   TalerErrorCode,
   TranslatedString,
-  WithdrawUriResult
+  WithdrawUriResult,
+  assertUnreachable,
 } from "@gnu-taler/taler-util";
 import {
   Attention,
   LocalNotificationBanner,
   notifyInfo,
   useLocalNotification,
-  useTranslationContext
+  useTranslationContext,
 } from "@gnu-taler/web-util/browser";
 import { ComponentChildren, Fragment, VNode, h } from "preact";
 import { mutate } from "swr";
@@ -41,20 +41,17 @@ import { useBankState } from "../hooks/bank-state.js";
 import { usePreferences } from "../hooks/preferences.js";
 import { LoginForm } from "./LoginForm.js";
 import { RenderAmount } from "./PaytoWireTransferForm.js";
-import { assertUnreachable } from "./WithdrawalOperationPage.js";
-
-const logger = new Logger("WithdrawalConfirmationQuestion");
 
 interface Props {
   onAborted: () => void;
   withdrawUri: WithdrawUriResult;
   details: {
-    account: PaytoUri,
-    reserve: string,
-    username: string,
-    amount: AmountJson,
-  },
-  onAuthorizationRequired: () => void,
+    account: PaytoUri;
+    reserve: string;
+    username: string;
+    amount: AmountJson;
+  };
+  onAuthorizationRequired: () => void;
 }
 /**
  * Additional authentication required to complete the operation.
@@ -67,101 +64,116 @@ export function WithdrawalConfirmationQuestion({
   withdrawUri,
 }: Props): VNode {
   const { i18n } = useTranslationContext();
-  const [settings] = usePreferences()
+  const [settings] = usePreferences();
   const { state: credentials } = useBackendState();
-  const creds = credentials.status !== "loggedIn" ? undefined : credentials
-  const [, updateBankState] = useBankState()
+  const creds = credentials.status !== "loggedIn" ? undefined : credentials;
+  const [, updateBankState] = useBankState();
 
-  const [notification, notify, handleError] = useLocalNotification()
+  const [notification, notify, handleError] = useLocalNotification();
 
-  const { config, api } = useBankCoreApiContext()
+  const { config, api } = useBankCoreApiContext();
 
   async function doTransfer() {
     await handleError(async () => {
       if (!creds) return;
-      const resp = await api.confirmWithdrawalById(creds, 
withdrawUri.withdrawalOperationId);
+      const resp = await api.confirmWithdrawalById(
+        creds,
+        withdrawUri.withdrawalOperationId,
+      );
       if (resp.type === "ok") {
-        mutate(() => true)// clean any info that we have
+        mutate(() => true); // clean any info that we have
         if (!settings.showWithdrawalSuccess) {
-          notifyInfo(i18n.str`Wire transfer completed!`)
+          notifyInfo(i18n.str`Wire transfer completed!`);
         }
       } else {
         switch (resp.case) {
-          case TalerErrorCode.BANK_CONFIRM_ABORT_CONFLICT: return notify({
-            type: "error",
-            title: i18n.str`The withdrawal has been aborted previously and 
can't be confirmed`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          });
-          case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: return notify({
-            type: "error",
-            title: i18n.str`The withdrawal operation can't be confirmed before 
a wallet accepted the transaction.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          });
-          case HttpStatusCode.BadRequest: return notify({
-            type: "error",
-            title: i18n.str`The operation id is invalid.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case HttpStatusCode.NotFound: return notify({
-            type: "error",
-            title: i18n.str`The operation was not found.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return notify({
-            type: "error",
-            title: i18n.str`Your balance is not enough for the operation.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
+          case TalerErrorCode.BANK_CONFIRM_ABORT_CONFLICT:
+            return notify({
+              type: "error",
+              title: i18n.str`The withdrawal has been aborted previously and 
can't be confirmed`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_CONFIRM_INCOMPLETE:
+            return notify({
+              type: "error",
+              title: i18n.str`The withdrawal operation can't be confirmed 
before a wallet accepted the transaction.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case HttpStatusCode.BadRequest:
+            return notify({
+              type: "error",
+              title: i18n.str`The operation id is invalid.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case HttpStatusCode.NotFound:
+            return notify({
+              type: "error",
+              title: i18n.str`The operation was not found.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_UNALLOWED_DEBIT:
+            return notify({
+              type: "error",
+              title: i18n.str`Your balance is not enough for the operation.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
           case HttpStatusCode.Accepted: {
             updateBankState("currentChallenge", {
               operation: "confirm-withdrawal",
               id: String(resp.body.challenge_id),
               sent: AbsoluteTime.never(),
               request: withdrawUri.withdrawalOperationId,
-            })
-            return onAuthorizationRequired()
+            });
+            return onAuthorizationRequired();
           }
-          default: assertUnreachable(resp)
+          default:
+            assertUnreachable(resp);
         }
       }
-    })
+    });
   }
 
   async function doCancel() {
     await handleError(async () => {
       if (!creds) return;
-      const resp = await api.abortWithdrawalById(creds, 
withdrawUri.withdrawalOperationId);
+      const resp = await api.abortWithdrawalById(
+        creds,
+        withdrawUri.withdrawalOperationId,
+      );
       if (resp.type === "ok") {
         onAborted();
       } else {
         switch (resp.case) {
-          case HttpStatusCode.Conflict: return notify({
-            type: "error",
-            title: i18n.str`The reserve operation has been confirmed 
previously and can't be aborted`
-          });
-          case HttpStatusCode.BadRequest: return notify({
-            type: "error",
-            title: i18n.str`The operation id is invalid.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case HttpStatusCode.NotFound: return notify({
-            type: "error",
-            title: i18n.str`The operation was not found.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
+          case HttpStatusCode.Conflict:
+            return notify({
+              type: "error",
+              title: i18n.str`The reserve operation has been confirmed 
previously and can't be aborted`,
+            });
+          case HttpStatusCode.BadRequest:
+            return notify({
+              type: "error",
+              title: i18n.str`The operation id is invalid.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case HttpStatusCode.NotFound:
+            return notify({
+              type: "error",
+              title: i18n.str`The operation was not found.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
           default: {
-            assertUnreachable(resp)
+            assertUnreachable(resp);
           }
         }
       }
-    })
+    });
   }
 
   return (
@@ -174,74 +186,100 @@ export function WithdrawalConfirmationQuestion({
             <i18n.Translate>Confirm the withdrawal operation</i18n.Translate>
           </h3>
           <div class="mt-3 text-sm leading-6">
-
             <ShouldBeSameUser username={details.username}>
               <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 
md:grid-cols-2 bg-gray-100 my-4 px-4 pb-4 rounded-lg">
                 <form
                   class="bg-white shadow-sm ring-1 ring-gray-900/5 
sm:rounded-xl md:col-span-2"
                   autoCapitalize="none"
                   autoCorrect="off"
-                  onSubmit={e => {
-                    e.preventDefault()
+                  onSubmit={(e) => {
+                    e.preventDefault();
                   }}
                 >
                   <div class="px-4 mt-4">
                     <div class="w-full">
                       <div class="px-4 sm:px-0 text-sm">
-                        <p><i18n.Translate>Wire transfer 
details</i18n.Translate></p>
+                        <p>
+                          <i18n.Translate>Wire transfer 
details</i18n.Translate>
+                        </p>
                       </div>
                       <div class="mt-6 border-t border-gray-100">
                         <dl class="divide-y divide-gray-100">
                           {((): VNode => {
                             switch (details.account.targetType) {
                               case "iban": {
-                                const p = details.account as PaytoUriIBAN
-                                const name = p.params["receiver-name"]
-                                return <Fragment>
-                                  <div class="px-4 py-2 sm:grid sm:grid-cols-3 
sm:gap-4 sm:px-0">
-                                    <dt class="text-sm font-medium leading-6 
text-gray-900">
-                                      <i18n.Translate>Taler Exchange 
operator's account</i18n.Translate>
-                                    </dt>
-                                    <dd class="mt-1 text-sm leading-6 
text-gray-700 sm:col-span-2 sm:mt-0">{p.iban}</dd>
-                                  </div>
-                                  {name &&
+                                const p = details.account as PaytoUriIBAN;
+                                const name = p.params["receiver-name"];
+                                return (
+                                  <Fragment>
                                     <div class="px-4 py-2 sm:grid 
sm:grid-cols-3 sm:gap-4 sm:px-0">
                                       <dt class="text-sm font-medium leading-6 
text-gray-900">
-                                        <i18n.Translate>Taler Exchange 
operator's name</i18n.Translate>
+                                        <i18n.Translate>
+                                          Taler Exchange operator's account
+                                        </i18n.Translate>
                                       </dt>
-                                      <dd class="mt-1 text-sm leading-6 
text-gray-700 sm:col-span-2 sm:mt-0">{p.params["receiver-name"]}</dd>
+                                      <dd class="mt-1 text-sm leading-6 
text-gray-700 sm:col-span-2 sm:mt-0">
+                                        {p.iban}
+                                      </dd>
                                     </div>
-                                  }
-                                </Fragment>
+                                    {name && (
+                                      <div class="px-4 py-2 sm:grid 
sm:grid-cols-3 sm:gap-4 sm:px-0">
+                                        <dt class="text-sm font-medium 
leading-6 text-gray-900">
+                                          <i18n.Translate>
+                                            Taler Exchange operator's name
+                                          </i18n.Translate>
+                                        </dt>
+                                        <dd class="mt-1 text-sm leading-6 
text-gray-700 sm:col-span-2 sm:mt-0">
+                                          {p.params["receiver-name"]}
+                                        </dd>
+                                      </div>
+                                    )}
+                                  </Fragment>
+                                );
                               }
                               case "x-taler-bank": {
-                                const p = details.account as PaytoUriTalerBank
-                                const name = p.params["receiver-name"]
-                                return <Fragment>
-                                  <div class="px-4 py-2 sm:grid sm:grid-cols-3 
sm:gap-4 sm:px-0">
-                                    <dt class="text-sm font-medium leading-6 
text-gray-900">
-                                      <i18n.Translate>Taler Exchange 
operator's account</i18n.Translate>
-                                    </dt>
-                                    <dd class="mt-1 text-sm leading-6 
text-gray-700 sm:col-span-2 sm:mt-0">{p.account}</dd>
-                                  </div>
-                                  {name &&
+                                const p = details.account as PaytoUriTalerBank;
+                                const name = p.params["receiver-name"];
+                                return (
+                                  <Fragment>
                                     <div class="px-4 py-2 sm:grid 
sm:grid-cols-3 sm:gap-4 sm:px-0">
                                       <dt class="text-sm font-medium leading-6 
text-gray-900">
-                                        <i18n.Translate>Taler Exchange 
operator's name</i18n.Translate>
+                                        <i18n.Translate>
+                                          Taler Exchange operator's account
+                                        </i18n.Translate>
                                       </dt>
-                                      <dd class="mt-1 text-sm leading-6 
text-gray-700 sm:col-span-2 sm:mt-0">{p.params["receiver-name"]}</dd>
+                                      <dd class="mt-1 text-sm leading-6 
text-gray-700 sm:col-span-2 sm:mt-0">
+                                        {p.account}
+                                      </dd>
                                     </div>
-                                  }
-                                </Fragment>
+                                    {name && (
+                                      <div class="px-4 py-2 sm:grid 
sm:grid-cols-3 sm:gap-4 sm:px-0">
+                                        <dt class="text-sm font-medium 
leading-6 text-gray-900">
+                                          <i18n.Translate>
+                                            Taler Exchange operator's name
+                                          </i18n.Translate>
+                                        </dt>
+                                        <dd class="mt-1 text-sm leading-6 
text-gray-700 sm:col-span-2 sm:mt-0">
+                                          {p.params["receiver-name"]}
+                                        </dd>
+                                      </div>
+                                    )}
+                                  </Fragment>
+                                );
                               }
                               default:
-                                return <div class="px-4 py-2 sm:grid 
sm:grid-cols-3 sm:gap-4 sm:px-0">
-                                  <dt class="text-sm font-medium leading-6 
text-gray-900">
-                                    <i18n.Translate>Taler Exchange operator's 
account</i18n.Translate>
-                                  </dt>
-                                  <dd class="mt-1 text-sm leading-6 
text-gray-700 sm:col-span-2 sm:mt-0">{details.account.targetPath}</dd>
-                                </div>
-
+                                return (
+                                  <div class="px-4 py-2 sm:grid sm:grid-cols-3 
sm:gap-4 sm:px-0">
+                                    <dt class="text-sm font-medium leading-6 
text-gray-900">
+                                      <i18n.Translate>
+                                        Taler Exchange operator's account
+                                      </i18n.Translate>
+                                    </dt>
+                                    <dd class="mt-1 text-sm leading-6 
text-gray-700 sm:col-span-2 sm:mt-0">
+                                      {details.account.targetPath}
+                                    </dd>
+                                  </div>
+                                );
                             }
                           })()}
                           <div class="px-4 py-2 sm:grid sm:grid-cols-3 
sm:gap-4 sm:px-0">
@@ -249,58 +287,73 @@ export function WithdrawalConfirmationQuestion({
                               <i18n.Translate>Amount</i18n.Translate>
                             </dt>
                             <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">
-                              <RenderAmount value={details.amount} 
spec={config.currency_specification} />
+                              <RenderAmount
+                                value={details.amount}
+                                spec={config.currency_specification}
+                              />
                             </dd>
                           </div>
                         </dl>
                       </div>
                     </div>
-
                   </div>
 
                   <div class="flex items-center justify-between gap-x-6 
border-t border-gray-900/10 px-4 py-4 sm:px-8">
-                    <button type="button" class="text-sm font-semibold 
leading-6 text-gray-900"
+                    <button
+                      type="button"
+                      class="text-sm font-semibold leading-6 text-gray-900"
                       onClick={doCancel}
                     >
-                      <i18n.Translate>Cancel</i18n.Translate></button>
-                    <button type="submit"
+                      <i18n.Translate>Cancel</i18n.Translate>
+                    </button>
+                    <button
+                      type="submit"
                       class="disabled:opacity-50 disabled:cursor-default 
cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold 
text-white shadow-sm hover:bg-indigo-500 focus-visible:outline 
focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
                       onClick={(e) => {
-                        e.preventDefault()
-                        doTransfer()
+                        e.preventDefault();
+                        doTransfer();
                       }}
                     >
                       <i18n.Translate>Transfer</i18n.Translate>
                     </button>
                   </div>
-
                 </form>
               </div>
             </ShouldBeSameUser>
           </div>
         </div>
       </div>
-
-    </Fragment >
+    </Fragment>
   );
 }
 
-export function ShouldBeSameUser({ username, children }: { username: string, 
children: ComponentChildren }): VNode {
+export function ShouldBeSameUser({
+  username,
+  children,
+}: {
+  username: string;
+  children: ComponentChildren;
+}): VNode {
   const { state: credentials } = useBackendState();
-  const { i18n } = useTranslationContext()
+  const { i18n } = useTranslationContext();
   if (credentials.status === "loggedOut") {
-    return <Fragment>
-      <Attention type="info" title={i18n.str`Authentication required`} />
-      <LoginForm currentUser={username} fixedUser />
-    </Fragment>
+    return (
+      <Fragment>
+        <Attention type="info" title={i18n.str`Authentication required`} />
+        <LoginForm currentUser={username} fixedUser />
+      </Fragment>
+    );
   }
   if (credentials.username !== username) {
-    return <Fragment>
-      <Attention type="warning" title={i18n.str`This operation was created 
with other username`} />
-      <LoginForm currentUser={username} fixedUser />
-    </Fragment>
+    return (
+      <Fragment>
+        <Attention
+          type="warning"
+          title={i18n.str`This operation was created with other username`}
+        />
+        <LoginForm currentUser={username} fixedUser />
+      </Fragment>
+    );
   }
-  return <Fragment>
-    {children}
-  </Fragment>
-}
\ No newline at end of file
+  return <Fragment>{children}</Fragment>;
+}
diff --git a/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx 
b/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx
index 7ed5e4b0a..e69a4dfb2 100644
--- a/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -14,32 +14,26 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import {
-  Logger,
-  parseWithdrawUri,
-  stringifyWithdrawUri
-} from "@gnu-taler/taler-util";
-import {
-  Attention,
-  useTranslationContext
-} from "@gnu-taler/web-util/browser";
-import { Fragment, VNode, h } from "preact";
+import { parseWithdrawUri, stringifyWithdrawUri } from "@gnu-taler/taler-util";
+import { Attention, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { VNode, h } from "preact";
 import { useBankCoreApiContext } from "../context/config.js";
 import { useBankState } from "../hooks/bank-state.js";
+import { RouteDefinition } from "../route.js";
 import { WithdrawalQRCode } from "./WithdrawalQRCode.js";
 
-const logger = new Logger("AccountPage");
-
 export function WithdrawalOperationPage({
   operationId,
   onAuthorizationRequired,
-  onContinue,
+  onOperationAborted,
+  routeClose,
 }: {
   onAuthorizationRequired: () => void;
   operationId: string;
-  onContinue: () => void;
+  onOperationAborted: () => void;
+  routeClose: RouteDefinition<Record<string, never>>;
 }): VNode {
-  const { api } = useBankCoreApiContext()
+  const { api } = useBankCoreApiContext();
   const uri = stringifyWithdrawUri({
     bankIntegrationApiBaseUrl: api.getIntegrationAPI().baseUrl,
     withdrawalOperationId: operationId,
@@ -48,25 +42,26 @@ export function WithdrawalOperationPage({
   const { i18n } = useTranslationContext();
   const [, updateBankState] = useBankState();
 
-
   if (!parsedUri) {
-    return <Attention type="danger" title={i18n.str`The Withdrawal URI is not 
valid`}>
-      {uri}
-    </Attention>
+    return (
+      <Attention
+        type="danger"
+        title={i18n.str`The Withdrawal URI is not valid`}
+      >
+        {uri}
+      </Attention>
+    );
   }
 
   return (
     <WithdrawalQRCode
       withdrawUri={parsedUri}
       onAuthorizationRequired={onAuthorizationRequired}
-      onClose={() => {
-        updateBankState("currentWithdrawalOperationId", undefined)
-        onContinue()
+      onOperationAborted={() => {
+        updateBankState("currentWithdrawalOperationId", undefined);
+        onOperationAborted();
       }}
+      routeClose={routeClose}
     />
   );
 }
-
-export function assertUnreachable(x: never): never {
-  throw new Error("Didn't expect to get here");
-}
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx 
b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 787eeca52..3cf552f39 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -17,26 +17,29 @@
 import {
   Amounts,
   HttpStatusCode,
-  Logger,
   TalerError,
   WithdrawUriResult,
-  parsePaytoUri
+  assertUnreachable,
+  parsePaytoUri,
 } from "@gnu-taler/taler-util";
-import { Attention, Loading, notifyInfo, useTranslationContext } from 
"@gnu-taler/web-util/browser";
-import { Fragment, VNode, h } from "preact";
+import {
+  Attention,
+  Loading,
+  notifyInfo,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
+import { VNode, h } from "preact";
 import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js";
 import { useWithdrawalDetails } from "../hooks/access.js";
+import { RouteDefinition } from "../route.js";
 import { QrCodeSection } from "./QrCodeSection.js";
 import { WithdrawalConfirmationQuestion } from 
"./WithdrawalConfirmationQuestion.js";
-import { assertUnreachable } from "./WithdrawalOperationPage.js";
-
-const logger = new Logger("WithdrawalQRCode");
 
 interface Props {
   withdrawUri: WithdrawUriResult;
-  onClose: () => void;
-  onAuthorizationRequired: () => void,
-
+  onOperationAborted: () => void;
+  routeClose: RouteDefinition<Record<string, never>>;
+  onAuthorizationRequired: () => void;
 }
 /**
  * Offer the QR code (and a clickable taler://-link) to
@@ -45,90 +48,107 @@ interface Props {
  */
 export function WithdrawalQRCode({
   withdrawUri,
-  onClose,
+  onOperationAborted,
+  routeClose,
   onAuthorizationRequired,
 }: Props): VNode {
   const { i18n } = useTranslationContext();
   const result = useWithdrawalDetails(withdrawUri.withdrawalOperationId);
 
   if (!result) {
-    return <Loading />
+    return <Loading />;
   }
   if (result instanceof TalerError) {
-    return <ErrorLoadingWithDebug error={result} />
+    return <ErrorLoadingWithDebug error={result} />;
   }
   if (result.type === "fail") {
     switch (result.case) {
       case HttpStatusCode.BadRequest:
-      case HttpStatusCode.NotFound: return <OperationNotFound 
onClose={onClose} />
-      default: assertUnreachable(result)
+      case HttpStatusCode.NotFound:
+        return <OperationNotFound routeClose={routeClose} />;
+      default:
+        assertUnreachable(result);
     }
   }
 
   const { body: data } = result;
 
   if (data.status === "aborted") {
-    return <section id="main" class="content">
-      <h1 class="nav">{i18n.str`Operation aborted`}</h1>
-      <section>
-        <p>
-          <i18n.Translate>
-            The wire transfer to the Taler Exchange operator's account was 
aborted, your balance
-            was not affected.
-          </i18n.Translate>
-        </p>
-        <p>
-          <i18n.Translate>
-            You can close this page now or continue to the account page.
-          </i18n.Translate>
-        </p>
-        <a class="pure-button pure-button-primary"
-          style={{ float: "right" }}
-          onClick={async (e) => {
-            e.preventDefault();
-            onClose()
-          }}>
-          {i18n.str`Continue`}
-        </a>
-
+    return (
+      <section id="main" class="content">
+        <h1 class="nav">{i18n.str`Operation aborted`}</h1>
+        <section>
+          <p>
+            <i18n.Translate>
+              The wire transfer to the Taler Exchange operator's account was
+              aborted, your balance was not affected.
+            </i18n.Translate>
+          </p>
+          <p>
+            <i18n.Translate>
+              You can close this page now or continue to the account page.
+            </i18n.Translate>
+          </p>
+          <a
+            href={routeClose.url({})}
+            class="pure-button pure-button-primary"
+            style={{ float: "right" }}
+          >
+            <i18n.Translate>Continue</i18n.Translate>
+          </a>
+        </section>
       </section>
-    </section>
+    );
   }
 
   if (data.status === "confirmed") {
-    return <div class="relative ml-auto mr-auto transform overflow-hidden 
rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 
sm:w-full sm:max-w-sm sm:p-6">
-      <div>
-        <div class="mx-auto flex h-12 w-12 items-center justify-center 
rounded-full bg-green-100">
-          <svg class="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" 
stroke-width="1.5" stroke="currentColor" aria-hidden="true">
-            <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 
12.75l6 6 9-13.5" />
-          </svg>
-        </div>
-        <div class="mt-3 text-center sm:mt-5">
-          <h3 class="text-base font-semibold leading-6 text-gray-900" 
id="modal-title">
-            <i18n.Translate>Withdrawal confirmed</i18n.Translate>
-          </h3>
-          <div class="mt-2">
-            <p class="text-sm text-gray-500">
-              <i18n.Translate>
-                The wire transfer to the Taler operator has been initiated. 
You will soon receive the requested amount in your Taler wallet.
-              </i18n.Translate>
-            </p>
+    return (
+      <div class="relative ml-auto mr-auto transform overflow-hidden 
rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 
sm:w-full sm:max-w-sm sm:p-6">
+        <div>
+          <div class="mx-auto flex h-12 w-12 items-center justify-center 
rounded-full bg-green-100">
+            <svg
+              class="h-6 w-6 text-green-600"
+              fill="none"
+              viewBox="0 0 24 24"
+              stroke-width="1.5"
+              stroke="currentColor"
+              aria-hidden="true"
+            >
+              <path
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                d="M4.5 12.75l6 6 9-13.5"
+              />
+            </svg>
+          </div>
+          <div class="mt-3 text-center sm:mt-5">
+            <h3
+              class="text-base font-semibold leading-6 text-gray-900"
+              id="modal-title"
+            >
+              <i18n.Translate>Withdrawal confirmed</i18n.Translate>
+            </h3>
+            <div class="mt-2">
+              <p class="text-sm text-gray-500">
+                <i18n.Translate>
+                  The wire transfer to the Taler operator has been initiated.
+                  You will soon receive the requested amount in your Taler
+                  wallet.
+                </i18n.Translate>
+              </p>
+            </div>
           </div>
         </div>
+        <div class="mt-5 sm:mt-6">
+          <a
+            href={routeClose.url({})}
+            class="inline-flex w-full justify-center rounded-md bg-indigo-600 
px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
+          >
+            <i18n.Translate>Done</i18n.Translate>
+          </a>
+        </div>
       </div>
-      <div class="mt-5 sm:mt-6">
-        <button type="button"
-          class="inline-flex w-full justify-center rounded-md bg-indigo-600 
px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
-          onClick={async (e) => {
-            e.preventDefault();
-            onClose()
-          }}>
-          <i18n.Translate>Done</i18n.Translate>
-        </button>
-      </div>
-    </div>
-
-
+    );
   }
   if (data.status === "pending") {
     return (
@@ -136,33 +156,55 @@ export function WithdrawalQRCode({
         withdrawUri={withdrawUri}
         onAborted={() => {
           notifyInfo(i18n.str`Operation canceled`);
-          onClose()
+          onOperationAborted();
         }}
       />
     );
   }
 
-  const account = !data.selected_exchange_account ? undefined : 
parsePaytoUri(data.selected_exchange_account)
+  const account = !data.selected_exchange_account
+    ? undefined
+    : parsePaytoUri(data.selected_exchange_account);
 
   if (!data.selected_reserve_pub && account) {
-    return <Attention type="danger"
-      title={i18n.str`The operation is marked as 'selected' but some step in 
the withdrawal failed`} >
-      <i18n.Translate>The account is selected but no withdrawal identification 
found.</i18n.Translate>
-    </Attention>
+    return (
+      <Attention
+        type="danger"
+        title={i18n.str`The operation is marked as 'selected' but some step in 
the withdrawal failed`}
+      >
+        <i18n.Translate>
+          The account is selected but no withdrawal identification found.
+        </i18n.Translate>
+      </Attention>
+    );
   }
 
   if (!account && data.selected_reserve_pub) {
-    return <Attention type="danger"
-      title={i18n.str`The operation is marked as 'selected' but some step in 
the withdrawal failed`}>
-      <i18n.Translate>There is a withdrawal identification but no account has 
been selected or the selected account is invalid.</i18n.Translate>
-    </Attention>
+    return (
+      <Attention
+        type="danger"
+        title={i18n.str`The operation is marked as 'selected' but some step in 
the withdrawal failed`}
+      >
+        <i18n.Translate>
+          There is a withdrawal identification but no account has been selected
+          or the selected account is invalid.
+        </i18n.Translate>
+      </Attention>
+    );
   }
 
   if (!account || !data.selected_reserve_pub) {
-    return <Attention type="danger"
-      title={i18n.str`The operation is marked as 'selected' but some step in 
the withdrawal failed`}>
-      <i18n.Translate>No withdrawal ID found and no account has been selected 
or the selected account is invalid.</i18n.Translate>
-    </Attention>
+    return (
+      <Attention
+        type="danger"
+        title={i18n.str`The operation is marked as 'selected' but some step in 
the withdrawal failed`}
+      >
+        <i18n.Translate>
+          No withdrawal ID found and no account has been selected or the
+          selected account is invalid.
+        </i18n.Translate>
+      </Attention>
+    );
   }
 
   return (
@@ -172,53 +214,71 @@ export function WithdrawalQRCode({
         username: data.username,
         account,
         reserve: data.selected_reserve_pub,
-        amount: Amounts.parseOrThrow(data.amount)
+        amount: Amounts.parseOrThrow(data.amount),
       }}
       onAuthorizationRequired={onAuthorizationRequired}
       onAborted={() => {
         notifyInfo(i18n.str`Operation canceled`);
-        onClose()
+        onOperationAborted();
       }}
     />
   );
 }
 
-
-export function OperationNotFound({ onClose }: { onClose: (() => void) | 
undefined }): VNode {
+export function OperationNotFound({
+  routeClose,
+}: {
+  routeClose: RouteDefinition<Record<string, never>> | undefined;
+}): VNode {
   const { i18n } = useTranslationContext();
-  return <div class="relative ml-auto mr-auto transform overflow-hidden 
rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 
sm:w-full sm:max-w-sm sm:p-6">
-    <div>
-      <div class="mx-auto flex h-12 w-12 items-center justify-center 
rounded-full bg-red-100 ">
-        <svg class="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" 
stroke-width="1.5" stroke="currentColor" aria-hidden="true">
-          <path stroke-linecap="round" stroke-linejoin="round" d="M12 
9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 
1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 
15.75h.007v.008H12v-.008z" />
-        </svg>
-      </div>
+  return (
+    <div class="relative ml-auto mr-auto transform overflow-hidden rounded-lg 
bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full 
sm:max-w-sm sm:p-6">
+      <div>
+        <div class="mx-auto flex h-12 w-12 items-center justify-center 
rounded-full bg-red-100 ">
+          <svg
+            class="h-6 w-6 text-red-600"
+            fill="none"
+            viewBox="0 0 24 24"
+            stroke-width="1.5"
+            stroke="currentColor"
+            aria-hidden="true"
+          >
+            <path
+              stroke-linecap="round"
+              stroke-linejoin="round"
+              d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 
3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 
3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
+            />
+          </svg>
+        </div>
 
-      <div class="mt-3 text-center sm:mt-5">
-        <h3 class="text-base font-semibold leading-6 text-gray-900" 
id="modal-title">
-          <i18n.Translate>Operation not found</i18n.Translate>
-        </h3>
-        <div class="mt-2">
-          <p class="text-sm text-gray-500">
-            <i18n.Translate>
-              This operation is not known by the server. The operation id is 
wrong or the
-              server deleted the operation information before reaching here.
-            </i18n.Translate>
-          </p>
+        <div class="mt-3 text-center sm:mt-5">
+          <h3
+            class="text-base font-semibold leading-6 text-gray-900"
+            id="modal-title"
+          >
+            <i18n.Translate>Operation not found</i18n.Translate>
+          </h3>
+          <div class="mt-2">
+            <p class="text-sm text-gray-500">
+              <i18n.Translate>
+                This operation is not known by the server. The operation id is
+                wrong or the server deleted the operation information before
+                reaching here.
+              </i18n.Translate>
+            </p>
+          </div>
         </div>
       </div>
+      {routeClose && (
+        <div class="mt-5 sm:mt-6">
+          <a
+            href={routeClose.url({})}
+            class="inline-flex w-full justify-center rounded-md bg-indigo-600 
px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
+          >
+            <i18n.Translate>Cotinue to dashboard</i18n.Translate>
+          </a>
+        </div>
+      )}
     </div>
-    {onClose &&
-      <div class="mt-5 sm:mt-6">
-        <button type="button"
-          class="inline-flex w-full justify-center rounded-md bg-indigo-600 
px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
-          onClick={async (e) => {
-            e.preventDefault();
-            onClose()
-          }}>
-          <i18n.Translate>Cotinue to dashboard</i18n.Translate>
-        </button>
-      </div>
-    }
-  </div>
-}
\ No newline at end of file
+  );
+}
diff --git a/packages/demobank-ui/src/pages/account/CashoutListForAccount.tsx 
b/packages/demobank-ui/src/pages/account/CashoutListForAccount.tsx
index 1676d8b6a..670bbaea0 100644
--- a/packages/demobank-ui/src/pages/account/CashoutListForAccount.tsx
+++ b/packages/demobank-ui/src/pages/account/CashoutListForAccount.tsx
@@ -1,41 +1,66 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022-2024 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
 import { 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 { ProfileNavigation } from "../ProfileNavigation.js";
-import { CreateNewAccount } from "../admin/CreateNewAccount.js";
 import { CreateCashout } from "../business/CreateCashout.js";
+import { RouteDefinition } from "../../route.js";
 
 interface Props {
-  account: string,
-  onClose: () => void,
-  onAuthorizationRequired: () => void,
-  onSelected: (cid: number) => void
+  account: string;
+  routeClose: RouteDefinition<Record<string, never>>;
+  onAuthorizationRequired: () => void;
+  routeCashoutDetails: RouteDefinition<{ cid: string }>;
 }
 
-export function CashoutListForAccount({ account, onAuthorizationRequired, 
onSelected, onClose }: Props): VNode {
+export function CashoutListForAccount({
+  account,
+  onAuthorizationRequired,
+  routeCashoutDetails,
+  routeClose,
+}: Props): VNode {
   const { i18n } = useTranslationContext();
 
   const { state: credentials } = useBackendState();
 
-  const accountIsTheCurrentUser = credentials.status === "loggedIn" ?
-    credentials.username === account : false
-
-  return <Fragment>
-    {accountIsTheCurrentUser ?
-      <ProfileNavigation current="cashouts" />
-      :
-      <h1 class="text-base font-semibold leading-6 text-gray-900">
-        <i18n.Translate>Cashout for account {account}</i18n.Translate>
-      </h1>
-    }
-
-    <CreateCashout focus onCancel={onClose} 
onAuthorizationRequired={onAuthorizationRequired} account={account} />
-
-    <Cashouts
-      account={account}
-      onSelected={onSelected}
-    />
-  </Fragment>
-}
+  const accountIsTheCurrentUser =
+    credentials.status === "loggedIn"
+      ? credentials.username === account
+      : false;
 
+  return (
+    <Fragment>
+      {accountIsTheCurrentUser ? (
+        <ProfileNavigation current="cashouts" />
+      ) : (
+        <h1 class="text-base font-semibold leading-6 text-gray-900">
+          <i18n.Translate>Cashout for account {account}</i18n.Translate>
+        </h1>
+      )}
+
+      <CreateCashout
+        focus
+        routeClose={routeClose}
+        onAuthorizationRequired={onAuthorizationRequired}
+        account={account}
+      />
+
+      <Cashouts account={account} routeCashoutDetails={routeCashoutDetails} />
+    </Fragment>
+  );
+}
diff --git a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx 
b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
index 0dfdb39f3..9f8fb72bc 100644
--- a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
+++ b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
@@ -1,112 +1,156 @@
-import { AbsoluteTime, HttpStatusCode, TalerCorebankApi, TalerError, 
TalerErrorCode, TranslatedString } from "@gnu-taler/taler-util";
-import { Loading, LocalNotificationBanner, notifyInfo, useLocalNotification, 
useTranslationContext } from "@gnu-taler/web-util/browser";
+/*
+ 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 {
+  AbsoluteTime,
+  HttpStatusCode,
+  TalerCorebankApi,
+  TalerError,
+  TalerErrorCode,
+  TranslatedString,
+  assertUnreachable,
+} from "@gnu-taler/taler-util";
+import {
+  Loading,
+  LocalNotificationBanner,
+  notifyInfo,
+  useLocalNotification,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
 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 { useBankState } from "../../hooks/bank-state.js";
+import { RouteDefinition } from "../../route.js";
 import { LoginForm } from "../LoginForm.js";
 import { ProfileNavigation } from "../ProfileNavigation.js";
-import { assertUnreachable } from "../WithdrawalOperationPage.js";
 import { AccountForm } from "../admin/AccountForm.js";
-import { useBankState } from "../../hooks/bank-state.js";
 
 export function ShowAccountDetails({
   account,
-  onClear,
+  routeClose,
   onUpdateSuccess,
   onAuthorizationRequired,
 }: {
-  onClear?: () => void;
+  routeClose: RouteDefinition<Record<string, never>>;
   onUpdateSuccess: () => void;
-  onAuthorizationRequired: () => void,
+  onAuthorizationRequired: () => void;
   account: string;
 }): VNode {
   const { i18n } = useTranslationContext();
   const { state: credentials } = useBackendState();
-  const creds = credentials.status !== "loggedIn" ? undefined : credentials
-  const { api } = useBankCoreApiContext()
-  const accountIsTheCurrentUser = credentials.status === "loggedIn" ?
-    credentials.username === account : false
+  const creds = credentials.status !== "loggedIn" ? undefined : credentials;
+  const { api } = useBankCoreApiContext();
+  const accountIsTheCurrentUser =
+    credentials.status === "loggedIn"
+      ? credentials.username === account
+      : false;
 
   const [update, setUpdate] = useState(false);
-  const [submitAccount, setSubmitAccount] = 
useState<TalerCorebankApi.AccountReconfiguration | undefined>();
-  const [notification, notify, handleError] = useLocalNotification()
-  const [, updateBankState] = useBankState()
+  const [submitAccount, setSubmitAccount] = useState<
+    TalerCorebankApi.AccountReconfiguration | undefined
+  >();
+  const [notification, notify, handleError] = useLocalNotification();
+  const [, updateBankState] = useBankState();
 
   const result = useAccountDetails(account);
   if (!result) {
-    return <Loading />
+    return <Loading />;
   }
   if (result instanceof TalerError) {
-    return <ErrorLoadingWithDebug error={result} />
+    return <ErrorLoadingWithDebug error={result} />;
   }
   if (result.type === "fail") {
     switch (result.case) {
       case HttpStatusCode.Unauthorized:
-      case HttpStatusCode.NotFound: return <LoginForm currentUser={account} />
-      default: assertUnreachable(result)
+      case HttpStatusCode.NotFound:
+        return <LoginForm currentUser={account} />;
+      default:
+        assertUnreachable(result);
     }
   }
 
   async function doUpdate() {
     if (!update || !submitAccount || !creds) return;
     await handleError(async () => {
-      const resp = await api.updateAccount({
-        token: creds.token,
-        username: account,
-      }, submitAccount);
+      const resp = await api.updateAccount(
+        {
+          token: creds.token,
+          username: account,
+        },
+        submitAccount,
+      );
 
       if (resp.type === "ok") {
         notifyInfo(i18n.str`Account updated`);
         onUpdateSuccess();
       } else {
         switch (resp.case) {
-          case HttpStatusCode.Unauthorized: return notify({
-            type: "error",
-            title: i18n.str`The rights to change the account are not 
sufficient`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case HttpStatusCode.NotFound: return notify({
-            type: "error",
-            title: i18n.str`The username was not found`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case TalerErrorCode.BANK_NON_ADMIN_PATCH_LEGAL_NAME: return notify({
-            type: "error",
-            title: i18n.str`You can't change the legal name, please contact 
the your account administrator.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT: return notify({
-            type: "error",
-            title: i18n.str`You can't change the debt limit, please contact 
the your account administrator.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case TalerErrorCode.BANK_NON_ADMIN_PATCH_CASHOUT: return notify({
-            type: "error",
-            title: i18n.str`You can't change the cashout address, please 
contact the your account administrator.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case TalerErrorCode.BANK_MISSING_TAN_INFO: return notify({
-            type: "error",
-            title: i18n.str`No information for the selected authentication 
channel.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
+          case HttpStatusCode.Unauthorized:
+            return notify({
+              type: "error",
+              title: i18n.str`The rights to change the account are not 
sufficient`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case HttpStatusCode.NotFound:
+            return notify({
+              type: "error",
+              title: i18n.str`The username was not found`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_NON_ADMIN_PATCH_LEGAL_NAME:
+            return notify({
+              type: "error",
+              title: i18n.str`You can't change the legal name, please contact 
the your account administrator.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT:
+            return notify({
+              type: "error",
+              title: i18n.str`You can't change the debt limit, please contact 
the your account administrator.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_NON_ADMIN_PATCH_CASHOUT:
+            return notify({
+              type: "error",
+              title: i18n.str`You can't change the cashout address, please 
contact the your account administrator.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_MISSING_TAN_INFO:
+            return notify({
+              type: "error",
+              title: i18n.str`No information for the selected authentication 
channel.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
           case HttpStatusCode.Accepted: {
             updateBankState("currentChallenge", {
               operation: "update-account",
               id: String(resp.body.challenge_id),
               sent: AbsoluteTime.never(),
               request: submitAccount,
-            })
-            return onAuthorizationRequired()
+            });
+            return onAuthorizationRequired();
           }
           case TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED: {
             return notify({
@@ -116,39 +160,53 @@ export function ShowAccountDetails({
               debug: resp.detail,
             });
           }
-          default: assertUnreachable(resp)
+          default:
+            assertUnreachable(resp);
         }
       }
-    })
-
+    });
   }
 
   return (
     <Fragment>
       <LocalNotificationBanner notification={notification} showDebug={true} />
-      {accountIsTheCurrentUser ?
+      {accountIsTheCurrentUser ? (
         <ProfileNavigation current="details" />
-        :
+      ) : (
         <h1 class="text-base font-semibold leading-6 text-gray-900">
           <i18n.Translate>Account "{account}"</i18n.Translate>
         </h1>
-
-      }
+      )}
 
       <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 
bg-gray-100 my-4 px-4 pb-4 rounded-lg">
         <div class="px-4 sm:px-0">
           <h2 class="text-base font-semibold leading-7 text-gray-900">
             <div class="flex items-center justify-between">
               <span class="flex flex-grow flex-col">
-                <span class="text-sm text-black font-semibold leading-6 " 
id="availability-label">
+                <span
+                  class="text-sm text-black font-semibold leading-6 "
+                  id="availability-label"
+                >
                   <i18n.Translate>Change details</i18n.Translate>
                 </span>
               </span>
-              <button type="button" data-enabled={!update} 
class="bg-indigo-600 data-[enabled=true]:bg-gray-200 relative inline-flex h-5 
w-10 flex-shrink-0 cursor-pointer rounded-full ring-2 border-gray-600 
transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 
focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" 
aria-labelledby="availability-label" aria-describedby="availability-description"
+              <button
+                type="button"
+                data-enabled={!update}
+                class="bg-indigo-600 data-[enabled=true]:bg-gray-200 relative 
inline-flex h-5 w-10 flex-shrink-0 cursor-pointer rounded-full ring-2 
border-gray-600 transition-colors duration-200 ease-in-out focus:outline-none 
focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
+                role="switch"
+                aria-checked="false"
+                aria-labelledby="availability-label"
+                aria-describedby="availability-description"
                 onClick={() => {
-                  setUpdate(!update)
-                }}>
-                <span aria-hidden="true" data-enabled={!update} 
class="translate-x-5 data-[enabled=true]:translate-x-0 pointer-events-none 
inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition 
duration-200 ease-in-out"></span>
+                  setUpdate(!update);
+                }}
+              >
+                <span
+                  aria-hidden="true"
+                  data-enabled={!update}
+                  class="translate-x-5 data-[enabled=true]:translate-x-0 
pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow 
ring-0 transition duration-200 ease-in-out"
+                ></span>
               </button>
             </div>
           </h2>
@@ -162,15 +220,14 @@ export function ShowAccountDetails({
           onChange={(a) => setSubmitAccount(a)}
         >
           <div class="flex items-center justify-between gap-x-6 border-t 
border-gray-900/10 px-4 py-4 sm:px-8">
-            {onClear ?
-              <button type="button" class="text-sm font-semibold leading-6 
text-gray-900"
-                onClick={onClear}
-              >
-                <i18n.Translate>Cancel</i18n.Translate>
-              </button>
-              : <div />
-            }
-            <button type="submit"
+            <a
+              href={routeClose.url({})}
+              class="text-sm font-semibold leading-6 text-gray-900"
+            >
+              <i18n.Translate>Cancel</i18n.Translate>
+            </a>
+            <button
+              type="submit"
               class="disabled:opacity-50 disabled:cursor-default 
cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold 
text-white shadow-sm hover:bg-indigo-500 focus-visible:outline 
focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
               disabled={!update || !submitAccount}
               onClick={doUpdate}
@@ -183,4 +240,3 @@ export function ShowAccountDetails({
     </Fragment>
   );
 }
-
diff --git a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx 
b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
index 32e100e43..3b35c1fe1 100644
--- a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
+++ b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
@@ -1,45 +1,76 @@
-import { notifyInfo, useLocalNotification, useTranslationContext } from 
"@gnu-taler/web-util/browser";
+/*
+ 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 {
+  AbsoluteTime,
+  HttpStatusCode,
+  TalerErrorCode,
+  assertUnreachable,
+} from "@gnu-taler/taler-util";
+import {
+  LocalNotificationBanner,
+  ShowInputErrorLabel,
+  notifyInfo,
+  useLocalNotification,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
-import { ShowInputErrorLabel } from "@gnu-taler/web-util/browser";
 import { useBankCoreApiContext } from "../../context/config.js";
 import { useBackendState } from "../../hooks/backend.js";
-import { undefinedIfEmpty, withRuntimeErrorHandling } from "../../utils.js";
+import { useBankState } from "../../hooks/bank-state.js";
+import { RouteDefinition } from "../../route.js";
+import { undefinedIfEmpty } from "../../utils.js";
 import { doAutoFocus } from "../PaytoWireTransferForm.js";
 import { ProfileNavigation } from "../ProfileNavigation.js";
-import { assertUnreachable } from "../WithdrawalOperationPage.js";
-import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
-import { AbsoluteTime, HttpStatusCode, TalerErrorCode } from 
"@gnu-taler/taler-util";
-import { useBankState } from "../../hooks/bank-state.js";
 
 export function UpdateAccountPassword({
   account: accountName,
-  onCancel,
+  routeClose,
   onUpdateSuccess,
   onAuthorizationRequired,
   focus,
 }: {
-  onCancel: () => void;
-  focus?: boolean,
-  onAuthorizationRequired: () => void,
+  routeClose: RouteDefinition<Record<string, never>>;
+  focus?: boolean;
+  onAuthorizationRequired: () => void;
   onUpdateSuccess: () => void;
   account: string;
 }): VNode {
   const { i18n } = useTranslationContext();
   const { state: credentials } = useBackendState();
-  const token = credentials.status !== "loggedIn" ? undefined : 
credentials.token
+  const token =
+    credentials.status !== "loggedIn" ? undefined : credentials.token;
   const { api } = useBankCoreApiContext();
 
   const [current, setCurrent] = useState<string | undefined>();
   const [password, setPassword] = useState<string | undefined>();
   const [repeat, setRepeat] = useState<string | undefined>();
-  const [, updateBankState] = useBankState()
+  const [, updateBankState] = useBankState();
 
-  const accountIsTheCurrentUser = credentials.status === "loggedIn" ?
-    credentials.username === accountName : false
+  const accountIsTheCurrentUser =
+    credentials.status === "loggedIn"
+      ? credentials.username === accountName
+      : false;
 
   const errors = undefinedIfEmpty({
-    current: !accountIsTheCurrentUser ? undefined : !current ? 
i18n.str`required` : undefined,
+    current: !accountIsTheCurrentUser
+      ? undefined
+      : !current
+        ? i18n.str`required`
+        : undefined,
     password: !password ? i18n.str`required` : undefined,
     repeat: !repeat
       ? i18n.str`required`
@@ -47,8 +78,7 @@ export function UpdateAccountPassword({
         ? i18n.str`password doesn't match`
         : undefined,
   });
-  const [notification, notify, handleError] = useLocalNotification()
-
+  const [notification, notify, handleError] = useLocalNotification();
 
   async function doChangePassword() {
     if (!!errors || !password || !token) return;
@@ -56,54 +86,62 @@ export function UpdateAccountPassword({
       const request = {
         old_password: current,
         new_password: password,
-      }
-      const resp = await api.updatePassword({ username: accountName, token }, 
request);
+      };
+      const resp = await api.updatePassword(
+        { username: accountName, token },
+        request,
+      );
       if (resp.type === "ok") {
         notifyInfo(i18n.str`Password changed`);
         onUpdateSuccess();
       } else {
         switch (resp.case) {
-          case HttpStatusCode.Unauthorized: return notify({
-            type: "error",
-            title: i18n.str`Not authorized to change the password, maybe the 
session is invalid.`
-          })
-          case HttpStatusCode.NotFound: return notify({
-            type: "error",
-            title: i18n.str`Account not found`
-          })
-          case TalerErrorCode.BANK_NON_ADMIN_PATCH_MISSING_OLD_PASSWORD: 
return notify({
-            type: "error",
-            title: i18n.str`You need to provide the old password. If you don't 
have it contact your account administrator.`
-          })
-          case TalerErrorCode.BANK_PATCH_BAD_OLD_PASSWORD: return notify({
-            type: "error",
-            title: i18n.str`Your current password doesn't match, can't change 
to a new password.`
-          })
+          case HttpStatusCode.Unauthorized:
+            return notify({
+              type: "error",
+              title: i18n.str`Not authorized to change the password, maybe the 
session is invalid.`,
+            });
+          case HttpStatusCode.NotFound:
+            return notify({
+              type: "error",
+              title: i18n.str`Account not found`,
+            });
+          case TalerErrorCode.BANK_NON_ADMIN_PATCH_MISSING_OLD_PASSWORD:
+            return notify({
+              type: "error",
+              title: i18n.str`You need to provide the old password. If you 
don't have it contact your account administrator.`,
+            });
+          case TalerErrorCode.BANK_PATCH_BAD_OLD_PASSWORD:
+            return notify({
+              type: "error",
+              title: i18n.str`Your current password doesn't match, can't 
change to a new password.`,
+            });
           case HttpStatusCode.Accepted: {
             updateBankState("currentChallenge", {
               operation: "update-password",
               id: String(resp.body.challenge_id),
               sent: AbsoluteTime.never(),
               request,
-            })
-            return onAuthorizationRequired()
+            });
+            return onAuthorizationRequired();
           }
-          default: assertUnreachable(resp)
+          default:
+            assertUnreachable(resp);
         }
       }
-    })
+    });
   }
 
   return (
     <Fragment>
       <LocalNotificationBanner notification={notification} />
-      {accountIsTheCurrentUser ?
-        <ProfileNavigation current="credentials" /> :
+      {accountIsTheCurrentUser ? (
+        <ProfileNavigation current="credentials" />
+      ) : (
         <h1 class="text-base font-semibold leading-6 text-gray-900">
           <i18n.Translate>Account "{accountName}"</i18n.Translate>
         </h1>
-
-      }
+      )}
 
       <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 
bg-gray-100 my-4 px-4 pb-4 rounded-lg">
         <div class="px-4 sm:px-0">
@@ -115,8 +153,8 @@ export function UpdateAccountPassword({
           class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl 
md:col-span-2"
           autoCapitalize="none"
           autoCorrect="off"
-          onSubmit={e => {
-            e.preventDefault()
+          onSubmit={(e) => {
+            e.preventDefault();
           }}
         >
           <div class="px-4 py-6 sm:p-8">
@@ -138,7 +176,7 @@ export function UpdateAccountPassword({
                     data-error={!!errors?.password && password !== undefined}
                     value={password ?? ""}
                     onChange={(e) => {
-                      setPassword(e.currentTarget.value)
+                      setPassword(e.currentTarget.value);
                     }}
                     autocomplete="off"
                   />
@@ -165,7 +203,7 @@ export function UpdateAccountPassword({
                     data-error={!!errors?.repeat && repeat !== undefined}
                     value={repeat ?? ""}
                     onChange={(e) => {
-                      setRepeat(e.currentTarget.value)
+                      setRepeat(e.currentTarget.value);
                     }}
                     // placeholder=""
                     autocomplete="off"
@@ -175,12 +213,12 @@ export function UpdateAccountPassword({
                     isDirty={repeat !== undefined}
                   />
                 </div>
-                <p class="mt-2 text-sm text-gray-500" >
+                <p class="mt-2 text-sm text-gray-500">
                   <i18n.Translate>repeat the same password</i18n.Translate>
                 </p>
               </div>
 
-              {accountIsTheCurrentUser ?
+              {accountIsTheCurrentUser ? (
                 <div class="sm:col-span-5">
                   <label
                     class="block text-sm font-medium leading-6 text-gray-900"
@@ -197,7 +235,7 @@ export function UpdateAccountPassword({
                       data-error={!!errors?.current && current !== undefined}
                       value={current ?? ""}
                       onChange={(e) => {
-                        setCurrent(e.currentTarget.value)
+                        setCurrent(e.currentTarget.value);
                       }}
                       autocomplete="off"
                     />
@@ -206,29 +244,29 @@ export function UpdateAccountPassword({
                       isDirty={current !== undefined}
                     />
                   </div>
-                  <p class="mt-2 text-sm text-gray-500" >
-                    <i18n.Translate>your current password, for 
security</i18n.Translate>
+                  <p class="mt-2 text-sm text-gray-500">
+                    <i18n.Translate>
+                      your current password, for security
+                    </i18n.Translate>
                   </p>
                 </div>
-                : undefined}
-
+              ) : undefined}
             </div>
           </div>
           <div class="flex items-center justify-between gap-x-6 border-t 
border-gray-900/10 px-4 py-4 sm:px-8">
-            {onCancel ?
-              <button type="button" class="text-sm font-semibold leading-6 
text-gray-900"
-                onClick={onCancel}
-              >
-                <i18n.Translate>Cancel</i18n.Translate>
-              </button>
-              : <div />
-            }
-            <button type="submit"
+            <a
+              href={routeClose.url({})}
+              class="text-sm font-semibold leading-6 text-gray-900"
+            >
+              <i18n.Translate>Cancel</i18n.Translate>
+            </a>
+            <button
+              type="submit"
               class="disabled:opacity-50 disabled:cursor-default 
cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold 
text-white shadow-sm hover:bg-indigo-500 focus-visible:outline 
focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
               disabled={!!errors}
               onClick={(e) => {
-                e.preventDefault()
-                doChangePassword()
+                e.preventDefault();
+                doChangePassword();
               }}
             >
               <i18n.Translate>Change</i18n.Translate>
@@ -237,6 +275,5 @@ export function UpdateAccountPassword({
         </form>
       </div>
     </Fragment>
-
   );
-}
\ No newline at end of file
+}
diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx 
b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
index e08fee8bc..05b9d6a72 100644
--- a/packages/demobank-ui/src/pages/admin/AccountForm.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
@@ -1,13 +1,47 @@
-import { AmountString, Amounts, PaytoString, TalerCorebankApi, 
TranslatedString, buildPayto, parsePaytoUri, stringifyPaytoUri } from 
"@gnu-taler/taler-util";
-import { Attention, CopyButton, ShowInputErrorLabel, useTranslationContext } 
from "@gnu-taler/web-util/browser";
-import { ComponentChildren, Fragment, VNode, h } from "preact";
+/*
+ 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 {
+  AmountString,
+  Amounts,
+  PaytoString,
+  TalerCorebankApi,
+  TranslatedString,
+  assertUnreachable,
+  buildPayto,
+  parsePaytoUri,
+  stringifyPaytoUri,
+} from "@gnu-taler/taler-util";
+import {
+  Attention,
+  CopyButton,
+  ShowInputErrorLabel,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
+import { ComponentChildren, VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { VersionHint, useBankCoreApiContext } from "../../context/config.js";
-import { ErrorMessageMappingFor, PartialButDefined, TanChannel, 
WithIntermediate, undefinedIfEmpty, validateIBAN } from "../../utils.js";
+import { useBackendState } from "../../hooks/backend.js";
+import {
+  ErrorMessageMappingFor,
+  TanChannel,
+  undefinedIfEmpty,
+  validateIBAN,
+} from "../../utils.js";
 import { InputAmount, doAutoFocus } from "../PaytoWireTransferForm.js";
-import { assertUnreachable } from "../WithdrawalOperationPage.js";
 import { getRandomPassword } from "../rnd.js";
-import { useBackendState } from "../../hooks/backend.js";
 
 const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/;
 const EMAIL_REGEX =
@@ -15,29 +49,29 @@ const EMAIL_REGEX =
 const REGEX_JUST_NUMBERS_REGEX = /^\+[0-9 ]*$/;
 
 export type AccountFormData = {
-  debit_threshold?: string,
-  isExchange?: boolean,
-  isPublic?: boolean,
-  name?: string,
-  username?: string,
-  payto_uri?: string,
-  cashout_payto_uri?: string,
-  email?: string,
-  phone?: string,
-  tan_channel?: TanChannel | "remove",
-}
+  debit_threshold?: string;
+  isExchange?: boolean;
+  isPublic?: boolean;
+  name?: string;
+  username?: string;
+  payto_uri?: string;
+  cashout_payto_uri?: string;
+  email?: string;
+  phone?: string;
+  tan_channel?: TanChannel | "remove";
+};
 
 type ChangeByPurposeType = {
-  "create": (a: TalerCorebankApi.RegisterAccountRequest | undefined) => void,
-  "update": (a: TalerCorebankApi.AccountReconfiguration | undefined) => void,
-  "show": undefined
-}
+  create: (a: TalerCorebankApi.RegisterAccountRequest | undefined) => void;
+  update: (a: TalerCorebankApi.AccountReconfiguration | undefined) => void;
+  show: undefined;
+};
 /**
  * FIXME:
  * is_public is missing on PATCH
  * account email/password should require 2FA
- * 
- * 
+ *
+ *
  * @param param0
  * @returns
  */
@@ -49,14 +83,14 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
   focus,
   children,
 }: {
-  focus?: boolean,
-  children: ComponentChildren,
-  username?: string,
+  focus?: boolean;
+  children: ComponentChildren;
+  username?: string;
   template: TalerCorebankApi.AccountData | undefined;
   onChange: ChangeByPurposeType[PurposeType];
   purpose: PurposeType;
 }): VNode {
-  const { config, hints } = useBankCoreApiContext()
+  const { config, hints } = useBankCoreApiContext();
   const { i18n } = useTranslationContext();
   const { state: credentials } = useBackendState();
   const [form, setForm] = useState<AccountFormData>({});
@@ -65,87 +99,115 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
     ErrorMessageMappingFor<typeof defaultValue> | undefined
   >(undefined);
 
-
   const defaultValue: AccountFormData = {
-    debit_threshold: Amounts.stringifyValue(template?.debit_threshold ?? 
config.default_debit_threshold),
+    debit_threshold: Amounts.stringifyValue(
+      template?.debit_threshold ?? config.default_debit_threshold,
+    ),
     isExchange: template?.is_taler_exchange,
     isPublic: template?.is_public,
     name: template?.name ?? "",
-    cashout_payto_uri: stringifyIbanPayto(template?.cashout_payto_uri) ?? "" 
as PaytoString,
-    payto_uri: stringifyIbanPayto(template?.payto_uri) ?? "" as PaytoString,
+    cashout_payto_uri:
+      stringifyIbanPayto(template?.cashout_payto_uri) ?? ("" as PaytoString),
+    payto_uri: stringifyIbanPayto(template?.payto_uri) ?? ("" as PaytoString),
     email: template?.contact_data?.email ?? "",
     phone: template?.contact_data?.phone ?? "",
     username: username ?? "",
     tan_channel: template?.tan_channel,
-  }
-
-  const OLD_CASHOUT_API = hints.indexOf(VersionHint.CASHOUT_BEFORE_2FA) !== -1
-
-  const showingCurrentUserInfo = credentials.status !== "loggedIn" ? false : 
username === credentials.username
-  const userIsAdmin = credentials.status !== "loggedIn" ? false : 
credentials.isUserAdministrator
-
-  const editableUsername = (purpose === "create")
-  const editableName = (purpose === "create" || purpose === "update" && 
(config.allow_edit_name || userIsAdmin))
-  const editableCashout = showingCurrentUserInfo && (purpose === "create" || 
purpose === "update" && (config.allow_edit_cashout_payto_uri || userIsAdmin))
-  const editableThreshold = userIsAdmin && (purpose === "create" || purpose 
=== "update")
-  const editableAccount = purpose === "create" && userIsAdmin
-
-  const hasPhone = !!defaultValue.phone || !!form.phone
-  const hasEmail = !!defaultValue.email || !!form.email
+  };
+
+  const OLD_CASHOUT_API = hints.indexOf(VersionHint.CASHOUT_BEFORE_2FA) !== -1;
+
+  const showingCurrentUserInfo =
+    credentials.status !== "loggedIn"
+      ? false
+      : username === credentials.username;
+  const userIsAdmin =
+    credentials.status !== "loggedIn" ? false : 
credentials.isUserAdministrator;
+
+  const editableUsername = purpose === "create";
+  const editableName =
+    purpose === "create" ||
+    (purpose === "update" && (config.allow_edit_name || userIsAdmin));
+  const editableCashout =
+    showingCurrentUserInfo &&
+    (purpose === "create" ||
+      (purpose === "update" &&
+        (config.allow_edit_cashout_payto_uri || userIsAdmin)));
+  const editableThreshold =
+    userIsAdmin && (purpose === "create" || purpose === "update");
+  const editableAccount = purpose === "create" && userIsAdmin;
+
+  const hasPhone = !!defaultValue.phone || !!form.phone;
+  const hasEmail = !!defaultValue.email || !!form.email;
 
   function updateForm(newForm: typeof defaultValue): void {
     const cashoutParsed = !newForm.cashout_payto_uri
       ? undefined
-      : buildPayto("iban", newForm.cashout_payto_uri, undefined);;
+      : buildPayto("iban", newForm.cashout_payto_uri, undefined);
 
     const internalParsed = !newForm.payto_uri
       ? undefined
-      : buildPayto("iban", newForm.payto_uri, undefined);;
+      : buildPayto("iban", newForm.payto_uri, undefined);
 
     const trimmedAmountStr = newForm.debit_threshold?.trim();
-    const parsedAmount = 
Amounts.parse(`${config.currency}:${trimmedAmountStr}`);
-
-    const errors = undefinedIfEmpty<ErrorMessageMappingFor<typeof 
defaultValue>>({
-      cashout_payto_uri: (!newForm.cashout_payto_uri
-        ? undefined :
-        !editableCashout ? undefined :
-          !cashoutParsed
-            ? i18n.str`it doesnt have the pattern of an IBAN number` :
-            !cashoutParsed.isKnown || cashoutParsed.targetType !== "iban"
-              ? i18n.str`only "IBAN" target are supported` :
-              !IBAN_REGEX.test(cashoutParsed.iban)
-                ? i18n.str`IBAN should have just uppercased letters and 
numbers` :
-                validateIBAN(cashoutParsed.iban, i18n)),
-      payto_uri: (!newForm.payto_uri
-        ? undefined :
-        !editableAccount ? undefined :
-          !internalParsed
-            ? i18n.str`it doesnt have the pattern of an IBAN number` :
-            !internalParsed.isKnown || internalParsed.targetType !== "iban"
-              ? i18n.str`only "IBAN" target are supported` :
-              !IBAN_REGEX.test(internalParsed.iban)
-                ? i18n.str`IBAN should have just uppercased letters and 
numbers` :
-                validateIBAN(internalParsed.iban, i18n)),
+    const parsedAmount = Amounts.parse(
+      `${config.currency}:${trimmedAmountStr}`,
+    );
+
+    const errors = undefinedIfEmpty<
+      ErrorMessageMappingFor<typeof defaultValue>
+    >({
+      cashout_payto_uri: !newForm.cashout_payto_uri
+        ? undefined
+        : !editableCashout
+          ? undefined
+          : !cashoutParsed
+            ? i18n.str`it doesnt have the pattern of an IBAN number`
+            : !cashoutParsed.isKnown || cashoutParsed.targetType !== "iban"
+              ? i18n.str`only "IBAN" target are supported`
+              : !IBAN_REGEX.test(cashoutParsed.iban)
+                ? i18n.str`IBAN should have just uppercased letters and 
numbers`
+                : validateIBAN(cashoutParsed.iban, i18n),
+      payto_uri: !newForm.payto_uri
+        ? undefined
+        : !editableAccount
+          ? undefined
+          : !internalParsed
+            ? i18n.str`it doesnt have the pattern of an IBAN number`
+            : !internalParsed.isKnown || internalParsed.targetType !== "iban"
+              ? i18n.str`only "IBAN" target are supported`
+              : !IBAN_REGEX.test(internalParsed.iban)
+                ? i18n.str`IBAN should have just uppercased letters and 
numbers`
+                : validateIBAN(internalParsed.iban, i18n),
       email: !newForm.email
-        ? undefined :
-        !EMAIL_REGEX.test(newForm.email)
-          ? i18n.str`it doesnt have the pattern of an email` :
-          undefined,
+        ? undefined
+        : !EMAIL_REGEX.test(newForm.email)
+          ? i18n.str`it doesnt have the pattern of an email`
+          : undefined,
       phone: !newForm.phone
-        ? undefined :
-        !newForm.phone.startsWith("+") // FIXME: better phone number check
-          ? i18n.str`should start with +` :
-          !REGEX_JUST_NUMBERS_REGEX.test(newForm.phone)
+        ? undefined
+        : !newForm.phone.startsWith("+") // FIXME: better phone number check
+          ? i18n.str`should start with +`
+          : !REGEX_JUST_NUMBERS_REGEX.test(newForm.phone)
             ? i18n.str`phone number can't have other than numbers`
-            :
-            undefined,
-      debit_threshold: !editableThreshold ? undefined :
-        !trimmedAmountStr ? undefined :
-          !parsedAmount ? i18n.str`not valid` :
-            undefined,
-      name: !editableName ? undefined : //disabled
-        !newForm.name ? i18n.str`required` : undefined,
-      username: !editableUsername ? undefined : !newForm.username ? 
i18n.str`required` : undefined,
+            : undefined,
+      debit_threshold: !editableThreshold
+        ? undefined
+        : !trimmedAmountStr
+          ? undefined
+          : !parsedAmount
+            ? i18n.str`not valid`
+            : undefined,
+      name: !editableName
+        ? undefined // disabled
+        : !newForm.name
+          ? i18n.str`required`
+          : undefined,
+      username: !editableUsername
+        ? undefined
+        : !newForm.username
+          ? i18n.str`required`
+          : undefined,
     });
     setErrors(errors);
 
@@ -153,20 +215,26 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
     if (!onChange) return;
 
     if (errors) {
-      onChange(undefined)
+      onChange(undefined);
     } else {
-      const cashout = !newForm.cashout_payto_uri ? undefined : 
buildPayto("iban", newForm.cashout_payto_uri, undefined)
-      const cashoutURI = !cashout ? undefined : stringifyPaytoUri(cashout)
+      const cashout = !newForm.cashout_payto_uri
+        ? undefined
+        : buildPayto("iban", newForm.cashout_payto_uri, undefined);
+      const cashoutURI = !cashout ? undefined : stringifyPaytoUri(cashout);
 
-      const internal = !newForm.payto_uri ? undefined : buildPayto("iban", 
newForm.payto_uri, undefined);
-      const internalURI = !internal ? undefined : stringifyPaytoUri(internal)
+      const internal = !newForm.payto_uri
+        ? undefined
+        : buildPayto("iban", newForm.payto_uri, undefined);
+      const internalURI = !internal ? undefined : stringifyPaytoUri(internal);
 
-      const threshold = !parsedAmount ? undefined : 
Amounts.stringify(parsedAmount)
+      const threshold = !parsedAmount
+        ? undefined
+        : Amounts.stringify(parsedAmount);
 
       switch (purpose) {
         case "create": {
-          //typescript doesn't correctly narrow a generic type
-          const callback = onChange as ChangeByPurposeType["create"]
+          // typescript doesn't correctly narrow a generic type
+          const callback = onChange as ChangeByPurposeType["create"];
           const result: TalerCorebankApi.RegisterAccountRequest = {
             name: newForm.name!,
             password: getRandomPassword(),
@@ -180,15 +248,17 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
             payto_uri: internalURI,
             is_public: !!newForm.isPublic,
             is_taler_exchange: !!newForm.isExchange,
-            // @ts-ignore
-            tan_channel: newForm.tan_channel === "remove" ? null : 
newForm.tan_channel,
-          }
-          callback(result)
+            tan_channel:
+              newForm.tan_channel === "remove"
+                ? undefined
+                : newForm.tan_channel,
+          };
+          callback(result);
           return;
         }
         case "update": {
-          //typescript doesn't correctly narrow a generic type
-          const callback = onChange as ChangeByPurposeType["update"]
+          // typescript doesn't correctly narrow a generic type
+          const callback = onChange as ChangeByPurposeType["update"];
 
           const result: TalerCorebankApi.AccountReconfiguration = {
             cashout_payto_uri: cashoutURI,
@@ -199,17 +269,17 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
             debit_threshold: threshold,
             is_public: !!newForm.isPublic,
             name: newForm.name,
-            // @ts-ignore
-            tan_channel: newForm?.tan_channel === "remove" ? null : 
newForm.tan_channel,
-          }
-          callback(result)
+            tan_channel:
+              newForm.tan_channel === "remove" ? null : newForm.tan_channel,
+          };
+          callback(result);
           return;
         }
         case "show": {
           return;
         }
         default: {
-          assertUnreachable(purpose)
+          assertUnreachable(purpose);
         }
       }
     }
@@ -219,13 +289,12 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
       class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl 
md:col-span-2"
       autoCapitalize="none"
       autoCorrect="off"
-      onSubmit={e => {
-        e.preventDefault()
+      onSubmit={(e) => {
+        e.preventDefault();
       }}
     >
       <div class="px-4 py-6 sm:p-8">
         <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
-
           <div class="sm:col-span-5">
             <label
               class="block text-sm font-medium leading-6 text-gray-900"
@@ -256,8 +325,10 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
                 isDirty={form.username !== undefined}
               />
             </div>
-            <p class="mt-2 text-sm text-gray-500" >
-              <i18n.Translate>account identification in the 
bank</i18n.Translate>
+            <p class="mt-2 text-sm text-gray-500">
+              <i18n.Translate>
+                account identification in the bank
+              </i18n.Translate>
             </p>
           </div>
 
@@ -290,26 +361,30 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
                 isDirty={form.name !== undefined}
               />
             </div>
-            <p class="mt-2 text-sm text-gray-500" >
-              <i18n.Translate>name of the person owner the 
account</i18n.Translate>
+            <p class="mt-2 text-sm text-gray-500">
+              <i18n.Translate>
+                name of the person owner the account
+              </i18n.Translate>
             </p>
           </div>
 
-
           <PaytoField
             type="iban"
             name="internal-account"
             label={i18n.str`Internal IBAN`}
-            help={purpose === "create" ?
-              i18n.str`if empty a random account number will be assigned` :
-              i18n.str`account identification for bank transfer`}
+            help={
+              purpose === "create"
+                ? i18n.str`if empty a random account number will be assigned`
+                : i18n.str`account identification for bank transfer`
+            }
             value={(form.payto_uri ?? defaultValue.payto_uri) as PaytoString}
             disabled={!editableAccount}
             error={errors?.payto_uri}
             onChange={(e) => {
-              form.payto_uri = e as PaytoString
-              updateForm(structuredClone(form))
-            }} />
+              form.payto_uri = e as PaytoString;
+              updateForm(structuredClone(form));
+            }}
+          />
 
           <div class="sm:col-span-5">
             <label
@@ -369,71 +444,114 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
             </div>
           </div>
 
-          {showingCurrentUserInfo &&
+          {showingCurrentUserInfo && (
             <PaytoField
               type="iban"
               name="cashout-account"
               label={i18n.str`Cashout IBAN`}
               help={i18n.str`account number where the money is going to be 
sent when doing cashouts`}
-              value={(form.cashout_payto_uri ?? 
defaultValue.cashout_payto_uri) as PaytoString}
+              value={
+                (form.cashout_payto_uri ??
+                  defaultValue.cashout_payto_uri) as PaytoString
+              }
               disabled={!editableCashout}
               error={errors?.cashout_payto_uri}
               onChange={(e) => {
-                form.cashout_payto_uri = e as PaytoString
-                updateForm(structuredClone(form))
-              }} />
-          }
+                form.cashout_payto_uri = e as PaytoString;
+                updateForm(structuredClone(form));
+              }}
+            />
+          )}
 
           <div class="sm:col-span-5">
-            <label for="debit" class="block text-sm font-medium leading-6 
text-gray-900">{i18n.str`Max debt`}</label>
+            <label
+              for="debit"
+              class="block text-sm font-medium leading-6 text-gray-900"
+            >{i18n.str`Max debt`}</label>
             <InputAmount
               name="debit"
               left
               currency={config.currency}
               value={form.debit_threshold ?? defaultValue.debit_threshold}
-              onChange={!editableThreshold ? undefined : (e) => {
-                form.debit_threshold = e as AmountString
-                updateForm(structuredClone(form))
-              }}
+              onChange={
+                !editableThreshold
+                  ? undefined
+                  : (e) => {
+                    form.debit_threshold = e as AmountString;
+                    updateForm(structuredClone(form));
+                  }
+              }
             />
             <ShowInputErrorLabel
-              message={errors?.debit_threshold ? 
String(errors?.debit_threshold) : undefined}
+              message={
+                errors?.debit_threshold
+                  ? String(errors?.debit_threshold)
+                  : undefined
+              }
               isDirty={form.debit_threshold !== undefined}
             />
-            <p class="mt-2 text-sm text-gray-500" >
-              <i18n.Translate>how much is user able to transfer after zero 
balance</i18n.Translate>
+            <p class="mt-2 text-sm text-gray-500">
+              <i18n.Translate>
+                how much is user able to transfer after zero balance
+              </i18n.Translate>
             </p>
           </div>
 
-          {purpose !== "create" || !userIsAdmin ? undefined :
+          {purpose !== "create" || !userIsAdmin ? undefined : (
             <div class="sm:col-span-5">
               <div class="flex items-center justify-between">
                 <span class="flex flex-grow flex-col">
-                  <span class="text-sm text-black font-medium leading-6 " 
id="availability-label">
+                  <span
+                    class="text-sm text-black font-medium leading-6 "
+                    id="availability-label"
+                  >
                     <i18n.Translate>Is this a Taler Exchange?</i18n.Translate>
                   </span>
                 </span>
-                <button type="button" data-enabled={form.isExchange ?? 
defaultValue.isExchange ? "true" : "false"} class="bg-indigo-600 
data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 
cursor-pointer rounded-full border-2 border-transparent transition-colors 
duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 
focus:ring-offset-2" role="switch" aria-checked="false" 
aria-labelledby="availability-label" aria-describedby="availability-de [...]
-
+                <button
+                  type="button"
+                  data-enabled={
+                    form.isExchange ?? defaultValue.isExchange
+                      ? "true"
+                      : "false"
+                  }
+                  class="bg-indigo-600 data-[enabled=false]:bg-gray-200 
relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full 
border-2 border-transparent transition-colors duration-200 ease-in-out 
focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
+                  role="switch"
+                  aria-checked="false"
+                  aria-labelledby="availability-label"
+                  aria-describedby="availability-description"
                   onClick={() => {
-                    form.isExchange = !form.isExchange
-                    updateForm(structuredClone(form))
-                  }}>
-                  <span aria-hidden="true" data-enabled={form.isExchange ?? 
defaultValue.isExchange ? "true" : "false"} class="translate-x-5 
data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 
transform rounded-full bg-white shadow ring-0 transition duration-200 
ease-in-out"></span>
+                    form.isExchange = !form.isExchange;
+                    updateForm(structuredClone(form));
+                  }}
+                >
+                  <span
+                    aria-hidden="true"
+                    data-enabled={
+                      form.isExchange ?? defaultValue.isExchange
+                        ? "true"
+                        : "false"
+                    }
+                    class="translate-x-5 data-[enabled=false]:translate-x-0 
pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow 
ring-0 transition duration-200 ease-in-out"
+                  ></span>
                 </button>
               </div>
             </div>
-          }
+          )}
           {/* channel, not shown if old cashout api */}
-          {OLD_CASHOUT_API ? undefined : config.supported_tan_channels.length 
=== 0 ?
+          {OLD_CASHOUT_API ? undefined : config.supported_tan_channels
+            .length === 0 ? (
             <div class="sm:col-span-5">
-              <Attention type="warning" title={i18n.str`No cashout channel 
available`}>
+              <Attention
+                type="warning"
+                title={i18n.str`No cashout channel available`}
+              >
                 <i18n.Translate>
                   This server doesn't support second factor authentication.
                 </i18n.Translate>
               </Attention>
             </div>
-            :
+          ) : (
             <div class="sm:col-span-5">
               <label
                 class="block text-sm font-medium leading-6 text-gray-900"
@@ -443,85 +561,166 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
               </label>
               <div class="mt-2 max-w-xl text-sm text-gray-500">
                 <div class="px-4 mt-4 grid grid-cols-1 gap-y-6">
-                  {config.supported_tan_channels.indexOf(TanChannel.EMAIL) === 
-1 ? undefined :
-                    <label onClick={(e) => {
-                      if (!hasEmail) return;
-                      if (form.tan_channel === TanChannel.EMAIL) {
-                        form.tan_channel = "remove"
-                      } else {
-                        form.tan_channel = TanChannel.EMAIL
+                  {config.supported_tan_channels.indexOf(TanChannel.EMAIL) ===
+                    -1 ? undefined : (
+                    <label
+                      onClick={(e) => {
+                        if (!hasEmail) return;
+                        if (form.tan_channel === TanChannel.EMAIL) {
+                          form.tan_channel = "remove";
+                        } else {
+                          form.tan_channel = TanChannel.EMAIL;
+                        }
+                        updateForm(structuredClone(form));
+                        e.preventDefault();
+                      }}
+                      data-disabled={purpose === "show" || !hasEmail}
+                      data-selected={
+                        (form.tan_channel ?? defaultValue.tan_channel) ===
+                        TanChannel.EMAIL
                       }
-                      updateForm(structuredClone(form))
-                      e.preventDefault()
-                    }} data-disabled={purpose === "show" || !hasEmail} 
data-selected={(form.tan_channel ?? defaultValue.tan_channel) === 
TanChannel.EMAIL}
-                      class="relative flex 
data-[disabled=false]:cursor-pointer rounded-lg border bg-white 
data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none 
border-gray-300 data-[selected=true]:ring-2 
data-[selected=true]:ring-indigo-600">
-                      <input type="radio" name="channel" value="Newsletter" 
class="sr-only" />
+                      class="relative flex 
data-[disabled=false]:cursor-pointer rounded-lg border bg-white 
data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none 
border-gray-300 data-[selected=true]:ring-2 
data-[selected=true]:ring-indigo-600"
+                    >
+                      <input
+                        type="radio"
+                        name="channel"
+                        value="Newsletter"
+                        class="sr-only"
+                      />
                       <span class="flex flex-1">
                         <span class="flex flex-col">
-                          <span id="project-type-0-label" class="block text-sm 
font-medium text-gray-900 ">
+                          <span
+                            id="project-type-0-label"
+                            class="block text-sm font-medium text-gray-900 "
+                          >
                             <i18n.Translate>Using email</i18n.Translate>
                           </span>
-                          {purpose !== "show" && !hasEmail && i18n.str`add a 
email in your profile to enable this option`}
+                          {purpose !== "show" &&
+                            !hasEmail &&
+                            i18n.str`add a email in your profile to enable 
this option`}
                         </span>
                       </span>
-                      <svg data-selected={(form.tan_channel ?? 
defaultValue.tan_channel) === TanChannel.EMAIL} class="h-5 w-5 text-indigo-600 
data-[selected=false]:hidden" viewBox="0 0 20 20" fill="currentColor" 
aria-hidden="true">
-                        <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 
000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 
10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
+                      <svg
+                        data-selected={
+                          (form.tan_channel ?? defaultValue.tan_channel) ===
+                          TanChannel.EMAIL
+                        }
+                        class="h-5 w-5 text-indigo-600 
data-[selected=false]:hidden"
+                        viewBox="0 0 20 20"
+                        fill="currentColor"
+                        aria-hidden="true"
+                      >
+                        <path
+                          fill-rule="evenodd"
+                          d="M10 18a8 8 0 100-16 8 8 0 000 
16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 
1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
+                          clip-rule="evenodd"
+                        />
                       </svg>
                     </label>
-                  }
-
-                  {config.supported_tan_channels.indexOf(TanChannel.SMS) === 
-1 ? undefined :
-                    <label onClick={(e) => {
-                      if (!hasPhone) return;
-                      if (form.tan_channel === TanChannel.SMS) {
-                        form.tan_channel = "remove"
-                      } else {
-                        form.tan_channel = TanChannel.SMS
+                  )}
+
+                  {config.supported_tan_channels.indexOf(TanChannel.SMS) ===
+                    -1 ? undefined : (
+                    <label
+                      onClick={(e) => {
+                        if (!hasPhone) return;
+                        if (form.tan_channel === TanChannel.SMS) {
+                          form.tan_channel = "remove";
+                        } else {
+                          form.tan_channel = TanChannel.SMS;
+                        }
+                        updateForm(structuredClone(form));
+                        e.preventDefault();
+                      }}
+                      data-disabled={purpose === "show" || !hasPhone}
+                      data-selected={
+                        (form.tan_channel ?? defaultValue.tan_channel) ===
+                        TanChannel.SMS
                       }
-                      updateForm(structuredClone(form))
-                      e.preventDefault()
-                    }} data-disabled={purpose === "show" || !hasPhone} 
data-selected={(form.tan_channel ?? defaultValue.tan_channel) === 
TanChannel.SMS}
-                      class="relative flex 
data-[disabled=false]:cursor-pointer rounded-lg border 
data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none 
border-gray-300 data-[selected=true]:ring-2 
data-[selected=true]:ring-indigo-600">
-                      <input type="radio" name="channel" value="Existing 
Customers" class="sr-only" />
+                      class="relative flex 
data-[disabled=false]:cursor-pointer rounded-lg border 
data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none 
border-gray-300 data-[selected=true]:ring-2 
data-[selected=true]:ring-indigo-600"
+                    >
+                      <input
+                        type="radio"
+                        name="channel"
+                        value="Existing Customers"
+                        class="sr-only"
+                      />
                       <span class="flex flex-1">
                         <span class="flex flex-col">
-                          <span id="project-type-1-label" class="block text-sm 
font-medium text-gray-900">
+                          <span
+                            id="project-type-1-label"
+                            class="block text-sm font-medium text-gray-900"
+                          >
                             <i18n.Translate>Using SMS</i18n.Translate>
                           </span>
-                          {purpose !== "show" && !hasPhone && i18n.str`add a 
phone number in your profile to enable this option`}
+                          {purpose !== "show" &&
+                            !hasPhone &&
+                            i18n.str`add a phone number in your profile to 
enable this option`}
                         </span>
                       </span>
-                      <svg data-selected={(form.tan_channel ?? 
defaultValue.tan_channel) === TanChannel.SMS} class="h-5 w-5 text-indigo-600 
data-[selected=false]:hidden" viewBox="0 0 20 20" fill="currentColor" 
aria-hidden="true">
-                        <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 
000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 
10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
+                      <svg
+                        data-selected={
+                          (form.tan_channel ?? defaultValue.tan_channel) ===
+                          TanChannel.SMS
+                        }
+                        class="h-5 w-5 text-indigo-600 
data-[selected=false]:hidden"
+                        viewBox="0 0 20 20"
+                        fill="currentColor"
+                        aria-hidden="true"
+                      >
+                        <path
+                          fill-rule="evenodd"
+                          d="M10 18a8 8 0 100-16 8 8 0 000 
16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 
1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
+                          clip-rule="evenodd"
+                        />
                       </svg>
                     </label>
-                  }
+                  )}
                 </div>
               </div>
             </div>
-          }
+          )}
 
           <div class="sm:col-span-5">
             <div class="flex items-center justify-between">
               <span class="flex flex-grow flex-col">
-                <span class="text-sm text-black font-medium leading-6 " 
id="availability-label">
+                <span
+                  class="text-sm text-black font-medium leading-6 "
+                  id="availability-label"
+                >
                   <i18n.Translate>Is this account public?</i18n.Translate>
                 </span>
               </span>
-              <button type="button" data-enabled={form.isPublic ?? 
defaultValue.isPublic ? "true" : "false"} class="bg-indigo-600 
data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 
cursor-pointer rounded-full border-2 border-transparent transition-colors 
duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 
focus:ring-offset-2" role="switch" aria-checked="false" 
aria-labelledby="availability-label" aria-describedby="availability-description"
-
+              <button
+                type="button"
+                data-enabled={
+                  form.isPublic ?? defaultValue.isPublic ? "true" : "false"
+                }
+                class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative 
inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 
border-transparent transition-colors duration-200 ease-in-out 
focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
+                role="switch"
+                aria-checked="false"
+                aria-labelledby="availability-label"
+                aria-describedby="availability-description"
                 onClick={() => {
-                  form.isPublic = !form.isPublic
-                  updateForm(structuredClone(form))
-                }}>
-                <span aria-hidden="true" data-enabled={form.isPublic ?? 
defaultValue.isPublic ? "true" : "false"} class="translate-x-5 
data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 
transform rounded-full bg-white shadow ring-0 transition duration-200 
ease-in-out"></span>
+                  form.isPublic = !form.isPublic;
+                  updateForm(structuredClone(form));
+                }}
+              >
+                <span
+                  aria-hidden="true"
+                  data-enabled={
+                    form.isPublic ?? defaultValue.isPublic ? "true" : "false"
+                  }
+                  class="translate-x-5 data-[enabled=false]:translate-x-0 
pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow 
ring-0 transition duration-200 ease-in-out"
+                ></span>
               </button>
             </div>
-            <p class="mt-2 text-sm text-gray-500" >
-              <i18n.Translate>public accounts have their balance publicly 
accesible</i18n.Translate>
+            <p class="mt-2 text-sm text-gray-500">
+              <i18n.Translate>
+                public accounts have their balance publicly accesible
+              </i18n.Translate>
             </p>
           </div>
-
         </div>
       </div>
       {children}
@@ -530,15 +729,16 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
 }
 
 function stringifyIbanPayto(s: PaytoString | undefined): string | undefined {
-  if (s === undefined) return undefined
-  const p = parsePaytoUri(s)
-  if (p === undefined) return undefined
-  if (!p.isKnown) return undefined
-  if (p.targetType !== "iban") return undefined
-  return p.iban
+  if (s === undefined) return undefined;
+  const p = parsePaytoUri(s);
+  if (p === undefined) return undefined;
+  if (!p.isKnown) return undefined;
+  if (p.targetType !== "iban") return undefined;
+  return p.iban;
 }
 
-{/* <div class="sm:col-span-5">
+{
+  /* <div class="sm:col-span-5">
             <label
               class="block text-sm font-medium leading-6 text-gray-900"
               for="cashout"
@@ -572,112 +772,129 @@ function stringifyIbanPayto(s: PaytoString | 
undefined): string | undefined {
             <p class="mt-2 text-sm text-gray-500" >
               <i18n.Translate></i18n.Translate>
             </p>
-          </div> */}
+          </div> */
+}
 
-function PaytoField({ name, label, help, type, value, disabled, onChange, 
error }: { error: TranslatedString | undefined, name: string, label: 
TranslatedString, help: TranslatedString, onChange: (s: string) => void, type: 
"iban" | "x-taler-bank" | "bitcoin", disabled?: boolean, value: string | 
undefined }): VNode {
+function PaytoField({
+  name,
+  label,
+  help,
+  type,
+  value,
+  disabled,
+  onChange,
+  error,
+}: {
+  error: TranslatedString | undefined;
+  name: string;
+  label: TranslatedString;
+  help: TranslatedString;
+  onChange: (s: string) => void;
+  type: "iban" | "x-taler-bank" | "bitcoin";
+  disabled?: boolean;
+  value: string | undefined;
+}): VNode {
   if (type === "iban") {
-    return <div class="sm:col-span-5">
-      <label
-        class="block text-sm font-medium leading-6 text-gray-900"
-        for={name}
-      >
-        {label}
-      </label>
-      <div class="mt-2">
-        <div class="flex justify-between">
-          <input
-            type="text"
-            class="mr-4 w-full block-inline  disabled:bg-gray-100 rounded-md 
border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 
focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
-            name={name}
-            id={name}
-            disabled={disabled}
-            value={value ?? ""}
-            onChange={(e) => {
-              onChange(e.currentTarget.value)
-            }}
-          />
-          <CopyButton
-            class="p-2 rounded-full  text-black shadow-sm  
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
-            getContent={() => value ?? ""}
-          />
+    return (
+      <div class="sm:col-span-5">
+        <label
+          class="block text-sm font-medium leading-6 text-gray-900"
+          for={name}
+        >
+          {label}
+        </label>
+        <div class="mt-2">
+          <div class="flex justify-between">
+            <input
+              type="text"
+              class="mr-4 w-full block-inline  disabled:bg-gray-100 rounded-md 
border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 
focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+              name={name}
+              id={name}
+              disabled={disabled}
+              value={value ?? ""}
+              onChange={(e) => {
+                onChange(e.currentTarget.value);
+              }}
+            />
+            <CopyButton
+              class="p-2 rounded-full  text-black shadow-sm  
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
+              getContent={() => value ?? ""}
+            />
+          </div>
+          <ShowInputErrorLabel message={error} isDirty={value !== undefined} />
         </div>
-        <ShowInputErrorLabel
-          message={error}
-          isDirty={value !== undefined}
-        />
+        <p class="mt-2 text-sm text-gray-500">{help}</p>
       </div>
-      <p class="mt-2 text-sm text-gray-500" >
-        {help}
-      </p>
-    </div>
+    );
   }
   if (type === "x-taler-bank") {
-    return <div class="sm:col-span-5">
-      <label
-        class="block text-sm font-medium leading-6 text-gray-900"
-        for={name}
-      >
-        {label}
-      </label>
-      <div class="mt-2">
-        <div class="flex justify-between">
-          <input
-            type="text"
-            class="mr-4 w-full block-inline  disabled:bg-gray-100 rounded-md 
border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 
focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
-            name={name}
-            id={name}
-            disabled={disabled}
-            value={value ?? ""}
-          />
-          <CopyButton
-            class="p-2 rounded-full  text-black shadow-sm  
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
-            getContent={() => value ?? ""}
-          />
+    return (
+      <div class="sm:col-span-5">
+        <label
+          class="block text-sm font-medium leading-6 text-gray-900"
+          for={name}
+        >
+          {label}
+        </label>
+        <div class="mt-2">
+          <div class="flex justify-between">
+            <input
+              type="text"
+              class="mr-4 w-full block-inline  disabled:bg-gray-100 rounded-md 
border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 
focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+              name={name}
+              id={name}
+              disabled={disabled}
+              value={value ?? ""}
+            />
+            <CopyButton
+              class="p-2 rounded-full  text-black shadow-sm  
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
+              getContent={() => value ?? ""}
+            />
+          </div>
+          <ShowInputErrorLabel message={error} isDirty={value !== undefined} />
         </div>
-        <ShowInputErrorLabel
-          message={error}
-          isDirty={value !== undefined}
-        />
+        <p class="mt-2 text-sm text-gray-500">
+          {/* <i18n.Translate>internal account id</i18n.Translate> */}
+          {help}
+        </p>
       </div>
-      <p class="mt-2 text-sm text-gray-500" >
-        {/* <i18n.Translate>internal account id</i18n.Translate> */}
-        {help}
-      </p>
-    </div>
+    );
   }
   if (type === "bitcoin") {
-    return <div class="sm:col-span-5">
-      <label
-        class="block text-sm font-medium leading-6 text-gray-900"
-        for={name}
-      >
-        {label}
-      </label>
-      <div class="mt-2">
-        <div class="flex justify-between">
-          <input
-            type="text"
-            class="mr-4 w-full block-inline  disabled:bg-gray-100 rounded-md 
border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 
focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
-            name={name}
-            id={name}
-            disabled={disabled}
-            value={value ?? ""}
-          />
-          <CopyButton
-            class="p-2 rounded-full  text-black shadow-sm  
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
-            getContent={() => value ?? ""}
-          />
-          <ShowInputErrorLabel
-            message={error}
-            isDirty={value !== undefined}
-          />
+    return (
+      <div class="sm:col-span-5">
+        <label
+          class="block text-sm font-medium leading-6 text-gray-900"
+          for={name}
+        >
+          {label}
+        </label>
+        <div class="mt-2">
+          <div class="flex justify-between">
+            <input
+              type="text"
+              class="mr-4 w-full block-inline  disabled:bg-gray-100 rounded-md 
border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 
focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+              name={name}
+              id={name}
+              disabled={disabled}
+              value={value ?? ""}
+            />
+            <CopyButton
+              class="p-2 rounded-full  text-black shadow-sm  
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
+              getContent={() => value ?? ""}
+            />
+            <ShowInputErrorLabel
+              message={error}
+              isDirty={value !== undefined}
+            />
+          </div>
         </div>
+        <p class="mt-2 text-sm text-gray-500">
+          {/* <i18n.Translate>bitcoin address</i18n.Translate> */}
+          {help}
+        </p>
       </div>
-      <p class="mt-2 text-sm text-gray-500" >
-        {/* <i18n.Translate>bitcoin address</i18n.Translate> */}
-        {help}
-      </p>
-    </div>
+    );
   }
-  assertUnreachable(type)
+  assertUnreachable(type);
 }
diff --git a/packages/demobank-ui/src/pages/admin/AccountList.tsx 
b/packages/demobank-ui/src/pages/admin/AccountList.tsx
index 4ec25660b..1cee4c58a 100644
--- a/packages/demobank-ui/src/pages/admin/AccountList.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountList.tsx
@@ -1,143 +1,205 @@
-import { Amounts, HttpStatusCode, TalerError } from "@gnu-taler/taler-util";
+/*
+ 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 {
+  Amounts,
+  HttpStatusCode,
+  TalerError,
+  assertUnreachable,
+} from "@gnu-taler/taler-util";
 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 { RenderAmount } from "../PaytoWireTransferForm.js";
-import { assertUnreachable } from "../WithdrawalOperationPage.js";
+import { RouteDefinition } from "../../route.js";
 
 interface Props {
-  onCreateAccount: () => void;
+  routeCreate: RouteDefinition<Record<string, never>>;
 
-  onShowAccountDetails: (aid: string) => void;
-  onRemoveAccount: (aid: string) => void;
-  onUpdateAccountPassword: (aid: string) => void;
-  onShowCashoutForAccount: (aid: string) => void;
+  routeShowAccount: RouteDefinition<{ account: string }>;
+  routeRemoveAccount: RouteDefinition<{ account: string }>;
+  routeUpdatePasswordAccount: RouteDefinition<{ account: string }>;
+  routeShowCashoutsAccount: RouteDefinition<{ account: string }>;
 }
 
-export function AccountList({ onRemoveAccount, onShowAccountDetails, 
onUpdateAccountPassword, onShowCashoutForAccount, onCreateAccount }: Props): 
VNode {
+export function AccountList({
+  routeCreate,
+  routeRemoveAccount,
+  routeShowAccount,
+  routeShowCashoutsAccount,
+  routeUpdatePasswordAccount,
+}: Props): VNode {
   const result = useBusinessAccounts();
   const { i18n } = useTranslationContext();
-  const { config } = useBankCoreApiContext()
+  const { config } = useBankCoreApiContext();
 
   if (!result) {
-    return <Loading />
+    return <Loading />;
   }
   if (result instanceof TalerError) {
-    return <ErrorLoadingWithDebug error={result} />
+    return <ErrorLoadingWithDebug error={result} />;
   }
   if (result.data.type === "fail") {
     switch (result.data.case) {
-      case HttpStatusCode.Unauthorized: return <Fragment />
-      default: assertUnreachable(result.data.case)
+      case HttpStatusCode.Unauthorized:
+        return <Fragment />;
+      default:
+        assertUnreachable(result.data.case);
     }
   }
 
   const { accounts } = result.data.body;
-  return <Fragment>
-    <div class="px-4 sm:px-6 lg:px-8 mt-4">
-      <div class="sm:flex sm:items-center">
-        <div class="sm:flex-auto">
-          <h1 class="text-base font-semibold leading-6 text-gray-900">
-            <i18n.Translate>Accounts</i18n.Translate>
-          </h1>
-          <p class="mt-2 text-sm text-gray-700">
-            <i18n.Translate>A list of all business account in the 
bank.</i18n.Translate>
-          </p>
-        </div>
-        <div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
-          <button type="button" class="block rounded-md bg-indigo-600 px-3 
py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
-            onClick={(e) => {
-              e.preventDefault()
-              onCreateAccount()
-            }}>
-            <i18n.Translate>Create account</i18n.Translate>
-          </button>
+  return (
+    <Fragment>
+      <div class="px-4 sm:px-6 lg:px-8 mt-4">
+        <div class="sm:flex sm:items-center">
+          <div class="sm:flex-auto">
+            <h1 class="text-base font-semibold leading-6 text-gray-900">
+              <i18n.Translate>Accounts</i18n.Translate>
+            </h1>
+            <p class="mt-2 text-sm text-gray-700">
+              <i18n.Translate>
+                A list of all business account in the bank.
+              </i18n.Translate>
+            </p>
+          </div>
+          <div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
+            <a
+              href={routeCreate.url({})}
+              type="button"
+              class="block rounded-md bg-indigo-600 px-3 py-2 text-center 
text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
+            >
+              <i18n.Translate>Create account</i18n.Translate>
+            </a>
+          </div>
         </div>
-      </div>
-      <div class="mt-8 flow-root">
-        <div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
-          <div class="inline-block min-w-full py-2 align-middle sm:px-6 
lg:px-8">
-            {!accounts.length ? (
-              <div></div>
-            ) : (
-              <table class="min-w-full divide-y divide-gray-300">
-                <thead>
-                  <tr>
-                    <th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm 
font-semibold text-gray-900 sm:pl-0">{i18n.str`Username`}</th>
-                    <th scope="col" class="px-3 py-3.5 text-left text-sm 
font-semibold text-gray-900">{i18n.str`Name`}</th>
-                    <th scope="col" class="px-3 py-3.5 text-left text-sm 
font-semibold text-gray-900">{i18n.str`Balance`}</th>
-                    <th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-0">
-                      <span class="sr-only">{i18n.str`Actions`}</span>
-                    </th>
-                  </tr>
-                </thead>
-                <tbody class="divide-y divide-gray-200">
-                  {accounts.map((item, idx) => {
-                    const balance = !item.balance
-                      ? undefined
-                      : Amounts.parse(item.balance.amount);
-                    const noBalance = Amounts.isZero(item.balance.amount)
-                    const balanceIsDebit =
-                      item.balance &&
-                      item.balance.credit_debit_indicator == "debit";
-
-                    return <tr key={idx}>
-                      <td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm 
font-medium text-gray-900 sm:pl-0">
-                        <a href="#" class="text-indigo-600 
hover:text-indigo-900"
-                          onClick={(e) => {
-                            e.preventDefault();
-                            onShowAccountDetails(item.username)
-                          }}
-                        >
-                          {item.username}
-                        </a>
-
-
-                      </td>
-                      <td class="whitespace-nowrap px-3 py-4 text-sm 
text-gray-500">
-                        {item.name}
-                      </td>
-                      <td data-negative={noBalance ? undefined : 
balanceIsDebit ? "true" : "false"} class="whitespace-nowrap px-3 py-4 text-sm 
text-gray-500 data-[negative=false]:text-green-600 
data-[negative=true]:text-red-600 ">
-                        {!balance ? (
-                          i18n.str`unknown`
-                        ) : (
-                          <span class="amount">
-                            <RenderAmount value={balance} 
negative={balanceIsDebit} spec={config.currency_specification} />
-                          </span>
-                        )}
-                      </td>
-                      <td class="relative whitespace-nowrap py-4 pl-3 pr-4 
text-right text-sm font-medium sm:pr-0">
-                        <a href="#" class="text-indigo-600 
hover:text-indigo-900"
-                          onClick={(e) => {
-                            e.preventDefault();
-                            onUpdateAccountPassword(item.username)
-                          }}
-                        >
-                          <i18n.Translate>change password</i18n.Translate>
-                        </a>
-                        <br />
-                        {noBalance ?
-                          <a href="#" class="text-indigo-600 
hover:text-indigo-900" onClick={(e) => {
-                            e.preventDefault();
-                            onRemoveAccount(item.username)
-                          }}
-                          >
-                            <i18n.Translate>remove</i18n.Translate>
-                          </a>
-                          : undefined}
-                      </td>
+        <div class="mt-8 flow-root">
+          <div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
+            <div class="inline-block min-w-full py-2 align-middle sm:px-6 
lg:px-8">
+              {!accounts.length ? (
+                <div></div>
+              ) : (
+                <table class="min-w-full divide-y divide-gray-300">
+                  <thead>
+                    <tr>
+                      <th
+                        scope="col"
+                        class="py-3.5 pl-4 pr-3 text-left text-sm 
font-semibold text-gray-900 sm:pl-0"
+                      >{i18n.str`Username`}</th>
+                      <th
+                        scope="col"
+                        class="px-3 py-3.5 text-left text-sm font-semibold 
text-gray-900"
+                      >{i18n.str`Name`}</th>
+                      <th
+                        scope="col"
+                        class="px-3 py-3.5 text-left text-sm font-semibold 
text-gray-900"
+                      >{i18n.str`Balance`}</th>
+                      <th scope="col" class="relative py-3.5 pl-3 pr-4 
sm:pr-0">
+                        <span class="sr-only">{i18n.str`Actions`}</span>
+                      </th>
                     </tr>
-                  })}
+                  </thead>
+                  <tbody class="divide-y divide-gray-200">
+                    {accounts.map((item, idx) => {
+                      const balance = !item.balance
+                        ? undefined
+                        : Amounts.parse(item.balance.amount);
+                      const noBalance = Amounts.isZero(item.balance.amount);
+                      const balanceIsDebit =
+                        item.balance &&
+                        item.balance.credit_debit_indicator == "debit";
 
-                </tbody>
-              </table>
-            )}
+                      return (
+                        <tr key={idx}>
+                          <td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm 
font-medium text-gray-900 sm:pl-0">
+                            <a
+                              href={routeShowAccount.url({
+                                account: item.username,
+                              })}
+                              class="text-indigo-600 hover:text-indigo-900"
+                            >
+                              {item.username}
+                            </a>
+                          </td>
+                          <td class="whitespace-nowrap px-3 py-4 text-sm 
text-gray-500">
+                            {item.name}
+                          </td>
+                          <td
+                            data-negative={
+                              noBalance
+                                ? undefined
+                                : balanceIsDebit
+                                  ? "true"
+                                  : "false"
+                            }
+                            class="whitespace-nowrap px-3 py-4 text-sm 
text-gray-500 data-[negative=false]:text-green-600 
data-[negative=true]:text-red-600 "
+                          >
+                            {!balance ? (
+                              i18n.str`unknown`
+                            ) : (
+                              <span class="amount">
+                                <RenderAmount
+                                  value={balance}
+                                  negative={balanceIsDebit}
+                                  spec={config.currency_specification}
+                                />
+                              </span>
+                            )}
+                          </td>
+                          <td class="relative whitespace-nowrap py-4 pl-3 pr-4 
text-right text-sm font-medium sm:pr-0">
+                            <a
+                              href={routeUpdatePasswordAccount.url({
+                                account: item.username,
+                              })}
+                              class="text-indigo-600 hover:text-indigo-900"
+                            >
+                              <i18n.Translate>change password</i18n.Translate>
+                            </a>
+                            <br />
+                            <a
+                              href={routeShowCashoutsAccount.url({
+                                account: item.username,
+                              })}
+                              class="text-indigo-600 hover:text-indigo-900"
+                            >
+                              <i18n.Translate>cashouts</i18n.Translate>
+                            </a>
+                            <br />
+                            {noBalance ? (
+                              <a
+                                href={routeRemoveAccount.url({
+                                  account: item.username,
+                                })}
+                                class="text-indigo-600 hover:text-indigo-900"
+                              >
+                                <i18n.Translate>remove</i18n.Translate>
+                              </a>
+                            ) : undefined}
+                          </td>
+                        </tr>
+                      );
+                    })}
+                  </tbody>
+                </table>
+              )}
+            </div>
           </div>
         </div>
       </div>
-    </div>
-  </Fragment>
-
-}
\ No newline at end of file
+    </Fragment>
+  );
+}
diff --git a/packages/demobank-ui/src/pages/admin/AdminHome.tsx 
b/packages/demobank-ui/src/pages/admin/AdminHome.tsx
index b7ef3aa00..4a8eb5b97 100644
--- a/packages/demobank-ui/src/pages/admin/AdminHome.tsx
+++ b/packages/demobank-ui/src/pages/admin/AdminHome.tsx
@@ -1,8 +1,43 @@
-import { AmountString, Amounts, CurrencySpecification, HttpStatusCode, 
TalerCorebankApi, TalerError, assertUnreachable } from "@gnu-taler/taler-util";
-import { Attention, useLang, useTranslationContext } from 
"@gnu-taler/web-util/browser";
-import { format, getDate, getHours, getMonth, getYear, setDate, setHours, 
setMonth, setYear, sub } from "date-fns";
+/*
+ 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 {
+  AmountString,
+  Amounts,
+  CurrencySpecification,
+  HttpStatusCode,
+  TalerCorebankApi,
+  TalerError,
+  assertUnreachable,
+} from "@gnu-taler/taler-util";
+import { Attention, useTranslationContext } from "@gnu-taler/web-util/browser";
+import {
+  format,
+  getDate,
+  getHours,
+  getMonth,
+  getYear,
+  setDate,
+  setHours,
+  setMonth,
+  setYear,
+  sub,
+} from "date-fns";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
+import { privatePages } from "../../Routing.js";
 import { ErrorLoadingWithDebug } from 
"../../components/ErrorLoadingWithDebug.js";
 import { Transactions } from "../../components/Transactions/index.js";
 import { useBankCoreApiContext } from "../../context/config.js";
@@ -10,247 +45,464 @@ import { useConversionInfo, useLastMonitorInfo } from 
"../../hooks/circuit.js";
 import { RenderAmount } from "../PaytoWireTransferForm.js";
 import { WireTransfer } from "../WireTransfer.js";
 import { AccountList } from "./AccountList.js";
+import { RouteDefinition } from "../../route.js";
 
 /**
  * Query account information and show QR code if there is pending withdrawal
  */
 interface Props {
-  onCreateAccount: () => void;
-  onShowAccountDetails: (aid: string) => void;
-  onRemoveAccount: (aid: string) => void;
-  onUpdateAccountPassword: (aid: string) => void;
-  onShowCashoutForAccount: (aid: string) => void;
+  routeCreate: RouteDefinition<Record<string, never>>;
+
+  routeShowAccount: RouteDefinition<{ account: string }>;
+  routeRemoveAccount: RouteDefinition<{ account: string }>;
+  routeUpdatePasswordAccount: RouteDefinition<{ account: string }>;
+  routeShowCashoutsAccount: RouteDefinition<{ account: string }>;
   onAuthorizationRequired: () => void;
 }
-export function AdminHome({ onCreateAccount, onAuthorizationRequired, 
onRemoveAccount, onShowAccountDetails, onShowCashoutForAccount, 
onUpdateAccountPassword }: Props): VNode {
-  return <Fragment>
-    <Metrics />
-    <WireTransfer onAuthorizationRequired={onAuthorizationRequired} />
-
-    <Transactions account="admin" />
-    <AccountList
-      onCreateAccount={onCreateAccount}
-      onRemoveAccount={onRemoveAccount}
-      onShowCashoutForAccount={onShowCashoutForAccount}
-      onShowAccountDetails={onShowAccountDetails}
-      onUpdateAccountPassword={onUpdateAccountPassword}
-    />
+export function AdminHome({
+  routeCreate,
+  routeRemoveAccount,
+  routeShowAccount,
+  routeShowCashoutsAccount,
+  routeUpdatePasswordAccount,
+  onAuthorizationRequired,
+}: Props): VNode {
+  return (
+    <Fragment>
+      <Metrics />
+      <WireTransfer onAuthorizationRequired={onAuthorizationRequired} />
 
-  </Fragment>
+      <Transactions account="admin" />
+      <AccountList
+        routeCreate={routeCreate}
+        routeRemoveAccount={routeRemoveAccount}
+        routeShowAccount={routeShowAccount}
+        routeShowCashoutsAccount={routeShowCashoutsAccount}
+        routeUpdatePasswordAccount={routeUpdatePasswordAccount}
+      />
+    </Fragment>
+  );
 }
 
-function getDateForTimeframe(which: number, timeframe: 
TalerCorebankApi.MonitorTimeframeParam, locale: Locale): string {
-  const time = Date.now()
+function getDateForTimeframe(
+  which: number,
+  timeframe: TalerCorebankApi.MonitorTimeframeParam,
+  locale: Locale,
+): string {
+  const time = Date.now();
   switch (timeframe) {
-    case TalerCorebankApi.MonitorTimeframeParam.hour: return 
`${format(setHours(time, which), "HH", { locale })}hs`;
-    case TalerCorebankApi.MonitorTimeframeParam.day: return 
format(setDate(time, which), "EEEE", { locale });
-    case TalerCorebankApi.MonitorTimeframeParam.month: return 
format(setMonth(time, which), "MMMM", { locale });
-    case TalerCorebankApi.MonitorTimeframeParam.year: return 
format(setYear(time, which), "yyyy", { locale });
-    case TalerCorebankApi.MonitorTimeframeParam.decade: return 
format(setYear(time, which), "yyyy", { locale });
+    case TalerCorebankApi.MonitorTimeframeParam.hour:
+      return `${format(setHours(time, which), "HH", { locale })}hs`;
+    case TalerCorebankApi.MonitorTimeframeParam.day:
+      return format(setDate(time, which), "EEEE", { locale });
+    case TalerCorebankApi.MonitorTimeframeParam.month:
+      return format(setMonth(time, which), "MMMM", { locale });
+    case TalerCorebankApi.MonitorTimeframeParam.year:
+      return format(setYear(time, which), "yyyy", { locale });
+    case TalerCorebankApi.MonitorTimeframeParam.decade:
+      return format(setYear(time, which), "yyyy", { locale });
   }
-  assertUnreachable(timeframe)
+  assertUnreachable(timeframe);
 }
 
-export function getTimeframesForDate(time: Date, timeframe: 
TalerCorebankApi.MonitorTimeframeParam): { current: number, previous: number } {
+export function getTimeframesForDate(
+  time: Date,
+  timeframe: TalerCorebankApi.MonitorTimeframeParam,
+): { current: number; previous: number } {
   switch (timeframe) {
-    case TalerCorebankApi.MonitorTimeframeParam.hour: return {
-      current: getHours(sub(time, { hours: 1 })),
-      previous: getHours(sub(time, { hours: 2 }))
-    }
-    case TalerCorebankApi.MonitorTimeframeParam.day: return {
-      current: getDate(sub(time, { days: 1 })),
-      previous: getDate(sub(time, { days: 2 }))
-    }
-    case TalerCorebankApi.MonitorTimeframeParam.month: return {
-      current: getMonth(sub(time, { months: 1 })),
-      previous: getMonth(sub(time, { months: 2 }))
-    }
-    case TalerCorebankApi.MonitorTimeframeParam.year: return {
-      current: getYear(sub(time, { years: 1 })),
-      previous: getYear(sub(time, { years: 2 }))
-    }
-    case TalerCorebankApi.MonitorTimeframeParam.decade: return {
-      current: getYear(sub(time, { years: 10 })),
-      previous: getYear(sub(time, { years: 20 }))
-    }
-    default: assertUnreachable(timeframe)
+    case TalerCorebankApi.MonitorTimeframeParam.hour:
+      return {
+        current: getHours(sub(time, { hours: 1 })),
+        previous: getHours(sub(time, { hours: 2 })),
+      };
+    case TalerCorebankApi.MonitorTimeframeParam.day:
+      return {
+        current: getDate(sub(time, { days: 1 })),
+        previous: getDate(sub(time, { days: 2 })),
+      };
+    case TalerCorebankApi.MonitorTimeframeParam.month:
+      return {
+        current: getMonth(sub(time, { months: 1 })),
+        previous: getMonth(sub(time, { months: 2 })),
+      };
+    case TalerCorebankApi.MonitorTimeframeParam.year:
+      return {
+        current: getYear(sub(time, { years: 1 })),
+        previous: getYear(sub(time, { years: 2 })),
+      };
+    case TalerCorebankApi.MonitorTimeframeParam.decade:
+      return {
+        current: getYear(sub(time, { years: 10 })),
+        previous: getYear(sub(time, { years: 20 })),
+      };
+    default:
+      assertUnreachable(timeframe);
   }
 }
 
-
 function Metrics(): VNode {
-  const { i18n, dateLocale } = useTranslationContext()
-  const [metricType, setMetricType] = 
useState<TalerCorebankApi.MonitorTimeframeParam>(TalerCorebankApi.MonitorTimeframeParam.hour);
+  const { i18n, dateLocale } = useTranslationContext();
+  const [metricType, setMetricType] =
+    useState<TalerCorebankApi.MonitorTimeframeParam>(
+      TalerCorebankApi.MonitorTimeframeParam.hour,
+    );
   const { config } = useBankCoreApiContext();
-  const respInfo = useConversionInfo()
-  const params = getTimeframesForDate(new Date(), metricType)
+  const respInfo = useConversionInfo();
+  const params = getTimeframesForDate(new Date(), metricType);
 
   const resp = useLastMonitorInfo(params.current, params.previous, metricType);
   if (!resp) return <Fragment />;
   if (resp instanceof TalerError) {
-    return <ErrorLoadingWithDebug error={resp} />
+    return <ErrorLoadingWithDebug error={resp} />;
   }
   if (!respInfo) return <Fragment />;
   if (respInfo instanceof TalerError) {
-    return <ErrorLoadingWithDebug error={respInfo} />
+    return <ErrorLoadingWithDebug error={respInfo} />;
   }
   if (respInfo.type === "fail") {
     switch (respInfo.case) {
       case HttpStatusCode.NotImplemented: {
-        return <Attention type="danger" title={i18n.str`Cashout not 
implemented`}>
-        </Attention>;
+        return (
+          <Attention
+            type="danger"
+            title={i18n.str`Cashout not implemented`}
+          ></Attention>
+        );
       }
-      default: assertUnreachable(respInfo.case)
+      default:
+        assertUnreachable(respInfo.case);
     }
   }
 
   if (resp.current.type !== "ok" || resp.previous.type !== "ok") {
-    return <Fragment />
+    return <Fragment />;
   }
-  return <Fragment>
-    <div class="sm:hidden">
-      <label for="tabs" class="sr-only"><i18n.Translate>Select a 
section</i18n.Translate></label>
-      <select id="tabs" name="tabs" class="block w-full rounded-md 
border-gray-300 focus:border-indigo-500 focus:ring-indigo-500" onChange={(e) => 
{
-        // const op = e.currentTarget.value as typeof metricType
-        setMetricType(e.currentTarget.value as any)
-      }}>
-        <option value={TalerCorebankApi.MonitorTimeframeParam.hour} 
selected={metricType == 
TalerCorebankApi.MonitorTimeframeParam.hour}><i18n.Translate>Last 
hour</i18n.Translate></option>
-        <option value={TalerCorebankApi.MonitorTimeframeParam.day} 
selected={metricType == 
TalerCorebankApi.MonitorTimeframeParam.day}><i18n.Translate>Last 
day</i18n.Translate></option>
-        <option value={TalerCorebankApi.MonitorTimeframeParam.month} 
selected={metricType == 
TalerCorebankApi.MonitorTimeframeParam.month}><i18n.Translate>Last 
month</i18n.Translate></option>
-        <option value={TalerCorebankApi.MonitorTimeframeParam.year} 
selected={metricType == 
TalerCorebankApi.MonitorTimeframeParam.year}><i18n.Translate>Last 
year</i18n.Translate></option>
-      </select>
-    </div>
-    <div class="hidden sm:block">
-      <nav class="isolate flex divide-x divide-gray-200 rounded-lg shadow" 
aria-label="Tabs">
-        <a href="#" onClick={(e) => { e.preventDefault(); 
setMetricType(TalerCorebankApi.MonitorTimeframeParam.hour) }} 
data-selected={metricType == TalerCorebankApi.MonitorTimeframeParam.hour} 
class="rounded-l-lg text-gray-500 hover:text-gray-700 
data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 
overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium 
hover:bg-gray-50 focus:z-10" >
-          <span><i18n.Translate>Last hour</i18n.Translate></span>
-          <span aria-hidden="true" data-selected={metricType == 
TalerCorebankApi.MonitorTimeframeParam.hour} class="bg-transparent 
data-[selected=true]:bg-indigo-500 absolute inset-x-0 bottom-0 h-0.5"></span>
-        </a>
-        <a href="#" onClick={(e) => { e.preventDefault(); 
setMetricType(TalerCorebankApi.MonitorTimeframeParam.day) }} 
data-selected={metricType == TalerCorebankApi.MonitorTimeframeParam.day} 
aria-current="page" class="             text-gray-500 hover:text-gray-700 
data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 
overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium 
hover:bg-gray-50 focus:z-10">
-          <span><i18n.Translate>Last day</i18n.Translate></span>
-          <span aria-hidden="true" data-selected={metricType == 
TalerCorebankApi.MonitorTimeframeParam.day} class="bg-transparent 
data-[selected=true]:bg-indigo-500 absolute inset-x-0 bottom-0 h-0.5"></span>
-        </a>
-        <a href="#" onClick={(e) => { e.preventDefault(); 
setMetricType(TalerCorebankApi.MonitorTimeframeParam.month) }} 
data-selected={metricType == TalerCorebankApi.MonitorTimeframeParam.month} 
class="rounded-r-lg text-gray-500 hover:text-gray-700 
data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 
overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium 
hover:bg-gray-50 focus:z-10">
-          <span><i18n.Translate>Last month</i18n.Translate></span>
-          <span aria-hidden="true" data-selected={metricType == 
TalerCorebankApi.MonitorTimeframeParam.month} class="bg-transparent 
data-[selected=true]:bg-indigo-500 absolute inset-x-0 bottom-0 h-0.5"></span>
-        </a>
-        <a href="#" onClick={(e) => { e.preventDefault(); 
setMetricType(TalerCorebankApi.MonitorTimeframeParam.year) }} 
data-selected={metricType == TalerCorebankApi.MonitorTimeframeParam.year} 
class="rounded-r-lg text-gray-500 hover:text-gray-700 
data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 
overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium 
hover:bg-gray-50 focus:z-10">
-          <span><i18n.Translate>Last Year</i18n.Translate></span>
-          <span aria-hidden="true" data-selected={metricType == 
TalerCorebankApi.MonitorTimeframeParam.year} class="bg-transparent 
data-[selected=true]:bg-indigo-500 absolute inset-x-0 bottom-0 h-0.5"></span>
-        </a>
-      </nav>
-    </div>
-
-    <div class="w-full flex justify-between">
-      <h1 class="text-base font-semibold leading-7 text-gray-900 mt-5">
-        {i18n.str`Trading volume on ${getDateForTimeframe(params.current, 
metricType, dateLocale)} compared to ${getDateForTimeframe(params.previous, 
metricType, dateLocale)}`}
-      </h1>
-    </div>
-    <dl class="mt-5 grid grid-cols-1 md:grid-cols-2  divide-y divide-gray-200 
overflow-hidden rounded-lg bg-white shadow-lg md:divide-x md:divide-y-0">
-
-      {resp.current.body.type !== "with-conversions" || 
resp.previous.body.type !== "with-conversions" ? undefined :
-        <Fragment>
-          <div class="px-4 py-5 sm:p-6">
-            <dt class="text-base font-normal text-gray-900">
-              <i18n.Translate>Cashin</i18n.Translate>
-            </dt>
-            <MetricValue
-              current={resp.current.body.cashinFiatVolume}
-              previous={resp.previous.body.cashinFiatVolume}
-              spec={respInfo.body.fiat_currency_specification}
-            />
-          </div>
-          <div class="px-4 py-5 sm:p-6">
-            <dt class="text-base font-normal text-gray-900">
-              <i18n.Translate>Cashout</i18n.Translate>
-            </dt>
-            <MetricValue
-              current={resp.current.body.cashoutFiatVolume}
-              previous={resp.previous.body.cashoutFiatVolume}
-              spec={respInfo.body.fiat_currency_specification}
-            />
-          </div>
-        </Fragment>
-      }
-      <div class="px-4 py-5 sm:p-6">
-        <dt class="text-base font-normal text-gray-900">
-          <i18n.Translate>Payin</i18n.Translate>
-        </dt>
-        <MetricValue
-          current={resp.current.body.talerInVolume}
-          previous={resp.previous.body.talerInVolume}
-          spec={config.currency_specification}
-        />
+  return (
+    <Fragment>
+      <div class="sm:hidden">
+        <label for="tabs" class="sr-only">
+          <i18n.Translate>Select a section</i18n.Translate>
+        </label>
+        <select
+          id="tabs"
+          name="tabs"
+          class="block w-full rounded-md border-gray-300 
focus:border-indigo-500 focus:ring-indigo-500"
+          onChange={(e) => {
+            // const op = e.currentTarget.value as typeof metricType
+            setMetricType(
+              e.currentTarget
+                .value as unknown as TalerCorebankApi.MonitorTimeframeParam,
+            );
+          }}
+        >
+          <option
+            value={TalerCorebankApi.MonitorTimeframeParam.hour}
+            selected={metricType == 
TalerCorebankApi.MonitorTimeframeParam.hour}
+          >
+            <i18n.Translate>Last hour</i18n.Translate>
+          </option>
+          <option
+            value={TalerCorebankApi.MonitorTimeframeParam.day}
+            selected={metricType == TalerCorebankApi.MonitorTimeframeParam.day}
+          >
+            <i18n.Translate>Last day</i18n.Translate>
+          </option>
+          <option
+            value={TalerCorebankApi.MonitorTimeframeParam.month}
+            selected={
+              metricType == TalerCorebankApi.MonitorTimeframeParam.month
+            }
+          >
+            <i18n.Translate>Last month</i18n.Translate>
+          </option>
+          <option
+            value={TalerCorebankApi.MonitorTimeframeParam.year}
+            selected={metricType == 
TalerCorebankApi.MonitorTimeframeParam.year}
+          >
+            <i18n.Translate>Last year</i18n.Translate>
+          </option>
+        </select>
       </div>
-      <div class="px-4 py-5 sm:p-6">
-        <dt class="text-base font-normal text-gray-900">
-          <i18n.Translate>Payout</i18n.Translate>
-        </dt>
-        <MetricValue
-          current={resp.current.body.talerOutVolume}
-          previous={resp.previous.body.talerOutVolume}
-          spec={config.currency_specification}
-        />
+      <div class="hidden sm:block">
+        {/* FIXME: This should be LINKS */}
+        <nav
+          class="isolate flex divide-x divide-gray-200 rounded-lg shadow"
+          aria-label="Tabs"
+        >
+          <button
+            type="button"
+            onClick={(e) => {
+              e.preventDefault();
+              setMetricType(TalerCorebankApi.MonitorTimeframeParam.hour);
+            }}
+            data-selected={
+              metricType == TalerCorebankApi.MonitorTimeframeParam.hour
+            }
+            class="rounded-l-lg text-gray-500 hover:text-gray-700 
data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 
overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium 
hover:bg-gray-50 focus:z-10"
+          >
+            <span>
+              <i18n.Translate>Last hour</i18n.Translate>
+            </span>
+            <span
+              aria-hidden="true"
+              data-selected={
+                metricType == TalerCorebankApi.MonitorTimeframeParam.hour
+              }
+              class="bg-transparent data-[selected=true]:bg-indigo-500 
absolute inset-x-0 bottom-0 h-0.5"
+            ></span>
+          </button>
+          <button
+            type="button"
+            onClick={(e) => {
+              e.preventDefault();
+              setMetricType(TalerCorebankApi.MonitorTimeframeParam.day);
+            }}
+            data-selected={
+              metricType == TalerCorebankApi.MonitorTimeframeParam.day
+            }
+            class="             text-gray-500 hover:text-gray-700 
data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 
overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium 
hover:bg-gray-50 focus:z-10"
+          >
+            <span>
+              <i18n.Translate>Last day</i18n.Translate>
+            </span>
+            <span
+              aria-hidden="true"
+              data-selected={
+                metricType == TalerCorebankApi.MonitorTimeframeParam.day
+              }
+              class="bg-transparent data-[selected=true]:bg-indigo-500 
absolute inset-x-0 bottom-0 h-0.5"
+            ></span>
+          </button>
+          <button
+            type="button"
+            onClick={(e) => {
+              e.preventDefault();
+              setMetricType(TalerCorebankApi.MonitorTimeframeParam.month);
+            }}
+            data-selected={
+              metricType == TalerCorebankApi.MonitorTimeframeParam.month
+            }
+            class="rounded-r-lg text-gray-500 hover:text-gray-700 
data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 
overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium 
hover:bg-gray-50 focus:z-10"
+          >
+            <span>
+              <i18n.Translate>Last month</i18n.Translate>
+            </span>
+            <span
+              aria-hidden="true"
+              data-selected={
+                metricType == TalerCorebankApi.MonitorTimeframeParam.month
+              }
+              class="bg-transparent data-[selected=true]:bg-indigo-500 
absolute inset-x-0 bottom-0 h-0.5"
+            ></span>
+          </button>
+          <button
+            type="button"
+            onClick={(e) => {
+              e.preventDefault();
+              setMetricType(TalerCorebankApi.MonitorTimeframeParam.year);
+            }}
+            data-selected={
+              metricType == TalerCorebankApi.MonitorTimeframeParam.year
+            }
+            class="rounded-r-lg text-gray-500 hover:text-gray-700 
data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 
overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium 
hover:bg-gray-50 focus:z-10"
+          >
+            <span>
+              <i18n.Translate>Last Year</i18n.Translate>
+            </span>
+            <span
+              aria-hidden="true"
+              data-selected={
+                metricType == TalerCorebankApi.MonitorTimeframeParam.year
+              }
+              class="bg-transparent data-[selected=true]:bg-indigo-500 
absolute inset-x-0 bottom-0 h-0.5"
+            ></span>
+          </button>
+        </nav>
       </div>
-    </dl>
-    <div class="flex justify-end mt-2">
-      <a href="#/download-stats"
-        class="disabled:opacity-50 disabled:cursor-default cursor-pointer 
rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
-      ><i18n.Translate>
-          download stats as CSV
-        </i18n.Translate></a>
-    </div>
-  </Fragment>
 
+      <div class="w-full flex justify-between">
+        <h1 class="text-base font-semibold leading-7 text-gray-900 mt-5">
+          {i18n.str`Trading volume on ${getDateForTimeframe(
+            params.current,
+            metricType,
+            dateLocale,
+          )} compared to ${getDateForTimeframe(
+            params.previous,
+            metricType,
+            dateLocale,
+          )}`}
+        </h1>
+      </div>
+      <dl class="mt-5 grid grid-cols-1 md:grid-cols-2  divide-y 
divide-gray-200 overflow-hidden rounded-lg bg-white shadow-lg md:divide-x 
md:divide-y-0">
+        {resp.current.body.type !== "with-conversions" ||
+          resp.previous.body.type !== "with-conversions" ? undefined : (
+          <Fragment>
+            <div class="px-4 py-5 sm:p-6">
+              <dt class="text-base font-normal text-gray-900">
+                <i18n.Translate>Cashin</i18n.Translate>
+              </dt>
+              <MetricValue
+                current={resp.current.body.cashinFiatVolume}
+                previous={resp.previous.body.cashinFiatVolume}
+                spec={respInfo.body.fiat_currency_specification}
+              />
+            </div>
+            <div class="px-4 py-5 sm:p-6">
+              <dt class="text-base font-normal text-gray-900">
+                <i18n.Translate>Cashout</i18n.Translate>
+              </dt>
+              <MetricValue
+                current={resp.current.body.cashoutFiatVolume}
+                previous={resp.previous.body.cashoutFiatVolume}
+                spec={respInfo.body.fiat_currency_specification}
+              />
+            </div>
+          </Fragment>
+        )}
+        <div class="px-4 py-5 sm:p-6">
+          <dt class="text-base font-normal text-gray-900">
+            <i18n.Translate>Payin</i18n.Translate>
+          </dt>
+          <MetricValue
+            current={resp.current.body.talerInVolume}
+            previous={resp.previous.body.talerInVolume}
+            spec={config.currency_specification}
+          />
+        </div>
+        <div class="px-4 py-5 sm:p-6">
+          <dt class="text-base font-normal text-gray-900">
+            <i18n.Translate>Payout</i18n.Translate>
+          </dt>
+          <MetricValue
+            current={resp.current.body.talerOutVolume}
+            previous={resp.previous.body.talerOutVolume}
+            spec={config.currency_specification}
+          />
+        </div>
+      </dl>
+      <div class="flex justify-end mt-2">
+        <a
+          href={privatePages.statsDownload.url({})}
+          class="disabled:opacity-50 disabled:cursor-default cursor-pointer 
rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
+        >
+          <i18n.Translate>download stats as CSV</i18n.Translate>
+        </a>
+      </div>
+    </Fragment>
+  );
 }
 
-
-function MetricValue({ current, previous, spec }: { spec: 
CurrencySpecification, current: AmountString | undefined, previous: 
AmountString | undefined }): VNode {
-  const { i18n } = useTranslationContext()
+function MetricValue({
+  current,
+  previous,
+  spec,
+}: {
+  spec: CurrencySpecification;
+  current: AmountString | undefined;
+  previous: AmountString | undefined;
+}): VNode {
+  const { i18n } = useTranslationContext();
   const cmp = current && previous ? Amounts.cmp(current, previous) : 0;
-  const cv = !current ? undefined : Amounts.stringifyValue(current)
-  const currAmount = !cv ? undefined : Number.parseFloat(cv)
-  const prevAmount = !previous ? undefined : 
Number.parseFloat(Amounts.stringifyValue(previous))
-
-  const rate = !currAmount || Number.isNaN(currAmount) || !prevAmount || 
Number.isNaN(prevAmount) ? 0 :
-    cmp === -1 ? 1 - Math.round(currAmount) / Math.round(prevAmount) :
-      cmp === 1 ? (Math.round(currAmount) / Math.round(prevAmount)) - 1 : 0;
+  const cv = !current ? undefined : Amounts.stringifyValue(current);
+  const currAmount = !cv ? undefined : Number.parseFloat(cv);
+  const prevAmount = !previous
+    ? undefined
+    : Number.parseFloat(Amounts.stringifyValue(previous));
 
-  const negative = cmp === 0 ? undefined : cmp === -1
-  const rateStr = `${(Math.abs(rate) * 100).toFixed(2)}%`
-  return <Fragment>
-    <dd class="mt-1 block ">
-      <div class="flex justify-start text-2xl items-baseline font-semibold 
text-indigo-600">
-        {!current ? "-" : <RenderAmount value={Amounts.parseOrThrow(current)} 
spec={spec} hideSmall />}
-      </div>
-      <div class="flex flex-col">
+  const rate =
+    !currAmount ||
+      Number.isNaN(currAmount) ||
+      !prevAmount ||
+      Number.isNaN(prevAmount)
+      ? 0
+      : cmp === -1
+        ? 1 - Math.round(currAmount) / Math.round(prevAmount)
+        : cmp === 1
+          ? Math.round(currAmount) / Math.round(prevAmount) - 1
+          : 0;
 
-        <div class="flex justify-end items-baseline text-2xl font-semibold 
text-indigo-600">
-          <small class="ml-2 text-sm font-medium text-gray-500">
-            <i18n.Translate>from</i18n.Translate> {!previous ? "-" : 
<RenderAmount value={Amounts.parseOrThrow(previous)} spec={spec} hideSmall />}
-          </small>
+  const negative = cmp === 0 ? undefined : cmp === -1;
+  const rateStr = `${(Math.abs(rate) * 100).toFixed(2)}%`;
+  return (
+    <Fragment>
+      <dd class="mt-1 block ">
+        <div class="flex justify-start text-2xl items-baseline font-semibold 
text-indigo-600">
+          {!current ? (
+            "-"
+          ) : (
+            <RenderAmount
+              value={Amounts.parseOrThrow(current)}
+              spec={spec}
+              hideSmall
+            />
+          )}
         </div>
-        {!!rate &&
-          <span data-negative={negative} class="flex items-center gap-x-1.5 
w-fit rounded-md bg-green-100 text-green-800 data-[negative=true]:bg-red-100 
px-2 py-1 text-xs font-medium data-[negative=true]:text-red-700 whitespace-pre">
-            {negative ?
-              <svg xmlns="http://www.w3.org/2000/svg"; fill="none" viewBox="0 0 
24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
-                <path stroke-linecap="round" stroke-linejoin="round" d="M12 
4.5v15m0 0l6.75-6.75M12 19.5l-6.75-6.75" />
-              </svg>
-              :
-              <svg xmlns="http://www.w3.org/2000/svg"; fill="none" viewBox="0 0 
24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
-                <path stroke-linecap="round" stroke-linejoin="round" d="M12 
19.5v-15m0 0l-6.75 6.75M12 4.5l6.75 6.75" />
-              </svg>
-            }
-
-            {negative ?
-              <span class="sr-only"><i18n.Translate>Descreased 
by</i18n.Translate></span> :
-              <span class="sr-only"><i18n.Translate>Increased 
by</i18n.Translate></span>
-            }
-            {rateStr}
-          </span>
-        }
-      </div>
+        <div class="flex flex-col">
+          <div class="flex justify-end items-baseline text-2xl font-semibold 
text-indigo-600">
+            <small class="ml-2 text-sm font-medium text-gray-500">
+              <i18n.Translate>from</i18n.Translate>{" "}
+              {!previous ? (
+                "-"
+              ) : (
+                <RenderAmount
+                  value={Amounts.parseOrThrow(previous)}
+                  spec={spec}
+                  hideSmall
+                />
+              )}
+            </small>
+          </div>
+          {!!rate && (
+            <span
+              data-negative={negative}
+              class="flex items-center gap-x-1.5 w-fit rounded-md bg-green-100 
text-green-800 data-[negative=true]:bg-red-100 px-2 py-1 text-xs font-medium 
data-[negative=true]:text-red-700 whitespace-pre"
+            >
+              {negative ? (
+                <svg
+                  xmlns="http://www.w3.org/2000/svg";
+                  fill="none"
+                  viewBox="0 0 24 24"
+                  stroke-width="1.5"
+                  stroke="currentColor"
+                  class="w-6 h-6"
+                >
+                  <path
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                    d="M12 4.5v15m0 0l6.75-6.75M12 19.5l-6.75-6.75"
+                  />
+                </svg>
+              ) : (
+                <svg
+                  xmlns="http://www.w3.org/2000/svg";
+                  fill="none"
+                  viewBox="0 0 24 24"
+                  stroke-width="1.5"
+                  stroke="currentColor"
+                  class="w-6 h-6"
+                >
+                  <path
+                    stroke-linecap="round"
+                    stroke-linejoin="round"
+                    d="M12 19.5v-15m0 0l-6.75 6.75M12 4.5l6.75 6.75"
+                  />
+                </svg>
+              )}
 
-    </dd>
-  </Fragment>
+              {negative ? (
+                <span class="sr-only">
+                  <i18n.Translate>Descreased by</i18n.Translate>
+                </span>
+              ) : (
+                <span class="sr-only">
+                  <i18n.Translate>Increased by</i18n.Translate>
+                </span>
+              )}
+              {rateStr}
+            </span>
+          )}
+        </div>
+      </dd>
+    </Fragment>
+  );
 }
diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx 
b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
index 1cfbd8234..c4e4266f9 100644
--- a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
@@ -1,28 +1,57 @@
-import { HttpStatusCode, TalerCorebankApi, TalerErrorCode, TranslatedString } 
from "@gnu-taler/taler-util";
-import { Attention, LocalNotificationBanner, notifyInfo, useLocalNotification, 
useTranslationContext } from "@gnu-taler/web-util/browser";
+/*
+ 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 {
+  HttpStatusCode,
+  TalerCorebankApi,
+  TalerErrorCode,
+  TranslatedString,
+  assertUnreachable,
+} from "@gnu-taler/taler-util";
+import {
+  Attention,
+  LocalNotificationBanner,
+  notifyInfo,
+  useLocalNotification,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
 import { Fragment, 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 { assertUnreachable } from "../WithdrawalOperationPage.js";
-import { getRandomPassword } from "../rnd.js";
-import { AccountForm, AccountFormData } from "./AccountForm.js";
+import { RouteDefinition } from "../../route.js";
+import { AccountForm } from "./AccountForm.js";
 
 export function CreateNewAccount({
-  onCancel,
+  routeCancel,
   onCreateSuccess,
 }: {
-  onCancel: () => void;
+  routeCancel: RouteDefinition<Record<string, never>>;
   onCreateSuccess: () => void;
 }): VNode {
   const { i18n } = useTranslationContext();
-  const { state: credentials } = useBackendState()
-  const token = credentials.status !== "loggedIn" ? undefined : 
credentials.token
+  const { state: credentials } = useBackendState();
+  const token =
+    credentials.status !== "loggedIn" ? undefined : credentials.token;
   const { api } = useBankCoreApiContext();
 
-  const [submitAccount, setSubmitAccount] = 
useState<TalerCorebankApi.RegisterAccountRequest | undefined>();
-  const [notification, notify, handleError] = useLocalNotification()
+  const [submitAccount, setSubmitAccount] = useState<
+    TalerCorebankApi.RegisterAccountRequest | undefined
+  >();
+  const [notification, notify, handleError] = useLocalNotification();
 
   async function doCreate() {
     if (!submitAccount || !token) return;
@@ -41,83 +70,108 @@ export function CreateNewAccount({
 
       const resp = await api.createAccount(token, submitAccount);
       if (resp.type === "ok") {
-        mutate(() => true)// clean account list
+        mutate(() => true); // clean account list
         notifyInfo(
           i18n.str`Account created with password "${submitAccount.password}". 
The user must change the password on the next login.`,
         );
         onCreateSuccess();
       } else {
         switch (resp.case) {
-          case HttpStatusCode.BadRequest: return notify({
-            type: "error",
-            title: i18n.str`Server replied that phone or email is invalid`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case HttpStatusCode.Unauthorized: return notify({
-            type: "error",
-            title: i18n.str`The rights to perform the operation are not 
sufficient`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case TalerErrorCode.BANK_REGISTER_USERNAME_REUSE: return notify({
-            type: "error",
-            title: i18n.str`Account username is already taken`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case TalerErrorCode.BANK_REGISTER_PAYTO_URI_REUSE: return notify({
-            type: "error",
-            title: i18n.str`Account id is already taken`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return notify({
-            type: "error",
-            title: i18n.str`Bank ran out of bonus credit.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT: return notify({
-            type: "error",
-            title: i18n.str`Account username can't be used because is 
reserved`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT: return notify({
-            type: "error",
-            title: i18n.str`Only admin is allow to set debt limit.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case TalerErrorCode.BANK_MISSING_TAN_INFO: return notify({
-            type: "error",
-            title: i18n.str`No information for the selected authentication 
channel.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED: return notify({
-            type: "error",
-            title: i18n.str`Authentication channel is not supported.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case TalerErrorCode.BANK_NON_ADMIN_SET_TAN_CHANNEL: return notify({
-            type: "error",
-            title: i18n.str`Only admin can create accounts with second factor 
authentication.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          default: assertUnreachable(resp)
+          case HttpStatusCode.BadRequest:
+            return notify({
+              type: "error",
+              title: i18n.str`Server replied that phone or email is invalid`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case HttpStatusCode.Unauthorized:
+            return notify({
+              type: "error",
+              title: i18n.str`The rights to perform the operation are not 
sufficient`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_REGISTER_USERNAME_REUSE:
+            return notify({
+              type: "error",
+              title: i18n.str`Account username is already taken`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_REGISTER_PAYTO_URI_REUSE:
+            return notify({
+              type: "error",
+              title: i18n.str`Account id is already taken`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_UNALLOWED_DEBIT:
+            return notify({
+              type: "error",
+              title: i18n.str`Bank ran out of bonus credit.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT:
+            return notify({
+              type: "error",
+              title: i18n.str`Account username can't be used because is 
reserved`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT:
+            return notify({
+              type: "error",
+              title: i18n.str`Only admin is allow to set debt limit.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_MISSING_TAN_INFO:
+            return notify({
+              type: "error",
+              title: i18n.str`No information for the selected authentication 
channel.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED:
+            return notify({
+              type: "error",
+              title: i18n.str`Authentication channel is not supported.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_NON_ADMIN_SET_TAN_CHANNEL:
+            return notify({
+              type: "error",
+              title: i18n.str`Only admin can create accounts with second 
factor authentication.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          default:
+            assertUnreachable(resp);
         }
       }
-    })
+    });
   }
 
   if (!(credentials.status === "loggedIn" && credentials.isUserAdministrator)) 
{
-    return <Attention type="warning" title={i18n.str`Can't create accounts`} 
onClose={onCancel}>
-      <i18n.Translate>Only system admin can create accounts.</i18n.Translate>
-    </Attention>
+    return (
+      <Fragment>
+        <Attention type="warning" title={i18n.str`Can't create accounts`}>
+          <i18n.Translate>
+            Only system admin can create accounts.
+          </i18n.Translate>
+        </Attention>
+        <div class="mt-5 sm:mt-6">
+          <a
+            href={routeCancel.url({})}
+            class="inline-flex w-full justify-center rounded-md bg-indigo-600 
px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
+          >
+            <i18n.Translate>Close</i18n.Translate>
+          </a>
+        </div>
+      </Fragment>
+    );
   }
 
   return (
@@ -137,26 +191,24 @@ export function CreateNewAccount({
         }}
       >
         <div class="flex items-center justify-between gap-x-6 border-t 
border-gray-900/10 px-4 py-4 sm:px-8">
-          {onCancel ?
-            <button type="button" class="text-sm font-semibold leading-6 
text-gray-900"
-              onClick={onCancel}
-            >
-              <i18n.Translate>Cancel</i18n.Translate>
-            </button>
-            : <div />
-          }
-          <button type="submit"
+          <a
+            href={routeCancel.url({})}
+            class="text-sm font-semibold leading-6 text-gray-900"
+          >
+            <i18n.Translate>Cancel</i18n.Translate>
+          </a>
+          <button
+            type="submit"
             class="disabled:opacity-50 disabled:cursor-default cursor-pointer 
rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
             disabled={!submitAccount}
             onClick={(e) => {
-              e.preventDefault()
-              doCreate()
+              e.preventDefault();
+              doCreate();
             }}
           >
             <i18n.Translate>Create</i18n.Translate>
           </button>
         </div>
-
       </AccountForm>
     </div>
   );
diff --git a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx 
b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
index beadad957..36e1a4eac 100644
--- a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
@@ -1,5 +1,36 @@
-import { AbsoluteTime, Amounts, HttpStatusCode, TalerError, TalerErrorCode, 
TranslatedString } from "@gnu-taler/taler-util";
-import { Attention, Loading, LocalNotificationBanner, ShowInputErrorLabel, 
notifyInfo, useLocalNotification, useTranslationContext } from 
"@gnu-taler/web-util/browser";
+/*
+ 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 {
+  AbsoluteTime,
+  Amounts,
+  HttpStatusCode,
+  TalerError,
+  TalerErrorCode,
+  TranslatedString,
+  assertUnreachable,
+} from "@gnu-taler/taler-util";
+import {
+  Attention,
+  Loading,
+  LocalNotificationBanner,
+  ShowInputErrorLabel,
+  notifyInfo,
+  useLocalNotification,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { ErrorLoadingWithDebug } from 
"../../components/ErrorLoadingWithDebug.js";
@@ -9,43 +40,46 @@ import { useBackendState } from "../../hooks/backend.js";
 import { undefinedIfEmpty } from "../../utils.js";
 import { LoginForm } from "../LoginForm.js";
 import { doAutoFocus } from "../PaytoWireTransferForm.js";
-import { assertUnreachable } from "../WithdrawalOperationPage.js";
 import { useBankState } from "../../hooks/bank-state.js";
+import { RouteDefinition } from "../../route.js";
 
 export function RemoveAccount({
   account,
-  onCancel,
+  routeCancel,
   onUpdateSuccess,
   onAuthorizationRequired,
   focus,
 }: {
   focus?: boolean;
-  onAuthorizationRequired: () => void,
-  onCancel: () => void;
+  onAuthorizationRequired: () => void;
+  routeCancel: RouteDefinition<Record<string, never>>;
   onUpdateSuccess: () => void;
   account: string;
 }): VNode {
   const { i18n } = useTranslationContext();
   const result = useAccountDetails(account);
-  const [accountName, setAccountName] = useState<string | undefined>()
+  const [accountName, setAccountName] = useState<string | undefined>();
 
   const { state } = useBackendState();
-  const token = state.status !== "loggedIn" ? undefined : state.token
-  const { api } = useBankCoreApiContext()
-  const [notification, notify, handleError] = useLocalNotification()
-  const [, updateBankState] = useBankState()
+  const token = state.status !== "loggedIn" ? undefined : state.token;
+  const { api } = useBankCoreApiContext();
+  const [notification, notify, handleError] = useLocalNotification();
+  const [, updateBankState] = useBankState();
 
   if (!result) {
-    return <Loading />
+    return <Loading />;
   }
   if (result instanceof TalerError) {
-    return <ErrorLoadingWithDebug error={result} />
+    return <ErrorLoadingWithDebug error={result} />;
   }
   if (result.type === "fail") {
     switch (result.case) {
-      case HttpStatusCode.Unauthorized: return <LoginForm 
currentUser={account} />
-      case HttpStatusCode.NotFound: return <LoginForm currentUser={account} />
-      default: assertUnreachable(result)
+      case HttpStatusCode.Unauthorized:
+        return <LoginForm currentUser={account} />;
+      case HttpStatusCode.NotFound:
+        return <LoginForm currentUser={account} />;
+      default:
+        assertUnreachable(result);
     }
   }
 
@@ -55,9 +89,24 @@ export function RemoveAccount({
   }
   const isBalanceEmpty = Amounts.isZero(balance);
   if (!isBalanceEmpty) {
-    return <Attention type="warning" title={i18n.str`Can't delete the 
account`} onClose={onCancel}>
-      <i18n.Translate>The account can't be delete while still holding some 
balance. First make sure that the owner make a complete 
cashout.</i18n.Translate>
-    </Attention>
+    return (
+      <Fragment>
+        <Attention type="warning" title={i18n.str`Can't delete the account`}>
+          <i18n.Translate>
+            The account can't be delete while still holding some balance. First
+            make sure that the owner make a complete cashout.
+          </i18n.Translate>
+        </Attention>
+        <div class="mt-5 sm:mt-6">
+          <a
+            href={routeCancel.url({})}
+            class="inline-flex w-full justify-center rounded-md bg-indigo-600 
px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
+          >
+            <i18n.Translate>Close</i18n.Translate>
+          </a>
+        </div>
+      </Fragment>
+    );
   }
 
   async function doRemove() {
@@ -69,45 +118,49 @@ export function RemoveAccount({
         onUpdateSuccess();
       } else {
         switch (resp.case) {
-          case HttpStatusCode.Unauthorized: return notify({
-            type: "error",
-            title: i18n.str`No enough permission to delete the account.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case HttpStatusCode.NotFound: return notify({
-            type: "error",
-            title: i18n.str`The username was not found.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT: return notify({
-            type: "error",
-            title: i18n.str`Can't delete a reserved username.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
-          case TalerErrorCode.BANK_ACCOUNT_BALANCE_NOT_ZERO: return notify({
-            type: "error",
-            title: i18n.str`Can't delete an account with balance different 
than zero.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          })
+          case HttpStatusCode.Unauthorized:
+            return notify({
+              type: "error",
+              title: i18n.str`No enough permission to delete the account.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case HttpStatusCode.NotFound:
+            return notify({
+              type: "error",
+              title: i18n.str`The username was not found.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT:
+            return notify({
+              type: "error",
+              title: i18n.str`Can't delete a reserved username.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_ACCOUNT_BALANCE_NOT_ZERO:
+            return notify({
+              type: "error",
+              title: i18n.str`Can't delete an account with balance different 
than zero.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
           case HttpStatusCode.Accepted: {
             updateBankState("currentChallenge", {
               operation: "delete-account",
               id: String(resp.body.challenge_id),
               sent: AbsoluteTime.never(),
               request: account,
-            })
-            return onAuthorizationRequired()
+            });
+            return onAuthorizationRequired();
           }
           default: {
-            assertUnreachable(resp)
+            assertUnreachable(resp);
           }
         }
       }
-    })
+    });
   }
 
   const errors = undefinedIfEmpty({
@@ -118,12 +171,14 @@ export function RemoveAccount({
         : undefined,
   });
 
-
   return (
     <div>
       <LocalNotificationBanner notification={notification} />
 
-      <Attention type="warning" title={i18n.str`You are going to remove the 
account`}>
+      <Attention
+        type="warning"
+        title={i18n.str`You are going to remove the account`}
+      >
         <i18n.Translate>This step can't be undone.</i18n.Translate>
       </Attention>
 
@@ -137,13 +192,12 @@ export function RemoveAccount({
           class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl 
md:col-span-2"
           autoCapitalize="none"
           autoCorrect="off"
-          onSubmit={e => {
-            e.preventDefault()
+          onSubmit={(e) => {
+            e.preventDefault();
           }}
         >
           <div class="px-4 py-6 sm:p-8">
             <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 
sm:grid-cols-6">
-
               <div class="sm:col-span-5">
                 <label
                   class="block text-sm font-medium leading-6 text-gray-900"
@@ -158,10 +212,12 @@ export function RemoveAccount({
                     class="block w-full rounded-md border-0 py-1.5 
text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 
focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                     name="password"
                     id="password"
-                    data-error={!!errors?.accountName && accountName !== 
undefined}
+                    data-error={
+                      !!errors?.accountName && accountName !== undefined
+                    }
                     value={accountName ?? ""}
                     onChange={(e) => {
-                      setAccountName(e.currentTarget.value)
+                      setAccountName(e.currentTarget.value);
                     }}
                     placeholder={account}
                     autocomplete="off"
@@ -171,30 +227,28 @@ export function RemoveAccount({
                     isDirty={accountName !== undefined}
                   />
                 </div>
-                <p class="mt-2 text-sm text-gray-500" >
-                  <i18n.Translate>enter the account name that is going to be 
deleted</i18n.Translate>
+                <p class="mt-2 text-sm text-gray-500">
+                  <i18n.Translate>
+                    enter the account name that is going to be deleted
+                  </i18n.Translate>
                 </p>
               </div>
-
-
-
             </div>
           </div>
           <div class="flex items-center justify-between gap-x-6 border-t 
border-gray-900/10 px-4 py-4 sm:px-8">
-            {onCancel ?
-              <button type="button" class="text-sm font-semibold leading-6 
text-gray-900"
-                onClick={onCancel}
-              >
-                <i18n.Translate>Cancel</i18n.Translate>
-              </button>
-              : <div />
-            }
-            <button type="submit"
+            <a
+              href={routeCancel.url({})}
+              class="text-sm font-semibold leading-6 text-gray-900"
+            >
+              <i18n.Translate>Cancel</i18n.Translate>
+            </a>
+            <button
+              type="submit"
               class="disabled:opacity-50 disabled:cursor-default 
cursor-pointer rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white 
shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-red-600"
               disabled={!!errors}
               onClick={(e) => {
-                e.preventDefault()
-                doRemove()
+                e.preventDefault();
+                doRemove();
               }}
             >
               <i18n.Translate>Delete</i18n.Translate>
diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx 
b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
index a39a379de..93bd2c89d 100644
--- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx
+++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -17,13 +17,13 @@ import {
   AbsoluteTime,
   Amounts,
   HttpStatusCode,
-  TalerCorebankApi,
   TalerError,
   TalerErrorCode,
   TranslatedString,
+  assertUnreachable,
   encodeCrock,
   getRandomBytes,
-  parsePaytoUri
+  parsePaytoUri,
 } from "@gnu-taler/taler-util";
 import {
   Attention,
@@ -32,7 +32,7 @@ import {
   ShowInputErrorLabel,
   notifyInfo,
   useLocalNotification,
-  useTranslationContext
+  useTranslationContext,
 } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useEffect, useState } from "preact/hooks";
@@ -40,24 +40,22 @@ 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 {
-  useConversionInfo,
-  useEstimator
-} from "../../hooks/circuit.js";
-import {
-  TanChannel,
-  undefinedIfEmpty
-} from "../../utils.js";
-import { LoginForm } from "../LoginForm.js";
-import { InputAmount, RenderAmount, doAutoFocus } from 
"../PaytoWireTransferForm.js";
-import { assertUnreachable } from "../WithdrawalOperationPage.js";
 import { useBankState } from "../../hooks/bank-state.js";
+import { useConversionInfo, useEstimator } from "../../hooks/circuit.js";
+import { RouteDefinition } from "../../route.js";
+import { TanChannel, undefinedIfEmpty } from "../../utils.js";
+import { LoginForm } from "../LoginForm.js";
+import {
+  InputAmount,
+  RenderAmount,
+  doAutoFocus,
+} from "../PaytoWireTransferForm.js";
 
 interface Props {
   account: string;
-  focus?: boolean,
-  onAuthorizationRequired: () => void,
-  onCancel?: () => void;
+  focus?: boolean;
+  onAuthorizationRequired: () => void;
+  routeClose: RouteDefinition<Record<string, never>>;
 }
 
 type FormType = {
@@ -70,12 +68,11 @@ type ErrorFrom<T> = {
   [P in keyof T]+?: string;
 };
 
-
 export function CreateCashout({
   account: accountName,
   onAuthorizationRequired,
   focus,
-  onCancel,
+  routeClose,
 }: Props): VNode {
   const { i18n } = useTranslationContext();
   const resultAccount = useAccountDetails(accountName);
@@ -84,95 +81,130 @@ export function CreateCashout({
     estimateByDebit: calculateFromDebit,
   } = useEstimator();
   const { state: credentials } = useBackendState();
-  const creds = credentials.status !== "loggedIn" ? undefined : credentials
-  const [, updateBankState] = useBankState()
+  const creds = credentials.status !== "loggedIn" ? undefined : credentials;
+  const [, updateBankState] = useBankState();
 
-  const { api, config, hints } = useBankCoreApiContext()
-  const [form, setForm] = useState<Partial<FormType>>({ isDebit: true, });
-  const [notification, notify, handleError] = useLocalNotification()
+  const { api, config, hints } = useBankCoreApiContext();
+  const [form, setForm] = useState<Partial<FormType>>({ isDebit: true });
+  const [notification, notify, handleError] = useLocalNotification();
   const info = useConversionInfo();
 
   if (!config.allow_conversion) {
-    return <Attention type="warning" title={i18n.str`Unable to create a 
cashout`} onClose={onCancel}>
-      <i18n.Translate>The bank configuration does not support cashout 
operations.</i18n.Translate>
-    </Attention>
+    return (
+      <Fragment>
+        <Attention type="warning" title={i18n.str`Unable to create a cashout`}>
+          <i18n.Translate>
+            The bank configuration does not support cashout operations.
+          </i18n.Translate>
+        </Attention>
+        <div class="mt-5 sm:mt-6">
+          <a
+            href={routeClose.url({})}
+            class="inline-flex w-full justify-center rounded-md bg-indigo-600 
px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
+          >
+            <i18n.Translate>Close</i18n.Translate>
+          </a>
+        </div>
+      </Fragment>
+    );
   }
 
-  const OLD_CASHOUT_API = hints.indexOf(VersionHint.CASHOUT_BEFORE_2FA) !== -1
+  const OLD_CASHOUT_API = hints.indexOf(VersionHint.CASHOUT_BEFORE_2FA) !== -1;
 
   if (!resultAccount) {
-    return <Loading />
+    return <Loading />;
   }
   if (resultAccount instanceof TalerError) {
-    return <ErrorLoadingWithDebug error={resultAccount} />
+    return <ErrorLoadingWithDebug error={resultAccount} />;
   }
   if (resultAccount.type === "fail") {
     switch (resultAccount.case) {
-      case HttpStatusCode.Unauthorized: return <LoginForm 
currentUser={accountName} />
-      case HttpStatusCode.NotFound: return <LoginForm 
currentUser={accountName} />
-      default: assertUnreachable(resultAccount)
+      case HttpStatusCode.Unauthorized:
+        return <LoginForm currentUser={accountName} />;
+      case HttpStatusCode.NotFound:
+        return <LoginForm currentUser={accountName} />;
+      default:
+        assertUnreachable(resultAccount);
     }
   }
   if (!info) {
-    return <Loading />
+    return <Loading />;
   }
 
   if (info instanceof TalerError) {
-    return <ErrorLoadingWithDebug error={info} />
+    return <ErrorLoadingWithDebug error={info} />;
   }
   if (info.type === "fail") {
     switch (info.case) {
       case HttpStatusCode.NotImplemented: {
-        return <Attention type="danger" title={i18n.str`Cashout not 
implemented`}>
-        </Attention>;
+        return (
+          <Attention
+            type="danger"
+            title={i18n.str`Cashout not implemented`}
+          ></Attention>
+        );
       }
-      default: assertUnreachable(info.case)
+      default:
+        assertUnreachable(info.case);
     }
   }
 
-
-  const conversionInfo = info.body.conversion_rate
+  const conversionInfo = info.body.conversion_rate;
   if (!conversionInfo) {
-    return <div>conversion enabled but server replied without 
conversion_rate</div>
+    return (
+      <div>conversion enabled but server replied without conversion_rate</div>
+    );
   }
 
   const account = {
     balance: Amounts.parseOrThrow(resultAccount.body.balance.amount),
-    balanceIsDebit: resultAccount.body.balance.credit_debit_indicator == 
"debit",
-    debitThreshold: Amounts.parseOrThrow(resultAccount.body.debit_threshold)
-  }
+    balanceIsDebit:
+      resultAccount.body.balance.credit_debit_indicator == "debit",
+    debitThreshold: Amounts.parseOrThrow(resultAccount.body.debit_threshold),
+  };
 
-  const { fiat_currency, regional_currency, fiat_currency_specification, 
regional_currency_specification } = info.body
+  const {
+    fiat_currency,
+    regional_currency,
+    fiat_currency_specification,
+    regional_currency_specification,
+  } = info.body;
   const regionalZero = Amounts.zeroOfCurrency(regional_currency);
   const fiatZero = Amounts.zeroOfCurrency(fiat_currency);
   const limit = account.balanceIsDebit
     ? Amounts.sub(account.debitThreshold, account.balance).amount
     : Amounts.add(account.balance, account.debitThreshold).amount;
 
-  const zeroCalc = { debit: regionalZero, credit: fiatZero, beforeFee: 
fiatZero };
+  const zeroCalc = {
+    debit: regionalZero,
+    credit: fiatZero,
+    beforeFee: fiatZero,
+  };
   const [calc, setCalc] = useState(zeroCalc);
   const sellFee = Amounts.parseOrThrow(conversionInfo.cashout_fee);
-  const sellRate = conversionInfo.cashout_ratio
+  const sellRate = conversionInfo.cashout_ratio;
   /**
    * can be in regional currency or fiat currency
    * depending on the isDebit flag
    */
   const inputAmount = Amounts.parseOrThrow(
-    `${form.isDebit ? regional_currency : fiat_currency}:${!form.amount ? "0" 
: form.amount}`,
+    `${form.isDebit ? regional_currency : fiat_currency}:${
+      !form.amount ? "0" : form.amount
+    }`,
   );
 
   useEffect(() => {
     async function doAsync() {
       await handleError(async () => {
         if (Amounts.isNonZero(inputAmount)) {
-          const resp = await (form.isDebit ?
-            calculateFromDebit(inputAmount, sellFee) :
-            calculateFromCredit(inputAmount, sellFee));
-          setCalc(resp)
+          const resp = await (form.isDebit
+            ? calculateFromDebit(inputAmount, sellFee)
+            : calculateFromCredit(inputAmount, sellFee));
+          setCalc(resp);
         }
-      })
+      });
     }
-    doAsync()
+    doAsync();
   }, [form.amount, form.isDebit]);
 
   const balanceAfter = Amounts.sub(account.balance, calc.debit).amount;
@@ -198,10 +230,13 @@ export function CreateCashout({
   const trimmedAmountStr = form.amount?.trim();
 
   async function createCashout() {
-    const request_uid = encodeCrock(getRandomBytes(32))
+    const request_uid = encodeCrock(getRandomBytes(32));
     await handleError(async () => {
-      //new cashout api doesn't require channel
-      const validChannel = !OLD_CASHOUT_API || 
config.supported_tan_channels.length === 0 || form.channel
+      // new cashout api doesn't require channel
+      const validChannel =
+        !OLD_CASHOUT_API ||
+        config.supported_tan_channels.length === 0 ||
+        form.channel;
 
       if (!creds || !form.subject || !validChannel) return;
       const request = {
@@ -210,10 +245,10 @@ export function CreateCashout({
         amount_debit: Amounts.stringify(calc.debit),
         subject: form.subject,
         tan_channel: form.channel,
-      }
-      const resp = await api.createCashout(creds, request)
+      };
+      const resp = await api.createCashout(creds, request);
       if (resp.type === "ok") {
-        notifyInfo(i18n.str`Cashout created`)
+        notifyInfo(i18n.str`Cashout created`);
       } else {
         switch (resp.case) {
           case HttpStatusCode.Accepted: {
@@ -222,102 +257,127 @@ export function CreateCashout({
               id: String(resp.body.challenge_id),
               sent: AbsoluteTime.never(),
               request,
-            })
-            return onAuthorizationRequired()
+            });
+            return onAuthorizationRequired();
           }
-          case HttpStatusCode.NotFound: return notify({
-            type: "error",
-            title: i18n.str`Account not found`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          });
-          case TalerErrorCode.BANK_TRANSFER_REQUEST_UID_REUSED: return notify({
-            type: "error",
-            title: i18n.str`Duplicated request detected, check if the 
operation succeded or try again.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          });
-          case TalerErrorCode.BANK_BAD_CONVERSION: return notify({
-            type: "error",
-            title: i18n.str`The conversion rate was incorrectly applied`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          });
-          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return notify({
-            type: "error",
-            title: i18n.str`The account does not have sufficient funds`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          });
-          case HttpStatusCode.NotImplemented: return notify({
-            type: "error",
-            title: i18n.str`Cashouts are not supported`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          });
-          case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: return notify({
-            type: "error",
-            title: i18n.str`Missing cashout URI in the profile`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          });
-          case TalerErrorCode.BANK_TAN_CHANNEL_SCRIPT_FAILED: return notify({
-            type: "error",
-            title: i18n.str`Sending the confirmation message failed, retry 
later or contact the administrator.`,
-            description: resp.detail.hint as TranslatedString,
-            debug: resp.detail,
-          });
+          case HttpStatusCode.NotFound:
+            return notify({
+              type: "error",
+              title: i18n.str`Account not found`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_TRANSFER_REQUEST_UID_REUSED:
+            return notify({
+              type: "error",
+              title: i18n.str`Duplicated request detected, check if the 
operation succeded or try again.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_BAD_CONVERSION:
+            return notify({
+              type: "error",
+              title: i18n.str`The conversion rate was incorrectly applied`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_UNALLOWED_DEBIT:
+            return notify({
+              type: "error",
+              title: i18n.str`The account does not have sufficient funds`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case HttpStatusCode.NotImplemented:
+            return notify({
+              type: "error",
+              title: i18n.str`Cashouts are not supported`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_CONFIRM_INCOMPLETE:
+            return notify({
+              type: "error",
+              title: i18n.str`Missing cashout URI in the profile`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
+          case TalerErrorCode.BANK_TAN_CHANNEL_SCRIPT_FAILED:
+            return notify({
+              type: "error",
+              title: i18n.str`Sending the confirmation message failed, retry 
later or contact the administrator.`,
+              description: resp.detail.hint as TranslatedString,
+              debug: resp.detail,
+            });
         }
-        assertUnreachable(resp)
+        assertUnreachable(resp);
       }
-    })
+    });
   }
-  const cashoutDisabled = config.supported_tan_channels.length < 1 || 
!resultAccount.body.cashout_payto_uri
-  console.log("disab", cashoutDisabled)
-  const cashoutAccount = !resultAccount.body.cashout_payto_uri ? undefined :
-    parsePaytoUri(resultAccount.body.cashout_payto_uri);
-  const cashoutAccountName = !cashoutAccount ? undefined : 
cashoutAccount.targetPath
+  const cashoutDisabled =
+    config.supported_tan_channels.length < 1 ||
+    !resultAccount.body.cashout_payto_uri;
+  console.log("disab", cashoutDisabled);
+  const cashoutAccount = !resultAccount.body.cashout_payto_uri
+    ? undefined
+    : parsePaytoUri(resultAccount.body.cashout_payto_uri);
+  const cashoutAccountName = !cashoutAccount
+    ? undefined
+    : cashoutAccount.targetPath;
   return (
     <div>
       <LocalNotificationBanner notification={notification} />
 
       <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 
bg-gray-100 my-4 px-4 pb-4 rounded-lg">
-
         <section class="mt-4 rounded-sm px-4 py-6 p-8 ">
-          <h2 id="summary-heading" class="font-medium 
text-lg"><i18n.Translate>Cashout</i18n.Translate></h2>
+          <h2 id="summary-heading" class="font-medium text-lg">
+            <i18n.Translate>Cashout</i18n.Translate>
+          </h2>
 
           <dl class="mt-4 space-y-4">
             <div class="justify-between items-center flex">
-              <dt class="text-sm text-gray-600"><i18n.Translate>Convertion 
rate</i18n.Translate></dt>
+              <dt class="text-sm text-gray-600">
+                <i18n.Translate>Convertion rate</i18n.Translate>
+              </dt>
               <dd class="text-sm text-gray-900">{sellRate}</dd>
             </div>
 
-
             <div class="flex items-center justify-between border-t-2 afu pt-4">
               <dt class="flex items-center text-sm text-gray-600">
-                <span><i18n.Translate>Balance</i18n.Translate></span>
+                <span>
+                  <i18n.Translate>Balance</i18n.Translate>
+                </span>
               </dt>
               <dd class="text-sm text-gray-900">
-                <RenderAmount value={account.balance} 
spec={regional_currency_specification} />
+                <RenderAmount
+                  value={account.balance}
+                  spec={regional_currency_specification}
+                />
               </dd>
             </div>
             <div class="flex items-center justify-between border-t-2 afu pt-4">
               <dt class="flex items-center text-sm text-gray-600">
-                <span><i18n.Translate>Fee</i18n.Translate></span>
+                <span>
+                  <i18n.Translate>Fee</i18n.Translate>
+                </span>
               </dt>
               <dd class="text-sm text-gray-900">
-                <RenderAmount value={sellFee} 
spec={fiat_currency_specification} />
+                <RenderAmount
+                  value={sellFee}
+                  spec={fiat_currency_specification}
+                />
               </dd>
             </div>
-            {cashoutAccountName ?
+            {cashoutAccountName ? (
               <div class="flex items-center justify-between border-t-2 afu 
pt-4">
                 <dt class="flex items-center text-sm text-gray-600">
-                  <span><i18n.Translate>To account</i18n.Translate></span>
+                  <span>
+                    <i18n.Translate>To account</i18n.Translate>
+                  </span>
                 </dt>
-                <dd class="text-sm text-gray-900">
-                  {cashoutAccountName}
-                </dd>
-              </div> :
+                <dd class="text-sm text-gray-900">{cashoutAccountName}</dd>
+              </div>
+            ) : (
               <div class="flex items-center justify-between border-t-2 afu 
pt-4">
                 <Attention type="warning" title={i18n.str`No cashout account`}>
                   <i18n.Translate>
@@ -325,17 +385,15 @@ export function CreateCashout({
                   </i18n.Translate>
                 </Attention>
               </div>
-            }
-
+            )}
           </dl>
-
         </section>
         <form
           class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl 
md:col-span-2"
           autoCapitalize="none"
           autoCorrect="off"
-          onSubmit={e => {
-            e.preventDefault()
+          onSubmit={(e) => {
+            e.preventDefault();
           }}
         >
           <div class="px-4 py-6 sm:p-8">
@@ -370,7 +428,6 @@ export function CreateCashout({
                     isDirty={form.subject !== undefined}
                   />
                 </div>
-
               </div>
 
               {/* amount */}
@@ -384,14 +441,25 @@ export function CreateCashout({
                       ? i18n.str`Amount to send`
                       : i18n.str`Amount to receive`}
                   </label>
-                  <button type="button" data-enabled={form.isDebit} 
class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 
w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent 
transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 
focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" 
aria-labelledby="availability-label" aria-describedby="availability-description"
+                  <button
+                    type="button"
+                    data-enabled={form.isDebit}
+                    class="bg-indigo-600 data-[enabled=false]:bg-gray-200 
relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full 
border-2 border-transparent transition-colors duration-200 ease-in-out 
focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
+                    role="switch"
+                    aria-checked="false"
+                    aria-labelledby="availability-label"
+                    aria-describedby="availability-description"
                     onClick={() => {
-                      form.isDebit = !form.isDebit
-                      updateForm(structuredClone(form))
-                    }}>
-                    <span aria-hidden="true" data-enabled={form.isDebit} 
class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none 
inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition 
duration-200 ease-in-out"></span>
+                      form.isDebit = !form.isDebit;
+                      updateForm(structuredClone(form));
+                    }}
+                  >
+                    <span
+                      aria-hidden="true"
+                      data-enabled={form.isDebit}
+                      class="translate-x-5 data-[enabled=false]:translate-x-0 
pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow 
ring-0 transition duration-200 ease-in-out"
+                    ></span>
                   </button>
-
                 </div>
                 <div class="mt-2">
                   <InputAmount
@@ -399,53 +467,78 @@ export function CreateCashout({
                     left
                     currency={limit.currency}
                     value={trimmedAmountStr}
-                    onChange={cashoutDisabled ? undefined : (value) => {
-                      form.amount = value;
-                      updateForm(structuredClone(form));
-                    }}
+                    onChange={
+                      cashoutDisabled
+                        ? undefined
+                        : (value) => {
+                            form.amount = value;
+                            updateForm(structuredClone(form));
+                          }
+                    }
                   />
                   <ShowInputErrorLabel
                     message={errors?.amount}
                     isDirty={form.amount !== undefined}
                   />
                 </div>
-
               </div>
 
               {Amounts.isZero(calc.credit) ? undefined : (
                 <div class="sm:col-span-5">
                   <dl class="mt-4 space-y-4">
-
                     <div class="justify-between items-center flex ">
-                      <dt class="text-sm text-gray-600"><i18n.Translate>Total 
cost</i18n.Translate></dt>
+                      <dt class="text-sm text-gray-600">
+                        <i18n.Translate>Total cost</i18n.Translate>
+                      </dt>
                       <dd class="text-sm text-gray-900">
-                        <RenderAmount value={calc.debit} negative withColor 
spec={regional_currency_specification} />
+                        <RenderAmount
+                          value={calc.debit}
+                          negative
+                          withColor
+                          spec={regional_currency_specification}
+                        />
                       </dd>
                     </div>
 
-
                     <div class="flex items-center justify-between border-t-2 
afu pt-4">
                       <dt class="flex items-center text-sm text-gray-600">
-                        <span><i18n.Translate>Balance 
left</i18n.Translate></span>
+                        <span>
+                          <i18n.Translate>Balance left</i18n.Translate>
+                        </span>
                       </dt>
                       <dd class="text-sm text-gray-900">
-                        <RenderAmount value={balanceAfter} 
spec={regional_currency_specification} />
+                        <RenderAmount
+                          value={balanceAfter}
+                          spec={regional_currency_specification}
+                        />
                       </dd>
                     </div>
-                    {Amounts.isZero(sellFee) || Amounts.isZero(calc.beforeFee) 
? undefined : (
+                    {Amounts.isZero(sellFee) ||
+                    Amounts.isZero(calc.beforeFee) ? undefined : (
                       <div class="flex items-center justify-between border-t-2 
afu pt-4">
                         <dt class="flex items-center text-sm text-gray-600">
-                          <span><i18n.Translate>Before 
fee</i18n.Translate></span>
+                          <span>
+                            <i18n.Translate>Before fee</i18n.Translate>
+                          </span>
                         </dt>
                         <dd class="text-sm text-gray-900">
-                          <RenderAmount value={calc.beforeFee} 
spec={fiat_currency_specification} />
+                          <RenderAmount
+                            value={calc.beforeFee}
+                            spec={fiat_currency_specification}
+                          />
                         </dd>
                       </div>
                     )}
                     <div class="flex justify-between items-center border-t-2 
afu pt-4">
-                      <dt class="text-lg text-gray-900 
font-medium"><i18n.Translate>Total cashout transfer</i18n.Translate></dt>
+                      <dt class="text-lg text-gray-900 font-medium">
+                        <i18n.Translate>Total cashout transfer</i18n.Translate>
+                      </dt>
                       <dd class="text-lg text-gray-900 font-medium">
-                        <RenderAmount value={calc.credit} withColor 
spec={fiat_currency_specification} />
+                        <RenderAmount
+                          value={calc.credit}
+                          withColor
+                          spec={fiat_currency_specification}
+                        />
                       </dd>
                     </div>
                   </dl>
@@ -453,15 +546,20 @@ export function CreateCashout({
               )}
 
               {/* channel, not shown if new cashout api */}
-              {!OLD_CASHOUT_API ? undefined : 
config.supported_tan_channels.length === 0 ?
+              {!OLD_CASHOUT_API ? undefined : config.supported_tan_channels
+                  .length === 0 ? (
                 <div class="sm:col-span-5">
-                  <Attention type="warning" title={i18n.str`No cashout channel 
available`}>
+                  <Attention
+                    type="warning"
+                    title={i18n.str`No cashout channel available`}
+                  >
                     <i18n.Translate>
-                      Before doing a cashout the server need to provide an 
second channel to confirm the operation
+                      Before doing a cashout the server need to provide an
+                      second channel to confirm the operation
                     </i18n.Translate>
                   </Attention>
                 </div>
-                :
+              ) : (
                 <div class="sm:col-span-5">
                   <label
                     class="block text-sm font-medium leading-6 text-gray-900"
@@ -471,72 +569,124 @@ export function CreateCashout({
                   </label>
                   <div class="mt-2 max-w-xl text-sm text-gray-500">
                     <div class="px-4 mt-4 grid grid-cols-1 gap-y-6">
-                      {config.supported_tan_channels.indexOf(TanChannel.EMAIL) 
=== -1 ? undefined :
-                        <label onClick={() => {
-                          if (!resultAccount.body.contact_data?.email) return;
-                          form.channel = TanChannel.EMAIL
-                          updateForm(structuredClone(form))
-                        }} 
data-disabled={!resultAccount.body.contact_data?.email} 
data-selected={form.channel === TanChannel.EMAIL}
-                          class="relative flex 
data-[disabled=false]:cursor-pointer rounded-lg border bg-white 
data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none 
border-gray-300 data-[selected=true]:ring-2 
data-[selected=true]:ring-indigo-600">
-                          <input type="radio" name="channel" 
value="Newsletter" class="sr-only" />
+                      {config.supported_tan_channels.indexOf(
+                        TanChannel.EMAIL,
+                      ) === -1 ? undefined : (
+                        <label
+                          onClick={() => {
+                            if (!resultAccount.body.contact_data?.email) 
return;
+                            form.channel = TanChannel.EMAIL;
+                            updateForm(structuredClone(form));
+                          }}
+                          data-disabled={
+                            !resultAccount.body.contact_data?.email
+                          }
+                          data-selected={form.channel === TanChannel.EMAIL}
+                          class="relative flex 
data-[disabled=false]:cursor-pointer rounded-lg border bg-white 
data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none 
border-gray-300 data-[selected=true]:ring-2 
data-[selected=true]:ring-indigo-600"
+                        >
+                          <input
+                            type="radio"
+                            name="channel"
+                            value="Newsletter"
+                            class="sr-only"
+                          />
                           <span class="flex flex-1">
                             <span class="flex flex-col">
-                              <span id="project-type-0-label" class="block 
text-sm font-medium text-gray-900 ">
+                              <span
+                                id="project-type-0-label"
+                                class="block text-sm font-medium text-gray-900 
"
+                              >
                                 <i18n.Translate>Email</i18n.Translate>
                               </span>
-                              {!resultAccount.body.contact_data?.email && 
i18n.str`add a email in your profile to enable this option`}
+                              {!resultAccount.body.contact_data?.email &&
+                                i18n.str`add a email in your profile to enable 
this option`}
                             </span>
                           </span>
-                          <svg data-selected={form.channel === 
TanChannel.EMAIL} class="h-5 w-5 text-indigo-600 data-[selected=false]:hidden" 
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
-                            <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 
8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 
10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
+                          <svg
+                            data-selected={form.channel === TanChannel.EMAIL}
+                            class="h-5 w-5 text-indigo-600 
data-[selected=false]:hidden"
+                            viewBox="0 0 20 20"
+                            fill="currentColor"
+                            aria-hidden="true"
+                          >
+                            <path
+                              fill-rule="evenodd"
+                              d="M10 18a8 8 0 100-16 8 8 0 000 
16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 
1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
+                              clip-rule="evenodd"
+                            />
                           </svg>
                         </label>
-                      }
-
-                      {config.supported_tan_channels.indexOf(TanChannel.SMS) 
=== -1 ? undefined :
-                        <label onClick={() => {
-                          if (!resultAccount.body.contact_data?.phone) return;
-                          form.channel = TanChannel.SMS
-                          updateForm(structuredClone(form))
-                        }} 
data-disabled={!resultAccount.body.contact_data?.phone} 
data-selected={form.channel === TanChannel.SMS}
-                          class="relative flex 
data-[disabled=false]:cursor-pointer rounded-lg border 
data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none 
border-gray-300 data-[selected=true]:ring-2 
data-[selected=true]:ring-indigo-600">
-                          <input type="radio" name="channel" value="Existing 
Customers" class="sr-only" />
+                      )}
+
+                      {config.supported_tan_channels.indexOf(TanChannel.SMS) 
===
+                      -1 ? undefined : (
+                        <label
+                          onClick={() => {
+                            if (!resultAccount.body.contact_data?.phone) 
return;
+                            form.channel = TanChannel.SMS;
+                            updateForm(structuredClone(form));
+                          }}
+                          data-disabled={
+                            !resultAccount.body.contact_data?.phone
+                          }
+                          data-selected={form.channel === TanChannel.SMS}
+                          class="relative flex 
data-[disabled=false]:cursor-pointer rounded-lg border 
data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none 
border-gray-300 data-[selected=true]:ring-2 
data-[selected=true]:ring-indigo-600"
+                        >
+                          <input
+                            type="radio"
+                            name="channel"
+                            value="Existing Customers"
+                            class="sr-only"
+                          />
                           <span class="flex flex-1">
                             <span class="flex flex-col">
-                              <span id="project-type-1-label" class="block 
text-sm font-medium text-gray-900">
+                              <span
+                                id="project-type-1-label"
+                                class="block text-sm font-medium text-gray-900"
+                              >
                                 <i18n.Translate>SMS</i18n.Translate>
                               </span>
-                              {!resultAccount.body.contact_data?.phone && 
i18n.str`add a phone number in your profile to enable this option`}
+                              {!resultAccount.body.contact_data?.phone &&
+                                i18n.str`add a phone number in your profile to 
enable this option`}
                             </span>
                           </span>
-                          <svg data-selected={form.channel === TanChannel.SMS} 
class="h-5 w-5 text-indigo-600 data-[selected=false]:hidden" viewBox="0 0 20 
20" fill="currentColor" aria-hidden="true">
-                            <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 
8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 
10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
+                          <svg
+                            data-selected={form.channel === TanChannel.SMS}
+                            class="h-5 w-5 text-indigo-600 
data-[selected=false]:hidden"
+                            viewBox="0 0 20 20"
+                            fill="currentColor"
+                            aria-hidden="true"
+                          >
+                            <path
+                              fill-rule="evenodd"
+                              d="M10 18a8 8 0 100-16 8 8 0 000 
16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 
1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
+                              clip-rule="evenodd"
+                            />
                           </svg>
                         </label>
-                      }
+                      )}
                     </div>
                   </div>
-
                 </div>
-              }
+              )}
             </div>
           </div>
 
           <div class="flex items-center justify-between gap-x-6 border-t 
border-gray-900/10 px-4 py-4 sm:px-8">
-            {onCancel ?
-              <button type="button" class="text-sm font-semibold leading-6 
text-gray-900"
-                onClick={onCancel}
-              >
-                <i18n.Translate>Cancel</i18n.Translate>
-              </button>
-              : <div />
-            }
-            <button type="submit"
+            <a
+              href={routeClose.url({})}
+              type="button"
+              class="text-sm font-semibold leading-6 text-gray-900"
+            >
+              <i18n.Translate>Cancel</i18n.Translate>
+            </a>
+            <button
+              type="submit"
               class="disabled:opacity-50 disabled:cursor-default 
cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold 
text-white shadow-sm hover:bg-indigo-500 focus-visible:outline 
focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
               disabled={!!errors}
               onClick={(e) => {
-                e.preventDefault()
-                createCashout()
+                e.preventDefault();
+                createCashout();
               }}
             >
               <i18n.Translate>Cashout</i18n.Translate>
diff --git a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx 
b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
index b4d5b6584..589e29793 100644
--- a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
+++ b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -17,93 +17,99 @@ import {
   Amounts,
   HttpStatusCode,
   TalerError,
-  TalerErrorCode,
-  TranslatedString
+  assertUnreachable,
 } from "@gnu-taler/taler-util";
 import {
   Attention,
   Loading,
-  LocalNotificationBanner,
-  ShowInputErrorLabel,
-  useLocalNotification,
-  useTranslationContext
+  useTranslationContext,
 } from "@gnu-taler/web-util/browser";
 import { format } from "date-fns";
-import { Fragment, VNode, h } from "preact";
-import { useState } from "preact/hooks";
-import { mutate } from "swr";
+import { VNode, h } from "preact";
 import { ErrorLoadingWithDebug } from 
"../../components/ErrorLoadingWithDebug.js";
-import { useBankCoreApiContext } from "../../context/config.js";
-import { useBackendState } from "../../hooks/backend.js";
-import {
-  useCashoutDetails, useConversionInfo
-} from "../../hooks/circuit.js";
-import {
-  undefinedIfEmpty
-} from "../../utils.js";
+import { useCashoutDetails, useConversionInfo } from "../../hooks/circuit.js";
+import { RouteDefinition } from "../../route.js";
 import { RenderAmount } from "../PaytoWireTransferForm.js";
-import { assertUnreachable } from "../WithdrawalOperationPage.js";
 
 interface Props {
   id: string;
-  onCancel: () => void;
+  routeClose: RouteDefinition<Record<string, never>>;
 }
-export function ShowCashoutDetails({
-  id,
-  onCancel,
-}: Props): VNode {
+export function ShowCashoutDetails({ id, routeClose }: Props): VNode {
   const { i18n, dateLocale } = useTranslationContext();
-  const { state } = useBackendState();
-  const cid = Number.parseInt(id, 10)
+  const cid = Number.parseInt(id, 10);
 
   const result = useCashoutDetails(Number.isNaN(cid) ? undefined : cid);
   const info = useConversionInfo();
 
   if (Number.isNaN(cid)) {
-    return <Attention type="danger" title={i18n.str`cashout id should be a 
number`} />
+    return (
+      <Attention
+        type="danger"
+        title={i18n.str`cashout id should be a number`}
+      />
+    );
   }
   if (!result) {
-    return <Loading />
+    return <Loading />;
   }
   if (result instanceof TalerError) {
-    return <ErrorLoadingWithDebug error={result} />
+    return <ErrorLoadingWithDebug error={result} />;
   }
   if (result.type === "fail") {
     switch (result.case) {
-      case HttpStatusCode.NotFound: return <Attention type="warning" 
title={i18n.str`This cashout not found. Maybe already aborted.`}>
-      </Attention>
-      case HttpStatusCode.NotImplemented: return <Attention type="warning" 
title={i18n.str`Cashouts are not supported`}>
-      </Attention>
-      default: assertUnreachable(result)
+      case HttpStatusCode.NotFound:
+        return (
+          <Attention
+            type="warning"
+            title={i18n.str`This cashout not found. Maybe already aborted.`}
+          ></Attention>
+        );
+      case HttpStatusCode.NotImplemented:
+        return (
+          <Attention
+            type="warning"
+            title={i18n.str`Cashouts are not supported`}
+          ></Attention>
+        );
+      default:
+        assertUnreachable(result);
     }
   }
   if (!info) {
-    return <Loading />
+    return <Loading />;
   }
 
   if (info instanceof TalerError) {
-    return <ErrorLoadingWithDebug error={info} />
+    return <ErrorLoadingWithDebug error={info} />;
   }
   if (info.type === "fail") {
     switch (info.case) {
       case HttpStatusCode.NotImplemented: {
-        return <Attention type="danger" title={i18n.str`Cashout not 
implemented`} />
+        return (
+          <Attention type="danger" title={i18n.str`Cashout not implemented`} />
+        );
       }
-      default: assertUnreachable(info.case)
+      default:
+        assertUnreachable(info.case);
     }
   }
 
-  const { fiat_currency_specification, regional_currency_specification } = 
info.body
+  const { fiat_currency_specification, regional_currency_specification } =
+    info.body;
 
   return (
     <div>
       <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 
bg-gray-100 my-4 px-4 pb-4 rounded-lg">
-
         <section class="rounded-sm px-4">
-          <h2 id="summary-heading" class="font-medium 
text-lg"><i18n.Translate>Cashout detail</i18n.Translate></h2>
+          <h2 id="summary-heading" class="font-medium text-lg">
+            <i18n.Translate>Cashout detail</i18n.Translate>
+          </h2>
           <dl class="mt-8 space-y-4">
             <div class="justify-between items-center flex">
-              <dt class="text-sm 
text-gray-600"><i18n.Translate>Subject</i18n.Translate></dt>
+              <dt class="text-sm text-gray-600">
+                <i18n.Translate>Subject</i18n.Translate>
+              </dt>
               <dd class="text-sm ">{result.body.subject}</dd>
             </div>
           </dl>
@@ -113,48 +119,64 @@ export function ShowCashoutDetails({
             <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 
sm:grid-cols-6">
               <div class="sm:col-span-5">
                 <dl class="space-y-4">
-
-                  {result.body.creation_time.t_s !== "never" ?
+                  {result.body.creation_time.t_s !== "never" ? (
                     <div class="justify-between items-center flex ">
-                      <dt class=" 
text-gray-600"><i18n.Translate>Created</i18n.Translate></dt>
+                      <dt class=" text-gray-600">
+                        <i18n.Translate>Created</i18n.Translate>
+                      </dt>
                       <dd class="text-sm ">
-                        {format(result.body.creation_time.t_s * 1000, 
"dd/MM/yyyy HH:mm:ss", { locale: dateLocale })}
+                        {format(
+                          result.body.creation_time.t_s * 1000,
+                          "dd/MM/yyyy HH:mm:ss",
+                          { locale: dateLocale },
+                        )}
                       </dd>
                     </div>
-                    : undefined}
+                  ) : undefined}
 
                   <div class="flex justify-between items-center border-t-2 afu 
pt-4">
-                    <dt 
class="text-gray-600"><i18n.Translate>Debited</i18n.Translate></dt>
+                    <dt class="text-gray-600">
+                      <i18n.Translate>Debited</i18n.Translate>
+                    </dt>
                     <dd class=" font-medium">
-                      <RenderAmount 
value={Amounts.parseOrThrow(result.body.amount_debit)} negative withColor 
spec={regional_currency_specification} />
+                      <RenderAmount
+                        value={Amounts.parseOrThrow(result.body.amount_debit)}
+                        negative
+                        withColor
+                        spec={regional_currency_specification}
+                      />
                     </dd>
                   </div>
 
                   <div class="flex items-center justify-between border-t-2 afu 
pt-4">
                     <dt class="flex items-center text-gray-600">
-                      <span><i18n.Translate>Credited</i18n.Translate></span>
-
+                      <span>
+                        <i18n.Translate>Credited</i18n.Translate>
+                      </span>
                     </dt>
                     <dd class="text-sm ">
-                      <RenderAmount 
value={Amounts.parseOrThrow(result.body.amount_credit)} withColor 
spec={fiat_currency_specification} />
+                      <RenderAmount
+                        value={Amounts.parseOrThrow(result.body.amount_credit)}
+                        withColor
+                        spec={fiat_currency_specification}
+                      />
                     </dd>
                   </div>
-
                 </dl>
               </div>
             </div>
           </div>
-
         </div>
-
       </div>
 
       <br />
       <div style={{ display: "flex", justifyContent: "space-between" }}>
-        <button type="button" class="text-sm font-semibold leading-6 
text-gray-900"
-          onClick={onCancel}
+        <a
+          href={routeClose.url({})}
+          class="text-sm font-semibold leading-6 text-gray-900"
         >
-          <i18n.Translate>Cancel</i18n.Translate></button>
+          <i18n.Translate>Close</i18n.Translate>
+        </a>
       </div>
     </div>
   );
diff --git a/packages/demobank-ui/src/pages/index.stories.tsx 
b/packages/demobank-ui/src/pages/index.stories.tsx
index 168e9938e..823def5d7 100644
--- a/packages/demobank-ui/src/pages/index.stories.tsx
+++ b/packages/demobank-ui/src/pages/index.stories.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
diff --git a/packages/demobank-ui/src/pages/rnd.ts 
b/packages/demobank-ui/src/pages/rnd.ts
index 46111425e..d04a1515d 100644
--- a/packages/demobank-ui/src/pages/rnd.ts
+++ b/packages/demobank-ui/src/pages/rnd.ts
@@ -1,5 +1,19 @@
-import { createEddsaKeyPair, encodeCrock, getRandomBytes } from 
"@gnu-taler/taler-util"
+/*
+ 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 { encodeCrock, getRandomBytes } from "@gnu-taler/taler-util";
 
 const noun = [
   "people",
@@ -1526,8 +1540,8 @@ const noun = [
   "tomorrow",
   "wake",
   "wrap",
-  "yesterday"
-]
+  "yesterday",
+];
 
 const adj = [
   "abandoned",
@@ -2877,17 +2891,17 @@ const adj = [
   "zealous",
   "zesty",
   "zigzag",
-]
+];
 
-export function getRandomUsername(): { first: string, second: string } {
-  const n = Math.floor(Math.random() * noun.length)
-  const a = Math.floor(Math.random() * adj.length)
+export function getRandomUsername(): { first: string; second: string } {
+  const n = Math.floor(Math.random() * noun.length);
+  const a = Math.floor(Math.random() * adj.length);
   return {
     first: adj[a],
-    second: noun[n]
-  }
+    second: noun[n],
+  };
 }
 
 export function getRandomPassword(): string {
-  return encodeCrock(getRandomBytes(16))
-}
\ No newline at end of file
+  return encodeCrock(getRandomBytes(16));
+}
diff --git a/packages/demobank-ui/src/route.ts 
b/packages/demobank-ui/src/route.ts
index d54f9be83..72b405791 100644
--- a/packages/demobank-ui/src/route.ts
+++ b/packages/demobank-ui/src/route.ts
@@ -1,167 +1,200 @@
-import { createHashHistory } from "history";
-import { h as create, VNode } from "preact";
+/*
+ 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 { useEffect, useState } from "preact/hooks";
-const history = createHashHistory();
 
-type PageDefinition<DynamicPart extends Record<string, string>> = {
-  pattern: string;
-  (params: DynamicPart): string;
+export function urlPattern<
+  T extends Record<string, string> = Record<string, never>,
+>(pattern: RegExp, reverse: (p: T) => string): RouteDefinition<T> {
+  return {
+    pattern: new RegExp(pattern),
+    url: reverse,
+  };
+}
+
+// export function Router({
+//   pageList,
+//   onNotFound,
+// }: {
+//   pageList: Array<PageEntry<unknown>>;
+//   onNotFound: () => VNode;
+// }): VNode {
+//   const current = useCurrentLocation<unknown>(pageList);
+//   if (current !== undefined) {
+//     const d = current.page.url
+//     if (typeof current.page.url === "string") {
+//       const p = current.page.url
+//       return create(current.page.view, {});
+//     }
+//     const p = current.page.url
+//     return create(current.page.view, current.values);
+//   }
+//   return onNotFound();
+// }
+// type PagesMap<T extends object> = {
+//   [name in keyof T]: PageDefinition<any>;
+// };
+
+export type RouteDefinition<T> = {
+  pattern: RegExp;
+  url: (p: T) => string;
 };
 
-function replaceAll(
-  pattern: string,
-  vars: Record<string, string>,
-  values: Record<string, string>,
-): string {
-  let result = pattern;
-  for (const v in vars) {
-    result = result.replace(vars[v], !values[v] ? "" : values[v]);
-  }
-  return result;
+const nullRountDef = {
+  pattern: new RegExp(/.*/),
+  url: () => "",
+};
+export function buildNullRoutDefinition<T>(): RouteDefinition<T> {
+  return nullRountDef;
 }
 
-export function pageDefinition<T extends Record<string, string>>(
-  pattern: string,
-): PageDefinition<T> {
-  const patternParams = pattern.match(/(:[\w?]*)/g);
-  if (!patternParams)
-    throw Error(
-      `page definition pattern ${pattern} doesn't have any parameter`,
-    );
-
-  const vars = patternParams.reduce((prev, cur) => {
-    const pName = cur.match(/(\w+)/g);
-
-    //skip things like :? in the path pattern
-    if (!pName || !pName[0]) return prev;
-    const name = pName[0];
-    return { ...prev, [name]: cur };
-  }, {} as Record<string, string>);
-
-  const f = (values: T): string => replaceAll(pattern, vars, values);
-  f.pattern = pattern;
-  return f;
-}
+export type RouteMap<T> = {
+  [n in keyof T]: RouteDefinition<T[n]>;
+};
 
-export type PageEntry<T = unknown> = T extends Record<string, string>
-  ? {
-      url: PageDefinition<T>;
-      view: (props: T) => VNode;
-    }
-  : T extends unknown
-  ? {
-      url: string;
-      view: (props: {}) => VNode;
-    }
-  : never;
-
-export function Router({
-  pageList,
-  onNotFound,
-}: {
-  pageList: Array<PageEntry<any>>;
-  onNotFound: () => VNode;
-}): VNode {
-  const current = useCurrentLocation(pageList);
-  if (current !== undefined) {
-    return create(current.page.view, current.values);
-  }
-  return onNotFound();
-}
+export type RouteParamsType<
+  P,
+  T extends keyof P,
+> = P[T] extends RouteDefinition<infer ASD> ? ASD : never;
 
-type Location = {
-  page: PageEntry<any>;
-  path: string;
-  values: Record<string, string>;
+type Location<E, T extends RouteMap<E>, NAME extends keyof T> = {
+  parent: T;
+  name: NAME;
+  // mapped values from params and url
+  values: RouteParamsType<T, NAME>;
 };
-export function useCurrentLocation(pageList: Array<PageEntry<any>>) {
-  const [currentLocation, setCurrentLocation] = useState<Location>();
-  /**
-   * Search path in the pageList
-   * get the values from the path found
-   * add params from searchParams
-   *
-   * @param path
-   * @param params
-   */
-  function doSync(path: string, params: URLSearchParams) {
-    let result: typeof currentLocation;
-    for (let idx = 0; idx < pageList.length; idx++) {
-      const page = pageList[idx];
-      if (typeof page.url === "string") {
-        if (page.url === path) {
-          const values: Record<string, string> = {};
-          params.forEach((v, k) => {
-            values[k] = v;
-          });
-          result = { page, values, path };
-          break;
-        }
-      } else {
-        const values = doestUrlMatchToRoute(path, page.url.pattern);
-        if (values !== undefined) {
-          params.forEach((v, k) => {
-            values[k] = v;
-          });
-          result = { page, values, path };
-          break;
-        }
-      }
+
+const STARTUP_SPA_LOCATION =
+  typeof window !== "undefined" ? window.location.hash.substring(1) : "/";
+const STARTUP_SPA_PARAMS =
+  typeof window !== "undefined"
+    ? new URLSearchParams(window.location.search)
+    : new URLSearchParams();
+
+/**
+ * Search path in the pageList
+ * get the values from the path found
+ * add params from searchParams
+ *
+ * @param path
+ * @param params
+ */
+function doSync<DEF, RM extends RouteMap<DEF>, ROUTES extends keyof RM>(
+  pagesMap: RM,
+  pageList: Array<ROUTES>,
+  path: string,
+  params: URLSearchParams,
+): Location<DEF, RM, ROUTES> | undefined {
+  for (let idx = 0; idx < pageList.length; idx++) {
+    const name = pageList[idx];
+    const found = pagesMap[name].pattern.exec(path);
+    if (found !== null) {
+      const values =
+        found.groups === undefined ? {} : structuredClone(found.groups);
+      params.forEach((v, k) => {
+        values[k] = v;
+      });
+
+      // @ts-expect-error values is a map string which is equivalent to the 
RouteParamsType
+      return { name, parent: pagesMap, values };
     }
-    setCurrentLocation(result);
   }
+  return undefined;
+}
+
+const PopStateEventType = "popstate";
+
+export function useCurrentLocation<
+  DEF,
+  RM extends RouteMap<DEF>,
+  ROUTES extends keyof RM,
+>(pagesMap: RM) {
+  const pageList = Object.keys(pagesMap) as Array<ROUTES>;
+  const [currentLocation, setCurrentLocation] = useState<
+    Location<DEF, RM, ROUTES> | undefined
+  >(doSync(pagesMap, pageList, STARTUP_SPA_LOCATION, STARTUP_SPA_PARAMS));
   useEffect(() => {
-    doSync(window.location.hash, new URLSearchParams(window.location.search));
-    return history.listen(() => {
-      doSync(window.location.hash, new 
URLSearchParams(window.location.search));
+    window.addEventListener(PopStateEventType, () => {
+      const path = window.location.hash.substring(1);
+      console.log("event", path);
+      const l = doSync(
+        pagesMap,
+        pageList,
+        path,
+        new URLSearchParams(window.location.search),
+      );
+      setCurrentLocation(l);
     });
   }, []);
-  return currentLocation;
-}
-
-function doestUrlMatchToRoute(
-  url: string,
-  route: string,
-): undefined | Record<string, string> {
-  const paramsPattern = /(?:\?([^#]*))?$/;
-  // const paramsPattern = /(?:\?([^#]*))?(#.*)?$/;
-  const params = url.match(paramsPattern);
-  const urlWithoutParams = url.replace(paramsPattern, "");
-
-  const result: Record<string, string> = {};
-  if (params && params[1]) {
-    const paramList = params[1].split("&");
-    for (let i = 0; i < paramList.length; i++) {
-      const idx = paramList[i].indexOf("=");
-      const name = paramList[i].substring(0, idx);
-      const value = paramList[i].substring(idx + 1);
-      result[decodeURIComponent(name)] = decodeURIComponent(value);
-    }
-  }
-  const urlSeg = urlWithoutParams.split("/");
-  const routeSeg = route.split("/");
-  let max = Math.max(urlSeg.length, routeSeg.length);
-  for (let i = 0; i < max; i++) {
-    if (routeSeg[i] && routeSeg[i].charAt(0) === ":") {
-      const param = routeSeg[i].replace(/(^:|[+*?]+$)/g, "");
-
-      const flags = (routeSeg[i].match(/[+*?]+$/) || EMPTY)[0] || "";
-      const plus = ~flags.indexOf("+");
-      const star = ~flags.indexOf("*");
-      const val = urlSeg[i] || "";
-
-      if (!val && !star && (flags.indexOf("?") < 0 || plus)) {
-        return undefined;
-      }
-      result[param] = decodeURIComponent(val);
-      if (plus || star) {
-        result[param] = urlSeg.slice(i).map(decodeURIComponent).join("/");
-        break;
-      }
-    } else if (routeSeg[i] !== urlSeg[i]) {
-      return undefined;
-    }
+  function routeTo<N extends ROUTES>(
+    n: N,
+    values: RouteParamsType<RM, N>,
+  ): void {
+    setCurrentLocation({
+      parent: pagesMap,
+      name: n,
+      values,
+    });
   }
-  return result;
+  return [currentLocation, routeTo] as const;
 }
-const EMPTY: Record<string, string> = {};
+
+// function doestUrlMatchToRoute(
+//   url: string,
+//   route: string,
+// ): undefined | Record<string, string> {
+//   const paramsPattern = /(?:\?([^#]*))?$/;
+
+//   const urlSeg = url.replace(paramsPattern, "").split("/");
+//   const routeSeg = route.split("/");
+//   let max = Math.max(urlSeg.length, routeSeg.length);
+
+//   const result: Record<string, string> = {};
+//   for (let i = 0; i < max; i++) {
+//     if (routeSeg[i] && routeSeg[i].charAt(0) === ":") {
+//       const param = routeSeg[i].replace(/(^:|[+*?]+$)/g, "");
+
+//       const flags = (routeSeg[i].match(/[+*?]+$/) || EMPTY)[0] || "";
+//       const plus = ~flags.indexOf("+");
+//       const star = ~flags.indexOf("*");
+//       const val = urlSeg[i] || "";
+
+//       if (!val && !star && (flags.indexOf("?") < 0 || plus)) {
+//         return undefined;
+//       }
+//       result[param] = decodeURIComponent(val);
+//       if (plus || star) {
+//         result[param] = urlSeg.slice(i).map(decodeURIComponent).join("/");
+//         break;
+//       }
+//     } else if (routeSeg[i] !== urlSeg[i]) {
+//       return undefined;
+//     }
+//   }
+
+//   const params = url.match(paramsPattern);
+//   if (params && params[1]) {
+//     const paramList = params[1].split("&");
+//     for (let i = 0; i < paramList.length; i++) {
+//       const idx = paramList[i].indexOf("=");
+//       const name = paramList[i].substring(0, idx);
+//       const value = paramList[i].substring(idx + 1);
+//       result[decodeURIComponent(name)] = decodeURIComponent(value);
+//     }
+//   }
+
+//   return result;
+// }
+// const EMPTY: Record<string, string> = {};
diff --git a/packages/demobank-ui/src/settings.json 
b/packages/demobank-ui/src/settings.json
index a9246bc93..df5fe75ce 100644
--- a/packages/demobank-ui/src/settings.json
+++ b/packages/demobank-ui/src/settings.json
@@ -8,4 +8,4 @@
     "Bank": "http://bank-ui.taler.test:1180/";,
     "Merchant": "http://merchant.taler.test:1180/";
   }
-}
\ No newline at end of file
+}
diff --git a/packages/demobank-ui/src/settings.ts 
b/packages/demobank-ui/src/settings.ts
index 2c6ac1c67..91790d10d 100644
--- a/packages/demobank-ui/src/settings.ts
+++ b/packages/demobank-ui/src/settings.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -14,7 +14,14 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { Codec, buildCodecForObject, codecForBoolean, codecForList, 
codecForMap, codecForString, codecOptional } from "@gnu-taler/taler-util";
+import {
+  Codec,
+  buildCodecForObject,
+  codecForBoolean,
+  codecForMap,
+  codecForString,
+  codecOptional,
+} from "@gnu-taler/taler-util";
 
 export interface BankUiSettings {
   // Where libeufin backend is localted
@@ -31,7 +38,7 @@ export interface BankUiSettings {
   // Bank name shown in the header
   // default: "Taler Bank"
   bankName?: string;
-  // URL where the user is going to be redirected after 
+  // URL where the user is going to be redirected after
   // clicking in Taler Logo
   // default: home page
   iconLinkURL?: string;
@@ -58,41 +65,48 @@ const codecForBankUISettings = (): Codec<BankUiSettings> =>
   buildCodecForObject<BankUiSettings>()
     .property("backendBaseURL", codecOptional(codecForString()))
     .property("allowRandomAccountCreation", codecOptional(codecForBoolean()))
-    .property("simplePasswordForRandomAccounts", 
codecOptional(codecForBoolean()))
+    .property(
+      "simplePasswordForRandomAccounts",
+      codecOptional(codecForBoolean()),
+    )
     .property("bankName", codecOptional(codecForString()))
     .property("iconLinkURL", codecOptional(codecForString()))
     .property("topNavSites", codecOptional(codecForMap(codecForString())))
     .build("BankUiSettings");
 
-function removeUndefineField(obj: any): object {
-  return Object.keys(obj).reduce((prev, cur) => {
+function removeUndefineField<T extends object>(obj: T): T {
+  const keys = Object.keys(obj) as Array<keyof T>;
+  return keys.reduce((prev, cur) => {
     if (typeof prev[cur] === "undefined") {
-      delete prev[cur]
+      delete prev[cur];
     }
-    return prev
-  }, obj)
+    return prev;
+  }, obj);
 }
 
 export function fetchSettings(listener: (s: BankUiSettings) => void): void {
   fetch("./settings.json")
-    .then(resp => resp.json())
-    .then(json => codecForBankUISettings().decode(json))
-    .then(result => listener({
-      ...defaultSettings,
-      ...removeUndefineField(result),
-    }))
-    .catch(e => {
-      console.log("failed to fetch settings", e)
-      listener(defaultSettings)
-    })
+    .then((resp) => resp.json())
+    .then((json) => codecForBankUISettings().decode(json))
+    .then((result) =>
+      listener({
+        ...defaultSettings,
+        ...removeUndefineField(result),
+      }),
+    )
+    .catch((e) => {
+      console.log("failed to fetch settings", e);
+      listener(defaultSettings);
+    });
 }
 
-
-
 function buildDefaultBackendBaseURL(): string | undefined {
   if (typeof window !== "undefined") {
-    const currentLocation = new URL(window.location.pathname, 
window.location.origin).href
-    return currentLocation.replace("/webui", "")
+    const currentLocation = new URL(
+      window.location.pathname,
+      window.location.origin,
+    ).href;
+    return currentLocation.replace("/webui", "");
   }
-  return undefined
+  return undefined;
 }
diff --git a/packages/demobank-ui/src/stories.test.ts 
b/packages/demobank-ui/src/stories.test.ts
index ebd9e6d8a..747bf4083 100644
--- a/packages/demobank-ui/src/stories.test.ts
+++ b/packages/demobank-ui/src/stories.test.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -18,7 +18,12 @@
  *
  * @author Sebastian Javier Marchano (sebasjm)
  */
-import { AccessToken, AmountString, TalerCorebankApi, setupI18n } from 
"@gnu-taler/taler-util";
+import {
+  AccessToken,
+  AmountString,
+  TalerCorebankApi,
+  setupI18n,
+} from "@gnu-taler/taler-util";
 import { parseGroupImport } from "@gnu-taler/web-util/browser";
 import * as tests from "@gnu-taler/web-util/testing";
 import * as components from "./components/index.examples.js";
@@ -79,7 +84,7 @@ function DefaultTestingContext({
     },
     default_debit_threshold: "ARS:10" as AmountString,
     version: "1:0:0",
-  }
+  };
   const ctx2 = create(BankCoreApiProviderTesting, {
     children: ctx1,
     state: cfg,
diff --git a/packages/demobank-ui/src/stories.tsx 
b/packages/demobank-ui/src/stories.tsx
index 87848cb09..8342a8434 100644
--- a/packages/demobank-ui/src/stories.tsx
+++ b/packages/demobank-ui/src/stories.tsx
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
diff --git a/packages/demobank-ui/src/utils.ts 
b/packages/demobank-ui/src/utils.ts
index 7cdd8a861..1d3650772 100644
--- a/packages/demobank-ui/src/utils.ts
+++ b/packages/demobank-ui/src/utils.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -14,17 +14,20 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { AmountString, HttpStatusCode, PaytoString, TalerError, 
TalerErrorCode, TranslatedString } from "@gnu-taler/taler-util";
+import {
+  AmountString,
+  PaytoString,
+  TalerError,
+  TalerErrorCode,
+  TranslatedString,
+} from "@gnu-taler/taler-util";
 import {
   ErrorNotification,
-  ErrorType,
-  HttpError,
   notify,
   notifyError,
   useTranslationContext,
 } from "@gnu-taler/web-util/browser";
 
-
 /**
  * Validate (the number part of) an amount.  If needed,
  * replace comma with a dot.  Returns 'false' whenever
@@ -53,7 +56,9 @@ export function getIbanFromPayto(url: string): string {
 }
 
 export function undefinedIfEmpty<T extends object>(obj: T): T | undefined {
-  return Object.keys(obj).some((k) => (obj as any)[k] !== undefined)
+  return Object.keys(obj).some(
+    (k) => (obj as Record<string, T>)[k] !== undefined,
+  )
     ? obj
     : undefined;
 }
@@ -66,32 +71,38 @@ export type PartialButDefined<T> = {
  * every non-map field can be undefined
  */
 export type WithIntermediate<Type> = {
-  [prop in keyof Type]:
-  Type[prop] extends PaytoString ? Type[prop] | undefined :
-  Type[prop] extends AmountString ? Type[prop] | undefined :
-  Type[prop] extends TranslatedString ? Type[prop] | undefined :
-  Type[prop] extends object
-  ? WithIntermediate<Type[prop]>
-  : Type[prop] | undefined;
+  [prop in keyof Type]: Type[prop] extends PaytoString
+    ? Type[prop] | undefined
+    : Type[prop] extends AmountString
+      ? Type[prop] | undefined
+      : Type[prop] extends TranslatedString
+        ? Type[prop] | undefined
+        : Type[prop] extends object
+          ? WithIntermediate<Type[prop]>
+          : Type[prop] | undefined;
 };
 export type RecursivePartial<Type> = {
   [P in keyof Type]?: Type[P] extends (infer U)[]
-  ? RecursivePartial<U>[]
-  : Type[P] extends object
-  ? RecursivePartial<Type[P]>
-  : Type[P];
+    ? RecursivePartial<U>[]
+    : Type[P] extends object
+      ? RecursivePartial<Type[P]>
+      : Type[P];
 };
 export type ErrorMessageMappingFor<Type> = {
-  [prop in keyof Type]+?:
-  //enumerate known object
-  Exclude<Type[prop],undefined> extends PaytoString ? TranslatedString :
-  Exclude<Type[prop],undefined> extends AmountString ? TranslatedString :
-  Exclude<Type[prop],undefined> extends TranslatedString ? TranslatedString :
-  // arrays: every element
-  Exclude<Type[prop],undefined> extends (infer U)[] ? 
ErrorMessageMappingFor<U>[] : 
-  // map: every field
-  Exclude<Type[prop],undefined> extends object ? 
ErrorMessageMappingFor<Type[prop]>
-  : TranslatedString;
+  [prop in keyof Type]+?: // enumerate known object
+  Exclude<Type[prop], undefined> extends PaytoString
+    ? TranslatedString
+    : Exclude<Type[prop], undefined> extends AmountString
+      ? TranslatedString
+      : Exclude<Type[prop], undefined> extends TranslatedString
+        ? TranslatedString
+        : // arrays: every element
+          Exclude<Type[prop], undefined> extends (infer U)[]
+          ? ErrorMessageMappingFor<U>[]
+          : // map: every field
+            Exclude<Type[prop], undefined> extends object
+            ? ErrorMessageMappingFor<Type[prop]>
+            : TranslatedString;
 };
 
 export enum TanChannel {
@@ -108,31 +119,35 @@ export enum CashoutStatus {
   PENDING = "pending",
 }
 
-
 export const PAGE_SIZE = 20;
 export const MAX_RESULT_SIZE = PAGE_SIZE * 2 - 1;
 
-type Translator = ReturnType<typeof useTranslationContext>["i18n"]
+type Translator = ReturnType<typeof useTranslationContext>["i18n"];
 
-export async function withRuntimeErrorHandling<T>(i18n: Translator, cb: () => 
Promise<T>): Promise<void> {
+export async function withRuntimeErrorHandling<T>(
+  i18n: Translator,
+  cb: () => Promise<T>,
+): Promise<void> {
   try {
-    await cb()
+    await cb();
   } catch (error: unknown) {
     if (error instanceof TalerError) {
-      notify(buildRequestErrorMessage(i18n, error))
+      notify(buildRequestErrorMessage(i18n, error));
     } else {
       notifyError(
         i18n.str`Operation failed, please report`,
         (error instanceof Error
           ? error.message
-          : JSON.stringify(error)) as TranslatedString
-      )
+          : JSON.stringify(error)) as TranslatedString,
+      );
     }
   }
 }
 
-
-export function buildRequestErrorMessage(i18n: Translator, cause: TalerError): 
ErrorNotification {
+export function buildRequestErrorMessage(
+  i18n: Translator,
+  cause: TalerError,
+): ErrorNotification {
   let result: ErrorNotification;
   switch (cause.errorDetail.code) {
     case TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT: {
diff --git a/packages/demobank-ui/test.mjs b/packages/demobank-ui/test.mjs
index 9ff6055c6..baaaaa3ef 100755
--- a/packages/demobank-ui/test.mjs
+++ b/packages/demobank-ui/test.mjs
@@ -1,7 +1,7 @@
 #!/usr/bin/env node
 /*
  This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
+ (C) 2022-2024 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -24,8 +24,8 @@ await build({
   type: "test",
   source: {
     js: allTestFiles.files,
-    assets: [{base:"src",files:["src/index.html"]}],
-   
+    assets: [{ base: "src", files: ["src/index.html"] }],
+
   },
   destination: "./dist/test",
   css: "sass",
diff --git a/packages/pogen/src/po2ts.ts b/packages/pogen/src/po2ts.ts
index 0e2a0d6ea..ae5e7617b 100644
--- a/packages/pogen/src/po2ts.ts
+++ b/packages/pogen/src/po2ts.ts
@@ -59,7 +59,7 @@ export interface StringsType {
   completeness: number;
   'plural_forms': string;
   locale_data: {
-    messages: Record<string, any>;
+    messages: Record<string, unknown>;
   };
 };
 `;
diff --git a/packages/taler-util/src/http-client/types.ts 
b/packages/taler-util/src/http-client/types.ts
index 2172eee39..a014f63f8 100644
--- a/packages/taler-util/src/http-client/types.ts
+++ b/packages/taler-util/src/http-client/types.ts
@@ -1487,8 +1487,9 @@ export namespace TalerCorebankApi {
     // Only admin can change this property.
     debit_threshold?: AmountString;
 
+    //FIX: missing in SPEC
     // If present, enables 2FA and set the TAN channel used for challenges
-    tan_channel?: TanChannel;
+    tan_channel?: TanChannel | null;
 
   }
 
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index dbeed081f..222c1412d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,4 +1,4 @@
-lockfileVersion: '6.0'
+lockfileVersion: '6.1'
 
 settings:
   autoInstallPeers: true
@@ -89,7 +89,7 @@ importers:
         version: 0.19.9
       eslint-config-preact:
         specifier: ^1.2.0
-        version: 
1.3.0(@typescript-eslint/eslint-plugin@5.41.0)(eslint@8.55.0)(typescript@5.3.3)
+        version: 1.3.0(eslint@8.56.0)(typescript@5.3.3)
       mocha:
         specifier: ^9.2.0
         version: 9.2.2
@@ -341,85 +341,6 @@ importers:
         specifier: 5.3.3
         version: 5.3.3
 
-  packages/auditor-test:
-    dependencies:
-      '@gnu-taler/taler-util':
-        specifier: workspace:*
-        version: link:../taler-util
-      '@gnu-taler/web-util':
-        specifier: workspace:*
-        version: link:../web-util
-      '@headlessui/react':
-        specifier: ^1.7.14
-        version: 1.7.14(react-dom@18.2.0)(react@18.2.0)
-      '@heroicons/react':
-        specifier: ^2.0.17
-        version: 2.0.17(react@18.2.0)
-      date-fns:
-        specifier: 2.29.3
-        version: 2.29.3
-      history:
-        specifier: 4.10.1
-        version: 4.10.1
-      jed:
-        specifier: 1.1.1
-        version: 1.1.1
-      preact:
-        specifier: 10.11.3
-        version: 10.11.3
-      swr:
-        specifier: 2.0.3
-        version: 2.0.3(react@18.2.0)
-    devDependencies:
-      '@gnu-taler/pogen':
-        specifier: ^0.0.5
-        version: link:../pogen
-      '@tailwindcss/forms':
-        specifier: ^0.5.3
-        version: 0.5.3(tailwindcss@3.3.2)
-      '@tailwindcss/typography':
-        specifier: ^0.5.9
-        version: 0.5.9(tailwindcss@3.3.2)
-      '@types/chai':
-        specifier: ^4.3.0
-        version: 4.3.3
-      '@types/history':
-        specifier: ^4.7.8
-        version: 4.7.11
-      '@types/mocha':
-        specifier: ^10.0.1
-        version: 10.0.1
-      autoprefixer:
-        specifier: ^10.4.14
-        version: 10.4.14(postcss@8.4.32)
-      chai:
-        specifier: ^4.3.6
-        version: 4.3.6
-      esbuild:
-        specifier: ^0.19.9
-        version: 0.19.9
-      eslint-config-preact:
-        specifier: ^1.2.0
-        version: 
1.3.0(@typescript-eslint/eslint-plugin@5.41.0)(eslint@8.55.0)(typescript@5.3.3)
-      mocha:
-        specifier: ^9.2.0
-        version: 9.2.2
-      po2json:
-        specifier: ^0.4.5
-        version: 0.4.5
-      postcss:
-        specifier: ^8.4.23
-        version: 8.4.32
-      postcss-cli:
-        specifier: ^10.1.0
-        version: 10.1.0(postcss@8.4.32)
-      tailwindcss:
-        specifier: ^3.3.2
-        version: 3.3.2
-      typescript:
-        specifier: 5.3.3
-        version: 5.3.3
-
   packages/challenger-ui:
     devDependencies:
       '@gnu-taler/pogen':
@@ -464,18 +385,12 @@ importers:
       date-fns:
         specifier: 2.29.3
         version: 2.29.3
-      history:
-        specifier: 4.10.1
-        version: 4.10.1
       jed:
         specifier: 1.1.1
         version: 1.1.1
       preact:
         specifier: 10.11.3
         version: 10.11.3
-      preact-router:
-        specifier: 3.2.1
-        version: 3.2.1(preact@10.11.3)
       qrcode-generator:
         specifier: ^1.4.4
         version: 1.4.4
@@ -483,9 +398,6 @@ importers:
         specifier: 2.0.3
         version: 2.0.3(react@18.2.0)
     devDependencies:
-      '@creativebulma/bulma-tooltip':
-        specifier: ^1.2.0
-        version: 1.2.0
       '@gnu-taler/pogen':
         specifier: ^0.0.5
         version: link:../pogen
@@ -508,44 +420,35 @@ importers:
         specifier: ^18.11.17
         version: 18.11.17
       '@typescript-eslint/eslint-plugin':
-        specifier: ^5.41.0
-        version: 
5.41.0(@typescript-eslint/parser@5.41.0)(eslint@8.55.0)(typescript@5.3.3)
+        specifier: ^6.19.0
+        version: 
6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3)
       '@typescript-eslint/parser':
-        specifier: ^5.41.0
-        version: 5.41.0(eslint@8.55.0)(typescript@5.3.3)
+        specifier: ^6.19.0
+        version: 6.19.0(eslint@8.56.0)(typescript@5.3.3)
       autoprefixer:
         specifier: ^10.4.14
         version: 10.4.14(postcss@8.4.32)
-      bulma:
-        specifier: ^0.9.4
-        version: 0.9.4
-      bulma-checkbox:
-        specifier: ^1.1.1
-        version: 1.2.1
-      bulma-radio:
-        specifier: ^1.1.1
-        version: 1.2.0
       chai:
         specifier: ^4.3.6
         version: 4.3.6
       esbuild:
         specifier: ^0.19.9
         version: 0.19.9
-      eslint-config-preact:
-        specifier: ^1.2.0
-        version: 
1.3.0(@typescript-eslint/eslint-plugin@5.41.0)(eslint@8.55.0)(typescript@5.3.3)
+      eslint:
+        specifier: ^8.56.0
+        version: 8.56.0
+      eslint-config-prettier:
+        specifier: ^9.1.0
+        version: 9.1.0(eslint@8.56.0)
+      eslint-plugin-react:
+        specifier: ^7.33.2
+        version: 7.33.2(eslint@8.56.0)
       mocha:
-        specifier: ^9.2.0
-        version: 9.2.2
+        specifier: 9.2.0
+        version: 9.2.0
       po2json:
         specifier: ^0.4.5
         version: 0.4.5
-      preact-render-to-string:
-        specifier: ^5.2.6
-        version: 5.2.6(preact@10.11.3)
-      sass:
-        specifier: 1.56.1
-        version: 1.56.1
       tailwindcss:
         specifier: ^3.3.2
         version: 3.3.2
@@ -1366,7 +1269,7 @@ packages:
       semver: 6.3.1
     dev: true
 
-  /@babel/eslint-parser@7.19.1(@babel/core@7.18.9)(eslint@8.55.0):
+  /@babel/eslint-parser@7.19.1(@babel/core@7.18.9)(eslint@8.56.0):
     resolution: {integrity: 
sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==}
     engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0}
     peerDependencies:
@@ -1375,7 +1278,7 @@ packages:
     dependencies:
       '@babel/core': 7.18.9
       '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1
-      eslint: 8.55.0
+      eslint: 8.56.0
       eslint-visitor-keys: 2.1.0
       semver: 6.3.1
     dev: true
@@ -1440,8 +1343,8 @@ packages:
       '@babel/compat-data': 7.21.7
       '@babel/core': 7.13.16
       '@babel/helper-validator-option': 7.21.0
-      browserslist: 4.21.5
-      semver: 6.3.0
+      browserslist: 4.22.2
+      semver: 6.3.1
     dev: true
 
   /@babel/helper-compilation-targets@7.18.9(@babel/core@7.18.9):
@@ -1453,8 +1356,8 @@ packages:
       '@babel/compat-data': 7.21.7
       '@babel/core': 7.18.9
       '@babel/helper-validator-option': 7.21.0
-      browserslist: 4.21.5
-      semver: 6.3.0
+      browserslist: 4.22.2
+      semver: 6.3.1
 
   /@babel/helper-compilation-targets@7.18.9(@babel/core@7.22.1):
     resolution: {integrity: 
sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==}
@@ -1465,8 +1368,8 @@ packages:
       '@babel/compat-data': 7.21.7
       '@babel/core': 7.22.1
       '@babel/helper-validator-option': 7.21.0
-      browserslist: 4.21.5
-      semver: 6.3.0
+      browserslist: 4.22.2
+      semver: 6.3.1
     dev: true
 
   /@babel/helper-compilation-targets@7.21.5(@babel/core@7.22.1):
@@ -1478,7 +1381,7 @@ packages:
       '@babel/compat-data': 7.23.5
       '@babel/core': 7.22.1
       '@babel/helper-validator-option': 7.23.5
-      browserslist: 4.22.1
+      browserslist: 4.22.2
       lru-cache: 5.1.1
       semver: 6.3.1
     dev: true
@@ -1489,7 +1392,7 @@ packages:
     dependencies:
       '@babel/compat-data': 7.23.5
       '@babel/helper-validator-option': 7.23.5
-      browserslist: 4.22.1
+      browserslist: 4.22.2
       lru-cache: 5.1.1
       semver: 6.3.1
     dev: true
@@ -1594,7 +1497,7 @@ packages:
       '@babel/helper-plugin-utils': 7.22.5
       debug: 4.3.4
       lodash.debounce: 4.0.8
-      resolve: 1.22.2
+      resolve: 1.22.8
       semver: 6.3.1
     transitivePeerDependencies:
       - supports-color
@@ -1610,7 +1513,7 @@ packages:
       '@babel/helper-plugin-utils': 7.22.5
       debug: 4.3.4
       lodash.debounce: 4.0.8
-      resolve: 1.22.2
+      resolve: 1.22.8
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -1625,7 +1528,7 @@ packages:
       '@babel/helper-plugin-utils': 7.22.5
       debug: 4.3.4
       lodash.debounce: 4.0.8
-      resolve: 1.22.2
+      resolve: 1.22.8
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -5191,13 +5094,13 @@ packages:
     dev: true
     optional: true
 
-  /@eslint-community/eslint-utils@4.4.0(eslint@8.55.0):
+  /@eslint-community/eslint-utils@4.4.0(eslint@8.56.0):
     resolution: {integrity: 
sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
     dependencies:
-      eslint: 8.55.0
+      eslint: 8.56.0
       eslint-visitor-keys: 3.4.3
     dev: true
 
@@ -5257,8 +5160,8 @@ packages:
       - supports-color
     dev: true
 
-  /@eslint/js@8.55.0:
-    resolution: {integrity: 
sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==}
+  /@eslint/js@8.56.0:
+    resolution: {integrity: 
sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dev: true
 
@@ -6080,6 +5983,10 @@ packages:
     resolution: {integrity: 
sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
     dev: true
 
+  /@types/json-schema@7.0.15:
+    resolution: {integrity: 
sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+    dev: true
+
   /@types/json5@0.0.29:
     resolution: {integrity: 
sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
     dev: true
@@ -6163,6 +6070,10 @@ packages:
     resolution: {integrity: 
sha512-WwA1MW0++RfXmCr12xeYOOC5baSC9mSb0ZqCquFzKhcoF4TvHu5MKOuXsncgZcpVFhB1pXd5hZmM0ryAoCp12A==}
     dev: true
 
+  /@types/semver@7.5.6:
+    resolution: {integrity: 
sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==}
+    dev: true
+
   /@types/serve-index@1.9.1:
     resolution: {integrity: 
sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==}
     dependencies:
@@ -6281,27 +6192,30 @@ packages:
       - supports-color
     dev: true
 
-  
/@typescript-eslint/eslint-plugin@5.41.0(@typescript-eslint/parser@5.41.0)(eslint@8.55.0)(typescript@5.3.3):
-    resolution: {integrity: 
sha512-DXUS22Y57/LAFSg3x7Vi6RNAuLpTXwxB9S2nIA7msBb/Zt8p7XqMwdpdc1IU7CkOQUPgAqR5fWvxuKCbneKGmA==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+  
/@typescript-eslint/eslint-plugin@6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3):
+    resolution: {integrity: 
sha512-DUCUkQNklCQYnrBSSikjVChdc84/vMPDQSgJTHBZ64G9bA9w0Crc0rd2diujKbTdp6w2J47qkeHQLoi0rpLCdg==}
+    engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
-      '@typescript-eslint/parser': ^5.0.0
-      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+      '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha
+      eslint: ^7.0.0 || ^8.0.0
       typescript: '*'
     peerDependenciesMeta:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/parser': 5.41.0(eslint@8.55.0)(typescript@5.3.3)
-      '@typescript-eslint/scope-manager': 5.41.0
-      '@typescript-eslint/type-utils': 5.41.0(eslint@8.55.0)(typescript@5.3.3)
-      '@typescript-eslint/utils': 5.41.0(eslint@8.55.0)(typescript@5.3.3)
+      '@eslint-community/regexpp': 4.10.0
+      '@typescript-eslint/parser': 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/scope-manager': 6.19.0
+      '@typescript-eslint/type-utils': 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/utils': 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/visitor-keys': 6.19.0
       debug: 4.3.4
-      eslint: 8.55.0
-      ignore: 5.2.0
-      regexpp: 3.2.0
-      semver: 7.3.8
-      tsutils: 3.21.0(typescript@5.3.3)
+      eslint: 8.56.0
+      graphemer: 1.4.0
+      ignore: 5.3.0
+      natural-compare: 1.4.0
+      semver: 7.5.4
+      ts-api-utils: 1.0.3(typescript@5.3.3)
       typescript: 5.3.3
     transitivePeerDependencies:
       - supports-color
@@ -6338,14 +6252,14 @@ packages:
       - typescript
     dev: true
 
-  
/@typescript-eslint/experimental-utils@5.41.0(eslint@8.55.0)(typescript@5.3.3):
+  
/@typescript-eslint/experimental-utils@5.41.0(eslint@8.56.0)(typescript@5.3.3):
     resolution: {integrity: 
sha512-/qxT2Kd2q/A22JVIllvws4rvc00/3AT4rAo/0YgEN28y+HPhbJbk6X4+MAHEoZzpNyAOugIT7D/OLnKBW8FfhA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
     dependencies:
-      '@typescript-eslint/utils': 5.41.0(eslint@8.55.0)(typescript@5.3.3)
-      eslint: 8.55.0
+      '@typescript-eslint/utils': 5.41.0(eslint@8.56.0)(typescript@5.3.3)
+      eslint: 8.56.0
     transitivePeerDependencies:
       - supports-color
       - typescript
@@ -6391,21 +6305,22 @@ packages:
       - supports-color
     dev: true
 
-  /@typescript-eslint/parser@5.41.0(eslint@8.55.0)(typescript@5.3.3):
-    resolution: {integrity: 
sha512-HQVfix4+RL5YRWZboMD1pUfFN8MpRH4laziWkkAzyO1fvNOY/uinZcvo3QiFJVS/siNHupV8E5+xSwQZrl6PZA==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+  /@typescript-eslint/parser@6.19.0(eslint@8.56.0)(typescript@5.3.3):
+    resolution: {integrity: 
sha512-1DyBLG5SH7PYCd00QlroiW60YJ4rWMuUGa/JBV0iZuqi4l4IK3twKPq5ZkEebmGqRjXWVgsUzfd3+nZveewgow==}
+    engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
-      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+      eslint: ^7.0.0 || ^8.0.0
       typescript: '*'
     peerDependenciesMeta:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/scope-manager': 5.41.0
-      '@typescript-eslint/types': 5.41.0
-      '@typescript-eslint/typescript-estree': 5.41.0(typescript@5.3.3)
+      '@typescript-eslint/scope-manager': 6.19.0
+      '@typescript-eslint/types': 6.19.0
+      '@typescript-eslint/typescript-estree': 6.19.0(typescript@5.3.3)
+      '@typescript-eslint/visitor-keys': 6.19.0
       debug: 4.3.4
-      eslint: 8.55.0
+      eslint: 8.56.0
       typescript: 5.3.3
     transitivePeerDependencies:
       - supports-color
@@ -6427,6 +6342,14 @@ packages:
       '@typescript-eslint/visitor-keys': 5.41.0
     dev: true
 
+  /@typescript-eslint/scope-manager@6.19.0:
+    resolution: {integrity: 
sha512-dO1XMhV2ehBI6QN8Ufi7I10wmUovmLU0Oru3n5LVlM2JuzB4M+dVphCPLkVpKvGij2j/pHBWuJ9piuXx+BhzxQ==}
+    engines: {node: ^16.0.0 || >=18.0.0}
+    dependencies:
+      '@typescript-eslint/types': 6.19.0
+      '@typescript-eslint/visitor-keys': 6.19.0
+    dev: true
+
   /@typescript-eslint/type-utils@5.41.0(eslint@8.26.0)(typescript@5.3.3):
     resolution: {integrity: 
sha512-L30HNvIG6A1Q0R58e4hu4h+fZqaO909UcnnPbwKiN6Rc3BUEx6ez2wgN7aC0cBfcAjZfwkzE+E2PQQ9nEuoqfA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -6447,21 +6370,21 @@ packages:
       - supports-color
     dev: true
 
-  /@typescript-eslint/type-utils@5.41.0(eslint@8.55.0)(typescript@5.3.3):
-    resolution: {integrity: 
sha512-L30HNvIG6A1Q0R58e4hu4h+fZqaO909UcnnPbwKiN6Rc3BUEx6ez2wgN7aC0cBfcAjZfwkzE+E2PQQ9nEuoqfA==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+  /@typescript-eslint/type-utils@6.19.0(eslint@8.56.0)(typescript@5.3.3):
+    resolution: {integrity: 
sha512-mcvS6WSWbjiSxKCwBcXtOM5pRkPQ6kcDds/juxcy/727IQr3xMEcwr/YLHW2A2+Fp5ql6khjbKBzOyjuPqGi/w==}
+    engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
-      eslint: '*'
+      eslint: ^7.0.0 || ^8.0.0
       typescript: '*'
     peerDependenciesMeta:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/typescript-estree': 5.41.0(typescript@5.3.3)
-      '@typescript-eslint/utils': 5.41.0(eslint@8.55.0)(typescript@5.3.3)
+      '@typescript-eslint/typescript-estree': 6.19.0(typescript@5.3.3)
+      '@typescript-eslint/utils': 6.19.0(eslint@8.56.0)(typescript@5.3.3)
       debug: 4.3.4
-      eslint: 8.55.0
-      tsutils: 3.21.0(typescript@5.3.3)
+      eslint: 8.56.0
+      ts-api-utils: 1.0.3(typescript@5.3.3)
       typescript: 5.3.3
     transitivePeerDependencies:
       - supports-color
@@ -6477,6 +6400,11 @@ packages:
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dev: true
 
+  /@typescript-eslint/types@6.19.0:
+    resolution: {integrity: 
sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A==}
+    engines: {node: ^16.0.0 || >=18.0.0}
+    dev: true
+
   /@typescript-eslint/typescript-estree@4.33.0(typescript@5.3.3):
     resolution: {integrity: 
sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==}
     engines: {node: ^10.12.0 || >=12.0.0}
@@ -6491,7 +6419,7 @@ packages:
       debug: 4.3.4
       globby: 11.1.0
       is-glob: 4.0.3
-      semver: 7.3.8
+      semver: 7.5.4
       tsutils: 3.21.0(typescript@5.3.3)
       typescript: 5.3.3
     transitivePeerDependencies:
@@ -6512,13 +6440,35 @@ packages:
       debug: 4.3.4
       globby: 11.1.0
       is-glob: 4.0.3
-      semver: 7.3.8
+      semver: 7.5.4
       tsutils: 3.21.0(typescript@5.3.3)
       typescript: 5.3.3
     transitivePeerDependencies:
       - supports-color
     dev: true
 
+  /@typescript-eslint/typescript-estree@6.19.0(typescript@5.3.3):
+    resolution: {integrity: 
sha512-o/zefXIbbLBZ8YJ51NlkSAt2BamrK6XOmuxSR3hynMIzzyMY33KuJ9vuMdFSXW+H0tVvdF9qBPTHA91HDb4BIQ==}
+    engines: {node: ^16.0.0 || >=18.0.0}
+    peerDependencies:
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/types': 6.19.0
+      '@typescript-eslint/visitor-keys': 6.19.0
+      debug: 4.3.4
+      globby: 11.1.0
+      is-glob: 4.0.3
+      minimatch: 9.0.3
+      semver: 7.5.4
+      ts-api-utils: 1.0.3(typescript@5.3.3)
+      typescript: 5.3.3
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@typescript-eslint/utils@5.41.0(eslint@7.32.0)(typescript@5.3.3):
     resolution: {integrity: 
sha512-QlvfwaN9jaMga9EBazQ+5DDx/4sAdqDkcs05AsQHMaopluVCUyu1bTRUVKzXbgjDlrRAQrYVoi/sXJ9fmG+KLQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -6533,7 +6483,7 @@ packages:
       eslint: 7.32.0
       eslint-scope: 5.1.1
       eslint-utils: 3.0.0(eslint@7.32.0)
-      semver: 7.3.8
+      semver: 7.5.4
     transitivePeerDependencies:
       - supports-color
       - typescript
@@ -6553,13 +6503,13 @@ packages:
       eslint: 8.26.0
       eslint-scope: 5.1.1
       eslint-utils: 3.0.0(eslint@8.26.0)
-      semver: 7.3.8
+      semver: 7.5.4
     transitivePeerDependencies:
       - supports-color
       - typescript
     dev: true
 
-  /@typescript-eslint/utils@5.41.0(eslint@8.55.0)(typescript@5.3.3):
+  /@typescript-eslint/utils@5.41.0(eslint@8.56.0)(typescript@5.3.3):
     resolution: {integrity: 
sha512-QlvfwaN9jaMga9EBazQ+5DDx/4sAdqDkcs05AsQHMaopluVCUyu1bTRUVKzXbgjDlrRAQrYVoi/sXJ9fmG+KLQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     peerDependencies:
@@ -6570,10 +6520,29 @@ packages:
       '@typescript-eslint/scope-manager': 5.41.0
       '@typescript-eslint/types': 5.41.0
       '@typescript-eslint/typescript-estree': 5.41.0(typescript@5.3.3)
-      eslint: 8.55.0
+      eslint: 8.56.0
       eslint-scope: 5.1.1
-      eslint-utils: 3.0.0(eslint@8.55.0)
-      semver: 7.3.8
+      eslint-utils: 3.0.0(eslint@8.56.0)
+      semver: 7.5.4
+    transitivePeerDependencies:
+      - supports-color
+      - typescript
+    dev: true
+
+  /@typescript-eslint/utils@6.19.0(eslint@8.56.0)(typescript@5.3.3):
+    resolution: {integrity: 
sha512-QR41YXySiuN++/dC9UArYOg4X86OAYP83OWTewpVx5ct1IZhjjgTLocj7QNxGhWoTqknsgpl7L+hGygCO+sdYw==}
+    engines: {node: ^16.0.0 || >=18.0.0}
+    peerDependencies:
+      eslint: ^7.0.0 || ^8.0.0
+    dependencies:
+      '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0)
+      '@types/json-schema': 7.0.15
+      '@types/semver': 7.5.6
+      '@typescript-eslint/scope-manager': 6.19.0
+      '@typescript-eslint/types': 6.19.0
+      '@typescript-eslint/typescript-estree': 6.19.0(typescript@5.3.3)
+      eslint: 8.56.0
+      semver: 7.5.4
     transitivePeerDependencies:
       - supports-color
       - typescript
@@ -6592,7 +6561,15 @@ packages:
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dependencies:
       '@typescript-eslint/types': 5.41.0
-      eslint-visitor-keys: 3.3.0
+      eslint-visitor-keys: 3.4.3
+    dev: true
+
+  /@typescript-eslint/visitor-keys@6.19.0:
+    resolution: {integrity: 
sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ==}
+    engines: {node: ^16.0.0 || >=18.0.0}
+    dependencies:
+      '@typescript-eslint/types': 6.19.0
+      eslint-visitor-keys: 3.4.3
     dev: true
 
   /@ungap/promise-all-settled@1.1.2:
@@ -6832,12 +6809,6 @@ packages:
     engines: {node: '>=0.4.0'}
     dev: true
 
-  /acorn@8.10.0:
-    resolution: {integrity: 
sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==}
-    engines: {node: '>=0.4.0'}
-    hasBin: true
-    dev: true
-
   /acorn@8.11.2:
     resolution: {integrity: 
sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==}
     engines: {node: '>=0.4.0'}
@@ -7160,9 +7131,9 @@ packages:
     resolution: {integrity: 
sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==}
     engines: {node: '>= 0.4'}
     dependencies:
-      call-bind: 1.0.2
-      define-properties: 1.1.4
-      es-abstract: 1.20.4
+      call-bind: 1.0.5
+      define-properties: 1.2.1
+      es-abstract: 1.22.3
       es-array-method-boxes-properly: 1.0.0
       is-string: 1.0.7
     dev: true
@@ -7313,8 +7284,8 @@ packages:
     peerDependencies:
       postcss: ^8.1.0
     dependencies:
-      browserslist: 4.22.2
-      caniuse-lite: 1.0.30001570
+      browserslist: 4.21.5
+      caniuse-lite: 1.0.30001482
       fraction.js: 4.2.0
       normalize-range: 0.1.2
       picocolors: 1.0.0
@@ -7466,7 +7437,7 @@ packages:
     dependencies:
       '@babel/runtime': 7.19.4
       cosmiconfig: 7.0.1
-      resolve: 1.22.2
+      resolve: 1.22.8
     dev: true
 
   /babel-plugin-polyfill-corejs2@0.3.3(@babel/core@7.22.1):
@@ -7849,20 +7820,10 @@ packages:
     resolution: {integrity: 
sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==}
     engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
     dependencies:
-      caniuse-lite: 1.0.30001482
+      caniuse-lite: 1.0.30001570
       electron-to-chromium: 1.4.284
       node-releases: 2.0.10
       update-browserslist-db: 1.0.10(browserslist@4.21.5)
-
-  /browserslist@4.22.1:
-    resolution: {integrity: 
sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==}
-    engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
-    hasBin: true
-    dependencies:
-      caniuse-lite: 1.0.30001565
-      electron-to-chromium: 1.4.597
-      node-releases: 2.0.13
-      update-browserslist-db: 1.0.13(browserslist@4.22.1)
     dev: true
 
   /browserslist@4.22.2:
@@ -7874,7 +7835,6 @@ packages:
       electron-to-chromium: 1.4.613
       node-releases: 2.0.14
       update-browserslist-db: 1.0.13(browserslist@4.22.2)
-    dev: true
 
   /buffer-from@1.1.2:
     resolution: {integrity: 
sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
@@ -8065,13 +8025,6 @@ packages:
       write-file-atomic: 3.0.3
     dev: true
 
-  /call-bind@1.0.2:
-    resolution: {integrity: 
sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
-    dependencies:
-      function-bind: 1.1.1
-      get-intrinsic: 1.1.3
-    dev: true
-
   /call-bind@1.0.5:
     resolution: {integrity: 
sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==}
     dependencies:
@@ -8148,14 +8101,10 @@ packages:
 
   /caniuse-lite@1.0.30001482:
     resolution: {integrity: 
sha512-F1ZInsg53cegyjroxLNW9DmrEQ1SuGRTO1QlpA0o2/6OpQ0gFeDRoq1yFmnr8Sakn9qwwt9DmbxHB6w167OSuQ==}
-
-  /caniuse-lite@1.0.30001565:
-    resolution: {integrity: 
sha512-xrE//a3O7TP0vaJ8ikzkD2c2NgcVUvsEe2IvFTntV4Yd1Z9FVzh+gW+enX96L0psrbaFMcVcH2l90xNuGDWc8w==}
     dev: true
 
   /caniuse-lite@1.0.30001570:
     resolution: {integrity: 
sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw==}
-    dev: true
 
   /caseless@0.12.0:
     resolution: {integrity: 
sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==}
@@ -8709,13 +8658,13 @@ packages:
   /core-js-compat@3.26.0:
     resolution: {integrity: 
sha512-piOX9Go+Z4f9ZiBFLnZ5VrOpBl0h7IGCkiFUN11QTe6LjAvOT3ifL/5TdoizMh99hcGy5SoLyWbapIY/PIb/3A==}
     dependencies:
-      browserslist: 4.22.1
+      browserslist: 4.22.2
     dev: true
 
   /core-js-compat@3.33.3:
     resolution: {integrity: 
sha512-cNzGqFsh3Ot+529GIXacjTJ7kegdt5fPXxCBVS1G0iaZpuo/tBz399ymceLJveQhFFZ8qThHiP3fzuoQjKN2ow==}
     dependencies:
-      browserslist: 4.22.1
+      browserslist: 4.22.2
     dev: true
 
   /core-js@3.26.0:
@@ -9305,14 +9254,6 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
-  /define-properties@1.1.4:
-    resolution: {integrity: 
sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==}
-    engines: {node: '>= 0.4'}
-    dependencies:
-      has-property-descriptors: 1.0.0
-      object-keys: 1.1.1
-    dev: true
-
   /define-properties@1.2.1:
     resolution: {integrity: 
sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
     engines: {node: '>= 0.4'}
@@ -9584,6 +9525,7 @@ packages:
 
   /electron-to-chromium@1.4.284:
     resolution: {integrity: 
sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==}
+    dev: true
 
   /electron-to-chromium@1.4.597:
     resolution: {integrity: 
sha512-0XOQNqHhg2YgRVRUrS4M4vWjFCFIP2ETXcXe/0KIQBjXE9Cpy+tgzzYfuq6HGai3hWq0YywtG+5XK8fyG08EjA==}
@@ -9591,7 +9533,6 @@ packages:
 
   /electron-to-chromium@1.4.613:
     resolution: {integrity: 
sha512-r4x5+FowKG6q+/Wj0W9nidx7QO31BJwmR2uEo+Qh3YLGQ8SbBAFuDFpTxzly/I2gsbrFwBuIjrMp423L3O5U3w==}
-    dev: true
 
   /elliptic@6.5.4:
     resolution: {integrity: 
sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==}
@@ -9698,36 +9639,6 @@ packages:
       is-arrayish: 0.2.1
     dev: true
 
-  /es-abstract@1.20.4:
-    resolution: {integrity: 
sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==}
-    engines: {node: '>= 0.4'}
-    dependencies:
-      call-bind: 1.0.2
-      es-to-primitive: 1.2.1
-      function-bind: 1.1.1
-      function.prototype.name: 1.1.5
-      get-intrinsic: 1.1.3
-      get-symbol-description: 1.0.0
-      has: 1.0.3
-      has-property-descriptors: 1.0.0
-      has-symbols: 1.0.3
-      internal-slot: 1.0.3
-      is-callable: 1.2.7
-      is-negative-zero: 2.0.2
-      is-regex: 1.1.4
-      is-shared-array-buffer: 1.0.2
-      is-string: 1.0.7
-      is-weakref: 1.0.2
-      object-inspect: 1.12.2
-      object-keys: 1.1.1
-      object.assign: 4.1.4
-      regexp.prototype.flags: 1.4.3
-      safe-regex-test: 1.0.0
-      string.prototype.trimend: 1.0.5
-      string.prototype.trimstart: 1.0.5
-      unbox-primitive: 1.0.2
-    dev: true
-
   /es-abstract@1.22.3:
     resolution: {integrity: 
sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==}
     engines: {node: '>= 0.4'}
@@ -9956,21 +9867,21 @@ packages:
       - typescript
     dev: true
 
-  
/eslint-config-preact@1.3.0(@typescript-eslint/eslint-plugin@5.41.0)(eslint@8.55.0)(typescript@5.3.3):
+  /eslint-config-preact@1.3.0(eslint@8.56.0)(typescript@5.3.3):
     resolution: {integrity: 
sha512-yHYXg5qNzEJd3D/30AmsIW0W8MuY858KpApXp7xxBF08IYUljSKCOqMx+dVucXHQnAm7+11wOnMkgVHIBAechw==}
     peerDependencies:
       eslint: 6.x || 7.x || 8.x
     dependencies:
       '@babel/core': 7.18.9
-      '@babel/eslint-parser': 7.19.1(@babel/core@7.18.9)(eslint@8.55.0)
+      '@babel/eslint-parser': 7.19.1(@babel/core@7.18.9)(eslint@8.56.0)
       '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.18.9)
       '@babel/plugin-syntax-decorators': 7.19.0(@babel/core@7.18.9)
       '@babel/plugin-syntax-jsx': 7.21.4(@babel/core@7.18.9)
-      eslint: 8.55.0
-      eslint-plugin-compat: 4.0.2(eslint@8.55.0)
-      eslint-plugin-jest: 
25.7.0(@typescript-eslint/eslint-plugin@5.41.0)(eslint@8.55.0)(typescript@5.3.3)
-      eslint-plugin-react: 7.33.2(eslint@8.55.0)
-      eslint-plugin-react-hooks: 4.6.0(eslint@8.55.0)
+      eslint: 8.56.0
+      eslint-plugin-compat: 4.0.2(eslint@8.56.0)
+      eslint-plugin-jest: 25.7.0(eslint@8.56.0)(typescript@5.3.3)
+      eslint-plugin-react: 7.33.2(eslint@8.56.0)
+      eslint-plugin-react-hooks: 4.6.0(eslint@8.56.0)
     transitivePeerDependencies:
       - '@typescript-eslint/eslint-plugin'
       - jest
@@ -9978,6 +9889,15 @@ packages:
       - typescript
     dev: true
 
+  /eslint-config-prettier@9.1.0(eslint@8.56.0):
+    resolution: {integrity: 
sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==}
+    hasBin: true
+    peerDependencies:
+      eslint: '>=7.0.0'
+    dependencies:
+      eslint: 8.56.0
+    dev: true
+
   /eslint-import-resolver-node@0.3.9:
     resolution: {integrity: 
sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
     dependencies:
@@ -10034,7 +9954,7 @@ packages:
       semver: 7.3.5
     dev: true
 
-  /eslint-plugin-compat@4.0.2(eslint@8.55.0):
+  /eslint-plugin-compat@4.0.2(eslint@8.56.0):
     resolution: {integrity: 
sha512-xqvoO54CLTVaEYGMzhu35Wzwk/As7rCvz/2dqwnFiWi0OJccEtGIn+5qq3zqIu9nboXlpdBN579fZcItC73Ycg==}
     engines: {node: '>=9.x'}
     peerDependencies:
@@ -10045,7 +9965,7 @@ packages:
       browserslist: 4.22.2
       caniuse-lite: 1.0.30001570
       core-js: 3.26.0
-      eslint: 8.55.0
+      eslint: 8.56.0
       find-up: 5.0.0
       lodash.memoize: 4.1.2
       semver: 7.3.5
@@ -10115,7 +10035,7 @@ packages:
       - typescript
     dev: true
 
-  
/eslint-plugin-jest@25.7.0(@typescript-eslint/eslint-plugin@5.41.0)(eslint@8.55.0)(typescript@5.3.3):
+  /eslint-plugin-jest@25.7.0(eslint@8.56.0)(typescript@5.3.3):
     resolution: {integrity: 
sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==}
     engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
     peerDependencies:
@@ -10128,9 +10048,8 @@ packages:
       jest:
         optional: true
     dependencies:
-      '@typescript-eslint/eslint-plugin': 
5.41.0(@typescript-eslint/parser@5.41.0)(eslint@8.55.0)(typescript@5.3.3)
-      '@typescript-eslint/experimental-utils': 
5.41.0(eslint@8.55.0)(typescript@5.3.3)
-      eslint: 8.55.0
+      '@typescript-eslint/experimental-utils': 
5.41.0(eslint@8.56.0)(typescript@5.3.3)
+      eslint: 8.56.0
     transitivePeerDependencies:
       - supports-color
       - typescript
@@ -10179,13 +10098,13 @@ packages:
       eslint: 8.26.0
     dev: true
 
-  /eslint-plugin-react-hooks@4.6.0(eslint@8.55.0):
+  /eslint-plugin-react-hooks@4.6.0(eslint@8.56.0):
     resolution: {integrity: 
sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==}
     engines: {node: '>=10'}
     peerDependencies:
       eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
     dependencies:
-      eslint: 8.55.0
+      eslint: 8.56.0
     dev: true
 
   /eslint-plugin-react@7.33.2(eslint@7.32.0):
@@ -10238,7 +10157,7 @@ packages:
       string.prototype.matchall: 4.0.10
     dev: true
 
-  /eslint-plugin-react@7.33.2(eslint@8.55.0):
+  /eslint-plugin-react@7.33.2(eslint@8.56.0):
     resolution: {integrity: 
sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==}
     engines: {node: '>=4'}
     peerDependencies:
@@ -10249,7 +10168,7 @@ packages:
       array.prototype.tosorted: 1.1.2
       doctrine: 2.1.0
       es-iterator-helpers: 1.0.15
-      eslint: 8.55.0
+      eslint: 8.56.0
       estraverse: 5.3.0
       jsx-ast-utils: 3.3.5
       minimatch: 3.1.2
@@ -10322,13 +10241,13 @@ packages:
       eslint-visitor-keys: 2.1.0
     dev: true
 
-  /eslint-utils@3.0.0(eslint@8.55.0):
+  /eslint-utils@3.0.0(eslint@8.56.0):
     resolution: {integrity: 
sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
     engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
     peerDependencies:
       eslint: '>=5'
     dependencies:
-      eslint: 8.55.0
+      eslint: 8.56.0
       eslint-visitor-keys: 2.1.0
     dev: true
 
@@ -10447,15 +10366,15 @@ packages:
       - supports-color
     dev: true
 
-  /eslint@8.55.0:
-    resolution: {integrity: 
sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==}
+  /eslint@8.56.0:
+    resolution: {integrity: 
sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     hasBin: true
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@8.55.0)
+      '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0)
       '@eslint-community/regexpp': 4.10.0
       '@eslint/eslintrc': 2.1.4
-      '@eslint/js': 8.55.0
+      '@eslint/js': 8.56.0
       '@humanwhocodes/config-array': 0.11.13
       '@humanwhocodes/module-importer': 1.0.1
       '@nodelib/fs.walk': 1.2.8
@@ -10918,6 +10837,7 @@ packages:
 
   /flat@5.0.2:
     resolution: {integrity: 
sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}
+    hasBin: true
     dev: true
 
   /flatted@3.2.7:
@@ -11113,22 +11033,8 @@ packages:
     requiresBuild: true
     optional: true
 
-  /function-bind@1.1.1:
-    resolution: {integrity: 
sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
-
   /function-bind@1.1.2:
     resolution: {integrity: 
sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
-    dev: true
-
-  /function.prototype.name@1.1.5:
-    resolution: {integrity: 
sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==}
-    engines: {node: '>= 0.4'}
-    dependencies:
-      call-bind: 1.0.2
-      define-properties: 1.1.4
-      es-abstract: 1.20.4
-      functions-have-names: 1.2.3
-    dev: true
 
   /function.prototype.name@1.1.6:
     resolution: {integrity: 
sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==}
@@ -11181,14 +11087,6 @@ packages:
     resolution: {integrity: 
sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==}
     dev: true
 
-  /get-intrinsic@1.1.3:
-    resolution: {integrity: 
sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==}
-    dependencies:
-      function-bind: 1.1.1
-      has: 1.0.3
-      has-symbols: 1.0.3
-    dev: true
-
   /get-intrinsic@1.2.2:
     resolution: {integrity: 
sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==}
     dependencies:
@@ -11245,8 +11143,8 @@ packages:
     resolution: {integrity: 
sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==}
     engines: {node: '>= 0.4'}
     dependencies:
-      call-bind: 1.0.2
-      get-intrinsic: 1.1.3
+      call-bind: 1.0.5
+      get-intrinsic: 1.2.2
     dev: true
 
   /get-value@2.0.6:
@@ -11401,8 +11299,8 @@ packages:
     dependencies:
       array-union: 2.1.0
       dir-glob: 3.0.1
-      fast-glob: 3.3.1
-      ignore: 5.2.0
+      fast-glob: 3.3.2
+      ignore: 5.3.0
       merge2: 1.4.1
       slash: 3.0.0
     dev: true
@@ -11525,12 +11423,6 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
-  /has-property-descriptors@1.0.0:
-    resolution: {integrity: 
sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==}
-    dependencies:
-      get-intrinsic: 1.1.3
-    dev: true
-
   /has-property-descriptors@1.0.1:
     resolution: {integrity: 
sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==}
     dependencies:
@@ -11598,7 +11490,8 @@ packages:
     resolution: {integrity: 
sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
     engines: {node: '>= 0.4.0'}
     dependencies:
-      function-bind: 1.1.1
+      function-bind: 1.1.2
+    dev: true
 
   /hash-base@3.1.0:
     resolution: {integrity: 
sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==}
@@ -11633,10 +11526,10 @@ packages:
     engines: {node: '>= 0.4'}
     dependencies:
       function-bind: 1.1.2
-    dev: true
 
   /he@1.2.0:
     resolution: {integrity: 
sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
+    hasBin: true
     dev: true
 
   /hex-color-regex@1.1.0:
@@ -12028,15 +11921,6 @@ packages:
     resolution: {integrity: 
sha512-6W1eGIj8z/Yla6xJx5il6jJfCxMZS3kVkbiLQThbbjdsDLRIWkUVmpnhfW2l6WAwCW+qfy0zoXVGBZM1E5XF3g==}
     dev: true
 
-  /internal-slot@1.0.3:
-    resolution: {integrity: 
sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==}
-    engines: {node: '>= 0.4'}
-    dependencies:
-      get-intrinsic: 1.1.3
-      has: 1.0.3
-      side-channel: 1.0.4
-    dev: true
-
   /internal-slot@1.0.6:
     resolution: {integrity: 
sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==}
     engines: {node: '>= 0.4'}
@@ -12132,7 +12016,7 @@ packages:
     resolution: {integrity: 
sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
     engines: {node: '>= 0.4'}
     dependencies:
-      call-bind: 1.0.2
+      call-bind: 1.0.5
       has-tostringtag: 1.0.0
     dev: true
 
@@ -12162,16 +12046,10 @@ packages:
       rgba-regex: 1.0.0
     dev: true
 
-  /is-core-module@2.11.0:
-    resolution: {integrity: 
sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==}
-    dependencies:
-      has: 1.0.3
-
   /is-core-module@2.13.1:
     resolution: {integrity: 
sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==}
     dependencies:
       hasown: 2.0.0
-    dev: true
 
   /is-data-descriptor@0.1.4:
     resolution: {integrity: 
sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==}
@@ -12369,7 +12247,7 @@ packages:
     resolution: {integrity: 
sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}
     engines: {node: '>= 0.4'}
     dependencies:
-      call-bind: 1.0.2
+      call-bind: 1.0.5
       has-tostringtag: 1.0.0
     dev: true
 
@@ -12389,7 +12267,7 @@ packages:
   /is-shared-array-buffer@1.0.2:
     resolution: {integrity: 
sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==}
     dependencies:
-      call-bind: 1.0.2
+      call-bind: 1.0.5
     dev: true
 
   /is-stream@2.0.1:
@@ -12444,7 +12322,7 @@ packages:
   /is-weakref@1.0.2:
     resolution: {integrity: 
sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==}
     dependencies:
-      call-bind: 1.0.2
+      call-bind: 1.0.5
     dev: true
 
   /is-weakset@2.0.2:
@@ -12664,6 +12542,7 @@ packages:
 
   /js-yaml@4.1.0:
     resolution: {integrity: 
sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
+    hasBin: true
     dependencies:
       argparse: 2.0.1
     dev: true
@@ -13474,6 +13353,37 @@ packages:
     hasBin: true
     dev: true
 
+  /mocha@9.2.0:
+    resolution: {integrity: 
sha512-kNn7E8g2SzVcq0a77dkphPsDSN7P+iYkqE0ZsGCYWRsoiKjOt+NvXfaagik8vuDa6W5Zw3qxe8Jfpt5qKf+6/Q==}
+    engines: {node: '>= 12.0.0'}
+    hasBin: true
+    dependencies:
+      '@ungap/promise-all-settled': 1.1.2
+      ansi-colors: 4.1.1
+      browser-stdout: 1.3.1
+      chokidar: 3.5.3
+      debug: 4.3.3(supports-color@8.1.1)
+      diff: 5.0.0
+      escape-string-regexp: 4.0.0
+      find-up: 5.0.0
+      glob: 7.2.0
+      growl: 1.10.5
+      he: 1.2.0
+      js-yaml: 4.1.0
+      log-symbols: 4.1.0
+      minimatch: 3.0.4
+      ms: 2.1.3
+      nanoid: 3.2.0
+      serialize-javascript: 6.0.0
+      strip-json-comments: 3.1.1
+      supports-color: 8.1.1
+      which: 2.0.2
+      workerpool: 6.2.0
+      yargs: 16.2.0
+      yargs-parser: 20.2.4
+      yargs-unparser: 2.0.0
+    dev: true
+
   /mocha@9.2.2:
     resolution: {integrity: 
sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==}
     engines: {node: '>= 12.0.0'}
@@ -13565,6 +13475,12 @@ packages:
     resolution: {integrity: 
sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==}
     dev: false
 
+  /nanoid@3.2.0:
+    resolution: {integrity: 
sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==}
+    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+    hasBin: true
+    dev: true
+
   /nanoid@3.3.1:
     resolution: {integrity: 
sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==}
     engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -13717,6 +13633,7 @@ packages:
 
   /node-releases@2.0.10:
     resolution: {integrity: 
sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==}
+    dev: true
 
   /node-releases@2.0.13:
     resolution: {integrity: 
sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==}
@@ -13724,7 +13641,6 @@ packages:
 
   /node-releases@2.0.14:
     resolution: {integrity: 
sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
-    dev: true
 
   /nofilter@3.1.0:
     resolution: {integrity: 
sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==}
@@ -13874,10 +13790,6 @@ packages:
     resolution: {integrity: 
sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
     engines: {node: '>= 6'}
 
-  /object-inspect@1.12.2:
-    resolution: {integrity: 
sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==}
-    dev: true
-
   /object-inspect@1.13.1:
     resolution: {integrity: 
sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==}
     dev: true
@@ -13894,16 +13806,6 @@ packages:
       isobject: 3.0.1
     dev: true
 
-  /object.assign@4.1.4:
-    resolution: {integrity: 
sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==}
-    engines: {node: '>= 0.4'}
-    dependencies:
-      call-bind: 1.0.2
-      define-properties: 1.1.4
-      has-symbols: 1.0.3
-      object-keys: 1.1.1
-    dev: true
-
   /object.assign@4.1.5:
     resolution: {integrity: 
sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==}
     engines: {node: '>= 0.4'}
@@ -13937,9 +13839,9 @@ packages:
     engines: {node: '>= 0.8'}
     dependencies:
       array.prototype.reduce: 1.0.4
-      call-bind: 1.0.2
-      define-properties: 1.1.4
-      es-abstract: 1.20.4
+      call-bind: 1.0.5
+      define-properties: 1.2.1
+      es-abstract: 1.22.3
     dev: true
 
   /object.groupby@1.0.1:
@@ -14488,30 +14390,6 @@ packages:
       - ts-node
     dev: true
 
-  /postcss-cli@10.1.0(postcss@8.4.32):
-    resolution: {integrity: 
sha512-Zu7PLORkE9YwNdvOeOVKPmWghprOtjFQU3srMUGbdz3pHJiFh7yZ4geiZFMkjMfB0mtTFR3h8RemR62rPkbOPA==}
-    engines: {node: '>=14'}
-    hasBin: true
-    peerDependencies:
-      postcss: ^8.0.0
-    dependencies:
-      chokidar: 3.5.3
-      dependency-graph: 0.11.0
-      fs-extra: 11.1.1
-      get-stdin: 9.0.0
-      globby: 13.2.2
-      picocolors: 1.0.0
-      postcss: 8.4.32
-      postcss-load-config: 4.0.1(postcss@8.4.32)
-      postcss-reporter: 7.0.5(postcss@8.4.32)
-      pretty-hrtime: 1.0.3
-      read-cache: 1.0.0
-      slash: 5.1.0
-      yargs: 17.7.2
-    transitivePeerDependencies:
-      - ts-node
-    dev: true
-
   /postcss-colormin@4.0.3:
     resolution: {integrity: 
sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==}
     engines: {node: '>=6.9.0'}
@@ -14628,7 +14506,7 @@ packages:
       postcss: 8.4.23
       postcss-value-parser: 4.2.0
       read-cache: 1.0.0
-      resolve: 1.22.2
+      resolve: 1.22.8
 
   /postcss-js@4.0.1(postcss@8.4.23):
     resolution: {integrity: 
sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
@@ -14672,23 +14550,6 @@ packages:
       postcss: 8.4.23
       yaml: 2.2.2
 
-  /postcss-load-config@4.0.1(postcss@8.4.32):
-    resolution: {integrity: 
sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==}
-    engines: {node: '>= 14'}
-    peerDependencies:
-      postcss: '>=8.0.9'
-      ts-node: '>=9.0.0'
-    peerDependenciesMeta:
-      postcss:
-        optional: true
-      ts-node:
-        optional: true
-    dependencies:
-      lilconfig: 2.1.0
-      postcss: 8.4.32
-      yaml: 2.2.2
-    dev: true
-
   /postcss-loader@4.3.0(postcss@8.4.32)(webpack@4.46.0):
     resolution: {integrity: 
sha512-M/dSoIiNDOo8Rk0mUqoj4kpGq91gcxCfb9PoyZVdZ76/AuhxylHDYZblNE8o+EQ9AMSASeMFEKxZf5aU6wlx1Q==}
     engines: {node: '>= 10.13.0'}
@@ -15129,17 +14990,6 @@ packages:
       thenby: 1.3.4
     dev: true
 
-  /postcss-reporter@7.0.5(postcss@8.4.32):
-    resolution: {integrity: 
sha512-glWg7VZBilooZGOFPhN9msJ3FQs19Hie7l5a/eE6WglzYqVeH3ong3ShFcp9kDWJT1g2Y/wd59cocf9XxBtkWA==}
-    engines: {node: '>=10'}
-    peerDependencies:
-      postcss: ^8.1.0
-    dependencies:
-      picocolors: 1.0.0
-      postcss: 8.4.32
-      thenby: 1.3.4
-    dev: true
-
   /postcss-selector-parser@3.1.2:
     resolution: {integrity: 
sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==}
     engines: {node: '>=8'}
@@ -15816,15 +15666,6 @@ packages:
       safe-regex: 1.1.0
     dev: true
 
-  /regexp.prototype.flags@1.4.3:
-    resolution: {integrity: 
sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==}
-    engines: {node: '>= 0.4'}
-    dependencies:
-      call-bind: 1.0.2
-      define-properties: 1.1.4
-      functions-have-names: 1.2.3
-    dev: true
-
   /regexp.prototype.flags@1.5.1:
     resolution: {integrity: 
sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==}
     engines: {node: '>= 0.4'}
@@ -16021,7 +15862,7 @@ packages:
     resolution: {integrity: 
sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==}
     hasBin: true
     dependencies:
-      is-core-module: 2.11.0
+      is-core-module: 2.13.1
       path-parse: 1.0.7
       supports-preserve-symlinks-flag: 1.0.0
 
@@ -16032,7 +15873,6 @@ packages:
       is-core-module: 2.13.1
       path-parse: 1.0.7
       supports-preserve-symlinks-flag: 1.0.0
-    dev: true
 
   /resolve@2.0.0-next.5:
     resolution: {integrity: 
sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==}
@@ -16157,8 +15997,8 @@ packages:
   /safe-regex-test@1.0.0:
     resolution: {integrity: 
sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==}
     dependencies:
-      call-bind: 1.0.2
-      get-intrinsic: 1.1.3
+      call-bind: 1.0.5
+      get-intrinsic: 1.2.2
       is-regex: 1.1.4
     dev: true
 
@@ -16228,7 +16068,7 @@ packages:
     resolution: {integrity: 
sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==}
     engines: {node: '>= 10.13.0'}
     dependencies:
-      '@types/json-schema': 7.0.11
+      '@types/json-schema': 7.0.15
       ajv: 6.12.6
       ajv-keywords: 3.5.2(ajv@6.12.6)
     dev: true
@@ -16237,7 +16077,7 @@ packages:
     resolution: {integrity: 
sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==}
     engines: {node: '>= 12.13.0'}
     dependencies:
-      '@types/json-schema': 7.0.11
+      '@types/json-schema': 7.0.15
       ajv: 8.11.0
       ajv-formats: 2.1.1(ajv@8.11.0)
       ajv-keywords: 5.1.0(ajv@8.11.0)
@@ -16277,7 +16117,6 @@ packages:
   /semver@6.3.1:
     resolution: {integrity: 
sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
     hasBin: true
-    dev: true
 
   /semver@7.3.5:
     resolution: {integrity: 
sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==}
@@ -16469,9 +16308,9 @@ packages:
   /side-channel@1.0.4:
     resolution: {integrity: 
sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
     dependencies:
-      call-bind: 1.0.2
-      get-intrinsic: 1.1.3
-      object-inspect: 1.12.2
+      call-bind: 1.0.5
+      get-intrinsic: 1.2.2
+      object-inspect: 1.13.1
     dev: true
 
   /signal-exit@3.0.7:
@@ -16844,7 +16683,7 @@ packages:
     dependencies:
       eastasianwidth: 0.2.0
       emoji-regex: 9.2.2
-      strip-ansi: 7.0.1
+      strip-ansi: 7.1.0
     dev: false
 
   /string-width@7.0.0:
@@ -16879,14 +16718,6 @@ packages:
       es-abstract: 1.22.3
     dev: true
 
-  /string.prototype.trimend@1.0.5:
-    resolution: {integrity: 
sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==}
-    dependencies:
-      call-bind: 1.0.2
-      define-properties: 1.1.4
-      es-abstract: 1.20.4
-    dev: true
-
   /string.prototype.trimend@1.0.7:
     resolution: {integrity: 
sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==}
     dependencies:
@@ -16895,14 +16726,6 @@ packages:
       es-abstract: 1.22.3
     dev: true
 
-  /string.prototype.trimstart@1.0.5:
-    resolution: {integrity: 
sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==}
-    dependencies:
-      call-bind: 1.0.2
-      define-properties: 1.1.4
-      es-abstract: 1.20.4
-    dev: true
-
   /string.prototype.trimstart@1.0.7:
     resolution: {integrity: 
sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==}
     dependencies:
@@ -16935,6 +16758,7 @@ packages:
   /strip-ansi@0.1.1:
     resolution: {integrity: 
sha512-behete+3uqxecWlDAm5lmskaSaISA+ThQ4oNNBDTBJt0x2ppR6IPqfZNuj6BLaLJ/Sji4TPZlcRyOis8wXQTLg==}
     engines: {node: '>=0.8.0'}
+    hasBin: true
     dev: true
 
   /strip-ansi@3.0.1:
@@ -16950,13 +16774,6 @@ packages:
     dependencies:
       ansi-regex: 5.0.1
 
-  /strip-ansi@7.0.1:
-    resolution: {integrity: 
sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==}
-    engines: {node: '>=12'}
-    dependencies:
-      ansi-regex: 6.0.1
-    dev: false
-
   /strip-ansi@7.1.0:
     resolution: {integrity: 
sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
     engines: {node: '>=12'}
@@ -17329,7 +17146,7 @@ packages:
     engines: {node: '>=6.0.0'}
     hasBin: true
     dependencies:
-      acorn: 8.10.0
+      acorn: 8.11.2
       commander: 2.20.3
       source-map: 0.6.1
       source-map-support: 0.5.21
@@ -17340,7 +17157,7 @@ packages:
     engines: {node: '>=10'}
     dependencies:
       '@jridgewell/source-map': 0.3.2
-      acorn: 8.8.2
+      acorn: 8.11.2
       commander: 2.20.3
       source-map-support: 0.5.21
     dev: true
@@ -17504,6 +17321,15 @@ packages:
       punycode: 2.1.1
     dev: true
 
+  /ts-api-utils@1.0.3(typescript@5.3.3):
+    resolution: {integrity: 
sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==}
+    engines: {node: '>=16.13.0'}
+    peerDependencies:
+      typescript: '>=4.2.0'
+    dependencies:
+      typescript: 5.3.3
+    dev: true
+
   /ts-interface-checker@0.1.13:
     resolution: {integrity: 
sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
 
@@ -17729,7 +17555,7 @@ packages:
   /unbox-primitive@1.0.2:
     resolution: {integrity: 
sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
     dependencies:
-      call-bind: 1.0.2
+      call-bind: 1.0.5
       has-bigints: 1.0.2
       has-symbols: 1.0.3
       which-boxed-primitive: 1.0.2
@@ -17849,6 +17675,7 @@ packages:
       browserslist: 4.21.5
       escalade: 3.1.1
       picocolors: 1.0.0
+    dev: true
 
   /update-browserslist-db@1.0.13(browserslist@4.21.4):
     resolution: {integrity: 
sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==}
@@ -17861,17 +17688,6 @@ packages:
       picocolors: 1.0.0
     dev: true
 
-  /update-browserslist-db@1.0.13(browserslist@4.22.1):
-    resolution: {integrity: 
sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==}
-    hasBin: true
-    peerDependencies:
-      browserslist: '>= 4.21.0'
-    dependencies:
-      browserslist: 4.22.1
-      escalade: 3.1.1
-      picocolors: 1.0.0
-    dev: true
-
   /update-browserslist-db@1.0.13(browserslist@4.22.2):
     resolution: {integrity: 
sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==}
     hasBin: true
@@ -17881,7 +17697,6 @@ packages:
       browserslist: 4.22.2
       escalade: 3.1.1
       picocolors: 1.0.0
-    dev: true
 
   /update-notifier@5.1.0:
     resolution: {integrity: 
sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==}
@@ -17968,7 +17783,7 @@ packages:
   /util.promisify@1.0.0:
     resolution: {integrity: 
sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==}
     dependencies:
-      define-properties: 1.1.4
+      define-properties: 1.2.1
       object.getownpropertydescriptors: 2.1.4
     dev: true
 
@@ -18460,6 +18275,7 @@ packages:
   /which@2.0.2:
     resolution: {integrity: 
sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
     engines: {node: '>= 8'}
+    hasBin: true
     dependencies:
       isexe: 2.0.0
 

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]