From 0388d31d364139d0a3999126b06d8ac850117ab9 Mon Sep 17 00:00:00 2001
From: Sebastian <sebasjm@gmail.com>
Date: Tue, 19 Sep 2023 00:39:00 -0300
Subject: account page

---
 packages/demobank-ui/src/pages/AdminPage.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'packages/demobank-ui/src/pages/AdminPage.tsx')

diff --git a/packages/demobank-ui/src/pages/AdminPage.tsx b/packages/demobank-ui/src/pages/AdminPage.tsx
index ce0feebce..73a4f9ca3 100644
--- a/packages/demobank-ui/src/pages/AdminPage.tsx
+++ b/packages/demobank-ui/src/pages/AdminPage.tsx
@@ -43,7 +43,7 @@ import { ErrorBannerFloat } from "./BankFrame.js";
 import { ShowCashoutDetails } from "./BusinessAccount.js";
 import { handleNotOkResult } from "./HomePage.js";
 import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js";
-import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
+import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
 import { ErrorMessage, notifyInfo } from "../hooks/notification.js";
 
 const charset =
-- 
cgit v1.2.3


From e39d5c488e2e425bd7febf694caadc17d5126401 Mon Sep 17 00:00:00 2001
From: Sebastian <sebasjm@gmail.com>
Date: Wed, 20 Sep 2023 15:18:36 -0300
Subject: more ui

---
 packages/demobank-ui/src/assets/logo-2021.svg      |    9 +
 packages/demobank-ui/src/components/QR.tsx         |    5 +-
 .../src/components/Transactions/views.tsx          |   14 +-
 packages/demobank-ui/src/forms/simplest.ts         |   66 +
 packages/demobank-ui/src/hooks/notification.ts     |   54 -
 .../demobank-ui/src/pages/AccountPage/index.ts     |    4 +-
 .../demobank-ui/src/pages/AccountPage/state.ts     |   10 +-
 packages/demobank-ui/src/pages/AdminPage.tsx       |  150 +-
 packages/demobank-ui/src/pages/BankFrame.tsx       |  409 +--
 packages/demobank-ui/src/pages/BusinessAccount.tsx |  106 +-
 packages/demobank-ui/src/pages/HomePage.tsx        |   40 +-
 packages/demobank-ui/src/pages/LoginForm.tsx       |  335 +--
 packages/demobank-ui/src/pages/PaymentOptions.tsx  |   48 +-
 .../src/pages/PaytoWireTransferForm.tsx            |  221 +-
 packages/demobank-ui/src/pages/QrCodeSection.tsx   |  126 +-
 .../demobank-ui/src/pages/RegistrationPage.tsx     |  311 ++-
 .../demobank-ui/src/pages/WalletWithdrawForm.tsx   |  319 ++-
 .../src/pages/WithdrawalConfirmationQuestion.tsx   |  383 ++-
 .../demobank-ui/src/pages/WithdrawalQRCode.tsx     |   23 +-
 packages/demobank-ui/src/pages/rnd.ts              | 2890 ++++++++++++++++++++
 packages/demobank-ui/src/utils.ts                  |   18 +-
 pnpm-lock.yaml                                     |   10 +-
 22 files changed, 4451 insertions(+), 1100 deletions(-)
 create mode 100644 packages/demobank-ui/src/assets/logo-2021.svg
 create mode 100644 packages/demobank-ui/src/forms/simplest.ts
 delete mode 100644 packages/demobank-ui/src/hooks/notification.ts
 create mode 100644 packages/demobank-ui/src/pages/rnd.ts

(limited to 'packages/demobank-ui/src/pages/AdminPage.tsx')

diff --git a/packages/demobank-ui/src/assets/logo-2021.svg b/packages/demobank-ui/src/assets/logo-2021.svg
new file mode 100644
index 000000000..8c5ff3e5b
--- /dev/null
+++ b/packages/demobank-ui/src/assets/logo-2021.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 201 90">
+  <g fill="#0042b3" fill-rule="evenodd" stroke-width=".3">
+    <path d="M86.7 1.1c15.6 0 29 9.4 36 23.2h-5.9A35.1 35.1 0 0086.7 6.5C67 6.5 51 23.6 51 44.7c0 10.4 3.8 19.7 10 26.6a31.4 31.4 0 01-4.2 3A45.2 45.2 0 0146 44.7c0-24 18.2-43.6 40.7-43.6zm35.8 64.3a40.4 40.4 0 01-39 22.8c3-1.5 6-3.5 8.6-5.7a35.6 35.6 0 0024.6-17.1z" />
+    <path d="M64.2 1.1l3.1.1c-3 1.6-5.9 3.5-8.5 5.8a37.5 37.5 0 00-30.2 37.7c0 14.3 7.3 26.7 18 33.3a29.6 29.6 0 01-8.5.2c-9-8-14.6-20-14.6-33.5 0-24 18.2-43.6 40.7-43.6zm5.4 81.4a35.6 35.6 0 0024.6-17.1h5.9a40.4 40.4 0 01-39 22.8c3-1.5 5.9-3.5 8.5-5.7zm24.8-58.2a37 37 0 00-12.6-12.8 29.6 29.6 0 018.5-.2c4 3.6 7.4 8 9.9 13z" />
+    <path d="M41.8 1.1c1 0 2 0 3.1.2-3 1.5-5.9 3.4-8.5 5.6A37.5 37.5 0 006.1 44.7c0 21.1 16 38.3 35.7 38.3 12.6 0 23.6-7 30-17.6h5.8a40.4 40.4 0 01-35.8 23C19.3 88.4 1 68.8 1 44.7c0-24 18.2-43.6 40.7-43.6zm30.1 23.2a38.1 38.1 0 00-4.5-6.1c1.3-1.2 2.7-2.2 4.3-3 2.3 2.7 4.4 5.8 6 9.1z" />
+  </g>
+  <path d="M76.1 34.4h9.2v-5H61.9v5H71v26h5.1zM92.6 52.9h13.7l3 7.4h5.3l-12.7-31.2h-4.7L84.5 60.3h5.2zm11.8-4.9h-9.9l5-12.4zM123.8 29.4h-4.6v31h20.6v-5h-16zM166.5 29.4H145v31h21.6v-5H150v-8.3h14.5v-4.9h-14.5v-8h16.4zM191.2 39.5c0 1.6-.5 2.8-1.6 3.8s-2.6 1.4-4.4 1.4h-7.4V34.3h7.4c1.9 0 3.4.4 4.4 1.3 1 .9 1.6 2.2 1.6 3.9zm6 20.8l-7.7-11.7c1-.3 1.9-.7 2.7-1.3a8.8 8.8 0 003.6-4.6c.4-1 .5-2.2.5-3.5 0-1.5-.2-2.9-.7-4.1a8.4 8.4 0 00-2.1-3.1c-1-.8-2-1.5-3.4-2-1.3-.4-2.8-.6-4.5-.6h-12.9v31h5V49.4h6.5l7 10.8z" />
+</svg>
\ No newline at end of file
diff --git a/packages/demobank-ui/src/components/QR.tsx b/packages/demobank-ui/src/components/QR.tsx
index c1c159ef8..945a08867 100644
--- a/packages/demobank-ui/src/components/QR.tsx
+++ b/packages/demobank-ui/src/components/QR.tsx
@@ -33,7 +33,6 @@ export function QR({ text }: { text: string }): VNode {
   return (
     <div
       style={{
-        width: "100%",
         display: "flex",
         flexDirection: "column",
         alignItems: "left",
@@ -41,9 +40,7 @@ export function QR({ text }: { text: string }): VNode {
     >
       <div
         style={{
-          width: "50%",
-          minWidth: 200,
-          maxWidth: 300,
+          width: "100%",
           marginRight: "auto",
           marginLeft: "auto",
         }}
diff --git a/packages/demobank-ui/src/components/Transactions/views.tsx b/packages/demobank-ui/src/components/Transactions/views.tsx
index f4a78e516..6303037a1 100644
--- a/packages/demobank-ui/src/components/Transactions/views.tsx
+++ b/packages/demobank-ui/src/components/Transactions/views.tsx
@@ -39,21 +39,21 @@ export function ReadyView({ transactions }: State.Ready): VNode {
           <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 sm:rounded-lg">
+      <div class="-mx-4 mt-5 ring-1 ring-gray-300 sm:mx-0 sm:rounded-lg min-w-fit bg-white">
         <table class="min-w-full divide-y divide-gray-300">
           <thead>
             <tr>
-              <th scope="col" class="pl-4 pr-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:pl-6">{i18n.str`Date`}</th>
-              <th scope="col" class="px-3      py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell">{i18n.str`Amount`}</th>
-              <th scope="col" class="px-3      py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell">{i18n.str`Counterpart`}</th>
-              <th scope="col" class="px-3      py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell">{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="pl-2 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell">{i18n.str`Amount`}</th>
+              <th scope="col" class="pl-2 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell">{i18n.str`Counterpart`}</th>
+              <th scope="col" class="pl-2 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell">{i18n.str`Subject`}</th>
             </tr>
           </thead>
           <tbody>
             {transactions.map((item, idx) => {
               return (
                 <tr key={idx}>
-                  <td class="relative py-4 pl-4 pr-3 text-sm sm:pl-6">
+                  <td class="relative py-2 pl-2 pr-2 text-sm ">
                     <div class="font-medium text-gray-900">{item.when.t_ms === "never"
                       ? ""
                       : format(item.when.t_ms, "dd/MM/yyyy HH:mm:ss")}</div>
@@ -66,7 +66,7 @@ export function ReadyView({ transactions }: State.Ready): VNode {
                       <span style={{ color: "grey" }}>&lt;{i18n.str`invalid value`}&gt;</span>
                     )}</td>
                   <td class="px-3 py-3.5 text-sm text-gray-500">{item.counterpart}</td>
-                  <td class="px-3 py-3.5 text-sm text-gray-500">{item.subject}</td>
+                  <td class="px-3 py-3.5 text-sm text-gray-500 break-all min-w-md">{item.subject}</td>
                 </tr>
               );
             })}
diff --git a/packages/demobank-ui/src/forms/simplest.ts b/packages/demobank-ui/src/forms/simplest.ts
new file mode 100644
index 000000000..54b6b1c65
--- /dev/null
+++ b/packages/demobank-ui/src/forms/simplest.ts
@@ -0,0 +1,66 @@
+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: "date",
+        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/notification.ts b/packages/demobank-ui/src/hooks/notification.ts
deleted file mode 100644
index 9bf621b41..000000000
--- a/packages/demobank-ui/src/hooks/notification.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { TranslatedString } from "@gnu-taler/taler-util";
-import { memoryMap } from "@gnu-taler/web-util/browser";
-import { StateUpdater, useEffect, useState } from "preact/hooks";
-
-export type NotificationMessage = ErrorNotification | InfoNotification;
-
-//FIXME: this should not be exported since every notification
-// goes throw notify function
-export interface ErrorMessage {
-  description?: string;
-  title: TranslatedString;
-  debug?: string;
-}
-
-interface ErrorNotification {
-  type: "error";
-  error: ErrorMessage;
-}
-interface InfoNotification {
-  type: "info";
-  info: TranslatedString;
-}
-
-const storage = memoryMap<NotificationMessage>();
-const NOTIFICATION_KEY = "notification";
-
-export function onNotificationUpdate(
-  handler: (newValue: NotificationMessage | undefined) => void,
-) {
-  return storage.onUpdate(NOTIFICATION_KEY, () => {
-    const newValue = storage.get(NOTIFICATION_KEY);
-    handler(newValue);
-  });
-}
-
-export function notifyError(error: ErrorMessage) {
-  storage.set(NOTIFICATION_KEY, { type: "error", error });
-}
-export function notifyInfo(info: TranslatedString) {
-  storage.set(NOTIFICATION_KEY, { type: "info", info });
-}
-
-export function useNotifications(): [
-  NotificationMessage | undefined,
-  StateUpdater<NotificationMessage | undefined>,
-] {
-  const [value, setter] = useState<NotificationMessage | undefined>();
-  useEffect(() => {
-    return storage.onUpdate(NOTIFICATION_KEY, () => {
-      setter(storage.get(NOTIFICATION_KEY));
-    });
-  });
-  return [value, setter];
-}
diff --git a/packages/demobank-ui/src/pages/AccountPage/index.ts b/packages/demobank-ui/src/pages/AccountPage/index.ts
index 28fb7cb0c..ed6945f84 100644
--- a/packages/demobank-ui/src/pages/AccountPage/index.ts
+++ b/packages/demobank-ui/src/pages/AccountPage/index.ts
@@ -15,7 +15,7 @@
  */
 
 import { HttpError, HttpResponseOk, HttpResponsePaginated, utils } from "@gnu-taler/web-util/browser";
-import { AbsoluteTime, AmountJson, PaytoUriIBAN } from "@gnu-taler/taler-util";
+import { AbsoluteTime, AmountJson, PaytoUriIBAN, PaytoUriTalerBank } from "@gnu-taler/taler-util";
 import { Loading } from "../../components/Loading.js";
 import { useComponentState } from "./state.js";
 import { ReadyView, InvalidIbanView} from "./views.js";
@@ -51,7 +51,7 @@ export namespace State {
     status: "ready";
     error: undefined;
     account: string, 
-    payto: PaytoUriIBAN, 
+    payto: PaytoUriIBAN | PaytoUriTalerBank, 
     balance: AmountJson, 
     balanceIsDebit: boolean, 
     limit: AmountJson,
diff --git a/packages/demobank-ui/src/pages/AccountPage/state.ts b/packages/demobank-ui/src/pages/AccountPage/state.ts
index bc59c9374..2249b743e 100644
--- a/packages/demobank-ui/src/pages/AccountPage/state.ts
+++ b/packages/demobank-ui/src/pages/AccountPage/state.ts
@@ -15,10 +15,9 @@
  */
 
 import { Amounts, HttpStatusCode, parsePaytoUri } from "@gnu-taler/taler-util";
-import { ErrorType, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { ErrorType, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser";
 import { useBackendContext } from "../../context/backend.js";
 import { useAccountDetails } from "../../hooks/access.js";
-import { notifyError } from "../../hooks/notification.js";
 import { Props, State } from "./index.js";
 
 export function useComponentState({ account, onLoadNotOk }: Props): State {
@@ -43,9 +42,7 @@ export function useComponentState({ account, onLoadNotOk }: Props): State {
     //logout if there is any error, not if loading
     backend.logOut();
     if (result.status === HttpStatusCode.NotFound) {
-      notifyError({
-        title: i18n.str`Username or account label "${account}" not found`,
-      });
+      notifyError(i18n.str`Username or account label "${account}" not found`, undefined);
       return {
         status: "error-user-not-found",
         error: result,
@@ -62,7 +59,8 @@ export function useComponentState({ account, onLoadNotOk }: Props): State {
   const debitThreshold = Amounts.parseOrThrow(data.debitThreshold);
   const payto = parsePaytoUri(data.paytoUri);
 
-  if (!payto || !payto.isKnown || payto.targetType !== "iban") {
+  if (!payto || !payto.isKnown || (payto.targetType !== "iban" && payto.targetType !== "x-taler-bank")) {
+    console.log(payto)
     return {
       status: "invalid-iban",
       error: result
diff --git a/packages/demobank-ui/src/pages/AdminPage.tsx b/packages/demobank-ui/src/pages/AdminPage.tsx
index 73a4f9ca3..18462bdc3 100644
--- a/packages/demobank-ui/src/pages/AdminPage.tsx
+++ b/packages/demobank-ui/src/pages/AdminPage.tsx
@@ -14,11 +14,14 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { Amounts, HttpStatusCode, parsePaytoUri } from "@gnu-taler/taler-util";
+import { Amounts, HttpStatusCode, TranslatedString, parsePaytoUri } from "@gnu-taler/taler-util";
 import {
   ErrorType,
   HttpResponsePaginated,
   RequestError,
+  notify,
+  notifyError,
+  notifyInfo,
   useTranslationContext,
 } from "@gnu-taler/web-util/browser";
 import { Fragment, h, VNode } from "preact";
@@ -39,12 +42,10 @@ import {
   validateIBAN,
   WithIntermediate,
 } from "../utils.js";
-import { ErrorBannerFloat } from "./BankFrame.js";
 import { ShowCashoutDetails } from "./BusinessAccount.js";
 import { handleNotOkResult } from "./HomePage.js";
 import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js";
 import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
-import { ErrorMessage, notifyInfo } from "../hooks/notification.js";
 
 const charset =
   "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
@@ -362,6 +363,7 @@ function AdminAccount({ onRegister }: { onRegister: () => void }): VNode {
         onSuccess={() => {
           notifyInfo(i18n.str`Wire transfer created!`);
         }}
+        onCancel={undefined}
       />
     </Fragment>
   );
@@ -414,7 +416,6 @@ export function UpdateAccountPassword({
   const { changePassword } = useAdminAccountAPI();
   const [password, setPassword] = useState<string | undefined>();
   const [repeat, setRepeat] = useState<string | undefined>();
-  const [error, saveError] = useState<ErrorMessage | undefined>();
 
   if (!result.ok) {
     if (result.loading || result.type === ErrorType.TIMEOUT) {
@@ -431,8 +432,8 @@ export function UpdateAccountPassword({
     repeat: !repeat
       ? i18n.str`required`
       : password !== repeat
-      ? i18n.str`password doesn't match`
-      : undefined,
+        ? i18n.str`password doesn't match`
+        : undefined,
   });
 
   return (
@@ -442,9 +443,6 @@ export function UpdateAccountPassword({
           <i18n.Translate>Update password for {account}</i18n.Translate>
         </h1>
       </div>
-      {error && (
-        <ErrorBannerFloat error={error} onClear={() => saveError(undefined)} />
-      )}
 
       <div style={{ maxWidth: 600, overflowX: "hidden", margin: "auto" }}>
         <form class="pure-form">
@@ -507,15 +505,11 @@ export function UpdateAccountPassword({
                     onUpdateSuccess();
                   } catch (error) {
                     if (error instanceof RequestError) {
-                      saveError(buildRequestErrorMessage(i18n, error.cause));
+                      notify(buildRequestErrorMessage(i18n, error.cause));
                     } else {
-                      saveError({
-                        title: i18n.str`Operation failed, please report`,
-                        description:
-                          error instanceof Error
-                            ? error.message
-                            : JSON.stringify(error),
-                      });
+                      notifyError(i18n.str`Operation failed, please report`, (error instanceof Error
+                        ? error.message
+                        : JSON.stringify(error)) as TranslatedString)
                     }
                   }
                 }}
@@ -540,7 +534,6 @@ function CreateNewAccount({
   const [submitAccount, setSubmitAccount] = useState<
     SandboxBackend.Circuit.CircuitAccountData | undefined
   >();
-  const [error, saveError] = useState<ErrorMessage | undefined>();
   return (
     <div>
       <div>
@@ -548,9 +541,6 @@ function CreateNewAccount({
           <i18n.Translate>New account</i18n.Translate>
         </h1>
       </div>
-      {error && (
-        <ErrorBannerFloat error={error} onClear={() => saveError(undefined)} />
-      )}
 
       <div style={{ maxWidth: 600, overflowX: "hidden", margin: "auto" }}>
         <AccountForm
@@ -587,39 +577,38 @@ function CreateNewAccount({
                   if (!submitAccount) return;
                   try {
                     const account: SandboxBackend.Circuit.CircuitAccountRequest =
-                      {
-                        cashout_address: submitAccount.cashout_address,
-                        contact_data: submitAccount.contact_data,
-                        internal_iban: submitAccount.iban,
-                        name: submitAccount.name,
-                        username: submitAccount.username,
-                        password: randomPassword(),
-                      };
+                    {
+                      cashout_address: submitAccount.cashout_address,
+                      contact_data: submitAccount.contact_data,
+                      internal_iban: submitAccount.iban,
+                      name: submitAccount.name,
+                      username: submitAccount.username,
+                      password: randomPassword(),
+                    };
 
                     await createAccount(account);
                     onCreateSuccess(account.password);
                   } catch (error) {
                     if (error instanceof RequestError) {
-                      saveError(
+                      notify(
                         buildRequestErrorMessage(i18n, error.cause, {
                           onClientError: (status) =>
                             status === HttpStatusCode.Forbidden
                               ? i18n.str`The rights to perform the operation are not sufficient`
                               : status === HttpStatusCode.BadRequest
-                              ? i18n.str`Input data was invalid`
-                              : status === HttpStatusCode.Conflict
-                              ? i18n.str`At least one registration detail was not available`
-                              : undefined,
+                                ? i18n.str`Input data was invalid`
+                                : status === HttpStatusCode.Conflict
+                                  ? i18n.str`At least one registration detail was not available`
+                                  : undefined,
                         }),
                       );
                     } else {
-                      saveError({
-                        title: i18n.str`Operation failed, please report`,
-                        description:
-                          error instanceof Error
-                            ? error.message
-                            : JSON.stringify(error),
-                      });
+                      notifyError(
+                        i18n.str`Operation failed, please report`,
+                        (error instanceof Error
+                          ? error.message
+                          : JSON.stringify(error)) as TranslatedString
+                      )
                     }
                   }
                 }}
@@ -654,7 +643,6 @@ export function ShowAccountDetails({
   const [submitAccount, setSubmitAccount] = useState<
     SandboxBackend.Circuit.CircuitAccountData | undefined
   >();
-  const [error, saveError] = useState<ErrorMessage | undefined>();
 
   if (!result.ok) {
     if (result.loading || result.type === ErrorType.TIMEOUT) {
@@ -673,9 +661,6 @@ export function ShowAccountDetails({
           <i18n.Translate>Business account details</i18n.Translate>
         </h1>
       </div>
-      {error && (
-        <ErrorBannerFloat error={error} onClear={() => saveError(undefined)} />
-      )}
       <div style={{ maxWidth: 600, overflowX: "hidden", margin: "auto" }}>
         <AccountForm
           template={result.data}
@@ -740,24 +725,23 @@ export function ShowAccountDetails({
                         onUpdateSuccess();
                       } catch (error) {
                         if (error instanceof RequestError) {
-                          saveError(
+                          notify(
                             buildRequestErrorMessage(i18n, error.cause, {
                               onClientError: (status) =>
                                 status === HttpStatusCode.Forbidden
                                   ? i18n.str`The rights to change the account are not sufficient`
                                   : status === HttpStatusCode.NotFound
-                                  ? i18n.str`The username was not found`
-                                  : undefined,
+                                    ? i18n.str`The username was not found`
+                                    : undefined,
                             }),
                           );
                         } else {
-                          saveError({
-                            title: i18n.str`Operation failed, please report`,
-                            description:
-                              error instanceof Error
-                                ? error.message
-                                : JSON.stringify(error),
-                          });
+                          notifyError(
+                            i18n.str`Operation failed, please report`,
+                            (error instanceof Error
+                              ? error.message
+                              : JSON.stringify(error)) as TranslatedString
+                          )
                         }
                       }
                     }
@@ -788,7 +772,6 @@ function RemoveAccount({
   const { i18n } = useTranslationContext();
   const result = useAccountDetails(account);
   const { deleteAccount } = useAdminAccountAPI();
-  const [error, saveError] = useState<ErrorMessage | undefined>();
 
   if (!result.ok) {
     if (result.loading || result.type === ErrorType.TIMEOUT) {
@@ -812,7 +795,8 @@ function RemoveAccount({
           <i18n.Translate>Remove account: {account}</i18n.Translate>
         </h1>
       </div>
-      {!isBalanceEmpty && (
+      {/* {FXME: SHOW WARNING} */}
+      {/* {!isBalanceEmpty && (
         <ErrorBannerFloat
           error={{
             title: i18n.str`Can't delete the account`,
@@ -820,10 +804,7 @@ function RemoveAccount({
           }}
           onClear={() => saveError(undefined)}
         />
-      )}
-      {error && (
-        <ErrorBannerFloat error={error} onClear={() => saveError(undefined)} />
-      )}
+      )} */}
 
       <p>
         <div style={{ display: "flex", justifyContent: "space-between" }}>
@@ -852,26 +833,23 @@ function RemoveAccount({
                   onUpdateSuccess();
                 } catch (error) {
                   if (error instanceof RequestError) {
-                    saveError(
+                    notify(
                       buildRequestErrorMessage(i18n, error.cause, {
                         onClientError: (status) =>
                           status === HttpStatusCode.Forbidden
                             ? i18n.str`The administrator specified a institutional username`
                             : status === HttpStatusCode.NotFound
-                            ? i18n.str`The username was not found`
-                            : status === HttpStatusCode.PreconditionFailed
-                            ? i18n.str`Balance was not zero`
-                            : undefined,
+                              ? i18n.str`The username was not found`
+                              : status === HttpStatusCode.PreconditionFailed
+                                ? i18n.str`Balance was not zero`
+                                : undefined,
                       }),
                     );
                   } else {
-                    saveError({
-                      title: i18n.str`Operation failed, please report`,
-                      description:
-                        error instanceof Error
+                    notifyError(i18n.str`Operation failed, please report`,
+                      (error instanceof Error
                           ? error.message
-                          : JSON.stringify(error),
-                    });
+                          : JSON.stringify(error)) as TranslatedString);
                   }
                 }
               }}
@@ -915,31 +893,31 @@ function AccountForm({
       cashout_address: !newForm.cashout_address
         ? i18n.str`required`
         : !parsed
-        ? i18n.str`does not follow the pattern`
-        : !parsed.isKnown || parsed.targetType !== "iban"
-        ? i18n.str`only "IBAN" target are supported`
-        : !IBAN_REGEX.test(parsed.iban)
-        ? i18n.str`IBAN should have just uppercased letters and numbers`
-        : validateIBAN(parsed.iban, i18n),
+          ? i18n.str`does not follow the pattern`
+          : !parsed.isKnown || parsed.targetType !== "iban"
+            ? i18n.str`only "IBAN" target are supported`
+            : !IBAN_REGEX.test(parsed.iban)
+              ? i18n.str`IBAN should have just uppercased letters and numbers`
+              : validateIBAN(parsed.iban, i18n),
       contact_data: undefinedIfEmpty({
         email: !newForm.contact_data?.email
           ? i18n.str`required`
           : !EMAIL_REGEX.test(newForm.contact_data.email)
-          ? i18n.str`it should be an email`
-          : undefined,
+            ? i18n.str`it should be an email`
+            : undefined,
         phone: !newForm.contact_data?.phone
           ? i18n.str`required`
           : !newForm.contact_data.phone.startsWith("+")
-          ? i18n.str`should start with +`
-          : !REGEX_JUST_NUMBERS_REGEX.test(newForm.contact_data.phone)
-          ? i18n.str`phone number can't have other than numbers`
-          : undefined,
+            ? i18n.str`should start with +`
+            : !REGEX_JUST_NUMBERS_REGEX.test(newForm.contact_data.phone)
+              ? i18n.str`phone number can't have other than numbers`
+              : undefined,
       }),
       iban: !newForm.iban
         ? undefined //optional field
         : !IBAN_REGEX.test(newForm.iban)
-        ? i18n.str`IBAN should have just uppercased letters and numbers`
-        : validateIBAN(newForm.iban, i18n),
+          ? i18n.str`IBAN should have just uppercased letters and numbers`
+          : validateIBAN(newForm.iban, i18n),
       name: !newForm.name ? i18n.str`required` : undefined,
       username: !newForm.username ? i18n.str`required` : undefined,
     });
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx
index 5b6d95ade..d234845a0 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -15,17 +15,16 @@
  */
 
 import { Logger, PaytoUriIBAN, TranslatedString, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser";
 import { ComponentChildren, Fragment, h, VNode } from "preact";
 import { StateUpdater, useEffect, useState } from "preact/hooks";
-import talerLogo from "../assets/logo-white.svg";
 import { LangSelectorLikePy as LangSelector } from "../components/LangSelector.js";
 import { useBackendContext } from "../context/backend.js";
 import { useBusinessAccountDetails } from "../hooks/circuit.js";
 import { bankUiSettings } from "../settings.js";
 import { useSettings } from "../hooks/settings.js";
-import { ErrorMessage, onNotificationUpdate } from "../hooks/notification.js";
 import { CopyButton, CopyIcon } from "../components/CopyButton.js";
+import logo from "../assets/logo-2021.svg";
 
 const IS_PUBLIC_ACCOUNT_ENABLED = false;
 const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined;
@@ -81,16 +80,23 @@ export function BankFrame({
       </a>,
     );
 
-  return (<div class="min-h-full">
+  return (<div class="min-h-full flex flex-col m-0" style="min-height: 100vh;">
     <div class="bg-indigo-600 pb-32">
-      <nav class="border-b border-indigo-300 border-opacity-25 bg-indigo-600 lg:border-none">
+      <nav class="">
         <div class="mx-auto max-w-7xl px-2 sm:px-4 lg:px-8">
-          <div class="relative flex h-16 items-center justify-between lg:border-b lg:border-indigo-400 lg:border-opacity-25">
+          <div class="relative flex h-16 items-center justify-between ">
             <div class="flex items-center px-2 lg:px-0">
-              <div class="flex-shrink-0">
-                <img class="block h-8 w-8" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=300" alt="Your Company" />
+              <div class="flex-shrink-0 bg-white rounded-lg">
+                <a href="#/">
+                  <img
+                    class="h-8 w-auto"
+                    src={logo}
+                    alt="Taler"
+                    style={{ height: 35, margin: 10 }}
+                  />
+                </a>
               </div>
-              <div class="hidden lg:ml-10 lg:block">
+              <div class="hidden sm:block lg:ml-10 ">
                 <div class="flex space-x-4">
                   {/* <!-- Current: "bg-indigo-700 text-white", Default: "text-white hover:bg-indigo-500 hover:bg-opacity-75" --> */}
                   {bankUiSettings.demoSites.map(([name, url]) => {
@@ -100,62 +106,131 @@ export function BankFrame({
               </div>
             </div>
 
-            <div class="flex lg:hidden">
-              {/* <!-- Mobile menu button --> */}
-              <button type="button" class="relative inline-flex items-center justify-center rounded-md bg-indigo-600 p-2 text-indigo-200 hover:bg-indigo-500 hover:bg-opacity-75 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-indigo-600" aria-controls="mobile-menu" aria-expanded="false"
+            <div class="flex">
+              <button type="button" class="relative inline-flex items-center justify-center rounded-md bg-indigo-600 p-1 text-indigo-200 hover:bg-indigo-500 hover:bg-opacity-75 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-indigo-600" aria-controls="mobile-menu" aria-expanded="false"
                 onClick={(e) => {
                   setOpen(!open)
                 }}>
                 <span class="absolute -inset-0.5"></span>
                 <span class="sr-only">Open main menu</span>
-                {/* <!-- Menu open: "hidden", Menu closed: "block" --> */}
-                <svg class="block h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
+                <svg class="block h-10 w-10" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
                   <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
                 </svg>
-                {/* <!-- Menu open: "block", Menu closed: "hidden" --> */}
-                <svg class="hidden h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
-                  <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
-                </svg>
               </button>
             </div>
           </div>
         </div>
 
-        {/* <!-- Mobile menu, show/hide based on menu state. --> */}
         {open &&
-          <div class="lg:hidden" id="mobile-menu">
-            <div class="space-y-1 px-2 pb-3 pt-2">
-              {/* <!-- Current: "bg-indigo-700 text-white", Default: "text-white hover:bg-indigo-500 hover:bg-opacity-75" --> */}
-              {bankUiSettings.demoSites.map(([name, url]) => {
-                return <a href={url} class="text-white hover:bg-indigo-500 hover:bg-opacity-75 block rounded-md py-2 px-3 text-base font-medium">{name}</a>
-              })}
+          <Fragment>
+            <div class="relative z-10" aria-labelledby="slide-over-title" role="dialog" aria-modal="true"
+              onClick={() => {
+                setOpen(false)
+              }}>
+              <div class="fixed inset-0"></div>
+
+              <div class="fixed inset-0 overflow-hidden">
+                <div class="absolute inset-0 overflow-hidden">
+                  <div class="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10">
+                    <div class="pointer-events-auto w-screen max-w-md" >
+                      <div class="flex h-full flex-col overflow-y-scroll bg-white py-6 shadow-xl" onClick={(e) => {
+                        //do not trigger close if clicking inside the sidebar
+                        e.stopPropagation();
+                      }}>
+                        <div class="px-4 sm:px-6" >
+                          <div class="flex items-start justify-between" >
+                            <h2 class="text-base font-semibold leading-6 text-gray-900" id="slide-over-title">
+                              <i18n.Translate>Settings</i18n.Translate>
+                            </h2>
+                            <div class="ml-3 flex h-7 items-center">
+                              <button type="button" class="relative rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
+                                onClick={(e) => {
+                                  setOpen(false)
+                                }}
+
+                              >
+                                <span class="absolute -inset-2.5"></span>
+                                <span class="sr-only">
+                                  <i18n.Translate>Close panel</i18n.Translate>
+                                </span>
+                                <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
+                                  <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
+                                </svg>
+                              </button>
+                            </div>
+                          </div>
+                        </div>
+                        <div class="relative mt-6 flex-1 px-4 sm:px-6">
+                          {/* <!-- Your content --> */}
+
+                          <nav class="flex flex-1 flex-col" aria-label="Sidebar">
+                            <ul role="list" class="flex flex-1 flex-col gap-y-7">
+                              <li>
+                                <ul role="list" class="-mx-2 space-y-1">
+                                  <li>
+                                    <a href="#"
+                                      class="text-gray-700 hover:text-indigo-600 hover:bg-gray-100 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold"
+                                      onClick={() => {
+                                        backend.logOut();
+                                        setOpen(false)
+                                        updateSettings("currentWithdrawalOperationId", undefined);
+                                      }}
+                                    >
+                                      <svg class="h-6 w-6 shrink-0 text-indigo-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="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />
+                                      </svg>
+                                      Log out
+                                      {/* <span class="ml-auto w-9 min-w-max whitespace-nowrap rounded-full bg-gray-50 px-2.5 py-0.5 text-center text-xs font-medium leading-5 text-gray-600 ring-1 ring-inset ring-gray-200" aria-hidden="true">5</span> */}
+                                    </a>
+                                  </li>
+                                </ul>
+                              </li>
+                              <li class="sm:hidden">
+                                <div class="text-xs font-semibold leading-6 text-gray-400">
+                                  <i18n.Translate>Sites</i18n.Translate>
+                                </div>
+                                <ul role="list" class="-mx-2 mt-2 space-y-1">
+                                  {bankUiSettings.demoSites.map(([name, url]) => {
+                                    return <a href={url} target="_blank" rel="noopener noreferrer" class="text-gray-700 hover:text-indigo-600 hover:bg-gray-100 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
+                                      <span class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-[0.625rem] font-medium bg-white text-gray-400 border-gray-200 group-hover:border-indigo-600 group-hover:text-indigo-600">&gt;</span>
+                                      <span class="truncate">{name}</span>
+                                    </a>
+                                  })}
+                                </ul>
+                              </li>
+                            </ul>
+                          </nav>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div>
             </div>
 
-          </div>
+          </Fragment>
         }
       </nav >
-      <header class="py-10">
-
-        <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
-          <div class=" flex flex-wrap items-center justify-between sm:flex-nowrap">
-            {/* <h1 class="text-base font-semibold leading-6 text-gray-900"></h1> */}
-            <h1 class="text-3xl font-bold tracking-tight text-white"><WelcomeAccount /></h1>
-            <div>
 
-              <h2 class="text-3xl font-bold tracking-tight text-white">KUDOS 100.00</h2>
+      {true &&
+        <header class="py-5 border-t border-indigo-300 border-opacity-25 bg-indigo-600 lg:border-t lg:border-indigo-400 lg:border-opacity-25">
+          <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
+            <div class=" flex flex-wrap items-center justify-between sm:flex-nowrap">
+              <h3 class="text-2xl font-bold tracking-tight text-white"><WelcomeAccount /></h3>
+              <div>
+                <h3 class="text-2xl font-bold tracking-tight text-white"><AccountBalance /></h3>
+              </div>
             </div>
-            {/* <div class="ml-4 mt-2 flex-shrink-0">
-              <button type="button" class="relative inline-flex items-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">Create new job</button>
-            </div> */}
           </div>
-        </div>
-      </header>
+
+        </header>
+      }
     </div >
 
-    <main class="-mt-32">
+    <main class="-mt-32 flex-1">
       <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">
-          {/* <!-- Your content --> */}
+          <StatusBanner />
           {children}
         </div>
       </div>
@@ -207,15 +282,15 @@ export function BankFrame({
     //             />
     //           ) : undefined}
 
-    //           <LangSelector />
+    // <LangSelector />
 
     //           <a
     //             href="#"
     //             class="pure-button logout-button"
-    //             onClick={() => {
-    //               backend.logOut();
-    //               updateSettings("currentWithdrawalOperationId", undefined);
-    //             }}
+    // onClick={() => {
+    //   backend.logOut();
+    //   updateSettings("currentWithdrawalOperationId", undefined);
+    // }}
     //           >{i18n.str`Logout`}</a>
     //         </Fragment>
     //       ) : undefined}
@@ -225,149 +300,110 @@ export function BankFrame({
     //     <StatusBanner />
     //     {children}
     //   </section>
-    //   <section id="footer" class="footer">
-    //     <hr />
-    //     <div>
-    //       <p>
-    //         You can learn more about GNU Taler on our{" "}
-    //         <a href="https://taler.net">main website</a>.
-    //       </p>
-    //     </div>
-    //     <div style="flex-grow:1" />
-    //     <p>
-    //       Copyright &copy; 2014&mdash;2022 Taler Systems SA. {versionText}{" "}
-    //       <TestingTag />
-    //     </p>
-    //   </section>
     // </Fragment>
   );
 }
 
-function maybeDemoContent(content: VNode): VNode {
-  if (bankUiSettings.showDemoNav) {
-    return content;
-  }
-  return <Fragment />;
-}
+// function maybeDemoContent(content: VNode): VNode {
+//   if (bankUiSettings.showDemoNav) {
+//     return content;
+//   }
+//   return <Fragment />;
+// }
 
-export function ErrorBannerFloat({
-  error,
-  onClear,
-}: {
-  error: ErrorMessage;
-  onClear?: () => void;
-}): VNode {
-  return (
-    <div
-      style={{
-        position: "fixed",
-        top: 10,
-        zIndex: 200,
-        width: "90%",
-      }}
-    >
-      <ErrorBanner error={error} onClear={onClear} />
-    </div>
-  );
-}
+// export function ErrorBannerFloat({
+//   error,
+//   onClear,
+// }: {
+//   error: ErrorMessage;
+//   onClear?: () => void;
+// }): VNode {
+//   return (
+//     <div
+//       style={{
+//         position: "fixed",
+//         top: 10,
+//         zIndex: 200,
+//         width: "90%",
+//       }}
+//     >
+//       <ErrorBanner error={error} onClear={onClear} />
+//     </div>
+//   );
+// }
 
-function ErrorBanner({
-  error,
-  onClear,
-}: {
-  error: ErrorMessage;
-  onClear?: () => void;
-}): VNode {
-  return (
-    <div
-      class="informational informational-fail"
-      style={{
-        marginTop: 8,
-        paddingLeft: 16,
-        paddingRight: 16,
-      }}
-    >
-      <div style={{ display: "flex", justifyContent: "space-between" }}>
-        <p>
-          <b>{error.title}</b>
-        </p>
-        <div style={{ marginTop: "auto", marginBottom: "auto" }}>
-          {onClear && (
-            <input
-              type="button"
-              class="pure-button"
-              value="Clear"
-              onClick={(e) => {
-                e.preventDefault();
-                onClear();
-              }}
-            />
-          )}
-        </div>
-      </div>
-      <p>{error.description}</p>
-    </div>
-  );
-}
-
-function StatusBanner(): VNode | null {
-  const [info, setInfo] = useState<TranslatedString>();
-  const [error, setError] = useState<ErrorMessage>();
-  useEffect(() => {
-    return onNotificationUpdate((newValue) => {
-      if (newValue === undefined) {
-        setInfo(undefined);
-        setError(undefined);
-      } else {
-        if (newValue.type === "error") {
-          setError(newValue.error);
-        } else {
-          setInfo(newValue.info);
-        }
-      }
-    });
-  }, []);
-  return (
-    <div
-      style={{
-        position: "fixed",
-        top: 10,
-        zIndex: 200,
-        width: "90%",
-      }}
-    >
-      {!info ? undefined : (
-        <div
-          class="informational informational-ok"
-          style={{ marginTop: 8, paddingLeft: 16, paddingRight: 16 }}
-        >
-          <div style={{ display: "flex", justifyContent: "space-between" }}>
-            <p>
-              <b>{info}</b>
-            </p>
-            <div>
-              <input
-                type="button"
-                class="pure-button"
-                value="Clear"
-                onClick={async () => {
-                  setInfo(undefined);
-                }}
-              />
+function StatusBanner(): VNode {
+  const notifs = useNotifications()
+  return <div
+    class="fixed top-10 z-20 ml-4 mr-4"
+  > {
+      notifs.map(n => {
+        const info = n.message.type === "info" ? n.message : undefined
+        const error = n.message.type === "error" ? n.message : undefined
+        switch (n.message.type) {
+          case "error":
+            return <div class="rounded-md bg-red-50 p-4">
+            <div class="flex">
+              <div class="flex-shrink-0">
+                <svg class="h-5 w-5 text-red-400" 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 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clip-rule="evenodd" />
+                </svg>
+              </div>
+              <div class="ml-3 flex-1 md:flex md:justify-between">
+                <p class="text-sm font-medium text-red-800">{n.message.title}</p>
+                  <p class="mt-3 text-sm md:ml-6 md:mt-0">
+                    <button type="button" class="inline-flex font-semibold items-center rounded bg-white px-2 py-1 text-xs text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
+                      onClick={(e) => {
+                        e.preventDefault();
+                        n.remove()
+                      }}
+                    >
+                      Close
+                      <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+                        <path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
+                      </svg>
+                    </button>
+                  </p>     
+              </div>
             </div>
+            {n.message.description &&
+              <div class="mt-2 text-sm text-red-700">
+                {n.message.description}
+              </div>
+            }
           </div>
-        </div>
-      )}
-      {!error ? undefined : (
-        <ErrorBanner
-          error={error}
-          onClear={() => {
-            setError(undefined);
-          }}
-        />
-      )}
-    </div>
-  );
+          case "info":
+            return <div class="rounded-md bg-green-50 border-4 border-green-600 p-6">
+              <div class="flex">
+                <div class="flex-shrink-0">
+                  <svg class="h-8 w-8 text-green-400" 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>
+                </div>
+                <div class="ml-3 flex-1 md:flex md:justify-between">
+                  <h3 class="text-lg font-medium text-green-800">{n.message.title}</h3>
+
+                  <p class="mt-3 text-sm md:ml-6 md:mt-0">
+                    <button type="button" class="inline-flex font-semibold items-center rounded bg-white px-2 py-1 text-md text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
+                      onClick={(e) => {
+                        e.preventDefault();
+                        n.remove();
+                      }}
+                    >
+                      Close
+                      <svg class="h-8 w-8" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+                        <path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
+                      </svg>
+                    </button>
+                  </p>
+                </div>
+
+              </div>
+            </div>
+        }
+      })}
+  </div>
+
 }
 
 function TestingTag(): VNode {
@@ -392,12 +428,12 @@ function TestingTag(): VNode {
 
 function Footer() {
   return (
-    <footer class="absolute bottom-4">
+    <footer class="bottom-4 mb-4">
       <div class="mt-8 mx-8 md:order-1 md:mt-0">
         <div>
           <p class="text-xs leading-5 text-gray-400">
             You can learn more about GNU Taler on our{" "}
-            <a class="font-semibold text-gray-500 hover:text-gray-400" href="https://taler.net">main website</a>.
+            <a target="_blank" rel="noreferrer noopener" class="font-semibold text-gray-500 hover:text-gray-400" href="https://taler.net">main website</a>.
           </p>
         </div>
         <div style="flex-grow:1" />
@@ -418,4 +454,9 @@ function WelcomeAccount(): VNode {
     Welcome,  {account} (<a href={stringifyPaytoUri(payto)}>{payto.iban}</a>)! <CopyButton getContent={() => stringifyPaytoUri(payto)} />
   </i18n.Translate>
 
-}
\ No newline at end of file
+}
+
+function AccountBalance(): VNode {
+
+  return <div>KUDOS 100.00</div>
+}
diff --git a/packages/demobank-ui/src/pages/BusinessAccount.tsx b/packages/demobank-ui/src/pages/BusinessAccount.tsx
index 2faf83a1c..ec71ceca6 100644
--- a/packages/demobank-ui/src/pages/BusinessAccount.tsx
+++ b/packages/demobank-ui/src/pages/BusinessAccount.tsx
@@ -17,17 +17,21 @@ import {
   AmountJson,
   Amounts,
   HttpStatusCode,
-  TranslatedString,
+  TranslatedString
 } from "@gnu-taler/taler-util";
 import {
   HttpResponse,
   HttpResponsePaginated,
   RequestError,
+  notify,
+  notifyError,
+  notifyInfo,
   useTranslationContext,
 } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
-import { StateUpdater, useEffect, useState } from "preact/hooks";
+import { useEffect, useState } from "preact/hooks";
 import { Cashouts } from "../components/Cashouts/index.js";
+import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
 import { useBackendContext } from "../context/backend.js";
 import { useAccountDetails } from "../hooks/access.js";
 import {
@@ -42,12 +46,9 @@ import {
   undefinedIfEmpty,
 } from "../utils.js";
 import { ShowAccountDetails, UpdateAccountPassword } from "./AdminPage.js";
-import { ErrorBannerFloat } from "./BankFrame.js";
-import { LoginForm } from "./LoginForm.js";
-import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
 import { handleNotOkResult } from "./HomePage.js";
-import { ErrorMessage, notifyInfo } from "../hooks/notification.js";
-import { Amount } from "./WalletWithdrawForm.js";
+import { LoginForm } from "./LoginForm.js";
+import { Amount } from "./PaytoWireTransferForm.js";
 
 interface Props {
   onClose: () => void;
@@ -225,7 +226,6 @@ function CreateCashout({
   const { i18n } = useTranslationContext();
   const ratiosResult = useRatiosAndFeeConfig();
   const result = useAccountDetails(account);
-  const [error, saveError] = useState<ErrorMessage | undefined>();
   const {
     estimateByCredit: calculateFromCredit,
     estimateByDebit: calculateFromDebit,
@@ -268,15 +268,15 @@ function CreateCashout({
       calculateFromDebit(amount, sellFee, sellRate)
         .then((r) => {
           setCalc(r);
-          saveError(undefined);
         })
         .catch((error) => {
-          saveError(
+          notify(
             error instanceof RequestError
               ? buildRequestErrorMessage(i18n, error.cause)
               : {
+                  type: "error",
                   title: i18n.str`Could not estimate the cashout`,
-                  description: error.message,
+                  description: error.message as TranslatedString
                 },
           );
         });
@@ -284,13 +284,13 @@ function CreateCashout({
       calculateFromCredit(amount, sellFee, sellRate)
         .then((r) => {
           setCalc(r);
-          saveError(undefined);
         })
         .catch((error) => {
-          saveError(
+          notify(
             error instanceof RequestError
               ? buildRequestErrorMessage(i18n, error.cause)
               : {
+                  type: "error",
                   title: i18n.str`Could not estimate the cashout`,
                   description: error.message,
                 },
@@ -321,9 +321,6 @@ function CreateCashout({
 
   return (
     <div>
-      {error && (
-        <ErrorBannerFloat error={error} onClear={() => saveError(undefined)} />
-      )}
       <h1>New cashout</h1>
       <form class="pure-form">
         <fieldset>
@@ -341,13 +338,15 @@ function CreateCashout({
           />
         </fieldset>
         <fieldset>
-          <label>
+          <label for="amount">
             {form.isDebit
               ? i18n.str`Amount to send`
               : i18n.str`Amount to receive`}
+            
           </label>
           <div style={{ display: "flex" }}>
             <Amount
+              name="amount"
               currency={amount.currency}
               value={form.amount}
               onChange={(v) => {
@@ -376,24 +375,27 @@ function CreateCashout({
           <input value={sellRate} disabled />
         </fieldset>
         <fieldset>
-          <label>{i18n.str`Balance now`}</label>
+          <label for="balance-now">{i18n.str`Balance now`}</label>
           <Amount
+            name="banace-now"
             currency={balance.currency}
             value={Amounts.stringifyValue(balance)}
           />
         </fieldset>
         <fieldset>
-          <label
+          <label for="total-cost"
             style={{ fontWeight: "bold", color: "red" }}
           >{i18n.str`Total cost`}</label>
           <Amount
+            name="total-cost"
             currency={balance.currency}
             value={Amounts.stringifyValue(calc.debit)}
           />
         </fieldset>
         <fieldset>
-          <label>{i18n.str`Balance after`}</label>
+          <label for="balance-after">{i18n.str`Balance after`}</label>
           <Amount
+            name="balance-after"
             currency={balance.currency}
             value={balanceAfter ? Amounts.stringifyValue(balanceAfter) : ""}
           />
@@ -401,16 +403,18 @@ function CreateCashout({
         {Amounts.isZero(sellFee) ? undefined : (
           <Fragment>
             <fieldset>
-              <label>{i18n.str`Amount after conversion`}</label>
+              <label for="amount-conversiojn">{i18n.str`Amount after conversion`}</label>
               <Amount
+                name="amount-conversion"
                 currency={fiatCurrency}
                 value={Amounts.stringifyValue(calc.beforeFee)}
               />
             </fieldset>
 
             <fieldset>
-              <label>{i18n.str`Cashout fee`}</label>
+              <label form="cashout-fee">{i18n.str`Cashout fee`}</label>
               <Amount
+                name="cashout-fee"
                 currency={fiatCurrency}
                 value={Amounts.stringifyValue(sellFee)}
               />
@@ -418,10 +422,11 @@ function CreateCashout({
           </Fragment>
         )}
         <fieldset>
-          <label
+          <label for="total"
             style={{ fontWeight: "bold", color: "green" }}
           >{i18n.str`Total cashout transfer`}</label>
           <Amount
+            name="total"
             currency={fiatCurrency}
             value={Amounts.stringifyValue(calc.credit)}
           />
@@ -511,7 +516,7 @@ function CreateCashout({
                 onComplete(res.data.uuid);
               } catch (error) {
                 if (error instanceof RequestError) {
-                  saveError(
+                  notify(
                     buildRequestErrorMessage(i18n, error.cause, {
                       onClientError: (status) =>
                         status === HttpStatusCode.BadRequest
@@ -530,14 +535,13 @@ function CreateCashout({
                     }),
                   );
                 } else {
-                  saveError({
-                    title: i18n.str`Operation failed, please report`,
-                    description:
-                      error instanceof Error
-                        ? error.message
-                        : JSON.stringify(error),
-                  });
-                }
+                  notifyError(
+                    i18n.str`Operation failed, please report`,
+                    (error instanceof Error
+                      ? error.message
+                      : JSON.stringify(error)) as TranslatedString
+                  )
+            }
               }
             }}
           >
@@ -565,7 +569,6 @@ export function ShowCashoutDetails({
   const result = useCashoutDetails(id);
   const { abortCashout, confirmCashout } = useCircuitAccountAPI();
   const [code, setCode] = useState<string | undefined>(undefined);
-  const [error, saveError] = useState<ErrorMessage | undefined>();
   if (!result.ok) return onLoadNotOk(result);
   const errors = undefinedIfEmpty({
     code: !code ? i18n.str`required` : undefined,
@@ -574,9 +577,6 @@ export function ShowCashoutDetails({
   return (
     <div>
       <h1>Cashout details {id}</h1>
-      {error && (
-        <ErrorBannerFloat error={error} onClear={() => saveError(undefined)} />
-      )}
       <form class="pure-form">
         <fieldset>
           <label>
@@ -661,7 +661,7 @@ export function ShowCashoutDetails({
                   onCancel();
                 } catch (error) {
                   if (error instanceof RequestError) {
-                    saveError(
+                    notify(
                       buildRequestErrorMessage(i18n, error.cause, {
                         onClientError: (status) =>
                           status === HttpStatusCode.NotFound
@@ -672,14 +672,13 @@ export function ShowCashoutDetails({
                       }),
                     );
                   } else {
-                    saveError({
-                      title: i18n.str`Operation failed, please report`,
-                      description:
-                        error instanceof Error
-                          ? error.message
-                          : JSON.stringify(error),
-                    });
-                  }
+                    notifyError(
+                      i18n.str`Operation failed, please report`,
+                      (error instanceof Error
+                        ? error.message
+                        : JSON.stringify(error)) as TranslatedString
+                    )
+                }
                 }
               }}
             >
@@ -699,7 +698,7 @@ export function ShowCashoutDetails({
                   });
                 } catch (error) {
                   if (error instanceof RequestError) {
-                    saveError(
+                    notify(
                       buildRequestErrorMessage(i18n, error.cause, {
                         onClientError: (status) =>
                           status === HttpStatusCode.NotFound
@@ -714,14 +713,13 @@ export function ShowCashoutDetails({
                       }),
                     );
                   } else {
-                    saveError({
-                      title: i18n.str`Operation failed, please report`,
-                      description:
-                        error instanceof Error
-                          ? error.message
-                          : JSON.stringify(error),
-                    });
-                  }
+                    notifyError(
+                      i18n.str`Operation failed, please report`,
+                      (error instanceof Error
+                        ? error.message
+                        : JSON.stringify(error)) as TranslatedString
+                    )
+                }
                 }
               }}
             >
diff --git a/packages/demobank-ui/src/pages/HomePage.tsx b/packages/demobank-ui/src/pages/HomePage.tsx
index 86e511284..e00daf278 100644
--- a/packages/demobank-ui/src/pages/HomePage.tsx
+++ b/packages/demobank-ui/src/pages/HomePage.tsx
@@ -17,6 +17,7 @@
 import {
   HttpStatusCode,
   Logger,
+  TranslatedString,
   parseWithdrawUri,
   stringifyWithdrawUri,
 } from "@gnu-taler/taler-util";
@@ -24,18 +25,20 @@ import {
   ErrorType,
   HttpResponse,
   HttpResponsePaginated,
+  notify,
+  notifyError,
   useTranslationContext,
 } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { Loading } from "../components/Loading.js";
 import { useBackendContext } from "../context/backend.js";
 import { getInitialBackendBaseURL } from "../hooks/backend.js";
-import { notifyError, notifyInfo } from "../hooks/notification.js";
 import { useSettings } from "../hooks/settings.js";
 import { AccountPage } from "./AccountPage/index.js";
 import { AdminPage } from "./AdminPage.js";
 import { LoginForm } from "./LoginForm.js";
 import { WithdrawalQRCode } from "./WithdrawalQRCode.js";
+import { error } from "console";
 
 const logger = new Logger("AccountPage");
 
@@ -100,9 +103,10 @@ export function WithdrawalOperationPage({
   const { i18n } = useTranslationContext();
 
   if (!parsedUri) {
-    notifyError({
-      title: i18n.str`The Withdrawal URI is not valid: "${uri}"`,
-    });
+    notifyError(
+      i18n.str`The Withdrawal URI is not valid: "${uri}"`,
+      undefined
+    );
     return <Loading />;
   }
 
@@ -132,46 +136,46 @@ export function handleNotOkResult(
     if (!result.ok) {
       switch (result.type) {
         case ErrorType.TIMEOUT: {
-          notifyError({
-            title: i18n.str`Request timeout, try again later.`,
-          });
+          notifyError(i18n.str`Request timeout, try again later.`, undefined);
           break;
         }
         case ErrorType.CLIENT: {
           if (result.status === HttpStatusCode.Unauthorized) {
-            notifyError({
-              title: i18n.str`Wrong credentials`,
-            });
+            notifyError(i18n.str`Wrong credentials`, undefined);
             return <LoginForm onRegister={onRegister} />;
           }
           const errorData = result.payload;
-          notifyError({
+          notify({
+            type: "error",
             title: i18n.str`Could not load due to a client error`,
-            description: errorData.error.description,
+            description: errorData.error.description as TranslatedString,
             debug: JSON.stringify(result),
           });
           break;
         }
         case ErrorType.SERVER: {
-          notifyError({
+          notify({
+            type: "error", 
             title: i18n.str`Server returned with error`,
-            description: result.payload.error.description,
+            description: result.payload.error.description as TranslatedString,
             debug: JSON.stringify(result.payload),
           });
           break;
         }
         case ErrorType.UNREADABLE: {
-          notifyError({
+          notify({
+            type:"error",
             title: i18n.str`Unexpected error.`,
-            description: `Response from ${result.info?.url} is unreadable, http status: ${result.status}`,
+            description: i18n.str`Response from ${result.info?.url} is unreadable, http status: ${result.status}`,
             debug: JSON.stringify(result),
           });
           break;
         }
         case ErrorType.UNEXPECTED: {
-          notifyError({
+          notify({
+            type:"error",
             title: i18n.str`Unexpected error.`,
-            description: `Diagnostic from ${result.info?.url} is "${result.message}"`,
+            description: i18n.str`Diagnostic from ${result.info?.url} is "${result.message}"`,
             debug: JSON.stringify(result),
           });
           break;
diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx
index f0ae97d60..46039005a 100644
--- a/packages/demobank-ui/src/pages/LoginForm.tsx
+++ b/packages/demobank-ui/src/pages/LoginForm.tsx
@@ -14,16 +14,14 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { HttpStatusCode } from "@gnu-taler/taler-util";
-import { ErrorType, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
+import { ErrorType, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useEffect, useRef, useState } from "preact/hooks";
 import { useBackendContext } from "../context/backend.js";
 import { useCredentialsChecker } from "../hooks/backend.js";
-import { ErrorMessage } from "../hooks/notification.js";
 import { bankUiSettings } from "../settings.js";
 import { undefinedIfEmpty } from "../utils.js";
-import { ErrorBannerFloat } from "./BankFrame.js";
 import { USERNAME_REGEX } from "./RegistrationPage.js";
 import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
 
@@ -36,177 +34,202 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
   const [password, setPassword] = useState<string | undefined>();
   const { i18n } = useTranslationContext();
   const testLogin = useCredentialsChecker();
-  const [error, saveError] = useState<ErrorMessage | undefined>();
   const ref = useRef<HTMLInputElement>(null);
   useEffect(function focusInput() {
     ref.current?.focus();
   }, []);
+  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,
+        ? i18n.str`Use letters and numbers only, and start with a lowercase letter`
+        : undefined,
     password: !password ? i18n.str`Missing password` : undefined,
-  });
+  }) ?? busy;
+
+  function saveError({ title, description, debug }: { title: TranslatedString, description?: TranslatedString, debug?: any }) {
+    notifyError(title, description, debug)
+  }
+
+  async function doLogin() {
+    if (!username || !password) return;
+    setBusy({})
+    const testResult = await testLogin(username, password);
+    if (testResult.valid) {
+      backend.logIn({ username, password });
+    } else {
+      if (testResult.requestError) {
+        const { cause } = testResult;
+        switch (cause.type) {
+          case ErrorType.CLIENT: {
+            if (cause.status === HttpStatusCode.Unauthorized) {
+              saveError({
+                title: i18n.str`Wrong credentials for "${username}"`,
+              });
+            } else
+            if (cause.status === HttpStatusCode.NotFound) {
+              saveError({
+                title: i18n.str`Account not found`,
+              });
+            } else {
+              saveError({
+                title: i18n.str`Could not load due to a client error`,
+                description: cause.payload.error.description,
+                debug: JSON.stringify(cause.payload),
+              });
+            }
+            break;
+          }
+          case ErrorType.SERVER: {
+            saveError({
+              title: i18n.str`Server had a problem, try again later or report.`,
+              description: cause.payload.error.description,
+              debug: JSON.stringify(cause.payload),
+            });
+            break;
+          }
+          case ErrorType.TIMEOUT: {
+            saveError({
+              title: i18n.str`Request timeout, try again later.`,
+            });
+            break;
+          }
+          case ErrorType.UNREADABLE: {
+            saveError({
+              title: i18n.str`Unexpected error.`,
+              description: `Response from ${cause.info?.url} is unreadable, http status: ${cause.status}` as TranslatedString,
+              debug: JSON.stringify(cause),
+            });
+            break;
+          }
+          default: {
+            saveError({
+              title: i18n.str`Unexpected error, please report.`,
+              description: `Diagnostic from ${cause.info?.url} is "${cause.message}"` as TranslatedString,
+              debug: JSON.stringify(cause),
+            });
+            break;
+          }
+        }
+      } else {
+        saveError({
+          title: i18n.str`Unexpected error, please report.`,
+          debug: JSON.stringify(testResult.error),
+        });
+      }
+      backend.logOut();
+    }
+    setPassword(undefined);
+    setBusy(undefined)
+  }
 
   return (
     <Fragment>
-      <h1 class="nav">{i18n.str`Welcome to ${bankUiSettings.bankName}!`}</h1>
-      {error && (
+      <h1 class="nav"></h1>
+      {/* {error && (
         <ErrorBannerFloat error={error} onClear={() => saveError(undefined)} />
-      )}
-      <div class="login-div">
-        <form
-          class="login-form"
-          noValidate
-          onSubmit={(e) => {
-            e.preventDefault();
-          }}
-          autoCapitalize="none"
-          autoCorrect="off"
-        >
-          <div class="pure-form">
-            <h2>{i18n.str`Please login!`}</h2>
-            <p class="unameFieldLabel loginFieldLabel formFieldLabel">
-              <label for="username">{i18n.str`Username:`}</label>
-            </p>
-            <input
-              ref={ref}
-              autoFocus
-              type="text"
-              name="username"
-              id="username"
-              value={username ?? ""}
-              enterkeyhint="next"
-              placeholder="Username"
-              autocomplete="username"
-              required
-              onInput={(e): void => {
-                setUsername(e.currentTarget.value);
-              }}
-            />
-            <ShowInputErrorLabel
-              message={errors?.username}
-              isDirty={username !== undefined}
-            />
-            <p class="passFieldLabel loginFieldLabel formFieldLabel">
-              <label for="password">{i18n.str`Password:`}</label>
-            </p>
-            <input
-              type="password"
-              name="password"
-              id="password"
-              autocomplete="current-password"
-              enterkeyhint="send"
-              value={password ?? ""}
-              placeholder="Password"
-              required
-              onInput={(e): void => {
-                setPassword(e.currentTarget.value);
-              }}
-            />
-            <ShowInputErrorLabel
-              message={errors?.password}
-              isDirty={password !== undefined}
-            />
-            <br />
-            <button
-              type="submit"
-              class="pure-button pure-button-primary"
-              disabled={!!errors}
-              onClick={async (e) => {
-                e.preventDefault();
-                if (!username || !password) return;
-                const testResult = await testLogin(username, password);
-                if (testResult.valid) {
-                  backend.logIn({ username, password });
-                } else {
-                  if (testResult.requestError) {
-                    const { cause } = testResult;
-                    switch (cause.type) {
-                      case ErrorType.CLIENT: {
-                        if (cause.status === HttpStatusCode.Unauthorized) {
-                          saveError({
-                            title: i18n.str`Wrong credentials for "${username}"`,
-                          });
-                        }
-                        if (cause.status === HttpStatusCode.NotFound) {
-                          saveError({
-                            title: i18n.str`Account not found`,
-                          });
-                        } else {
-                          saveError({
-                            title: i18n.str`Could not load due to a client error`,
-                            description: cause.payload.error.description,
-                            debug: JSON.stringify(cause.payload),
-                          });
-                        }
-                        break;
-                      }
-                      case ErrorType.SERVER: {
-                        saveError({
-                          title: i18n.str`Server had a problem, try again later or report.`,
-                          description: cause.payload.error.description,
-                          debug: JSON.stringify(cause.payload),
-                        });
-                        break;
-                      }
-                      case ErrorType.TIMEOUT: {
-                        saveError({
-                          title: i18n.str`Request timeout, try again later.`,
-                        });
-                        break;
-                      }
-                      case ErrorType.UNREADABLE: {
-                        saveError({
-                          title: i18n.str`Unexpected error.`,
-                          description: `Response from ${cause.info?.url} is unreadable, http status: ${cause.status}`,
-                          debug: JSON.stringify(cause),
-                        });
-                        break;
-                      }
-                      default: {
-                        saveError({
-                          title: i18n.str`Unexpected error, please report.`,
-                          description: `Diagnostic from ${cause.info?.url} is "${cause.message}"`,
-                          debug: JSON.stringify(cause),
-                        });
-                        break;
-                      }
-                    }
-                  } else {
-                    saveError({
-                      title: i18n.str`Unexpected error, please report.`,
-                      debug: JSON.stringify(testResult.error),
-                    });
-                  }
-                  backend.logOut();
-                }
-                setUsername(undefined);
-                setPassword(undefined);
-              }}
-            >
-              {i18n.str`Login`}
-            </button>
-
-            {bankUiSettings.allowRegistrations && onRegister ? (
-              <button
-                class="pure-button pure-button-secondary btn-cancel"
+      )} */}
+
+      <div class="flex min-h-full flex-col justify-center">
+        <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 ${bankUiSettings.bankName}!`}</h2>
+        </div>
+
+        <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
+          <form class="space-y-6" noValidate
+            onSubmit={(e) => {
+              e.preventDefault();
+            }}
+            autoCapitalize="none"
+            autoCorrect="off"
+          >
+            <div>
+              <label for="username" class="block text-sm font-medium leading-6 text-gray-900">
+                <i18n.Translate>Username</i18n.Translate>
+              </label>
+              <div class="mt-2">
+                <input
+                  ref={ref}
+                  autoFocus
+                  type="text"
+                  name="username"
+                  id="username"
+                  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={username ?? ""}
+                  enterkeyhint="next"
+                  placeholder="identification"
+                  autocomplete="username"
+                  required
+                  onInput={(e): void => {
+                    setUsername(e.currentTarget.value);
+                  }}
+                />
+                <ShowInputErrorLabel
+                  message={errors?.username}
+                  isDirty={username !== undefined}
+                />
+              </div>
+            </div>
+
+            <div>
+              <div class="flex items-center justify-between">
+                <label for="password" class="block text-sm font-medium leading-6 text-gray-900">Password</label>
+              </div>
+              <div class="mt-2">
+                <input
+                  type="password"
+                  name="password"
+                  id="password"
+                  autocomplete="current-password"
+                  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"
+                  enterkeyhint="send"
+                  value={password ?? ""}
+                  placeholder="Password"
+                  required
+                  onInput={(e): void => {
+                    setPassword(e.currentTarget.value);
+                  }}
+                />
+                <ShowInputErrorLabel
+                  message={errors?.password}
+                  isDirty={password !== undefined}
+                />
+              </div>
+            </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();
-                  onRegister();
+                  e.preventDefault()
+                  doLogin()
                 }}
               >
-                {i18n.str`Register`}
+                <i18n.Translate>Log in</i18n.Translate>
               </button>
-            ) : (
-              <div />
-            )}
-          </div>
-        </form>
+            </div>
+          </form>
+
+          {bankUiSettings.allowRegistrations && onRegister &&
+            <p class="mt-10 text-center text-sm text-gray-500 border-t">
+              <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()
+                  onRegister()
+                }}
+              >
+                <i18n.Translate>Register</i18n.Translate>
+              </button>
+            </p>
+          }
+        </div>
       </div>
+
+
     </Fragment>
   );
 }
diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx b/packages/demobank-ui/src/pages/PaymentOptions.tsx
index cf3f41deb..c82c1b28d 100644
--- a/packages/demobank-ui/src/pages/PaymentOptions.tsx
+++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx
@@ -15,10 +15,9 @@
  */
 
 import { AmountJson } from "@gnu-taler/taler-util";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser";
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
-import { notifyInfo } from "../hooks/notification.js";
 import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js";
 import { WalletWithdrawForm } from "./WalletWithdrawForm.js";
 import { useSettings } from "../hooks/settings.js";
@@ -31,7 +30,8 @@ export function PaymentOptions({ limit }: { limit: AmountJson }): VNode {
   const { i18n } = useTranslationContext();
   const [settings, updateSettings] = useSettings();
 
-  const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>(undefined);
+  const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>();
+  // const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>(undefined);
 
   return (<fieldset>
     <legend class="px-4 text-base font-semibold leading-6 text-gray-900">
@@ -41,34 +41,32 @@ export function PaymentOptions({ limit }: { limit: AmountJson }): VNode {
     <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" onChange={() => {
+        <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")
         }} />
         <span class="flex flex-1">
           <span class="flex flex-col">
-            <span id="project-type-0-label" class="block text-sm font-semibold font-medium text-gray-900">
-              <i18n.Translate>a Taler Wallet</i18n.Translate>
+            <span id="project-type-0-label" class="block text-sm font-medium text-gray-900">
+              <i18n.Translate>a <b>Taler</b> wallet</i18n.Translate>
             </span>
             <span id="project-type-0-description-0" 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>
             </span>
           </span>
         </span>
-        {/* <!-- Not Checked: "invisible" --> */}
         <svg class="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>
       </label>
 
 
-      {/* <!-- 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 === "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" onChange={() => {
+        <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")
         }} />
         <span class="flex flex-1">
           <span class="flex flex-col">
-            <span id="project-type-1-label" class="block text-sm font-semibold font-medium text-gray-900">
+            <span id="project-type-1-label" class="block text-sm font-medium text-gray-900">
               <i18n.Translate>another bank account</i18n.Translate>
             </span>
             <span id="project-type-1-description-0" class="mt-1 flex items-center text-sm text-gray-500">
@@ -88,6 +86,9 @@ export function PaymentOptions({ limit }: { limit: AmountJson }): VNode {
         onSuccess={(id) => {
           updateSettings("currentWithdrawalOperationId", id);
         }}
+        onCancel={() => {
+          setTab(undefined)
+        }}
       />
     )}
     {tab === "wire-transfer" && (
@@ -97,32 +98,11 @@ export function PaymentOptions({ limit }: { limit: AmountJson }): VNode {
         onSuccess={() => {
           notifyInfo(i18n.str`Wire transfer created!`);
         }}
+        onCancel={() => {
+          setTab(undefined)
+        }}
       />
     )}
 
   </fieldset>)
-  {/* return (
-    <article>
-      <div class="payments">
-        <div class="tab">
-          <button
-            class={tab === "charge-wallet" ? "tablinks active" : "tablinks"}
-            onClick={(): void => {
-              setTab("charge-wallet");
-            }}
-          >
-            {i18n.str`Withdraw `}
-          </button>
-          <button
-            class={tab === "wire-transfer" ? "tablinks active" : "tablinks"}
-            onClick={(): void => {
-              setTab("wire-transfer");
-            }}
-          >
-            {i18n.str`Wire transfer`}
-          </button>
-        </div>
-      </div>
-    </article>
-  ); */}
 }
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index 1107360bd..5e0624cbf 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -19,17 +19,21 @@ import {
   Amounts,
   HttpStatusCode,
   Logger,
-  parsePaytoUri
+  TranslatedString,
+  buildPayto,
+  parsePaytoUri,
+  stringifyPaytoUri
 } from "@gnu-taler/taler-util";
 import {
   RequestError,
+  notify,
+  notifyError,
   useTranslationContext,
 } from "@gnu-taler/web-util/browser";
-import { h, VNode, Fragment } from "preact";
+import { h, VNode, Fragment, Ref } from "preact";
 import { useEffect, useRef, useState } from "preact/hooks";
 import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
 import { useAccessAPI } from "../hooks/access.js";
-import { notifyError } from "../hooks/notification.js";
 import {
   buildRequestErrorMessage,
   undefinedIfEmpty,
@@ -41,10 +45,12 @@ const logger = new Logger("PaytoWireTransferForm");
 export function PaytoWireTransferForm({
   focus,
   onSuccess,
+  onCancel,
   limit,
 }: {
   focus?: boolean;
   onSuccess: () => void;
+  onCancel: (() => void) | undefined;
   limit: AmountJson;
 }): VNode {
   const [isRawPayto, setIsRawPayto] = useState(false);
@@ -105,7 +111,51 @@ export function PaytoWireTransferForm({
                   ? i18n.str`IBAN should have just uppercased letters and numbers`
                   : validateIBAN(parsed.iban, i18n),
   });
-  // if (!isRawPayto) {
+
+  async function doSend() {
+    let paytoUri: string | undefined;
+
+    if (rawPaytoInput) {
+      paytoUri = rawPaytoInput
+    } else {
+      if (!iban || !subject) return;
+      const ibanPayto = buildPayto("iban", iban, undefined);
+      ibanPayto.params.message = encodeURIComponent(subject);
+      paytoUri = stringifyPaytoUri(ibanPayto);
+    }
+
+    try {
+      await createTransaction({
+        paytoUri,
+        amount: `${limit.currency}:${amount}`,
+      });
+      onSuccess();
+      setAmount(undefined);
+      setIban(undefined);
+      setSubject(undefined);
+      rawPaytoInputSetter(undefined)
+    } catch (error) {
+      if (error instanceof RequestError) {
+        notify(
+          buildRequestErrorMessage(i18n, error.cause, {
+            onClientError: (status) =>
+              status === HttpStatusCode.BadRequest
+                ? i18n.str`The request was invalid or the payto://-URI used unacceptable features.`
+                : undefined,
+          }),
+        );
+      } else {
+        notifyError(
+          i18n.str`Operation failed, please report`,
+          (error instanceof Error
+            ? error.message
+            : JSON.stringify(error)) as TranslatedString
+        )
+}
+    }
+
+  }
+
   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>Transfer details</i18n.Translate></h2>
@@ -118,7 +168,7 @@ export function PaytoWireTransferForm({
             }} />
             <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 class="block text-sm  font-medium text-gray-900">
                   <i18n.Translate>form</i18n.Translate>
                 </span>
               </span>
@@ -133,7 +183,7 @@ export function PaytoWireTransferForm({
             }} />
             <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 class="block text-sm  font-medium text-gray-900">
                   <i18n.Translate>payto://</i18n.Translate>
                 </span>
               </span>
@@ -143,23 +193,31 @@ export function PaytoWireTransferForm({
       </div>
     </div>
 
-    <form class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2">
+    <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()
+      }}
+    >
       <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">
           {!isRawPayto ?
             <Fragment>
 
-              <div class="sm:col-span-3">
-                <label for="first-name" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Account number`}</label>
+              <div class="sm:col-span-5">
+                <label for="iban" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Account number`}</label>
                 <div class="mt-2">
                   <input
                     ref={ref}
                     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"
-                    id="iban"
                     name="iban"
+                    id="iban"
                     value={iban ?? ""}
                     placeholder="CC0123456789"
+                    autocomplete="off"
                     required
                     pattern={ibanRegex}
                     onInput={(e): void => {
@@ -171,21 +229,18 @@ export function PaytoWireTransferForm({
                     isDirty={iban !== undefined}
                   />
                 </div>
-                <p class="mt-2 text-sm text-gray-500" id="email-description">the receiver of the money</p>
-              </div>
-
-              <div class="sm:col-span-3">
+                <p class="mt-2 text-sm text-gray-500" >the receiver of the money</p>
               </div>
 
-              <div class="sm:col-span-3">
-                <label for="first-name" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Transfer subject`}</label>
+              <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="subject"
                     value={subject ?? ""}
                     required
@@ -198,37 +253,40 @@ export function PaytoWireTransferForm({
                     isDirty={subject !== undefined}
                   />
                 </div>
-                <p class="mt-2 text-sm text-gray-500" id="email-description">some text to identify the transfer</p>
-
-              </div>
-
-              <div class="sm:col-span-3">
+                <p class="mt-2 text-sm text-gray-500" >some text to identify the transfer</p>
               </div>
 
-              <div class="sm:col-span-3">
-                <label for="first-name" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Amount`}</label>
-                <div class="mt-2">
-                  <input type="text" name="first-name" id="first-name" autocomplete="given-name" 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" />
-                </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>
+                <Amount
+                  name="amount"
+                  currency={limit.currency}
+                  value={trimmedAmountStr}
+                  onChange={(d) => {
+                    setAmount(d)
+                  }}
+                />
+                <ShowInputErrorLabel
+                  message={errorsWire?.subject}
+                  isDirty={subject !== undefined}
+                />
+                <p class="mt-2 text-sm text-gray-500" >amount to transfer</p>
               </div>
 
-              <div class="sm:col-span-3">
-              </div>
             </Fragment> :
             <Fragment>
               <div class="sm:col-span-6">
-                <label for="first-name" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`payto URI:`}</label>
+                <label for="address" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`payto URI:`}</label>
                 <div class="mt-2">
                   <input
                     name="address"
+                    id="address"
                     type="text"
                     size={50}
                     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" ref={ref}
-                    id="address"
                     value={rawPaytoInput ?? ""}
                     required
                     placeholder={i18n.str`payto://iban/[receiver-iban]?message=[subject]&amount=[${limit.currency}:X.Y]`}
-                    // pattern={`payto://iban/[A-Z][A-Z][0-9]+?message=[a-zA-Z0-9 ]+&amount=${currency}:[0-9]+(.[0-9]+)?`}
                     onInput={(e): void => {
                       rawPaytoInputSetter(e.currentTarget.value);
                     }}
@@ -244,9 +302,23 @@ export function PaytoWireTransferForm({
           }
         </div>
       </div>
-      <div class="flex items-center justify-end 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">Cancel</button>
-        <button type="submit" class="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">
+      <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"
+          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>
@@ -262,8 +334,6 @@ export function PaytoWireTransferForm({
   //       onSubmit={(e) => {
   //         e.preventDefault();
   //       }}
-  //       autoCapitalize="none"
-  //       autoCorrect="off"
   //     >
   //       <label for="iban">{i18n.str`Receiver IBAN:`}</label>&nbsp;
 
@@ -318,39 +388,7 @@ export function PaytoWireTransferForm({
   //             if (!(iban && subject && amount)) {
   //               return;
   //             }
-  //             const ibanPayto = buildPayto("iban", iban, undefined);
-  //             ibanPayto.params.message = encodeURIComponent(subject);
-  //             const paytoUri = stringifyPaytoUri(ibanPayto);
-
-  //             try {
-  //               await createTransaction({
-  //                 paytoUri,
-  //                 amount: `${limit.currency}:${amount}`,
-  //               });
-  //               onSuccess();
-  //               setAmount(undefined);
-  //               setIban(undefined);
-  //               setSubject(undefined);
-  //             } catch (error) {
-  //               if (error instanceof RequestError) {
-  //                 notifyError(
-  //                   buildRequestErrorMessage(i18n, error.cause, {
-  //                     onClientError: (status) =>
-  //                       status === HttpStatusCode.BadRequest
-  //                         ? i18n.str`The request was invalid or the payto://-URI used unacceptable features.`
-  //                         : undefined,
-  //                   }),
-  //                 );
-  //               } else {
-  //                 notifyError({
-  //                   title: i18n.str`Operation failed, please report`,
-  //                   description:
-  //                     error instanceof Error
-  //                       ? error.message
-  //                       : JSON.stringify(error),
-  //                 });
-  //               }
-  //             }
+
   //           }}
   //         />
   //         <input
@@ -389,3 +427,46 @@ export function PaytoWireTransferForm({
   //   </div>
   // );
 }
+export function Amount(
+  {
+    currency,
+    name,
+    value,
+    error,
+    onChange,
+  }: {
+    error?: string;
+    currency: string;
+    name: string;
+    value: string | undefined;
+    onChange?: (s: string) => void;
+  },
+  ref: Ref<HTMLInputElement>,
+): VNode {
+  return (
+    <div class="mt-2">
+      <div class="relative rounded-md shadow-sm">
+        <div class="pointer-events-none absolute inset-y-0 flex items-center pl-3">
+          <span class="text-gray-500 sm:text-sm">{currency}</span>
+        </div>
+        <input
+          type="number"
+          class="text-right block w-full rounded-md border-0 py-1.5 pl-16 text-gray-900 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"
+          placeholder="0.00" aria-describedby="price-currency"
+          ref={ref}
+          name={name}
+          id={name}
+          autocomplete="off"
+          value={value ?? ""}
+          disabled={!onChange}
+          onInput={(e): void => {
+            if (onChange) {
+              onChange(e.currentTarget.value);
+            }
+          }}
+        />
+      </div>
+      <ShowInputErrorLabel message={error} isDirty={value !== undefined} />
+    </div>
+  );
+}
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx b/packages/demobank-ui/src/pages/QrCodeSection.tsx
index c27984569..7c1b3bdc5 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx
@@ -17,17 +17,19 @@
 import {
   HttpStatusCode,
   stringifyWithdrawUri,
+  TranslatedString,
   WithdrawUriResult,
 } from "@gnu-taler/taler-util";
 import {
+  notify,
+  notifyError,
   RequestError,
   useTranslationContext,
 } from "@gnu-taler/web-util/browser";
-import { h, VNode } from "preact";
+import { Fragment, h, VNode } from "preact";
 import { useEffect } from "preact/hooks";
 import { QR } from "../components/QR.js";
 import { useAccessAnonAPI } from "../hooks/access.js";
-import { notifyError } from "../hooks/notification.js";
 import { buildRequestErrorMessage } from "../utils.js";
 
 export function QrCodeSection({
@@ -49,47 +51,87 @@ export function QrCodeSection({
   const talerWithdrawUri = stringifyWithdrawUri(withdrawUri);
 
   const { abortWithdrawal } = useAccessAnonAPI();
+
+  async function doAbort() {
+    try {
+      await abortWithdrawal(withdrawUri.withdrawalOperationId);
+      onAborted();
+    } catch (error) {
+      if (error instanceof RequestError) {
+        notify(
+          buildRequestErrorMessage(i18n, error.cause, {
+            onClientError: (status) =>
+              status === HttpStatusCode.Conflict
+                ? i18n.str`The reserve operation has been confirmed previously and can't be aborted`
+                : undefined,
+          }),
+        );
+      } else {
+        notifyError(
+          i18n.str`Operation failed, please report`,
+          (error instanceof Error
+            ? error.message
+            : JSON.stringify(error)) as TranslatedString
+        )
+      }
+    }
+  }
+
   return (
-    <section id="main" class="content">
-      <h1 class="nav">{i18n.str`Charge your GNU Taler wallet`}</h1>
-      <article>
-        <div class="qr-div ">
-          <a href={talerWithdrawUri} class="pure-button pure-button-primary">
-            <i18n.Translate>Continue with GNU Taler</i18n.Translate>
-          </a>
-          <p>{i18n.str`Or scan this QR code with your mobile to receive the coin in another device:`}</p>
-          <QR text={talerWithdrawUri} />
-          <a
-            class="pure-button btn-cancel"
-            onClick={async (e) => {
-              e.preventDefault();
-              try {
-                await abortWithdrawal(withdrawUri.withdrawalOperationId);
-                onAborted();
-              } catch (error) {
-                if (error instanceof RequestError) {
-                  notifyError(
-                    buildRequestErrorMessage(i18n, error.cause, {
-                      onClientError: (status) =>
-                        status === HttpStatusCode.Conflict
-                          ? i18n.str`The reserve operation has been confirmed previously and can't be aborted`
-                          : undefined,
-                    }),
-                  );
-                } else {
-                  notifyError({
-                    title: i18n.str`Operation failed, please report`,
-                    description:
-                      error instanceof Error
-                        ? error.message
-                        : JSON.stringify(error),
-                  });
-                }
-              }
-            }}
-          >{i18n.str`Cancel`}</a>
+    <Fragment>
+      <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>
+          </h3>
+          <div class="mt-4">
+            <a href={talerWithdrawUri}
+              // class="text-sm font-semibold leading-6 text-gray-900 btn "
+              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>Click here to start</i18n.Translate>
+            </a>
+          </div>
+          <div class="mt-4 max-w-xl 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 one you can install it from <a class="font-semibold text-gray-500 hover:text-gray-400" href="https://taler.net/en/wallet.html">here</a>.
+            </i18n.Translate></p>
+          </div>
+          <div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 pt-2 mt-2 ">
+            <div />
+            <button type="button"
+              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"
+              onClick={doAbort}
+            >
+              Cancel withdrawal
+            </button>
+          </div>
+        </div>
+      </div>
+
+      <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>
+          </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>
+          </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"
+            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"
+            onClick={doAbort}
+          >
+            Cancel withdrawal
+          </button>
         </div>
-      </article>
-    </section>
+      </div>
+
+    </Fragment>
   );
 }
diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx b/packages/demobank-ui/src/pages/RegistrationPage.tsx
index e52a5b11b..b912b9060 100644
--- a/packages/demobank-ui/src/pages/RegistrationPage.tsx
+++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx
@@ -13,19 +13,21 @@
  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, Logger } from "@gnu-taler/taler-util";
+import { HttpStatusCode, Logger, TranslatedString } from "@gnu-taler/taler-util";
 import {
   RequestError,
+  notify,
+  notifyError,
   useTranslationContext,
 } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { useBackendContext } from "../context/backend.js";
 import { useTestingAPI } from "../hooks/access.js";
-import { notifyError } from "../hooks/notification.js";
 import { bankUiSettings } from "../settings.js";
 import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
 import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
+import { getRandomPassword, getRandomUsername } from "./rnd.js";
 
 const logger = new Logger("RegistrationPage");
 
@@ -61,147 +63,214 @@ function RegistrationForm({ onComplete }: { onComplete: () => void }): VNode {
     username: !username
       ? i18n.str`Missing username`
       : !USERNAME_REGEX.test(username)
-      ? i18n.str`Use letters and numbers only, and start with a lowercase letter`
-      : undefined,
+        ? 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`
       : repeatPassword !== password
-      ? i18n.str`Passwords don't match`
-      : undefined,
+        ? i18n.str`Passwords don't match`
+        : undefined,
   });
 
+  async function doRegistrationStep() {
+    if (!username || !password) return;
+    try {
+      await register({ username, password });
+      setUsername(undefined);
+      backend.logIn({ username, password });
+      onComplete();
+    } catch (error) {
+      if (error instanceof RequestError) {
+        notify(
+          buildRequestErrorMessage(i18n, error.cause, {
+            onClientError: (status) =>
+              status === HttpStatusCode.Conflict
+                ? i18n.str`That username is already taken`
+                : undefined,
+          }),
+        );
+      } else {
+        notifyError(
+          i18n.str`Operation failed, please report`,
+          (error instanceof Error
+            ? error.message
+            : JSON.stringify(error)) as TranslatedString
+        )
+}
+    }
+    setPassword(undefined);
+    setRepeatPassword(undefined);
+}
+
+  async function delay(ms: number):Promise<void> {
+    return new Promise((resolve) => {
+      setTimeout(() => {
+        resolve(undefined);
+      }, ms)
+    })
+  }
+  async function doRandomRegistration(tries: number = 3) {
+    const user = getRandomUsername();
+    const pass = getRandomPassword();
+    try {
+      setUsername(undefined);
+      setPassword(undefined);
+      setRepeatPassword(undefined);
+      await register({ username: user, password: pass });
+      backend.logIn({ username: user, password: pass });
+      onComplete();
+    } catch (error) {
+      if (error instanceof RequestError) {
+        if (tries > 0) {
+          await delay(200)
+          await doRandomRegistration(tries-1)
+        } else {
+          notify(
+            buildRequestErrorMessage(i18n, error.cause, {
+              onClientError: (status) =>
+                status === HttpStatusCode.Conflict
+                  ? i18n.str`Could not create a random user`
+                  : undefined,
+            }),
+          );
+        }
+      } else {
+        notifyError(
+          i18n.str`Operation failed, please report`,
+          (error instanceof Error
+            ? error.message
+            : JSON.stringify(error)) as TranslatedString
+        )
+}
+    }
+  }
+
   return (
     <Fragment>
-      <h1 class="nav">{i18n.str`Welcome to ${bankUiSettings.bankName}!`}</h1>
-      <article>
-        <div class="register-div">
-          <form
-            class="register-form"
-            noValidate
+      <h1 class="nav"></h1>
+
+      <div class="flex min-h-full flex-col justify-center">
+        <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`Sign up!`}</h2>
+        </div>
+
+        <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
+          <form class="space-y-6" noValidate
             onSubmit={(e) => {
               e.preventDefault();
             }}
             autoCapitalize="none"
             autoCorrect="off"
           >
-            <div class="pure-form">
-              <h2>{i18n.str`Please register!`}</h2>
-              <p class="unameFieldLabel registerFieldLabel formFieldLabel">
-                <label for="register-un">{i18n.str`Username:`}</label>
-              </p>
-              <input
-                id="register-un"
-                name="register-un"
-                type="text"
-                placeholder="Username"
-                autocomplete="username"
-                value={username ?? ""}
-                onInput={(e): void => {
-                  setUsername(e.currentTarget.value);
-                }}
-              />
-              <ShowInputErrorLabel
-                message={errors?.username}
-                isDirty={username !== undefined}
-              />
-              <p class="unameFieldLabel registerFieldLabel formFieldLabel">
-                <label for="register-pw">{i18n.str`Password:`}</label>
-              </p>
-              <input
-                type="password"
-                name="register-pw"
-                id="register-pw"
-                placeholder="Password"
-                autocomplete="new-password"
-                value={password ?? ""}
-                required
-                onInput={(e): void => {
-                  setPassword(e.currentTarget.value);
-                }}
-              />
-              <ShowInputErrorLabel
-                message={errors?.password}
-                isDirty={password !== undefined}
-              />
-              <p class="unameFieldLabel registerFieldLabel formFieldLabel">
-                <label for="register-repeat">{i18n.str`Repeat Password:`}</label>
-              </p>
-              <input
-                type="password"
-                style={{ marginBottom: 8 }}
-                name="register-repeat"
-                id="register-repeat"
-                autocomplete="new-password"
-                placeholder="Same password"
-                value={repeatPassword ?? ""}
-                required
-                onInput={(e): void => {
-                  setRepeatPassword(e.currentTarget.value);
-                }}
-              />
-              <ShowInputErrorLabel
-                message={errors?.repeatPassword}
-                isDirty={repeatPassword !== undefined}
-              />
-              <br />
-              <button
-                class="pure-button pure-button-primary btn-register"
-                type="submit"
+            <div>
+              <label for="username" class="block text-sm font-medium leading-6 text-gray-900">
+                <i18n.Translate>Username</i18n.Translate>
+              </label>
+              <div class="mt-2">
+                <input
+                  autoFocus
+                  type="text"
+                  name="username"
+                  id="username"
+                  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={username ?? ""}
+                  enterkeyhint="next"
+                  placeholder="identification"
+                  autocomplete="username"
+                  required
+                  onInput={(e): void => {
+                    setUsername(e.currentTarget.value);
+                  }}
+                />
+                <ShowInputErrorLabel
+                  message={errors?.username}
+                  isDirty={username !== undefined}
+                />
+              </div>
+            </div>
+
+            <div>
+              <div class="flex items-center justify-between">
+                <label for="password" class="block text-sm font-medium leading-6 text-gray-900">Password</label>
+              </div>
+              <div class="mt-2">
+                <input
+                  type="password"
+                  name="password"
+                  id="password"
+                  autocomplete="current-password"
+                  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"
+                  enterkeyhint="send"
+                  value={password ?? ""}
+                  placeholder="Password"
+                  required
+                  onInput={(e): void => {
+                    setPassword(e.currentTarget.value);
+                  }}
+                />
+                <ShowInputErrorLabel
+                  message={errors?.password}
+                  isDirty={password !== undefined}
+                />
+              </div>
+            </div>
+
+            <div>
+              <div class="flex items-center justify-between">
+                <label for="register-repeat" class="block text-sm font-medium leading-6 text-gray-900">Repeat assword</label>
+              </div>
+              <div class="mt-2">
+                <input
+                  type="password"
+                  name="register-repeat"
+                  id="register-repeat"
+                  autocomplete="current-password"
+                  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"
+                  enterkeyhint="send"
+                  value={repeatPassword ?? ""}
+                  placeholder="Same password"
+                  required
+                  onInput={(e): void => {
+                    setRepeatPassword(e.currentTarget.value);
+                  }}
+                />
+                <ShowInputErrorLabel
+                  message={errors?.repeatPassword}
+                  isDirty={repeatPassword !== undefined}
+                />
+              </div>
+            </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={async (e) => {
-                  e.preventDefault();
-
-                  if (!username || !password) return;
-                  try {
-                    const credentials = { username, password };
-                    await register(credentials);
-                    setUsername(undefined);
-                    setPassword(undefined);
-                    setRepeatPassword(undefined);
-                    backend.logIn(credentials);
-                    onComplete();
-                  } catch (error) {
-                    if (error instanceof RequestError) {
-                      notifyError(
-                        buildRequestErrorMessage(i18n, error.cause, {
-                          onClientError: (status) =>
-                            status === HttpStatusCode.Conflict
-                              ? i18n.str`That username is already taken`
-                              : undefined,
-                        }),
-                      );
-                    } else {
-                      notifyError({
-                        title: i18n.str`Operation failed, please report`,
-                        description:
-                          error instanceof Error
-                            ? error.message
-                            : JSON.stringify(error),
-                      });
-                    }
-                  }
-                }}
-              >
-                {i18n.str`Register`}
-              </button>
-              {/* FIXME: should use a different color */}
-              <button
-                class="pure-button pure-button-secondary btn-cancel"
                 onClick={(e) => {
-                  e.preventDefault();
-                  setUsername(undefined);
-                  setPassword(undefined);
-                  setRepeatPassword(undefined);
-                  onComplete();
+                  e.preventDefault()
+                  doRegistrationStep()
                 }}
               >
-                {i18n.str`Cancel`}
+                <i18n.Translate>Confirm</i18n.Translate>
               </button>
             </div>
+
           </form>
+
+          <p class="mt-10 text-center text-sm text-gray-500 border-t">
+            <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()
+              }}
+            >
+              <i18n.Translate>Create a random user</i18n.Translate>
+            </button>
+          </p>
         </div>
-      </article>
+      </div>
+
     </Fragment>
   );
 }
diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
index da624f61b..6574ec934 100644
--- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
+++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
@@ -19,19 +19,21 @@ import {
   Amounts,
   HttpStatusCode,
   Logger,
+  TranslatedString,
   parseWithdrawUri,
 } from "@gnu-taler/taler-util";
 import {
   RequestError,
+  notify,
+  notifyError,
   useTranslationContext,
 } from "@gnu-taler/web-util/browser";
-import { Ref, VNode, h } from "preact";
+import { VNode, h } from "preact";
+import { forwardRef } from "preact/compat";
 import { useEffect, useRef, useState } from "preact/hooks";
 import { useAccessAPI } from "../hooks/access.js";
-import { notifyError } from "../hooks/notification.js";
 import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
-import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
-import { forwardRef } from "preact/compat";
+import { Amount } from "./PaytoWireTransferForm.js";
 
 const logger = new Logger("WalletWithdrawForm");
 const RefAmount = forwardRef(Amount);
@@ -40,10 +42,12 @@ export function WalletWithdrawForm({
   focus,
   limit,
   onSuccess,
+  onCancel,
 }: {
   limit: AmountJson;
   focus?: boolean;
   onSuccess: (operationId: string) => void;
+  onCancel: () => void;
 }): VNode {
   const { i18n } = useTranslationContext();
   const { createWithdrawal } = useAccessAPI();
@@ -71,136 +75,195 @@ export function WalletWithdrawForm({
             : undefined,
   });
 
-  return (
+  async function doStart() {
+    if (!parsedAmount) return;
+    try {
+      const result = await createWithdrawal({
+        amount: Amounts.stringify(parsedAmount),
+      });
+      const uri = parseWithdrawUri(result.data.taler_withdraw_uri);
+      if (!uri) {
+        return notifyError(
+          i18n.str`Server responded with an invalid  withdraw URI`,
+          i18n.str`Withdraw URI: ${result.data.taler_withdraw_uri}`);
+      } else {
+        onSuccess(uri.withdrawalOperationId);
+      }
+    } catch (error) {
+      if (error instanceof RequestError) {
+        notify(
+          buildRequestErrorMessage(i18n, error.cause, {
+            onClientError: (status) =>
+              status === HttpStatusCode.Forbidden
+                ? i18n.str`The operation was rejected due to insufficient funds`
+                : undefined,
+          }),
+        );
+      } else {
+        notifyError(
+          i18n.str`Operation failed, please report`,
+          (error instanceof Error
+            ? error.message
+            : JSON.stringify(error)) as TranslatedString
+        )
+}
+    }
+
+  }
+
+  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>Upon starting you will receive the money in your digital wallet, if you don't have one please <a class="font-semibold text-gray-500 hover:text-gray-400" href="https://taler.net/en/wallet.html">install one from here</a></i18n.Translate>.
+      </p>
+      <p class="mt-1 text-sm text-gray-500">
+        <i18n.Translate>After using your wallet you will be redirected here to confirm or cancel the operation.</i18n.Translate>
+      </p>
+    </div>
     <form
-      id="reserve-form"
-      class="pure-form"
-      name="tform"
-      onSubmit={(e) => {
-        e.preventDefault();
-      }}
+      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()
+      }}
     >
-      <p>
-        <label for="withdraw-amount">{i18n.str`Amount to withdraw:`}</label>
-        &nbsp;
-        <RefAmount
-          currency={limit.currency}
-          value={amountStr}
-          onChange={(v) => {
-            setAmountStr(v);
-          }}
-          error={errors?.amount}
-          ref={ref}
-        />
-      </p>
-      <p>
-        <div>
-          <input
-            id="select-exchange"
-            class="pure-button pure-button-primary"
-            type="submit"
-            disabled={!!errors}
-            value={i18n.str`Withdraw`}
-            onClick={async (e) => {
-              e.preventDefault();
-              if (!parsedAmount) return;
-              try {
-                const result = await createWithdrawal({
-                  amount: Amounts.stringify(parsedAmount),
-                });
-                const uri = parseWithdrawUri(result.data.taler_withdraw_uri);
-                if (!uri) {
-                  return notifyError({
-                    title: i18n.str`Server responded with an invalid  withdraw URI`,
-                    description: i18n.str`Withdraw URI: ${result.data.taler_withdraw_uri}`,
-                  });
-                } else {
-                  onSuccess(uri.withdrawalOperationId);
-                }
-              } catch (error) {
-                if (error instanceof RequestError) {
-                  notifyError(
-                    buildRequestErrorMessage(i18n, error.cause, {
-                      onClientError: (status) =>
-                        status === HttpStatusCode.Forbidden
-                          ? i18n.str`The operation was rejected due to insufficient funds`
-                          : undefined,
-                    }),
-                  );
-                } else {
-                  notifyError({
-                    title: i18n.str`Operation failed, please report`,
-                    description:
-                      error instanceof Error
-                        ? error.message
-                        : JSON.stringify(error),
-                  });
-                }
-              }
-            }}
-          />
-        </div>
-      </p>
-    </form>
-  );
-}
+      <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 for="withdraw-amount">{i18n.str`Amount`}</label>
+            <RefAmount
+              currency={limit.currency}
+              value={amountStr}
+              name="withdraw-amount"
+              onChange={(v) => {
+                setAmountStr(v);
+              }}
+              error={errors?.amount}
+              ref={ref}
+            />
+          </div>
+          <div class="sm:col-span-5">
+            <span class="isolate inline-flex rounded-md shadow-sm">
+              <button type="button"
+                class="relative               inline-flex px-3 py-2 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="relative -ml-px -mr-px inline-flex px-3 py-2 text-sm items-center              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")
+                }}
+              >
 
-export function Amount(
-  {
-    currency,
-    value,
-    error,
-    onChange,
-  }: {
-    error?: string;
-    currency: string;
-    value: string | undefined;
-    onChange?: (s: string) => void;
-  },
-  ref: Ref<HTMLInputElement>,
-): VNode {
-  return (
-    <div style={{ width: "max-content" }}>
-      <div>
-        <input
-          type="text"
-          readonly
-          class="currency-indicator"
-          size={currency.length}
-          maxLength={currency.length}
-          tabIndex={-1}
-          style={{
-            borderTopRightRadius: 0,
-            borderBottomRightRadius: 0,
-            borderRight: 0,
-          }}
-          value={currency}
-        />
-        <input
-          type="number"
-          ref={ref}
-          name="amount"
-          id="amount"
-          placeholder="0"
-          style={{
-            borderTopLeftRadius: 0,
-            borderBottomLeftRadius: 0,
-            borderLeft: 0,
-            width: 150,
-            color: "black",
-          }}
-          value={value ?? ""}
-          disabled={!onChange}
-          onInput={(e): void => {
-            if (onChange) {
-              onChange(e.currentTarget.value);
-            }
+                25.00
+              </button>
+              <button type="button"
+                class="relative -ml-px -mr-px inline-flex px-3 py-2 text-sm items-center              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="relative               inline-flex px-3 py-2 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>
+            </span>
+          </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>
-      <ShowInputErrorLabel message={error} isDirty={value !== undefined} />
-    </div>
+
+    </form>
+  </div>
   );
 }
+
+// export function Amount(
+//   {
+//     currency,
+//     value,
+//     error,
+//     onChange,
+//   }: {
+//     error?: string;
+//     currency: string;
+//     value: string | undefined;
+//     onChange?: (s: string) => void;
+//   },
+//   ref: Ref<HTMLInputElement>,
+// ): VNode {
+//   return (
+//     <div style={{ width: "max-content" }}>
+//       <div>
+//         <input
+//           type="text"
+//           readonly
+//           class="currency-indicator"
+//           size={currency.length}
+//           maxLength={currency.length}
+//           tabIndex={-1}
+//           style={{
+//             borderTopRightRadius: 0,
+//             borderBottomRightRadius: 0,
+//             borderRight: 0,
+//           }}
+//           value={currency}
+//         />
+//         <input
+//           type="number"
+//           ref={ref}
+//           name="amount"
+//           id="amount"
+//           placeholder="0"
+//           style={{
+//             borderTopLeftRadius: 0,
+//             borderBottomLeftRadius: 0,
+//             borderLeft: 0,
+//             width: 150,
+//             color: "black",
+//           }}
+//           value={value ?? ""}
+//           disabled={!onChange}
+//           onInput={(e): void => {
+//             if (onChange) {
+//               onChange(e.currentTarget.value);
+//             }
+//           }}
+//         />
+//       </div>
+//       <ShowInputErrorLabel message={error} isDirty={value !== undefined} />
+//     </div>
+//   );
+// }
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index 2fa8e51b5..28f00169d 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -15,26 +15,40 @@
  */
 
 import {
+  AmountJson,
+  Amounts,
   HttpStatusCode,
   Logger,
+  PaytoUri,
+  PaytoUriGeneric,
+  PaytoUriIBAN,
+  PaytoUriTalerBank,
+  TranslatedString,
   WithdrawUriResult,
 } from "@gnu-taler/taler-util";
 import {
   RequestError,
+  notify,
+  notifyError,
   useTranslationContext,
 } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useMemo, useState } from "preact/hooks";
 import { useAccessAnonAPI } from "../hooks/access.js";
-import { notifyError } from "../hooks/notification.js";
 import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
 import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
+import { Amount } from "./PaytoWireTransferForm.js";
 
 const logger = new Logger("WithdrawalConfirmationQuestion");
 
 interface Props {
   onAborted: () => void;
   withdrawUri: WithdrawUriResult;
+  details: {
+    account: PaytoUri,
+    reserve: string,
+    amount: AmountJson,
+  }
 }
 /**
  * Additional authentication required to complete the operation.
@@ -42,6 +56,7 @@ interface Props {
  */
 export function WithdrawalConfirmationQuestion({
   onAborted,
+  details,
   withdrawUri,
 }: Props): VNode {
   const { i18n } = useTranslationContext();
@@ -60,135 +75,257 @@ export function WithdrawalConfirmationQuestion({
     answer: !captchaAnswer
       ? i18n.str`Answer the question before continue`
       : Number.isNaN(answer)
-      ? i18n.str`The answer should be a number`
-      : answer !== captchaNumbers.a + captchaNumbers.b
-      ? i18n.str`The answer "${answer}" to "${captchaNumbers.a} + ${captchaNumbers.b}" is wrong.`
-      : undefined,
+        ? i18n.str`The answer should be a number`
+        : answer !== captchaNumbers.a + captchaNumbers.b
+          ? i18n.str`The answer "${answer}" to "${captchaNumbers.a} + ${captchaNumbers.b}" is wrong.`
+          : undefined,
   });
+
+  async function doTransfer() {
+    try {
+      await confirmWithdrawal(
+        withdrawUri.withdrawalOperationId,
+      );
+    } catch (error) {
+      if (error instanceof RequestError) {
+        notify(
+          buildRequestErrorMessage(i18n, error.cause, {
+            onClientError: (status) =>
+              status === HttpStatusCode.Conflict
+                ? i18n.str`The withdrawal has been aborted previously and can't be confirmed`
+                : status === HttpStatusCode.UnprocessableEntity
+                  ? i18n.str`The withdraw operation cannot be confirmed because no exchange and reserve public key selection happened before`
+                  : undefined,
+          }),
+        );
+      } else {
+        notifyError(
+          i18n.str`Operation failed, please report`,
+          (error instanceof Error
+            ? error.message
+            : JSON.stringify(error)) as TranslatedString
+        )
+      }
+    }
+  }
+
+  async function doCancel() {
+    try {
+      await abortWithdrawal(withdrawUri.withdrawalOperationId);
+      onAborted();
+    } catch (error) {
+      if (error instanceof RequestError) {
+        notify(
+          buildRequestErrorMessage(i18n, error.cause, {
+            onClientError: (status) =>
+              status === HttpStatusCode.Conflict
+                ? i18n.str`The reserve operation has been confirmed previously and can't be aborted`
+                : undefined,
+          }),
+        );
+      } else {
+        notifyError(
+          i18n.str`Operation failed, please report`,
+          (error instanceof Error
+            ? error.message
+            : JSON.stringify(error)) as TranslatedString
+        )
+      }
+    }
+  }
+
   return (
     <Fragment>
-      <h1 class="nav">{i18n.str`Confirm Withdrawal`}</h1>
-      <article>
-        <div class="challenge-div">
-          <form
-            class="challenge-form"
-            noValidate
-            onSubmit={(e) => {
-              e.preventDefault();
-            }}
-            autoCapitalize="none"
-            autoCorrect="off"
-          >
-            <div class="pure-form" id="captcha" name="capcha-form">
-              <h2>{i18n.str`Authorize withdrawal by solving challenge`}</h2>
-              <p>
-                <label for="answer">
-                  {i18n.str`What is`}&nbsp;
-                  <em>
-                    {captchaNumbers.a}&nbsp;+&nbsp;{captchaNumbers.b}
-                  </em>
-                  ?&nbsp;
-                </label>
-                &nbsp;
-                <input
-                  name="answer"
-                  id="answer"
-                  value={captchaAnswer ?? ""}
-                  type="text"
-                  autoFocus
-                  required
-                  onInput={(e): void => {
-                    setCaptchaAnswer(e.currentTarget.value);
-                  }}
-                />
-                <ShowInputErrorLabel
-                  message={errors?.answer}
-                  isDirty={captchaAnswer !== undefined}
-                />
-              </p>
-              <p>
-                <button
-                  type="submit"
-                  class="pure-button pure-button-primary btn-confirm"
-                  disabled={!!errors}
-                  onClick={async (e) => {
-                    e.preventDefault();
-                    try {
-                      await confirmWithdrawal(
-                        withdrawUri.withdrawalOperationId,
-                      );
-                    } catch (error) {
-                      if (error instanceof RequestError) {
-                        notifyError(
-                          buildRequestErrorMessage(i18n, error.cause, {
-                            onClientError: (status) =>
-                              status === HttpStatusCode.Conflict
-                                ? i18n.str`The withdrawal has been aborted previously and can't be confirmed`
-                                : status === HttpStatusCode.UnprocessableEntity
-                                ? i18n.str`The withdraw operation cannot be confirmed because no exchange and reserve public key selection happened before`
-                                : undefined,
-                          }),
-                        );
-                      } else {
-                        notifyError({
-                          title: i18n.str`Operation failed, please report`,
-                          description:
-                            error instanceof Error
-                              ? error.message
-                              : JSON.stringify(error),
-                        });
-                      }
-                    }
-                  }}
-                >
-                  {i18n.str`Confirm`}
-                </button>
-                &nbsp;
-                <button
-                  class="pure-button pure-button-secondary btn-cancel"
-                  onClick={async (e) => {
-                    e.preventDefault();
-                    try {
-                      await abortWithdrawal(withdrawUri.withdrawalOperationId);
-                      onAborted();
-                    } catch (error) {
-                      if (error instanceof RequestError) {
-                        notifyError(
-                          buildRequestErrorMessage(i18n, error.cause, {
-                            onClientError: (status) =>
-                              status === HttpStatusCode.Conflict
-                                ? i18n.str`The reserve operation has been confirmed previously and can't be aborted`
-                                : undefined,
-                          }),
-                        );
-                      } else {
-                        notifyError({
-                          title: i18n.str`Operation failed, please report`,
-                          description:
-                            error instanceof Error
-                              ? error.message
-                              : JSON.stringify(error),
-                        });
+      <div class="bg-white shadow 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>Confirm the withdrawal operation</i18n.Translate>
+          </h3>
+          <div class="mt-2 max-w-xl text-sm text-gray-500">
+            <div class="px-4 mt-4 ">
+              <div class="w-full">
+                <div class="px-4 sm:px-0">
+                  <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">Exchange account</dt>
+                              <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{p.iban}</dd>
+                            </div>
+                            {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">Exchange name</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">Exchange account</dt>
+                              <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{p.account}</dd>
+                            </div>
+                            {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">Exchange name</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">Exchange account</dt>
+                            <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{details.account.targetPath}</dd>
+                          </div>
+
                       }
-                    }
-                  }}
-                >
-                  {i18n.str`Cancel`}
-                </button>
-              </p>
+                    })()}
+                    <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">Withdrawal identification</dt>
+                      <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{details.reserve}</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">Amount</dt>
+                      <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{Amounts.stringifyValue(details.amount)}</dd>
+                    </div>
+                  </dl>
+                </div>
+              </div>
+
+            </div>
+            <div class="px-4 mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-3 sm:gap-x-3">
+
+              <label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-noneborder-indigo-600 ring-2 ring-indigo-600"}>
+                <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" />
+                <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 ">
+                      <i18n.Translate>challenge response test</i18n.Translate>
+                    </span>
+                  </span>
+                </span>
+                <svg class="h-5 w-5 text-indigo-600" 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>
+
+
+              <label class="relative flex cursor-pointer rounded-lg border bg-gray-100  p-4 shadow-sm focus:outline-none 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" />
+                <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">
+                      <i18n.Translate>using SMS</i18n.Translate>
+                    </span>
+                    <span id="project-type-1-description-0" class="mt-1 flex items-center text-sm text-gray-500">
+                      <i18n.Translate>not available</i18n.Translate>
+                    </span>
+                  </span>
+                </span>
+                <svg class="h-5 w-5 text-indigo-600 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>
+
+              <label class="relative flex cursor-pointer rounded-lg border bg-gray-100 p-4 shadow-sm focus:outline-none 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" />
+                <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">
+                      <i18n.Translate>one time password</i18n.Translate>
+                    </span>
+                    <span id="project-type-1-description-0" class="mt-1 flex items-center text-sm text-gray-500">
+                      <i18n.Translate>not available</i18n.Translate>
+                    </span>
+                  </span>
+                </span>
+                <svg class="h-5 w-5 text-indigo-600 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 class="mt-3 text-sm leading-6">
+
+            <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>Answer the next question to authorize the wire transfer</i18n.Translate></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()
+                }}
+              >
+                <div class="px-4 py-6 sm:p-8">
+                  <label for="withdraw-amount">{i18n.str`What is`}&nbsp;
+                    <em>
+                      {captchaNumbers.a}&nbsp;+&nbsp;{captchaNumbers.b}
+                    </em>
+                    ?
+                  </label>
+                  <div class="mt-2">
+                    <div class="relative rounded-md shadow-sm">
+                      <input
+                        type="text"
+                        // class="block w-full rounded-md border-0 py-1.5 pl-16 text-gray-900 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"
+                        aria-describedby="answer"
+                        autoFocus
+                        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={username ?? ""}
+                        required
+
+                        name="answer"
+                        id="answer"
+                        autocomplete="off"
+                      // value={value ?? ""}
+                      // disabled={!onChange}
+                      // onInput={(e): void => {
+                      //   if (onChange) {
+                      //     onChange(e.currentTarget.value);
+                      //   }
+                      // }}
+                      />
+                    </div>
+                    <ShowInputErrorLabel message={errors?.answer} isDirty={false} />
+                  </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>Transfer</i18n.Translate>
+                  </button>
+                </div>
+
+              </form>
             </div>
-          </form>
-          <div class="hint">
-            <p>
-              <i18n.Translate>
-                A this point, a <b>real</b> bank would ask for an additional
-                authentication proof (PIN/TAN, one time password, ..), instead
-                of a simple calculation.
-              </i18n.Translate>
-            </p>
           </div>
         </div>
-      </article>
+      </div>
+
     </Fragment>
   );
 }
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 80fdac3c8..3b983c2d4 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -15,15 +15,16 @@
  */
 
 import {
+  Amounts,
   HttpStatusCode,
   Logger,
   WithdrawUriResult,
+  parsePaytoUri,
 } from "@gnu-taler/taler-util";
-import { ErrorType, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { ErrorType, notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { Loading } from "../components/Loading.js";
 import { useWithdrawalDetails } from "../hooks/access.js";
-import { notifyInfo } from "../hooks/notification.js";
 import { useSettings } from "../hooks/settings.js";
 import { handleNotOkResult } from "./HomePage.js";
 import { QrCodeSection } from "./QrCodeSection.js";
@@ -127,6 +128,19 @@ export function WithdrawalQRCode({
       </section>
     </section>
   }
+  if (!data.selected_reserve_pub) {
+    return <div>
+      the exchange is selcted but no reserve pub
+    </div>
+  }
+
+  const account = !data.selected_exchange_account ? undefined : parsePaytoUri(data.selected_exchange_account)
+
+  if (!account) {
+    return <div>
+      the exchange is selcted but no account 
+    </div>
+  }
 
   if (!data.selection_done) {
     return (
@@ -144,6 +158,11 @@ export function WithdrawalQRCode({
   return (
     <WithdrawalConfirmationQuestion
       withdrawUri={withdrawUri}
+      details={{
+        account,
+        reserve: data.selected_reserve_pub,
+        amount: Amounts.parseOrThrow("usd:10.00")
+      }}
       onAborted={() => {
         notifyInfo(i18n.str`Operation canceled`);
         clearCurrentWithdrawal()
diff --git a/packages/demobank-ui/src/pages/rnd.ts b/packages/demobank-ui/src/pages/rnd.ts
new file mode 100644
index 000000000..8c9bae875
--- /dev/null
+++ b/packages/demobank-ui/src/pages/rnd.ts
@@ -0,0 +1,2890 @@
+import { createEddsaKeyPair, encodeCrock, getRandomBytes } from "@gnu-taler/taler-util"
+
+
+const noun = [
+    "people",
+    "history",
+    "way",
+    "art",
+    "world",
+    "information",
+    "map",
+    "two",
+    "family",
+    "government",
+    "health",
+    "system",
+    "computer",
+    "meat",
+    "year",
+    "thanks",
+    "music",
+    "person",
+    "reading",
+    "method",
+    "data",
+    "food",
+    "understanding",
+    "theory",
+    "law",
+    "bird",
+    "literature",
+    "problem",
+    "software",
+    "control",
+    "knowledge",
+    "power",
+    "ability",
+    "economics",
+    "love",
+    "internet",
+    "television",
+    "science",
+    "library",
+    "nature",
+    "fact",
+    "product",
+    "idea",
+    "temperature",
+    "investment",
+    "area",
+    "society",
+    "activity",
+    "story",
+    "industry",
+    "media",
+    "thing",
+    "oven",
+    "community",
+    "definition",
+    "safety",
+    "quality",
+    "development",
+    "language",
+    "management",
+    "player",
+    "variety",
+    "video",
+    "week",
+    "security",
+    "country",
+    "exam",
+    "movie",
+    "organization",
+    "equipment",
+    "physics",
+    "analysis",
+    "policy",
+    "series",
+    "thought",
+    "basis",
+    "boyfriend",
+    "direction",
+    "strategy",
+    "technology",
+    "army",
+    "camera",
+    "freedom",
+    "paper",
+    "environment",
+    "child",
+    "instance",
+    "month",
+    "truth",
+    "marketing",
+    "university",
+    "writing",
+    "article",
+    "department",
+    "difference",
+    "goal",
+    "news",
+    "audience",
+    "fishing",
+    "growth",
+    "income",
+    "marriage",
+    "user",
+    "combination",
+    "failure",
+    "meaning",
+    "medicine",
+    "philosophy",
+    "teacher",
+    "communication",
+    "night",
+    "chemistry",
+    "disease",
+    "disk",
+    "energy",
+    "nation",
+    "road",
+    "role",
+    "soup",
+    "advertising",
+    "location",
+    "success",
+    "addition",
+    "apartment",
+    "education",
+    "math",
+    "moment",
+    "painting",
+    "politics",
+    "attention",
+    "decision",
+    "event",
+    "property",
+    "shopping",
+    "student",
+    "wood",
+    "competition",
+    "distribution",
+    "entertainment",
+    "office",
+    "population",
+    "president",
+    "unit",
+    "category",
+    "cigarette",
+    "context",
+    "introduction",
+    "opportunity",
+    "performance",
+    "driver",
+    "flight",
+    "length",
+    "magazine",
+    "newspaper",
+    "relationship",
+    "teaching",
+    "cell",
+    "dealer",
+    "finding",
+    "lake",
+    "member",
+    "message",
+    "phone",
+    "scene",
+    "appearance",
+    "association",
+    "concept",
+    "customer",
+    "death",
+    "discussion",
+    "housing",
+    "inflation",
+    "insurance",
+    "mood",
+    "woman",
+    "advice",
+    "blood",
+    "effort",
+    "expression",
+    "importance",
+    "opinion",
+    "payment",
+    "reality",
+    "responsibility",
+    "situation",
+    "skill",
+    "statement",
+    "wealth",
+    "application",
+    "city",
+    "county",
+    "depth",
+    "estate",
+    "foundation",
+    "grandmother",
+    "heart",
+    "perspective",
+    "photo",
+    "recipe",
+    "studio",
+    "topic",
+    "collection",
+    "depression",
+    "imagination",
+    "passion",
+    "percentage",
+    "resource",
+    "setting",
+    "ad",
+    "agency",
+    "college",
+    "connection",
+    "criticism",
+    "debt",
+    "description",
+    "memory",
+    "patience",
+    "secretary",
+    "solution",
+    "administration",
+    "aspect",
+    "attitude",
+    "director",
+    "personality",
+    "psychology",
+    "recommendation",
+    "response",
+    "selection",
+    "storage",
+    "version",
+    "alcohol",
+    "argument",
+    "complaint",
+    "contract",
+    "emphasis",
+    "highway",
+    "loss",
+    "membership",
+    "possession",
+    "preparation",
+    "steak",
+    "union",
+    "agreement",
+    "cancer",
+    "currency",
+    "employment",
+    "engineering",
+    "entry",
+    "interaction",
+    "mixture",
+    "preference",
+    "region",
+    "republic",
+    "tradition",
+    "virus",
+    "actor",
+    "classroom",
+    "delivery",
+    "device",
+    "difficulty",
+    "drama",
+    "election",
+    "engine",
+    "football",
+    "guidance",
+    "hotel",
+    "owner",
+    "priority",
+    "protection",
+    "suggestion",
+    "tension",
+    "variation",
+    "anxiety",
+    "atmosphere",
+    "awareness",
+    "bath",
+    "bread",
+    "candidate",
+    "climate",
+    "comparison",
+    "confusion",
+    "construction",
+    "elevator",
+    "emotion",
+    "employee",
+    "employer",
+    "guest",
+    "height",
+    "leadership",
+    "mall",
+    "manager",
+    "operation",
+    "recording",
+    "sample",
+    "transportation",
+    "charity",
+    "cousin",
+    "disaster",
+    "editor",
+    "efficiency",
+    "excitement",
+    "extent",
+    "feedback",
+    "guitar",
+    "homework",
+    "leader",
+    "mom",
+    "outcome",
+    "permission",
+    "presentation",
+    "promotion",
+    "reflection",
+    "refrigerator",
+    "resolution",
+    "revenue",
+    "session",
+    "singer",
+    "tennis",
+    "basket",
+    "bonus",
+    "cabinet",
+    "childhood",
+    "church",
+    "clothes",
+    "coffee",
+    "dinner",
+    "drawing",
+    "hair",
+    "hearing",
+    "initiative",
+    "judgment",
+    "lab",
+    "measurement",
+    "mode",
+    "mud",
+    "orange",
+    "poetry",
+    "police",
+    "possibility",
+    "procedure",
+    "queen",
+    "ratio",
+    "relation",
+    "restaurant",
+    "satisfaction",
+    "sector",
+    "signature",
+    "significance",
+    "song",
+    "tooth",
+    "town",
+    "vehicle",
+    "volume",
+    "wife",
+    "accident",
+    "airport",
+    "appointment",
+    "arrival",
+    "assumption",
+    "baseball",
+    "chapter",
+    "committee",
+    "conversation",
+    "database",
+    "enthusiasm",
+    "error",
+    "explanation",
+    "farmer",
+    "gate",
+    "girl",
+    "hall",
+    "historian",
+    "hospital",
+    "injury",
+    "instruction",
+    "maintenance",
+    "manufacturer",
+    "meal",
+    "perception",
+    "pie",
+    "poem",
+    "presence",
+    "proposal",
+    "reception",
+    "replacement",
+    "revolution",
+    "river",
+    "son",
+    "speech",
+    "tea",
+    "village",
+    "warning",
+    "winner",
+    "worker",
+    "writer",
+    "assistance",
+    "breath",
+    "buyer",
+    "chest",
+    "chocolate",
+    "conclusion",
+    "contribution",
+    "cookie",
+    "courage",
+    "dad",
+    "desk",
+    "drawer",
+    "establishment",
+    "examination",
+    "garbage",
+    "grocery",
+    "honey",
+    "impression",
+    "improvement",
+    "independence",
+    "insect",
+    "inspection",
+    "inspector",
+    "king",
+    "ladder",
+    "menu",
+    "penalty",
+    "piano",
+    "potato",
+    "profession",
+    "professor",
+    "quantity",
+    "reaction",
+    "requirement",
+    "salad",
+    "sister",
+    "supermarket",
+    "tongue",
+    "weakness",
+    "wedding",
+    "affair",
+    "ambition",
+    "analyst",
+    "apple",
+    "assignment",
+    "assistant",
+    "bathroom",
+    "bedroom",
+    "beer",
+    "birthday",
+    "celebration",
+    "championship",
+    "cheek",
+    "client",
+    "consequence",
+    "departure",
+    "diamond",
+    "dirt",
+    "ear",
+    "fortune",
+    "friendship",
+    "funeral",
+    "gene",
+    "girlfriend",
+    "hat",
+    "indication",
+    "intention",
+    "lady",
+    "midnight",
+    "negotiation",
+    "obligation",
+    "passenger",
+    "pizza",
+    "platform",
+    "poet",
+    "pollution",
+    "recognition",
+    "reputation",
+    "shirt",
+    "sir",
+    "speaker",
+    "stranger",
+    "surgery",
+    "sympathy",
+    "tale",
+    "throat",
+    "trainer",
+    "uncle",
+    "youth",
+    "time",
+    "work",
+    "film",
+    "water",
+    "money",
+    "example",
+    "while",
+    "business",
+    "study",
+    "game",
+    "life",
+    "form",
+    "air",
+    "day",
+    "place",
+    "number",
+    "part",
+    "field",
+    "fish",
+    "back",
+    "process",
+    "heat",
+    "hand",
+    "experience",
+    "job",
+    "book",
+    "end",
+    "point",
+    "type",
+    "home",
+    "economy",
+    "value",
+    "body",
+    "market",
+    "guide",
+    "interest",
+    "state",
+    "radio",
+    "course",
+    "company",
+    "price",
+    "size",
+    "card",
+    "list",
+    "mind",
+    "trade",
+    "line",
+    "care",
+    "group",
+    "risk",
+    "word",
+    "fat",
+    "force",
+    "key",
+    "light",
+    "training",
+    "name",
+    "school",
+    "top",
+    "amount",
+    "level",
+    "order",
+    "practice",
+    "research",
+    "sense",
+    "service",
+    "piece",
+    "web",
+    "boss",
+    "sport",
+    "fun",
+    "house",
+    "page",
+    "term",
+    "test",
+    "answer",
+    "sound",
+    "focus",
+    "matter",
+    "kind",
+    "soil",
+    "board",
+    "oil",
+    "picture",
+    "access",
+    "garden",
+    "range",
+    "rate",
+    "reason",
+    "future",
+    "site",
+    "demand",
+    "exercise",
+    "image",
+    "case",
+    "cause",
+    "coast",
+    "action",
+    "age",
+    "bad",
+    "boat",
+    "record",
+    "result",
+    "section",
+    "building",
+    "mouse",
+    "cash",
+    "class",
+    "nothing",
+    "period",
+    "plan",
+    "store",
+    "tax",
+    "side",
+    "subject",
+    "space",
+    "rule",
+    "stock",
+    "weather",
+    "chance",
+    "figure",
+    "man",
+    "model",
+    "source",
+    "beginning",
+    "earth",
+    "program",
+    "chicken",
+    "design",
+    "feature",
+    "head",
+    "material",
+    "purpose",
+    "question",
+    "rock",
+    "salt",
+    "act",
+    "birth",
+    "car",
+    "dog",
+    "object",
+    "scale",
+    "sun",
+    "note",
+    "profit",
+    "rent",
+    "speed",
+    "style",
+    "war",
+    "bank",
+    "craft",
+    "half",
+    "inside",
+    "outside",
+    "standard",
+    "bus",
+    "exchange",
+    "eye",
+    "fire",
+    "position",
+    "pressure",
+    "stress",
+    "advantage",
+    "benefit",
+    "box",
+    "frame",
+    "issue",
+    "step",
+    "cycle",
+    "face",
+    "item",
+    "metal",
+    "paint",
+    "review",
+    "room",
+    "screen",
+    "structure",
+    "view",
+    "account",
+    "ball",
+    "discipline",
+    "medium",
+    "share",
+    "balance",
+    "bit",
+    "black",
+    "bottom",
+    "choice",
+    "gift",
+    "impact",
+    "machine",
+    "shape",
+    "tool",
+    "wind",
+    "address",
+    "average",
+    "career",
+    "culture",
+    "morning",
+    "pot",
+    "sign",
+    "table",
+    "task",
+    "condition",
+    "contact",
+    "credit",
+    "egg",
+    "hope",
+    "ice",
+    "network",
+    "north",
+    "square",
+    "attempt",
+    "date",
+    "effect",
+    "link",
+    "post",
+    "star",
+    "voice",
+    "capital",
+    "challenge",
+    "friend",
+    "self",
+    "shot",
+    "brush",
+    "couple",
+    "debate",
+    "exit",
+    "front",
+    "function",
+    "lack",
+    "living",
+    "plant",
+    "plastic",
+    "spot",
+    "summer",
+    "taste",
+    "theme",
+    "track",
+    "wing",
+    "brain",
+    "button",
+    "click",
+    "desire",
+    "foot",
+    "gas",
+    "influence",
+    "notice",
+    "rain",
+    "wall",
+    "base",
+    "damage",
+    "distance",
+    "feeling",
+    "pair",
+    "savings",
+    "staff",
+    "sugar",
+    "target",
+    "text",
+    "animal",
+    "author",
+    "budget",
+    "discount",
+    "file",
+    "ground",
+    "lesson",
+    "minute",
+    "officer",
+    "phase",
+    "reference",
+    "register",
+    "sky",
+    "stage",
+    "stick",
+    "title",
+    "trouble",
+    "bowl",
+    "bridge",
+    "campaign",
+    "character",
+    "club",
+    "edge",
+    "evidence",
+    "fan",
+    "letter",
+    "lock",
+    "maximum",
+    "novel",
+    "option",
+    "pack",
+    "park",
+    "plenty",
+    "quarter",
+    "skin",
+    "sort",
+    "weight",
+    "baby",
+    "background",
+    "carry",
+    "dish",
+    "factor",
+    "fruit",
+    "glass",
+    "joint",
+    "master",
+    "muscle",
+    "red",
+    "strength",
+    "traffic",
+    "trip",
+    "vegetable",
+    "appeal",
+    "chart",
+    "gear",
+    "ideal",
+    "kitchen",
+    "land",
+    "log",
+    "mother",
+    "net",
+    "party",
+    "principle",
+    "relative",
+    "sale",
+    "season",
+    "signal",
+    "spirit",
+    "street",
+    "tree",
+    "wave",
+    "belt",
+    "bench",
+    "commission",
+    "copy",
+    "drop",
+    "minimum",
+    "path",
+    "progress",
+    "project",
+    "sea",
+    "south",
+    "status",
+    "stuff",
+    "ticket",
+    "tour",
+    "angle",
+    "blue",
+    "breakfast",
+    "confidence",
+    "daughter",
+    "degree",
+    "doctor",
+    "dot",
+    "dream",
+    "duty",
+    "essay",
+    "father",
+    "fee",
+    "finance",
+    "hour",
+    "juice",
+    "limit",
+    "luck",
+    "milk",
+    "mouth",
+    "peace",
+    "pipe",
+    "seat",
+    "stable",
+    "storm",
+    "substance",
+    "team",
+    "trick",
+    "afternoon",
+    "bat",
+    "beach",
+    "blank",
+    "catch",
+    "chain",
+    "consideration",
+    "cream",
+    "crew",
+    "detail",
+    "gold",
+    "interview",
+    "kid",
+    "mark",
+    "match",
+    "mission",
+    "pain",
+    "pleasure",
+    "score",
+    "screw",
+    "sex",
+    "shop",
+    "shower",
+    "suit",
+    "tone",
+    "window",
+    "agent",
+    "band",
+    "block",
+    "bone",
+    "calendar",
+    "cap",
+    "coat",
+    "contest",
+    "corner",
+    "court",
+    "cup",
+    "district",
+    "door",
+    "east",
+    "finger",
+    "garage",
+    "guarantee",
+    "hole",
+    "hook",
+    "implement",
+    "layer",
+    "lecture",
+    "lie",
+    "manner",
+    "meeting",
+    "nose",
+    "parking",
+    "partner",
+    "profile",
+    "respect",
+    "rice",
+    "routine",
+    "schedule",
+    "swimming",
+    "telephone",
+    "tip",
+    "winter",
+    "airline",
+    "bag",
+    "battle",
+    "bed",
+    "bill",
+    "bother",
+    "cake",
+    "code",
+    "curve",
+    "designer",
+    "dimension",
+    "dress",
+    "ease",
+    "emergency",
+    "evening",
+    "extension",
+    "farm",
+    "fight",
+    "gap",
+    "grade",
+    "holiday",
+    "horror",
+    "horse",
+    "host",
+    "husband",
+    "loan",
+    "mistake",
+    "mountain",
+    "nail",
+    "noise",
+    "occasion",
+    "package",
+    "patient",
+    "pause",
+    "phrase",
+    "proof",
+    "race",
+    "relief",
+    "sand",
+    "sentence",
+    "shoulder",
+    "smoke",
+    "stomach",
+    "string",
+    "tourist",
+    "towel",
+    "vacation",
+    "west",
+    "wheel",
+    "wine",
+    "arm",
+    "aside",
+    "associate",
+    "bet",
+    "blow",
+    "border",
+    "branch",
+    "breast",
+    "brother",
+    "buddy",
+    "bunch",
+    "chip",
+    "coach",
+    "cross",
+    "document",
+    "draft",
+    "dust",
+    "expert",
+    "floor",
+    "god",
+    "golf",
+    "habit",
+    "iron",
+    "judge",
+    "knife",
+    "landscape",
+    "league",
+    "mail",
+    "mess",
+    "native",
+    "opening",
+    "parent",
+    "pattern",
+    "pin",
+    "pool",
+    "pound",
+    "request",
+    "salary",
+    "shame",
+    "shelter",
+    "shoe",
+    "silver",
+    "tackle",
+    "tank",
+    "trust",
+    "assist",
+    "bake",
+    "bar",
+    "bell",
+    "bike",
+    "blame",
+    "boy",
+    "brick",
+    "chair",
+    "closet",
+    "clue",
+    "collar",
+    "comment",
+    "conference",
+    "devil",
+    "diet",
+    "fear",
+    "fuel",
+    "glove",
+    "jacket",
+    "lunch",
+    "monitor",
+    "mortgage",
+    "nurse",
+    "pace",
+    "panic",
+    "peak",
+    "plane",
+    "reward",
+    "row",
+    "sandwich",
+    "shock",
+    "spite",
+    "spray",
+    "surprise",
+    "till",
+    "transition",
+    "weekend",
+    "welcome",
+    "yard",
+    "alarm",
+    "bend",
+    "bicycle",
+    "bite",
+    "blind",
+    "bottle",
+    "cable",
+    "candle",
+    "clerk",
+    "cloud",
+    "concert",
+    "counter",
+    "flower",
+    "grandfather",
+    "harm",
+    "knee",
+    "lawyer",
+    "leather",
+    "load",
+    "mirror",
+    "neck",
+    "pension",
+    "plate",
+    "purple",
+    "ruin",
+    "ship",
+    "skirt",
+    "slice",
+    "snow",
+    "specialist",
+    "stroke",
+    "switch",
+    "trash",
+    "tune",
+    "zone",
+    "anger",
+    "award",
+    "bid",
+    "bitter",
+    "boot",
+    "bug",
+    "camp",
+    "candy",
+    "carpet",
+    "cat",
+    "champion",
+    "channel",
+    "clock",
+    "comfort",
+    "cow",
+    "crack",
+    "engineer",
+    "entrance",
+    "fault",
+    "grass",
+    "guy",
+    "hell",
+    "highlight",
+    "incident",
+    "island",
+    "joke",
+    "jury",
+    "leg",
+    "lip",
+    "mate",
+    "motor",
+    "nerve",
+    "passage",
+    "pen",
+    "pride",
+    "priest",
+    "prize",
+    "promise",
+    "resident",
+    "resort",
+    "ring",
+    "roof",
+    "rope",
+    "sail",
+    "scheme",
+    "script",
+    "sock",
+    "station",
+    "toe",
+    "tower",
+    "truck",
+    "witness",
+    "a",
+    "you",
+    "it",
+    "can",
+    "will",
+    "if",
+    "one",
+    "many",
+    "most",
+    "other",
+    "use",
+    "make",
+    "good",
+    "look",
+    "help",
+    "go",
+    "great",
+    "being",
+    "few",
+    "might",
+    "still",
+    "public",
+    "read",
+    "keep",
+    "start",
+    "give",
+    "human",
+    "local",
+    "general",
+    "she",
+    "specific",
+    "long",
+    "play",
+    "feel",
+    "high",
+    "tonight",
+    "put",
+    "common",
+    "set",
+    "change",
+    "simple",
+    "past",
+    "big",
+    "possible",
+    "particular",
+    "today",
+    "major",
+    "personal",
+    "current",
+    "national",
+    "cut",
+    "natural",
+    "physical",
+    "show",
+    "try",
+    "check",
+    "second",
+    "call",
+    "move",
+    "pay",
+    "let",
+    "increase",
+    "single",
+    "individual",
+    "turn",
+    "ask",
+    "buy",
+    "guard",
+    "hold",
+    "main",
+    "offer",
+    "potential",
+    "professional",
+    "international",
+    "travel",
+    "cook",
+    "alternative",
+    "following",
+    "special",
+    "working",
+    "whole",
+    "dance",
+    "excuse",
+    "cold",
+    "commercial",
+    "low",
+    "purchase",
+    "deal",
+    "primary",
+    "worth",
+    "fall",
+    "necessary",
+    "positive",
+    "produce",
+    "search",
+    "present",
+    "spend",
+    "talk",
+    "creative",
+    "tell",
+    "cost",
+    "drive",
+    "green",
+    "support",
+    "glad",
+    "remove",
+    "return",
+    "run",
+    "complex",
+    "due",
+    "effective",
+    "middle",
+    "regular",
+    "reserve",
+    "independent",
+    "leave",
+    "original",
+    "reach",
+    "rest",
+    "serve",
+    "watch",
+    "beautiful",
+    "charge",
+    "active",
+    "break",
+    "negative",
+    "safe",
+    "stay",
+    "visit",
+    "visual",
+    "affect",
+    "cover",
+    "report",
+    "rise",
+    "walk",
+    "white",
+    "beyond",
+    "junior",
+    "pick",
+    "unique",
+    "anything",
+    "classic",
+    "final",
+    "lift",
+    "mix",
+    "private",
+    "stop",
+    "teach",
+    "western",
+    "concern",
+    "familiar",
+    "fly",
+    "official",
+    "broad",
+    "comfortable",
+    "gain",
+    "maybe",
+    "rich",
+    "save",
+    "stand",
+    "young",
+    "fail",
+    "heavy",
+    "hello",
+    "lead",
+    "listen",
+    "valuable",
+    "worry",
+    "handle",
+    "leading",
+    "meet",
+    "release",
+    "sell",
+    "finish",
+    "normal",
+    "press",
+    "ride",
+    "secret",
+    "spread",
+    "spring",
+    "tough",
+    "wait",
+    "brown",
+    "deep",
+    "display",
+    "flow",
+    "hit",
+    "objective",
+    "shoot",
+    "touch",
+    "cancel",
+    "chemical",
+    "cry",
+    "dump",
+    "extreme",
+    "push",
+    "conflict",
+    "eat",
+    "fill",
+    "formal",
+    "jump",
+    "kick",
+    "opposite",
+    "pass",
+    "pitch",
+    "remote",
+    "total",
+    "treat",
+    "vast",
+    "abuse",
+    "beat",
+    "burn",
+    "deposit",
+    "print",
+    "raise",
+    "sleep",
+    "somewhere",
+    "advance",
+    "anywhere",
+    "consist",
+    "dark",
+    "double",
+    "draw",
+    "equal",
+    "fix",
+    "hire",
+    "internal",
+    "join",
+    "kill",
+    "sensitive",
+    "tap",
+    "win",
+    "attack",
+    "claim",
+    "constant",
+    "drag",
+    "drink",
+    "guess",
+    "minor",
+    "pull",
+    "raw",
+    "soft",
+    "solid",
+    "wear",
+    "weird",
+    "wonder",
+    "annual",
+    "count",
+    "dead",
+    "doubt",
+    "feed",
+    "forever",
+    "impress",
+    "nobody",
+    "repeat",
+    "round",
+    "sing",
+    "slide",
+    "strip",
+    "whereas",
+    "wish",
+    "combine",
+    "command",
+    "dig",
+    "divide",
+    "equivalent",
+    "hang",
+    "hunt",
+    "initial",
+    "march",
+    "mention",
+    "smell",
+    "spiritual",
+    "survey",
+    "tie",
+    "adult",
+    "brief",
+    "crazy",
+    "escape",
+    "gather",
+    "hate",
+    "prior",
+    "repair",
+    "rough",
+    "sad",
+    "scratch",
+    "sick",
+    "strike",
+    "employ",
+    "external",
+    "hurt",
+    "illegal",
+    "laugh",
+    "lay",
+    "mobile",
+    "nasty",
+    "ordinary",
+    "respond",
+    "royal",
+    "senior",
+    "split",
+    "strain",
+    "struggle",
+    "swim",
+    "train",
+    "upper",
+    "wash",
+    "yellow",
+    "convert",
+    "crash",
+    "dependent",
+    "fold",
+    "funny",
+    "grab",
+    "hide",
+    "miss",
+    "permit",
+    "quote",
+    "recover",
+    "resolve",
+    "roll",
+    "sink",
+    "slip",
+    "spare",
+    "suspect",
+    "sweet",
+    "swing",
+    "twist",
+    "upstairs",
+    "usual",
+    "abroad",
+    "brave",
+    "calm",
+    "concentrate",
+    "estimate",
+    "grand",
+    "male",
+    "mine",
+    "prompt",
+    "quiet",
+    "refuse",
+    "regret",
+    "reveal",
+    "rush",
+    "shake",
+    "shift",
+    "shine",
+    "steal",
+    "suck",
+    "surround",
+    "anybody",
+    "bear",
+    "brilliant",
+    "dare",
+    "dear",
+    "delay",
+    "drunk",
+    "female",
+    "hurry",
+    "inevitable",
+    "invite",
+    "kiss",
+    "neat",
+    "pop",
+    "punch",
+    "quit",
+    "reply",
+    "representative",
+    "resist",
+    "rip",
+    "rub",
+    "silly",
+    "smile",
+    "spell",
+    "stretch",
+    "stupid",
+    "tear",
+    "temporary",
+    "tomorrow",
+    "wake",
+    "wrap",
+    "yesterday"
+]
+
+const adj = [
+    "abandoned",
+    "able",
+    "absolute",
+    "adorable",
+    "adventurous",
+    "academic",
+    "acceptable",
+    "acclaimed",
+    "accomplished",
+    "accurate",
+    "aching",
+    "acidic",
+    "acrobatic",
+    "active",
+    "actual",
+    "adept",
+    "admirable",
+    "admired",
+    "adolescent",
+    "adorable",
+    "adored",
+    "advanced",
+    "afraid",
+    "affectionate",
+    "aged",
+    "aggravating",
+    "aggressive",
+    "agile",
+    "agitated",
+    "agonizing",
+    "agreeable",
+    "ajar",
+    "alarmed",
+    "alarming",
+    "alert",
+    "alienated",
+    "alive",
+    "all",
+    "altruistic",
+    "amazing",
+    "ambitious",
+    "ample",
+    "amused",
+    "amusing",
+    "anchored",
+    "ancient",
+    "angelic",
+    "angry",
+    "anguished",
+    "animated",
+    "annual",
+    "another",
+    "antique",
+    "anxious",
+    "any",
+    "apprehensive",
+    "appropriate",
+    "apt",
+    "arctic",
+    "arid",
+    "aromatic",
+    "artistic",
+    "ashamed",
+    "assured",
+    "astonishing",
+    "athletic",
+    "attached",
+    "attentive",
+    "attractive",
+    "austere",
+    "authentic",
+    "authorized",
+    "automatic",
+    "avaricious",
+    "average",
+    "aware",
+    "awesome",
+    "awful",
+    "awkward",
+    "babyish",
+    "bad",
+    "back",
+    "baggy",
+    "bare",
+    "barren",
+    "basic",
+    "beautiful",
+    "belated",
+    "beloved",
+    "beneficial",
+    "better",
+    "best",
+    "bewitched",
+    "big",
+    "big-hearted",
+    "biodegradable",
+    "bite-sized",
+    "bitter",
+    "black",
+    "black-and-white",
+    "bland",
+    "blank",
+    "blaring",
+    "bleak",
+    "blind",
+    "blissful",
+    "blond",
+    "blue",
+    "blushing",
+    "bogus",
+    "boiling",
+    "bold",
+    "bony",
+    "boring",
+    "bossy",
+    "both",
+    "bouncy",
+    "bountiful",
+    "bowed",
+    "brave",
+    "breakable",
+    "brief",
+    "bright",
+    "brilliant",
+    "brisk",
+    "broken",
+    "bronze",
+    "brown",
+    "bruised",
+    "bubbly",
+    "bulky",
+    "bumpy",
+    "buoyant",
+    "burdensome",
+    "burly",
+    "bustling",
+    "busy",
+    "buttery",
+    "buzzing",
+    "calculating",
+    "calm",
+    "candid",
+    "canine",
+    "capital",
+    "carefree",
+    "careful",
+    "careless",
+    "caring",
+    "cautious",
+    "cavernous",
+    "celebrated",
+    "charming",
+    "cheap",
+    "cheerful",
+    "cheery",
+    "chief",
+    "chilly",
+    "chubby",
+    "circular",
+    "classic",
+    "clean",
+    "clear",
+    "clear-cut",
+    "clever",
+    "close",
+    "closed",
+    "cloudy",
+    "clueless",
+    "clumsy",
+    "cluttered",
+    "coarse",
+    "cold",
+    "colorful",
+    "colorless",
+    "colossal",
+    "comfortable",
+    "common",
+    "compassionate",
+    "competent",
+    "complete",
+    "complex",
+    "complicated",
+    "composed",
+    "concerned",
+    "concrete",
+    "confused",
+    "conscious",
+    "considerate",
+    "constant",
+    "content",
+    "conventional",
+    "cooked",
+    "cool",
+    "cooperative",
+    "coordinated",
+    "corny",
+    "corrupt",
+    "costly",
+    "courageous",
+    "courteous",
+    "crafty",
+    "crazy",
+    "creamy",
+    "creative",
+    "creepy",
+    "criminal",
+    "crisp",
+    "critical",
+    "crooked",
+    "crowded",
+    "cruel",
+    "crushing",
+    "cuddly",
+    "cultivated",
+    "cultured",
+    "cumbersome",
+    "curly",
+    "curvy",
+    "cute",
+    "cylindrical",
+    "damaged",
+    "damp",
+    "dangerous",
+    "dapper",
+    "daring",
+    "darling",
+    "dark",
+    "dazzling",
+    "dead",
+    "deadly",
+    "deafening",
+    "dear",
+    "dearest",
+    "decent",
+    "decimal",
+    "decisive",
+    "deep",
+    "defenseless",
+    "defensive",
+    "defiant",
+    "deficient",
+    "definite",
+    "definitive",
+    "delayed",
+    "delectable",
+    "delicious",
+    "delightful",
+    "delirious",
+    "demanding",
+    "dense",
+    "dental",
+    "dependable",
+    "dependent",
+    "descriptive",
+    "deserted",
+    "detailed",
+    "determined",
+    "devoted",
+    "different",
+    "difficult",
+    "digital",
+    "diligent",
+    "dim",
+    "dimpled",
+    "dimwitted",
+    "direct",
+    "disastrous",
+    "discrete",
+    "disfigured",
+    "disgusting",
+    "disloyal",
+    "dismal",
+    "distant",
+    "downright",
+    "dreary",
+    "dirty",
+    "disguised",
+    "dishonest",
+    "dismal",
+    "distant",
+    "distinct",
+    "distorted",
+    "dizzy",
+    "dopey",
+    "doting",
+    "double",
+    "downright",
+    "drab",
+    "drafty",
+    "dramatic",
+    "dreary",
+    "droopy",
+    "dry",
+    "dual",
+    "dull",
+    "dutiful",
+    "each",
+    "eager",
+    "earnest",
+    "early",
+    "easy",
+    "easy-going",
+    "ecstatic",
+    "edible",
+    "educated",
+    "elaborate",
+    "elastic",
+    "elated",
+    "elderly",
+    "electric",
+    "elegant",
+    "elementary",
+    "elliptical",
+    "embarrassed",
+    "embellished",
+    "eminent",
+    "emotional",
+    "empty",
+    "enchanted",
+    "enchanting",
+    "energetic",
+    "enlightened",
+    "enormous",
+    "enraged",
+    "entire",
+    "envious",
+    "equal",
+    "equatorial",
+    "essential",
+    "esteemed",
+    "ethical",
+    "euphoric",
+    "even",
+    "evergreen",
+    "everlasting",
+    "every",
+    "evil",
+    "exalted",
+    "excellent",
+    "exemplary",
+    "exhausted",
+    "excitable",
+    "excited",
+    "exciting",
+    "exotic",
+    "expensive",
+    "experienced",
+    "expert",
+    "extraneous",
+    "extroverted",
+    "extra-large",
+    "extra-small",
+    "fabulous",
+    "failing",
+    "faint",
+    "fair",
+    "faithful",
+    "fake",
+    "false",
+    "familiar",
+    "famous",
+    "fancy",
+    "fantastic",
+    "far",
+    "faraway",
+    "far-flung",
+    "far-off",
+    "fast",
+    "fat",
+    "fatal",
+    "fatherly",
+    "favorable",
+    "favorite",
+    "fearful",
+    "fearless",
+    "feisty",
+    "feline",
+    "female",
+    "feminine",
+    "few",
+    "fickle",
+    "filthy",
+    "fine",
+    "finished",
+    "firm",
+    "first",
+    "firsthand",
+    "fitting",
+    "fixed",
+    "flaky",
+    "flamboyant",
+    "flashy",
+    "flat",
+    "flawed",
+    "flawless",
+    "flickering",
+    "flimsy",
+    "flippant",
+    "flowery",
+    "fluffy",
+    "fluid",
+    "flustered",
+    "focused",
+    "fond",
+    "foolhardy",
+    "foolish",
+    "forceful",
+    "forked",
+    "formal",
+    "forsaken",
+    "forthright",
+    "fortunate",
+    "fragrant",
+    "frail",
+    "frank",
+    "frayed",
+    "free",
+    "French",
+    "fresh",
+    "frequent",
+    "friendly",
+    "frightened",
+    "frightening",
+    "frigid",
+    "frilly",
+    "frizzy",
+    "frivolous",
+    "front",
+    "frosty",
+    "frozen",
+    "frugal",
+    "fruitful",
+    "full",
+    "fumbling",
+    "functional",
+    "funny",
+    "fussy",
+    "fuzzy",
+    "gargantuan",
+    "gaseous",
+    "general",
+    "generous",
+    "gentle",
+    "genuine",
+    "giant",
+    "giddy",
+    "gigantic",
+    "gifted",
+    "giving",
+    "glamorous",
+    "glaring",
+    "glass",
+    "gleaming",
+    "gleeful",
+    "glistening",
+    "glittering",
+    "gloomy",
+    "glorious",
+    "glossy",
+    "glum",
+    "golden",
+    "good",
+    "good-natured",
+    "gorgeous",
+    "graceful",
+    "gracious",
+    "grand",
+    "grandiose",
+    "granular",
+    "grateful",
+    "grave",
+    "gray",
+    "great",
+    "greedy",
+    "green",
+    "gregarious",
+    "grim",
+    "grimy",
+    "gripping",
+    "grizzled",
+    "gross",
+    "grotesque",
+    "grouchy",
+    "grounded",
+    "growing",
+    "growling",
+    "grown",
+    "grubby",
+    "gruesome",
+    "grumpy",
+    "guilty",
+    "gullible",
+    "gummy",
+    "hairy",
+    "half",
+    "handmade",
+    "handsome",
+    "handy",
+    "happy",
+    "happy-go-lucky",
+    "hard",
+    "hard-to-find",
+    "harmful",
+    "harmless",
+    "harmonious",
+    "harsh",
+    "hasty",
+    "hateful",
+    "haunting",
+    "healthy",
+    "heartfelt",
+    "hearty",
+    "heavenly",
+    "heavy",
+    "hefty",
+    "helpful",
+    "helpless",
+    "hidden",
+    "hideous",
+    "high",
+    "high-level",
+    "hilarious",
+    "hoarse",
+    "hollow",
+    "homely",
+    "honest",
+    "honorable",
+    "honored",
+    "hopeful",
+    "horrible",
+    "hospitable",
+    "hot",
+    "huge",
+    "humble",
+    "humiliating",
+    "humming",
+    "humongous",
+    "hungry",
+    "hurtful",
+    "husky",
+    "icky",
+    "icy",
+    "ideal",
+    "idealistic",
+    "identical",
+    "idle",
+    "idiotic",
+    "idolized",
+    "ignorant",
+    "ill",
+    "illegal",
+    "ill-fated",
+    "ill-informed",
+    "illiterate",
+    "illustrious",
+    "imaginary",
+    "imaginative",
+    "immaculate",
+    "immaterial",
+    "immediate",
+    "immense",
+    "impassioned",
+    "impeccable",
+    "impartial",
+    "imperfect",
+    "imperturbable",
+    "impish",
+    "impolite",
+    "important",
+    "impossible",
+    "impractical",
+    "impressionable",
+    "impressive",
+    "improbable",
+    "impure",
+    "inborn",
+    "incomparable",
+    "incompatible",
+    "incomplete",
+    "inconsequential",
+    "incredible",
+    "indelible",
+    "inexperienced",
+    "indolent",
+    "infamous",
+    "infantile",
+    "infatuated",
+    "inferior",
+    "infinite",
+    "informal",
+    "innocent",
+    "insecure",
+    "insidious",
+    "insignificant",
+    "insistent",
+    "instructive",
+    "insubstantial",
+    "intelligent",
+    "intent",
+    "intentional",
+    "interesting",
+    "internal",
+    "international",
+    "intrepid",
+    "ironclad",
+    "irresponsible",
+    "irritating",
+    "itchy",
+    "jaded",
+    "jagged",
+    "jam-packed",
+    "jaunty",
+    "jealous",
+    "jittery",
+    "joint",
+    "jolly",
+    "jovial",
+    "joyful",
+    "joyous",
+    "jubilant",
+    "judicious",
+    "juicy",
+    "jumbo",
+    "junior",
+    "jumpy",
+    "juvenile",
+    "kaleidoscopic",
+    "keen",
+    "key",
+    "kind",
+    "kindhearted",
+    "kindly",
+    "klutzy",
+    "knobby",
+    "knotty",
+    "knowledgeable",
+    "knowing",
+    "known",
+    "kooky",
+    "kosher",
+    "lame",
+    "lanky",
+    "large",
+    "last",
+    "lasting",
+    "late",
+    "lavish",
+    "lawful",
+    "lazy",
+    "leading",
+    "lean",
+    "leafy",
+    "left",
+    "legal",
+    "legitimate",
+    "light",
+    "lighthearted",
+    "likable",
+    "likely",
+    "limited",
+    "limp",
+    "limping",
+    "linear",
+    "lined",
+    "liquid",
+    "little",
+    "live",
+    "lively",
+    "livid",
+    "loathsome",
+    "lone",
+    "lonely",
+    "long",
+    "long-term",
+    "loose",
+    "lopsided",
+    "lost",
+    "loud",
+    "lovable",
+    "lovely",
+    "loving",
+    "low",
+    "loyal",
+    "lucky",
+    "lumbering",
+    "luminous",
+    "lumpy",
+    "lustrous",
+    "luxurious",
+    "mad",
+    "made-up",
+    "magnificent",
+    "majestic",
+    "major",
+    "male",
+    "mammoth",
+    "married",
+    "marvelous",
+    "masculine",
+    "massive",
+    "mature",
+    "meager",
+    "mealy",
+    "mean",
+    "measly",
+    "meaty",
+    "medical",
+    "mediocre",
+    "medium",
+    "meek",
+    "mellow",
+    "melodic",
+    "memorable",
+    "menacing",
+    "merry",
+    "messy",
+    "metallic",
+    "mild",
+    "milky",
+    "mindless",
+    "miniature",
+    "minor",
+    "minty",
+    "miserable",
+    "miserly",
+    "misguided",
+    "misty",
+    "mixed",
+    "modern",
+    "modest",
+    "moist",
+    "monstrous",
+    "monthly",
+    "monumental",
+    "moral",
+    "mortified",
+    "motherly",
+    "motionless",
+    "mountainous",
+    "muddy",
+    "muffled",
+    "multicolored",
+    "mundane",
+    "murky",
+    "mushy",
+    "musty",
+    "muted",
+    "mysterious",
+    "naive",
+    "narrow",
+    "nasty",
+    "natural",
+    "naughty",
+    "nautical",
+    "near",
+    "neat",
+    "necessary",
+    "needy",
+    "negative",
+    "neglected",
+    "negligible",
+    "neighboring",
+    "nervous",
+    "new",
+    "next",
+    "nice",
+    "nifty",
+    "nimble",
+    "nippy",
+    "nocturnal",
+    "noisy",
+    "nonstop",
+    "normal",
+    "notable",
+    "noted",
+    "noteworthy",
+    "novel",
+    "noxious",
+    "numb",
+    "nutritious",
+    "nutty",
+    "obedient",
+    "obese",
+    "oblong",
+    "oily",
+    "oblong",
+    "obvious",
+    "occasional",
+    "odd",
+    "oddball",
+    "offbeat",
+    "offensive",
+    "official",
+    "old",
+    "old-fashioned",
+    "only",
+    "open",
+    "optimal",
+    "optimistic",
+    "opulent",
+    "orange",
+    "orderly",
+    "organic",
+    "ornate",
+    "ornery",
+    "ordinary",
+    "original",
+    "other",
+    "our",
+    "outlying",
+    "outgoing",
+    "outlandish",
+    "outrageous",
+    "outstanding",
+    "oval",
+    "overcooked",
+    "overdue",
+    "overjoyed",
+    "overlooked",
+    "palatable",
+    "pale",
+    "paltry",
+    "parallel",
+    "parched",
+    "partial",
+    "passionate",
+    "past",
+    "pastel",
+    "peaceful",
+    "peppery",
+    "perfect",
+    "perfumed",
+    "periodic",
+    "perky",
+    "personal",
+    "pertinent",
+    "pesky",
+    "pessimistic",
+    "petty",
+    "phony",
+    "physical",
+    "piercing",
+    "pink",
+    "pitiful",
+    "plain",
+    "plaintive",
+    "plastic",
+    "playful",
+    "pleasant",
+    "pleased",
+    "pleasing",
+    "plump",
+    "plush",
+    "polished",
+    "polite",
+    "political",
+    "pointed",
+    "pointless",
+    "poised",
+    "poor",
+    "popular",
+    "portly",
+    "posh",
+    "positive",
+    "possible",
+    "potable",
+    "powerful",
+    "powerless",
+    "practical",
+    "precious",
+    "present",
+    "prestigious",
+    "pretty",
+    "precious",
+    "previous",
+    "pricey",
+    "prickly",
+    "primary",
+    "prime",
+    "pristine",
+    "private",
+    "prize",
+    "probable",
+    "productive",
+    "profitable",
+    "profuse",
+    "proper",
+    "proud",
+    "prudent",
+    "punctual",
+    "pungent",
+    "puny",
+    "pure",
+    "purple",
+    "pushy",
+    "putrid",
+    "puzzled",
+    "puzzling",
+    "quaint",
+    "qualified",
+    "quarrelsome",
+    "quarterly",
+    "queasy",
+    "querulous",
+    "questionable",
+    "quick",
+    "quick-witted",
+    "quiet",
+    "quintessential",
+    "quirky",
+    "quixotic",
+    "quizzical",
+    "radiant",
+    "ragged",
+    "rapid",
+    "rare",
+    "rash",
+    "raw",
+    "recent",
+    "reckless",
+    "rectangular",
+    "ready",
+    "real",
+    "realistic",
+    "reasonable",
+    "red",
+    "reflecting",
+    "regal",
+    "regular",
+    "reliable",
+    "relieved",
+    "remarkable",
+    "remorseful",
+    "remote",
+    "repentant",
+    "required",
+    "respectful",
+    "responsible",
+    "repulsive",
+    "revolving",
+    "rewarding",
+    "rich",
+    "rigid",
+    "right",
+    "ringed",
+    "ripe",
+    "roasted",
+    "robust",
+    "rosy",
+    "rotating",
+    "rotten",
+    "rough",
+    "round",
+    "rowdy",
+    "royal",
+    "rubbery",
+    "rundown",
+    "ruddy",
+    "rude",
+    "runny",
+    "rural",
+    "rusty",
+    "sad",
+    "safe",
+    "salty",
+    "same",
+    "sandy",
+    "sane",
+    "sarcastic",
+    "sardonic",
+    "satisfied",
+    "scaly",
+    "scarce",
+    "scared",
+    "scary",
+    "scented",
+    "scholarly",
+    "scientific",
+    "scornful",
+    "scratchy",
+    "scrawny",
+    "second",
+    "secondary",
+    "second-hand",
+    "secret",
+    "self-assured",
+    "self-reliant",
+    "selfish",
+    "sentimental",
+    "separate",
+    "serene",
+    "serious",
+    "serpentine",
+    "several",
+    "severe",
+    "shabby",
+    "shadowy",
+    "shady",
+    "shallow",
+    "shameful",
+    "shameless",
+    "sharp",
+    "shimmering",
+    "shiny",
+    "shocked",
+    "shocking",
+    "shoddy",
+    "short",
+    "short-term",
+    "showy",
+    "shrill",
+    "shy",
+    "sick",
+    "silent",
+    "silky",
+    "silly",
+    "silver",
+    "similar",
+    "simple",
+    "simplistic",
+    "sinful",
+    "single",
+    "sizzling",
+    "skeletal",
+    "skinny",
+    "sleepy",
+    "slight",
+    "slim",
+    "slimy",
+    "slippery",
+    "slow",
+    "slushy",
+    "small",
+    "smart",
+    "smoggy",
+    "smooth",
+    "smug",
+    "snappy",
+    "snarling",
+    "sneaky",
+    "sniveling",
+    "snoopy",
+    "sociable",
+    "soft",
+    "soggy",
+    "solid",
+    "somber",
+    "some",
+    "spherical",
+    "sophisticated",
+    "sore",
+    "sorrowful",
+    "soulful",
+    "soupy",
+    "sour",
+    "Spanish",
+    "sparkling",
+    "sparse",
+    "specific",
+    "spectacular",
+    "speedy",
+    "spicy",
+    "spiffy",
+    "spirited",
+    "spiteful",
+    "splendid",
+    "spotless",
+    "spotted",
+    "spry",
+    "square",
+    "squeaky",
+    "squiggly",
+    "stable",
+    "staid",
+    "stained",
+    "stale",
+    "standard",
+    "starchy",
+    "stark",
+    "starry",
+    "steep",
+    "sticky",
+    "stiff",
+    "stimulating",
+    "stingy",
+    "stormy",
+    "straight",
+    "strange",
+    "steel",
+    "strict",
+    "strident",
+    "striking",
+    "striped",
+    "strong",
+    "studious",
+    "stunning",
+    "stupendous",
+    "stupid",
+    "sturdy",
+    "stylish",
+    "subdued",
+    "submissive",
+    "substantial",
+    "subtle",
+    "suburban",
+    "sudden",
+    "sugary",
+    "sunny",
+    "super",
+    "superb",
+    "superficial",
+    "superior",
+    "supportive",
+    "sure-footed",
+    "surprised",
+    "suspicious",
+    "svelte",
+    "sweaty",
+    "sweet",
+    "sweltering",
+    "swift",
+    "sympathetic",
+    "tall",
+    "talkative",
+    "tame",
+    "tan",
+    "tangible",
+    "tart",
+    "tasty",
+    "tattered",
+    "taut",
+    "tedious",
+    "teeming",
+    "tempting",
+    "tender",
+    "tense",
+    "tepid",
+    "terrible",
+    "terrific",
+    "testy",
+    "thankful",
+    "that",
+    "these",
+    "thick",
+    "thin",
+    "third",
+    "thirsty",
+    "this",
+    "thorough",
+    "thorny",
+    "those",
+    "thoughtful",
+    "threadbare",
+    "thrifty",
+    "thunderous",
+    "tidy",
+    "tight",
+    "timely",
+    "tinted",
+    "tiny",
+    "tired",
+    "torn",
+    "total",
+    "tough",
+    "traumatic",
+    "treasured",
+    "tremendous",
+    "tragic",
+    "trained",
+    "tremendous",
+    "triangular",
+    "tricky",
+    "trifling",
+    "trim",
+    "trivial",
+    "troubled",
+    "true",
+    "trusting",
+    "trustworthy",
+    "trusty",
+    "truthful",
+    "tubby",
+    "turbulent",
+    "twin",
+    "ugly",
+    "ultimate",
+    "unacceptable",
+    "unaware",
+    "uncomfortable",
+    "uncommon",
+    "unconscious",
+    "understated",
+    "unequaled",
+    "uneven",
+    "unfinished",
+    "unfit",
+    "unfolded",
+    "unfortunate",
+    "unhappy",
+    "unhealthy",
+    "uniform",
+    "unimportant",
+    "unique",
+    "united",
+    "unkempt",
+    "unknown",
+    "unlawful",
+    "unlined",
+    "unlucky",
+    "unnatural",
+    "unpleasant",
+    "unrealistic",
+    "unripe",
+    "unruly",
+    "unselfish",
+    "unsightly",
+    "unsteady",
+    "unsung",
+    "untidy",
+    "untimely",
+    "untried",
+    "untrue",
+    "unused",
+    "unusual",
+    "unwelcome",
+    "unwieldy",
+    "unwilling",
+    "unwitting",
+    "unwritten",
+    "upbeat",
+    "upright",
+    "upset",
+    "urban",
+    "usable",
+    "used",
+    "useful",
+    "useless",
+    "utilized",
+    "utter",
+    "vacant",
+    "vague",
+    "vain",
+    "valid",
+    "valuable",
+    "vapid",
+    "variable",
+    "vast",
+    "velvety",
+    "venerated",
+    "vengeful",
+    "verifiable",
+    "vibrant",
+    "vicious",
+    "victorious",
+    "vigilant",
+    "vigorous",
+    "villainous",
+    "violet",
+    "violent",
+    "virtual",
+    "virtuous",
+    "visible",
+    "vital",
+    "vivacious",
+    "vivid",
+    "voluminous",
+    "wan",
+    "warlike",
+    "warm",
+    "warmhearted",
+    "warped",
+    "wary",
+    "wasteful",
+    "watchful",
+    "waterlogged",
+    "watery",
+    "wavy",
+    "wealthy",
+    "weak",
+    "weary",
+    "webbed",
+    "wee",
+    "weekly",
+    "weepy",
+    "weighty",
+    "weird",
+    "welcome",
+    "well-documented",
+    "well-groomed",
+    "well-informed",
+    "well-lit",
+    "well-made",
+    "well-off",
+    "well-to-do",
+    "well-worn",
+    "wet",
+    "which",
+    "whimsical",
+    "whirlwind",
+    "whispered",
+    "white",
+    "whole",
+    "whopping",
+    "wicked",
+    "wide",
+    "wide-eyed",
+    "wiggly",
+    "wild",
+    "willing",
+    "wilted",
+    "winding",
+    "windy",
+    "winged",
+    "wiry",
+    "wise",
+    "witty",
+    "wobbly",
+    "woeful",
+    "wonderful",
+    "wooden",
+    "woozy",
+    "wordy",
+    "worldly",
+    "worn",
+    "worried",
+    "worrisome",
+    "worse",
+    "worst",
+    "worthless",
+    "worthwhile",
+    "worthy",
+    "wrathful",
+    "wretched",
+    "writhing",
+    "wrong",
+    "wry",
+    "yawning",
+    "yearly",
+    "yellow",
+    "yellowish",
+    "young",
+    "youthful",
+    "yummy",
+    "zany",
+    "zealous",
+    "zesty",
+    "zigzag",
+]
+
+export function getRandomUsername(): string {
+    const n = Math.random() * noun.length
+    const a = Math.random() * adj.length
+    return `tmp-${a}-${n}`
+}
+
+export function getRandomPassword(): string {
+    return encodeCrock(getRandomBytes(16))
+}
\ No newline at end of file
diff --git a/packages/demobank-ui/src/utils.ts b/packages/demobank-ui/src/utils.ts
index 4ce0f140e..c13b9a3cb 100644
--- a/packages/demobank-ui/src/utils.ts
+++ b/packages/demobank-ui/src/utils.ts
@@ -16,11 +16,12 @@
 
 import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
 import {
+  ErrorNotification,
   ErrorType,
   HttpError,
   useTranslationContext,
 } from "@gnu-taler/web-util/browser";
-import { ErrorMessage } from "./hooks/notification.js";
+
 
 /**
  * Validate (the number part of) an amount.  If needed,
@@ -120,11 +121,12 @@ export function buildRequestErrorMessage(
     onClientError?: (status: HttpStatusCode) => TranslatedString | undefined;
     onServerError?: (status: HttpStatusCode) => TranslatedString | undefined;
   } = {},
-): ErrorMessage {
-  let result: ErrorMessage;
+): ErrorNotification {
+  let result: ErrorNotification;
   switch (cause.type) {
     case ErrorType.TIMEOUT: {
       result = {
+        type: "error",
         title: i18n.str`Request timeout`,
       };
       break;
@@ -133,8 +135,9 @@ export function buildRequestErrorMessage(
       const title =
         specialCases.onClientError && specialCases.onClientError(cause.status);
       result = {
+        type: "error",
         title: title ? title : i18n.str`The server didn't accept the request`,
-        description: cause?.payload?.error?.description,
+        description: cause?.payload?.error?.description as TranslatedString,
         debug: JSON.stringify(cause),
       };
       break;
@@ -143,24 +146,27 @@ export function buildRequestErrorMessage(
       const title =
         specialCases.onServerError && specialCases.onServerError(cause.status);
       result = {
+        type: "error",
         title: title
           ? title
           : i18n.str`The server had problems processing the request`,
-        description: cause?.payload?.error?.description,
+        description: cause?.payload?.error?.description as TranslatedString,
         debug: JSON.stringify(cause),
       };
       break;
     }
     case ErrorType.UNREADABLE: {
       result = {
+        type: "error",
         title: i18n.str`Unexpected error`,
-        description: `Response from ${cause?.info?.url} is unreadable, status: ${cause?.status}`,
+        description: `Response from ${cause?.info?.url} is unreadable, status: ${cause?.status}` as TranslatedString,
         debug: JSON.stringify(cause),
       };
       break;
     }
     case ErrorType.UNEXPECTED: {
       result = {
+        type: "error",
         title: i18n.str`Unexpected error`,
         debug: JSON.stringify(cause),
       };
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 295074f61..392f34981 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,4 +1,4 @@
-lockfileVersion: '6.1'
+lockfileVersion: '6.0'
 
 settings:
   autoInstallPeers: true
@@ -877,6 +877,9 @@ importers:
       '@gnu-taler/taler-util':
         specifier: workspace:*
         version: link:../taler-util
+      '@heroicons/react':
+        specifier: ^2.0.17
+        version: 2.0.17(react@18.2.0)
       '@linaria/babel-preset':
         specifier: 4.4.5
         version: 4.4.5
@@ -907,6 +910,9 @@ importers:
       chokidar:
         specifier: ^3.5.3
         version: 3.5.3
+      date-fns:
+        specifier: 2.29.3
+        version: 2.29.3
       esbuild:
         specifier: ^0.17.7
         version: 0.17.7
@@ -4872,7 +4878,6 @@ packages:
       react: '>= 16'
     dependencies:
       react: 18.2.0
-    dev: false
 
   /@humanwhocodes/config-array@0.11.11:
     resolution: {integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==}
@@ -8680,7 +8685,6 @@ packages:
   /date-fns@2.29.3:
     resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==}
     engines: {node: '>=0.11'}
-    dev: false
 
   /date-time@3.1.0:
     resolution: {integrity: sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==}
-- 
cgit v1.2.3


From 062939d9cc016a186a282f7a48492c3e01cd740c Mon Sep 17 00:00:00 2001
From: Sebastian <sebasjm@gmail.com>
Date: Thu, 21 Sep 2023 10:31:10 -0300
Subject: admin refactor

---
 packages/demobank-ui/src/components/Routing.tsx    |   13 +-
 packages/demobank-ui/src/pages/AdminPage.tsx       | 1042 --------------------
 packages/demobank-ui/src/pages/BusinessAccount.tsx |  758 --------------
 packages/demobank-ui/src/pages/HomePage.tsx        |   19 +-
 .../demobank-ui/src/pages/ShowAccountDetails.tsx   |  143 +++
 .../src/pages/UpdateAccountPassword.tsx            |  131 +++
 .../src/pages/WithdrawalConfirmationQuestion.tsx   |    3 +-
 .../demobank-ui/src/pages/WithdrawalQRCode.tsx     |    4 -
 packages/demobank-ui/src/pages/admin/Account.tsx   |   56 ++
 .../demobank-ui/src/pages/admin/AccountForm.tsx    |  219 ++++
 .../demobank-ui/src/pages/admin/AccountList.tsx    |  120 +++
 .../src/pages/admin/CreateNewAccount.tsx           |  107 ++
 packages/demobank-ui/src/pages/admin/Home.tsx      |  162 +++
 .../demobank-ui/src/pages/admin/RemoveAccount.tsx  |  112 +++
 packages/demobank-ui/src/pages/business/Home.tsx   |  757 ++++++++++++++
 15 files changed, 1823 insertions(+), 1823 deletions(-)
 delete mode 100644 packages/demobank-ui/src/pages/AdminPage.tsx
 delete mode 100644 packages/demobank-ui/src/pages/BusinessAccount.tsx
 create mode 100644 packages/demobank-ui/src/pages/ShowAccountDetails.tsx
 create mode 100644 packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
 create mode 100644 packages/demobank-ui/src/pages/admin/Account.tsx
 create mode 100644 packages/demobank-ui/src/pages/admin/AccountForm.tsx
 create mode 100644 packages/demobank-ui/src/pages/admin/AccountList.tsx
 create mode 100644 packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
 create mode 100644 packages/demobank-ui/src/pages/admin/Home.tsx
 create mode 100644 packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
 create mode 100644 packages/demobank-ui/src/pages/business/Home.tsx

(limited to 'packages/demobank-ui/src/pages/AdminPage.tsx')

diff --git a/packages/demobank-ui/src/components/Routing.tsx b/packages/demobank-ui/src/components/Routing.tsx
index 890058a9b..ef11af76e 100644
--- a/packages/demobank-ui/src/components/Routing.tsx
+++ b/packages/demobank-ui/src/components/Routing.tsx
@@ -19,14 +19,14 @@ import { VNode, h } from "preact";
 import { Route, Router, route } from "preact-router";
 import { useEffect } from "preact/hooks";
 import { BankFrame } from "../pages/BankFrame.js";
-import { BusinessAccount } from "../pages/BusinessAccount.js";
+import { BusinessAccount } from "../pages/business/Home.js";
 import { HomePage, WithdrawalOperationPage } from "../pages/HomePage.js";
 import { PublicHistoriesPage } from "../pages/PublicHistoriesPage.js";
 import { RegistrationPage } from "../pages/RegistrationPage.js";
 import { Test } from "../pages/Test.js";
 import { useBackendContext } from "../context/backend.js";
 import { LoginForm } from "../pages/LoginForm.js";
-import { AdminPage } from "../pages/AdminPage.js";
+import { AdminHome } from "../pages/admin/Home.js";
 
 export function Routing(): VNode {
   const history = createHashHistory();
@@ -34,6 +34,7 @@ export function Routing(): VNode {
 
   if (backend.state.status === "loggedOut") {
     return <BankFrame
+      account={undefined}
       goToBusinessAccount={() => {
         route("/business");
       }}
@@ -63,7 +64,7 @@ export function Routing(): VNode {
       </Router>
     </BankFrame>
   }
-  const isAdmin = backend.state.isUserAdministrator
+  const { isUserAdministrator, username } = backend.state
 
   return (
     <BankFrame
@@ -108,14 +109,15 @@ export function Routing(): VNode {
         <Route
           path="/account"
           component={() => {
-            if (isAdmin) {
-              return <AdminPage
+            if (isUserAdministrator) {
+              return <AdminHome
                 onRegister={() => {
                   route("/register");
                 }}
               />;
             } else {
               return <HomePage
+                account={username}
                 onPendingOperationFound={(wopid) => {
                   route(`/operation/${wopid}`);
                 }}
@@ -130,6 +132,7 @@ export function Routing(): VNode {
           path="/business"
           component={() => (
             <BusinessAccount
+              account={username}
               onClose={() => {
                 route("/account");
               }}
diff --git a/packages/demobank-ui/src/pages/AdminPage.tsx b/packages/demobank-ui/src/pages/AdminPage.tsx
deleted file mode 100644
index 18462bdc3..000000000
--- a/packages/demobank-ui/src/pages/AdminPage.tsx
+++ /dev/null
@@ -1,1042 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 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, TranslatedString, parsePaytoUri } from "@gnu-taler/taler-util";
-import {
-  ErrorType,
-  HttpResponsePaginated,
-  RequestError,
-  notify,
-  notifyError,
-  notifyInfo,
-  useTranslationContext,
-} from "@gnu-taler/web-util/browser";
-import { Fragment, h, VNode } from "preact";
-import { useState } from "preact/hooks";
-import { Cashouts } from "../components/Cashouts/index.js";
-import { useBackendContext } from "../context/backend.js";
-import { useAccountDetails } from "../hooks/access.js";
-import {
-  useAdminAccountAPI,
-  useBusinessAccountDetails,
-  useBusinessAccounts,
-} from "../hooks/circuit.js";
-import {
-  buildRequestErrorMessage,
-  PartialButDefined,
-  RecursivePartial,
-  undefinedIfEmpty,
-  validateIBAN,
-  WithIntermediate,
-} from "../utils.js";
-import { ShowCashoutDetails } from "./BusinessAccount.js";
-import { handleNotOkResult } from "./HomePage.js";
-import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js";
-import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
-
-const charset =
-  "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
-const upperIdx = charset.indexOf("A");
-
-function randomPassword(): string {
-  const random = Array.from({ length: 16 }).map(() => {
-    return charset.charCodeAt(Math.random() * charset.length);
-  });
-  // first char can't be upper
-  const charIdx = charset.indexOf(String.fromCharCode(random[0]));
-  random[0] =
-    charIdx > upperIdx ? charset.charCodeAt(charIdx - upperIdx) : random[0];
-  return String.fromCharCode(...random);
-}
-
-interface Props {
-  onRegister: () => void;
-}
-/**
- * Query account information and show QR code if there is pending withdrawal
- */
-export function AdminPage({ onRegister }: Props): VNode {
-  const [account, setAccount] = useState<string | undefined>();
-  const [showDetails, setShowDetails] = useState<string | undefined>();
-  const [showCashouts, setShowCashouts] = useState<string | undefined>();
-  const [updatePassword, setUpdatePassword] = useState<string | undefined>();
-  const [removeAccount, setRemoveAccount] = useState<string | undefined>();
-  const [showCashoutDetails, setShowCashoutDetails] = useState<
-    string | undefined
-  >();
-
-  const [createAccount, setCreateAccount] = useState(false);
-
-  const result = useBusinessAccounts({ account });
-  const { i18n } = useTranslationContext();
-
-  if (result.loading) return <div />;
-  if (!result.ok) {
-    return handleNotOkResult(i18n, onRegister)(result);
-  }
-
-  const { customers } = result.data;
-
-  if (showCashoutDetails) {
-    return (
-      <ShowCashoutDetails
-        id={showCashoutDetails}
-        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
-        onCancel={() => {
-          setShowCashoutDetails(undefined);
-        }}
-      />
-    );
-  }
-
-  if (showCashouts) {
-    return (
-      <div>
-        <div>
-          <h1 class="nav welcome-text">
-            <i18n.Translate>Cashout for account {showCashouts}</i18n.Translate>
-          </h1>
-        </div>
-        <Cashouts
-          account={showCashouts}
-          onSelected={(id) => {
-            setShowCashouts(id);
-            setShowCashouts(undefined);
-          }}
-        />
-        <p>
-          <input
-            class="pure-button"
-            type="submit"
-            value={i18n.str`Close`}
-            onClick={async (e) => {
-              e.preventDefault();
-              setShowCashouts(undefined);
-            }}
-          />
-        </p>
-      </div>
-    );
-  }
-
-  if (showDetails) {
-    return (
-      <ShowAccountDetails
-        account={showDetails}
-        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
-        onChangePassword={() => {
-          setUpdatePassword(showDetails);
-          setShowDetails(undefined);
-        }}
-        onUpdateSuccess={() => {
-          notifyInfo(i18n.str`Account updated`);
-          setShowDetails(undefined);
-        }}
-        onClear={() => {
-          setShowDetails(undefined);
-        }}
-      />
-    );
-  }
-  if (removeAccount) {
-    return (
-      <RemoveAccount
-        account={removeAccount}
-        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
-        onUpdateSuccess={() => {
-          notifyInfo(i18n.str`Account removed`);
-          setRemoveAccount(undefined);
-        }}
-        onClear={() => {
-          setRemoveAccount(undefined);
-        }}
-      />
-    );
-  }
-  if (updatePassword) {
-    return (
-      <UpdateAccountPassword
-        account={updatePassword}
-        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
-        onUpdateSuccess={() => {
-          notifyInfo(i18n.str`Password changed`);
-          setUpdatePassword(undefined);
-        }}
-        onClear={() => {
-          setUpdatePassword(undefined);
-        }}
-      />
-    );
-  }
-  if (createAccount) {
-    return (
-      <CreateNewAccount
-        onClose={() => setCreateAccount(false)}
-        onCreateSuccess={(password) => {
-          notifyInfo(
-            i18n.str`Account created with password "${password}". The user must change the password on the next login.`,
-          );
-          setCreateAccount(false);
-        }}
-      />
-    );
-  }
-
-  return (
-    <Fragment>
-      <div>
-        <h1 class="nav welcome-text">
-          <i18n.Translate>Admin panel</i18n.Translate>
-        </h1>
-      </div>
-
-      <p>
-        <div style={{ display: "flex", justifyContent: "space-between" }}>
-          <div></div>
-          <div>
-            <input
-              class="pure-button pure-button-primary content"
-              type="submit"
-              value={i18n.str`Create account`}
-              onClick={async (e) => {
-                e.preventDefault();
-
-                setCreateAccount(true);
-              }}
-            />
-          </div>
-        </div>
-      </p>
-
-      <AdminAccount onRegister={onRegister} />
-      <section
-        id="main"
-        style={{ width: 600, marginLeft: "auto", marginRight: "auto" }}
-      >
-        {!customers.length ? (
-          <div></div>
-        ) : (
-          <article>
-            <h2>{i18n.str`Accounts:`}</h2>
-            <div class="results">
-              <table class="pure-table pure-table-striped">
-                <thead>
-                  <tr>
-                    <th>{i18n.str`Username`}</th>
-                    <th>{i18n.str`Name`}</th>
-                    <th>{i18n.str`Balance`}</th>
-                    <th>{i18n.str`Actions`}</th>
-                  </tr>
-                </thead>
-                <tbody>
-                  {customers.map((item, idx) => {
-                    const balance = !item.balance
-                      ? undefined
-                      : Amounts.parse(item.balance.amount);
-                    const balanceIsDebit =
-                      item.balance &&
-                      item.balance.credit_debit_indicator == "debit";
-                    return (
-                      <tr key={idx}>
-                        <td>
-                          <a
-                            href="#"
-                            onClick={(e) => {
-                              e.preventDefault();
-                              setShowDetails(item.username);
-                            }}
-                          >
-                            {item.username}
-                          </a>
-                        </td>
-                        <td>{item.name}</td>
-                        <td>
-                          {!balance ? (
-                            i18n.str`unknown`
-                          ) : (
-                            <span class="amount">
-                              {balanceIsDebit ? <b>-</b> : null}
-                              <span class="value">{`${Amounts.stringifyValue(
-                                balance,
-                              )}`}</span>
-                              &nbsp;
-                              <span class="currency">{`${balance.currency}`}</span>
-                            </span>
-                          )}
-                        </td>
-                        <td>
-                          <a
-                            href="#"
-                            onClick={(e) => {
-                              e.preventDefault();
-                              setUpdatePassword(item.username);
-                            }}
-                          >
-                            change password
-                          </a>
-                          &nbsp;
-                          <a
-                            href="#"
-                            onClick={(e) => {
-                              e.preventDefault();
-                              setShowCashouts(item.username);
-                            }}
-                          >
-                            cashouts
-                          </a>
-                          &nbsp;
-                          <a
-                            href="#"
-                            onClick={(e) => {
-                              e.preventDefault();
-                              setRemoveAccount(item.username);
-                            }}
-                          >
-                            remove
-                          </a>
-                        </td>
-                      </tr>
-                    );
-                  })}
-                </tbody>
-              </table>
-            </div>
-          </article>
-        )}
-      </section>
-    </Fragment>
-  );
-}
-
-function AdminAccount({ onRegister }: { onRegister: () => void }): VNode {
-  const { i18n } = useTranslationContext();
-  const r = useBackendContext();
-  const account = r.state.status === "loggedIn" ? r.state.username : "admin";
-  const result = useAccountDetails(account);
-
-  if (!result.ok) {
-    return handleNotOkResult(i18n, onRegister)(result);
-  }
-  const { data } = result;
-  const balance = Amounts.parseOrThrow(data.balance.amount);
-  const debitThreshold = Amounts.parseOrThrow(result.data.debitThreshold);
-  const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit";
-  const limit = balanceIsDebit
-    ? Amounts.sub(debitThreshold, balance).amount
-    : Amounts.add(balance, debitThreshold).amount;
-  if (!balance) return <Fragment />;
-  return (
-    <Fragment>
-      <section id="assets">
-        <div class="asset-summary">
-          <h2>{i18n.str`Bank account balance`}</h2>
-          {!balance ? (
-            <div class="large-amount" style={{ color: "gray" }}>
-              Waiting server response...
-            </div>
-          ) : (
-            <div class="large-amount amount">
-              {balanceIsDebit ? <b>-</b> : null}
-              <span class="value">{`${Amounts.stringifyValue(balance)}`}</span>
-              &nbsp;
-              <span class="currency">{`${balance.currency}`}</span>
-            </div>
-          )}
-        </div>
-      </section>
-      <PaytoWireTransferForm
-        focus
-        limit={limit}
-        onSuccess={() => {
-          notifyInfo(i18n.str`Wire transfer created!`);
-        }}
-        onCancel={undefined}
-      />
-    </Fragment>
-  );
-}
-
-const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/;
-const EMAIL_REGEX =
-  /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
-const REGEX_JUST_NUMBERS_REGEX = /^\+[0-9 ]*$/;
-
-function initializeFromTemplate(
-  account: SandboxBackend.Circuit.CircuitAccountData | undefined,
-): WithIntermediate<SandboxBackend.Circuit.CircuitAccountData> {
-  const emptyAccount = {
-    cashout_address: undefined,
-    iban: undefined,
-    name: undefined,
-    username: undefined,
-    contact_data: undefined,
-  };
-  const emptyContact = {
-    email: undefined,
-    phone: undefined,
-  };
-
-  const initial: PartialButDefined<SandboxBackend.Circuit.CircuitAccountData> =
-    structuredClone(account) ?? emptyAccount;
-  if (typeof initial.contact_data === "undefined") {
-    initial.contact_data = emptyContact;
-  }
-  initial.contact_data.email;
-  return initial as any;
-}
-
-export function UpdateAccountPassword({
-  account,
-  onClear,
-  onUpdateSuccess,
-  onLoadNotOk,
-}: {
-  onLoadNotOk: <T>(
-    error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
-  ) => VNode;
-  onClear: () => void;
-  onUpdateSuccess: () => void;
-  account: string;
-}): VNode {
-  const { i18n } = useTranslationContext();
-  const result = useBusinessAccountDetails(account);
-  const { changePassword } = useAdminAccountAPI();
-  const [password, setPassword] = useState<string | undefined>();
-  const [repeat, setRepeat] = useState<string | undefined>();
-
-  if (!result.ok) {
-    if (result.loading || result.type === ErrorType.TIMEOUT) {
-      return onLoadNotOk(result);
-    }
-    if (result.status === HttpStatusCode.NotFound) {
-      return <div>account not found</div>;
-    }
-    return onLoadNotOk(result);
-  }
-
-  const errors = undefinedIfEmpty({
-    password: !password ? i18n.str`required` : undefined,
-    repeat: !repeat
-      ? i18n.str`required`
-      : password !== repeat
-        ? i18n.str`password doesn't match`
-        : undefined,
-  });
-
-  return (
-    <div>
-      <div>
-        <h1 class="nav welcome-text">
-          <i18n.Translate>Update password for {account}</i18n.Translate>
-        </h1>
-      </div>
-
-      <div style={{ maxWidth: 600, overflowX: "hidden", margin: "auto" }}>
-        <form class="pure-form">
-          <fieldset>
-            <label>{i18n.str`Password`}</label>
-            <input
-              type="password"
-              value={password ?? ""}
-              onChange={(e) => {
-                setPassword(e.currentTarget.value);
-              }}
-            />
-            <ShowInputErrorLabel
-              message={errors?.password}
-              isDirty={password !== undefined}
-            />
-          </fieldset>
-          <fieldset>
-            <label>{i18n.str`Repeat password`}</label>
-            <input
-              type="password"
-              value={repeat ?? ""}
-              onChange={(e) => {
-                setRepeat(e.currentTarget.value);
-              }}
-            />
-            <ShowInputErrorLabel
-              message={errors?.repeat}
-              isDirty={repeat !== undefined}
-            />
-          </fieldset>
-        </form>
-        <p>
-          <div style={{ display: "flex", justifyContent: "space-between" }}>
-            <div>
-              <input
-                class="pure-button"
-                type="submit"
-                value={i18n.str`Close`}
-                onClick={async (e) => {
-                  e.preventDefault();
-                  onClear();
-                }}
-              />
-            </div>
-            <div>
-              <input
-                id="select-exchange"
-                class="pure-button pure-button-primary content"
-                disabled={!!errors}
-                type="submit"
-                value={i18n.str`Confirm`}
-                onClick={async (e) => {
-                  e.preventDefault();
-                  if (!!errors || !password) return;
-                  try {
-                    const r = await changePassword(account, {
-                      new_password: password,
-                    });
-                    onUpdateSuccess();
-                  } catch (error) {
-                    if (error instanceof RequestError) {
-                      notify(buildRequestErrorMessage(i18n, error.cause));
-                    } else {
-                      notifyError(i18n.str`Operation failed, please report`, (error instanceof Error
-                        ? error.message
-                        : JSON.stringify(error)) as TranslatedString)
-                    }
-                  }
-                }}
-              />
-            </div>
-          </div>
-        </p>
-      </div>
-    </div>
-  );
-}
-
-function CreateNewAccount({
-  onClose,
-  onCreateSuccess,
-}: {
-  onClose: () => void;
-  onCreateSuccess: (password: string) => void;
-}): VNode {
-  const { i18n } = useTranslationContext();
-  const { createAccount } = useAdminAccountAPI();
-  const [submitAccount, setSubmitAccount] = useState<
-    SandboxBackend.Circuit.CircuitAccountData | undefined
-  >();
-  return (
-    <div>
-      <div>
-        <h1 class="nav welcome-text">
-          <i18n.Translate>New account</i18n.Translate>
-        </h1>
-      </div>
-
-      <div style={{ maxWidth: 600, overflowX: "hidden", margin: "auto" }}>
-        <AccountForm
-          template={undefined}
-          purpose="create"
-          onChange={(a) => {
-            setSubmitAccount(a);
-          }}
-        />
-
-        <p>
-          <div style={{ display: "flex", justifyContent: "space-between" }}>
-            <div>
-              <input
-                class="pure-button"
-                type="submit"
-                value={i18n.str`Close`}
-                onClick={async (e) => {
-                  e.preventDefault();
-                  onClose();
-                }}
-              />
-            </div>
-            <div>
-              <input
-                id="select-exchange"
-                class="pure-button pure-button-primary content"
-                disabled={!submitAccount}
-                type="submit"
-                value={i18n.str`Confirm`}
-                onClick={async (e) => {
-                  e.preventDefault();
-
-                  if (!submitAccount) return;
-                  try {
-                    const account: SandboxBackend.Circuit.CircuitAccountRequest =
-                    {
-                      cashout_address: submitAccount.cashout_address,
-                      contact_data: submitAccount.contact_data,
-                      internal_iban: submitAccount.iban,
-                      name: submitAccount.name,
-                      username: submitAccount.username,
-                      password: randomPassword(),
-                    };
-
-                    await createAccount(account);
-                    onCreateSuccess(account.password);
-                  } catch (error) {
-                    if (error instanceof RequestError) {
-                      notify(
-                        buildRequestErrorMessage(i18n, error.cause, {
-                          onClientError: (status) =>
-                            status === HttpStatusCode.Forbidden
-                              ? i18n.str`The rights to perform the operation are not sufficient`
-                              : status === HttpStatusCode.BadRequest
-                                ? i18n.str`Input data was invalid`
-                                : status === HttpStatusCode.Conflict
-                                  ? i18n.str`At least one registration detail was not available`
-                                  : undefined,
-                        }),
-                      );
-                    } else {
-                      notifyError(
-                        i18n.str`Operation failed, please report`,
-                        (error instanceof Error
-                          ? error.message
-                          : JSON.stringify(error)) as TranslatedString
-                      )
-                    }
-                  }
-                }}
-              />
-            </div>
-          </div>
-        </p>
-      </div>
-    </div>
-  );
-}
-
-export function ShowAccountDetails({
-  account,
-  onClear,
-  onUpdateSuccess,
-  onLoadNotOk,
-  onChangePassword,
-}: {
-  onLoadNotOk: <T>(
-    error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
-  ) => VNode;
-  onClear?: () => void;
-  onChangePassword: () => void;
-  onUpdateSuccess: () => void;
-  account: string;
-}): VNode {
-  const { i18n } = useTranslationContext();
-  const result = useBusinessAccountDetails(account);
-  const { updateAccount } = useAdminAccountAPI();
-  const [update, setUpdate] = useState(false);
-  const [submitAccount, setSubmitAccount] = useState<
-    SandboxBackend.Circuit.CircuitAccountData | undefined
-  >();
-
-  if (!result.ok) {
-    if (result.loading || result.type === ErrorType.TIMEOUT) {
-      return onLoadNotOk(result);
-    }
-    if (result.status === HttpStatusCode.NotFound) {
-      return <div>account not found</div>;
-    }
-    return onLoadNotOk(result);
-  }
-
-  return (
-    <div>
-      <div>
-        <h1 class="nav welcome-text">
-          <i18n.Translate>Business account details</i18n.Translate>
-        </h1>
-      </div>
-      <div style={{ maxWidth: 600, overflowX: "hidden", margin: "auto" }}>
-        <AccountForm
-          template={result.data}
-          purpose={update ? "update" : "show"}
-          onChange={(a) => setSubmitAccount(a)}
-        />
-
-        <p class="buttons-account">
-          <div
-            style={{
-              display: "flex",
-              justifyContent: "space-between",
-              flexFlow: "wrap-reverse",
-            }}
-          >
-            <div>
-              {onClear ? (
-                <input
-                  class="pure-button"
-                  type="submit"
-                  value={i18n.str`Close`}
-                  onClick={async (e) => {
-                    e.preventDefault();
-                    onClear();
-                  }}
-                />
-              ) : undefined}
-            </div>
-            <div style={{ display: "flex" }}>
-              <div>
-                <input
-                  id="select-exchange"
-                  class="pure-button pure-button-primary content"
-                  disabled={update && !submitAccount}
-                  type="submit"
-                  value={i18n.str`Change password`}
-                  onClick={async (e) => {
-                    e.preventDefault();
-                    onChangePassword();
-                  }}
-                />
-              </div>
-              <div>
-                <input
-                  id="select-exchange"
-                  class="pure-button pure-button-primary content"
-                  disabled={update && !submitAccount}
-                  type="submit"
-                  value={update ? i18n.str`Confirm` : i18n.str`Update`}
-                  onClick={async (e) => {
-                    e.preventDefault();
-
-                    if (!update) {
-                      setUpdate(true);
-                    } else {
-                      if (!submitAccount) return;
-                      try {
-                        await updateAccount(account, {
-                          cashout_address: submitAccount.cashout_address,
-                          contact_data: submitAccount.contact_data,
-                        });
-                        onUpdateSuccess();
-                      } catch (error) {
-                        if (error instanceof RequestError) {
-                          notify(
-                            buildRequestErrorMessage(i18n, error.cause, {
-                              onClientError: (status) =>
-                                status === HttpStatusCode.Forbidden
-                                  ? i18n.str`The rights to change the account are not sufficient`
-                                  : status === HttpStatusCode.NotFound
-                                    ? i18n.str`The username was not found`
-                                    : undefined,
-                            }),
-                          );
-                        } else {
-                          notifyError(
-                            i18n.str`Operation failed, please report`,
-                            (error instanceof Error
-                              ? error.message
-                              : JSON.stringify(error)) as TranslatedString
-                          )
-                        }
-                      }
-                    }
-                  }}
-                />
-              </div>
-            </div>
-          </div>
-        </p>
-      </div>
-    </div>
-  );
-}
-
-function RemoveAccount({
-  account,
-  onClear,
-  onUpdateSuccess,
-  onLoadNotOk,
-}: {
-  onLoadNotOk: <T>(
-    error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
-  ) => VNode;
-  onClear: () => void;
-  onUpdateSuccess: () => void;
-  account: string;
-}): VNode {
-  const { i18n } = useTranslationContext();
-  const result = useAccountDetails(account);
-  const { deleteAccount } = useAdminAccountAPI();
-
-  if (!result.ok) {
-    if (result.loading || result.type === ErrorType.TIMEOUT) {
-      return onLoadNotOk(result);
-    }
-    if (result.status === HttpStatusCode.NotFound) {
-      return <div>account not found</div>;
-    }
-    return onLoadNotOk(result);
-  }
-
-  const balance = Amounts.parse(result.data.balance.amount);
-  if (!balance) {
-    return <div>there was an error reading the balance</div>;
-  }
-  const isBalanceEmpty = Amounts.isZero(balance);
-  return (
-    <div>
-      <div>
-        <h1 class="nav welcome-text">
-          <i18n.Translate>Remove account: {account}</i18n.Translate>
-        </h1>
-      </div>
-      {/* {FXME: SHOW WARNING} */}
-      {/* {!isBalanceEmpty && (
-        <ErrorBannerFloat
-          error={{
-            title: i18n.str`Can't delete the account`,
-            description: i18n.str`Balance is not empty`,
-          }}
-          onClear={() => saveError(undefined)}
-        />
-      )} */}
-
-      <p>
-        <div style={{ display: "flex", justifyContent: "space-between" }}>
-          <div>
-            <input
-              class="pure-button"
-              type="submit"
-              value={i18n.str`Cancel`}
-              onClick={async (e) => {
-                e.preventDefault();
-                onClear();
-              }}
-            />
-          </div>
-          <div>
-            <input
-              id="select-exchange"
-              class="pure-button pure-button-primary content"
-              disabled={!isBalanceEmpty}
-              type="submit"
-              value={i18n.str`Confirm`}
-              onClick={async (e) => {
-                e.preventDefault();
-                try {
-                  const r = await deleteAccount(account);
-                  onUpdateSuccess();
-                } catch (error) {
-                  if (error instanceof RequestError) {
-                    notify(
-                      buildRequestErrorMessage(i18n, error.cause, {
-                        onClientError: (status) =>
-                          status === HttpStatusCode.Forbidden
-                            ? i18n.str`The administrator specified a institutional username`
-                            : status === HttpStatusCode.NotFound
-                              ? i18n.str`The username was not found`
-                              : status === HttpStatusCode.PreconditionFailed
-                                ? i18n.str`Balance was not zero`
-                                : undefined,
-                      }),
-                    );
-                  } else {
-                    notifyError(i18n.str`Operation failed, please report`,
-                      (error instanceof Error
-                          ? error.message
-                          : JSON.stringify(error)) as TranslatedString);
-                  }
-                }
-              }}
-            />
-          </div>
-        </div>
-      </p>
-    </div>
-  );
-}
-/**
- * Create valid account object to update or create
- * Take template as initial values for the form
- * Purpose indicate if all field al read only (show), part of them (update)
- * or none (create)
- * @param param0
- * @returns
- */
-function AccountForm({
-  template,
-  purpose,
-  onChange,
-}: {
-  template: SandboxBackend.Circuit.CircuitAccountData | undefined;
-  onChange: (a: SandboxBackend.Circuit.CircuitAccountData | undefined) => void;
-  purpose: "create" | "update" | "show";
-}): VNode {
-  const initial = initializeFromTemplate(template);
-  const [form, setForm] = useState(initial);
-  const [errors, setErrors] = useState<
-    RecursivePartial<typeof initial> | undefined
-  >(undefined);
-  const { i18n } = useTranslationContext();
-
-  function updateForm(newForm: typeof initial): void {
-    const parsed = !newForm.cashout_address
-      ? undefined
-      : parsePaytoUri(newForm.cashout_address);
-
-    const errors = undefinedIfEmpty<RecursivePartial<typeof initial>>({
-      cashout_address: !newForm.cashout_address
-        ? i18n.str`required`
-        : !parsed
-          ? i18n.str`does not follow the pattern`
-          : !parsed.isKnown || parsed.targetType !== "iban"
-            ? i18n.str`only "IBAN" target are supported`
-            : !IBAN_REGEX.test(parsed.iban)
-              ? i18n.str`IBAN should have just uppercased letters and numbers`
-              : validateIBAN(parsed.iban, i18n),
-      contact_data: undefinedIfEmpty({
-        email: !newForm.contact_data?.email
-          ? i18n.str`required`
-          : !EMAIL_REGEX.test(newForm.contact_data.email)
-            ? i18n.str`it should be an email`
-            : undefined,
-        phone: !newForm.contact_data?.phone
-          ? i18n.str`required`
-          : !newForm.contact_data.phone.startsWith("+")
-            ? i18n.str`should start with +`
-            : !REGEX_JUST_NUMBERS_REGEX.test(newForm.contact_data.phone)
-              ? i18n.str`phone number can't have other than numbers`
-              : undefined,
-      }),
-      iban: !newForm.iban
-        ? undefined //optional field
-        : !IBAN_REGEX.test(newForm.iban)
-          ? i18n.str`IBAN should have just uppercased letters and numbers`
-          : validateIBAN(newForm.iban, i18n),
-      name: !newForm.name ? i18n.str`required` : undefined,
-      username: !newForm.username ? i18n.str`required` : undefined,
-    });
-    setErrors(errors);
-    setForm(newForm);
-    onChange(errors === undefined ? (newForm as any) : undefined);
-  }
-
-  return (
-    <form class="pure-form">
-      <fieldset>
-        <label for="username">
-          {i18n.str`Username`}
-          {purpose === "create" && <b style={{ color: "red" }}>*</b>}
-        </label>
-        <input
-          name="username"
-          type="text"
-          disabled={purpose !== "create"}
-          value={form.username}
-          onChange={(e) => {
-            form.username = e.currentTarget.value;
-            updateForm(structuredClone(form));
-          }}
-        />{" "}
-        <ShowInputErrorLabel
-          message={errors?.username}
-          isDirty={form.username !== undefined}
-        />
-      </fieldset>
-      <fieldset>
-        <label>
-          {i18n.str`Name`}
-          {purpose === "create" && <b style={{ color: "red" }}>*</b>}
-        </label>
-        <input
-          disabled={purpose !== "create"}
-          value={form.name ?? ""}
-          onChange={(e) => {
-            form.name = e.currentTarget.value;
-            updateForm(structuredClone(form));
-          }}
-        />
-        <ShowInputErrorLabel
-          message={errors?.name}
-          isDirty={form.name !== undefined}
-        />
-      </fieldset>
-      {purpose !== "create" && (
-        <fieldset>
-          <label>{i18n.str`Internal IBAN`}</label>
-          <input
-            disabled={true}
-            value={form.iban ?? ""}
-            onChange={(e) => {
-              form.iban = e.currentTarget.value;
-              updateForm(structuredClone(form));
-            }}
-          />
-          <ShowInputErrorLabel
-            message={errors?.iban}
-            isDirty={form.iban !== undefined}
-          />
-        </fieldset>
-      )}
-      <fieldset>
-        <label>
-          {i18n.str`Email`}
-          {purpose !== "show" && <b style={{ color: "red" }}>*</b>}
-        </label>
-        <input
-          disabled={purpose === "show"}
-          value={form.contact_data.email ?? ""}
-          onChange={(e) => {
-            form.contact_data.email = e.currentTarget.value;
-            updateForm(structuredClone(form));
-          }}
-        />
-        <ShowInputErrorLabel
-          message={errors?.contact_data?.email}
-          isDirty={form.contact_data.email !== undefined}
-        />
-      </fieldset>
-      <fieldset>
-        <label>
-          {i18n.str`Phone`}
-          {purpose !== "show" && <b style={{ color: "red" }}>*</b>}
-        </label>
-        <input
-          disabled={purpose === "show"}
-          value={form.contact_data.phone ?? ""}
-          onChange={(e) => {
-            form.contact_data.phone = e.currentTarget.value;
-            updateForm(structuredClone(form));
-          }}
-        />
-        <ShowInputErrorLabel
-          message={errors?.contact_data?.phone}
-          isDirty={form.contact_data?.phone !== undefined}
-        />
-      </fieldset>
-      <fieldset>
-        <label>
-          {i18n.str`Cashout address`}
-          {purpose !== "show" && <b style={{ color: "red" }}>*</b>}
-        </label>
-        <input
-          disabled={purpose === "show"}
-          value={(form.cashout_address ?? "").substring("payto://iban/".length)}
-          onChange={(e) => {
-            form.cashout_address = "payto://iban/" + e.currentTarget.value;
-            updateForm(structuredClone(form));
-          }}
-        />
-        <ShowInputErrorLabel
-          message={errors?.cashout_address}
-          isDirty={form.cashout_address !== undefined}
-        />
-      </fieldset>
-    </form>
-  );
-}
diff --git a/packages/demobank-ui/src/pages/BusinessAccount.tsx b/packages/demobank-ui/src/pages/BusinessAccount.tsx
deleted file mode 100644
index ec71ceca6..000000000
--- a/packages/demobank-ui/src/pages/BusinessAccount.tsx
+++ /dev/null
@@ -1,758 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-import {
-  AmountJson,
-  Amounts,
-  HttpStatusCode,
-  TranslatedString
-} from "@gnu-taler/taler-util";
-import {
-  HttpResponse,
-  HttpResponsePaginated,
-  RequestError,
-  notify,
-  notifyError,
-  notifyInfo,
-  useTranslationContext,
-} from "@gnu-taler/web-util/browser";
-import { Fragment, VNode, h } from "preact";
-import { useEffect, useState } from "preact/hooks";
-import { Cashouts } from "../components/Cashouts/index.js";
-import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
-import { useBackendContext } from "../context/backend.js";
-import { useAccountDetails } from "../hooks/access.js";
-import {
-  useCashoutDetails,
-  useCircuitAccountAPI,
-  useEstimator,
-  useRatiosAndFeeConfig,
-} from "../hooks/circuit.js";
-import {
-  TanChannel,
-  buildRequestErrorMessage,
-  undefinedIfEmpty,
-} from "../utils.js";
-import { ShowAccountDetails, UpdateAccountPassword } from "./AdminPage.js";
-import { handleNotOkResult } from "./HomePage.js";
-import { LoginForm } from "./LoginForm.js";
-import { Amount } from "./PaytoWireTransferForm.js";
-
-interface Props {
-  onClose: () => void;
-  onRegister: () => void;
-  onLoadNotOk: () => void;
-}
-export function BusinessAccount({
-  onClose,
-  onLoadNotOk,
-  onRegister,
-}: Props): VNode {
-  const { i18n } = useTranslationContext();
-  const backend = useBackendContext();
-  const [updatePassword, setUpdatePassword] = useState(false);
-  const [newCashout, setNewcashout] = useState(false);
-  const [showCashoutDetails, setShowCashoutDetails] = useState<
-    string | undefined
-  >();
-
-  if (backend.state.status === "loggedOut") {
-    return <LoginForm onRegister={onRegister} />;
-  }
-
-  if (newCashout) {
-    return (
-      <CreateCashout
-        account={backend.state.username}
-        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
-        onCancel={() => {
-          setNewcashout(false);
-        }}
-        onComplete={(id) => {
-          notifyInfo(
-            i18n.str`Cashout created. You need to confirm the operation to complete the transaction.`,
-          );
-          setNewcashout(false);
-          setShowCashoutDetails(id);
-        }}
-      />
-    );
-  }
-  if (showCashoutDetails) {
-    return (
-      <ShowCashoutDetails
-        id={showCashoutDetails}
-        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
-        onCancel={() => {
-          setShowCashoutDetails(undefined);
-        }}
-      />
-    );
-  }
-  if (updatePassword) {
-    return (
-      <UpdateAccountPassword
-        account={backend.state.username}
-        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
-        onUpdateSuccess={() => {
-          notifyInfo(i18n.str`Password changed`);
-          setUpdatePassword(false);
-        }}
-        onClear={() => {
-          setUpdatePassword(false);
-        }}
-      />
-    );
-  }
-  return (
-    <div>
-      <ShowAccountDetails
-        account={backend.state.username}
-        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
-        onUpdateSuccess={() => {
-          notifyInfo(i18n.str`Account updated`);
-        }}
-        onChangePassword={() => {
-          setUpdatePassword(true);
-        }}
-        onClear={onClose}
-      />
-      <section style={{ marginTop: "2em" }}>
-        <div class="active">
-          <h3>{i18n.str`Latest cashouts`}</h3>
-          <Cashouts
-            account={backend.state.username}
-            onSelected={(id) => {
-              setShowCashoutDetails(id);
-            }}
-          />
-        </div>
-        <br />
-        <div style={{ display: "flex", justifyContent: "space-between" }}>
-          <div />
-          <input
-            class="pure-button pure-button-primary content"
-            type="submit"
-            value={i18n.str`New cashout`}
-            onClick={async (e) => {
-              e.preventDefault();
-              setNewcashout(true);
-            }}
-          />
-        </div>
-      </section>
-    </div>
-  );
-}
-
-interface PropsCashout {
-  account: string;
-  onComplete: (id: string) => void;
-  onCancel: () => void;
-  onLoadNotOk: <T>(
-    error:
-      | HttpResponsePaginated<T, SandboxBackend.SandboxError>
-      | HttpResponse<T, SandboxBackend.SandboxError>,
-  ) => VNode;
-}
-
-type FormType = {
-  isDebit: boolean;
-  amount: string;
-  subject: string;
-  channel: TanChannel;
-};
-type ErrorFrom<T> = {
-  [P in keyof T]+?: string;
-};
-
-// check #7719
-function useRatiosAndFeeConfigWithChangeDetection(): HttpResponse<
-  SandboxBackend.Circuit.Config & { hasChanged?: boolean },
-  SandboxBackend.SandboxError
-> {
-  const result = useRatiosAndFeeConfig();
-  const [oldResult, setOldResult] = useState<
-    SandboxBackend.Circuit.Config | undefined
-  >(undefined);
-  const dataFromBackend = result.ok ? result.data : undefined;
-  useEffect(() => {
-    // save only the first result of /config to the backend
-    if (!dataFromBackend || oldResult !== undefined) return;
-    setOldResult(dataFromBackend);
-  }, [dataFromBackend]);
-
-  if (!result.ok) return result;
-
-  const data = !oldResult ? result.data : oldResult;
-  const hasChanged =
-    oldResult &&
-    (result.data.name !== oldResult.name ||
-      result.data.version !== oldResult.version ||
-      result.data.ratios_and_fees.buy_at_ratio !==
-        oldResult.ratios_and_fees.buy_at_ratio ||
-      result.data.ratios_and_fees.buy_in_fee !==
-        oldResult.ratios_and_fees.buy_in_fee ||
-      result.data.ratios_and_fees.sell_at_ratio !==
-        oldResult.ratios_and_fees.sell_at_ratio ||
-      result.data.ratios_and_fees.sell_out_fee !==
-        oldResult.ratios_and_fees.sell_out_fee ||
-      result.data.fiat_currency !== oldResult.fiat_currency);
-
-  return {
-    ...result,
-    data: { ...data, hasChanged },
-  };
-}
-
-function CreateCashout({
-  account,
-  onComplete,
-  onCancel,
-  onLoadNotOk,
-}: PropsCashout): VNode {
-  const { i18n } = useTranslationContext();
-  const ratiosResult = useRatiosAndFeeConfig();
-  const result = useAccountDetails(account);
-  const {
-    estimateByCredit: calculateFromCredit,
-    estimateByDebit: calculateFromDebit,
-  } = useEstimator();
-  const [form, setForm] = useState<Partial<FormType>>({ isDebit: true });
-
-  const { createCashout } = useCircuitAccountAPI();
-  if (!result.ok) return onLoadNotOk(result);
-  if (!ratiosResult.ok) return onLoadNotOk(ratiosResult);
-  const config = ratiosResult.data;
-
-  const balance = Amounts.parseOrThrow(result.data.balance.amount);
-  const debitThreshold = Amounts.parseOrThrow(result.data.debitThreshold);
-  const zero = Amounts.zeroOfCurrency(balance.currency);
-  const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit";
-  const limit = balanceIsDebit
-    ? Amounts.sub(debitThreshold, balance).amount
-    : Amounts.add(balance, debitThreshold).amount;
-
-  const zeroCalc = { debit: zero, credit: zero, beforeFee: zero };
-  const [calc, setCalc] = useState(zeroCalc);
-  const sellRate = config.ratios_and_fees.sell_at_ratio;
-  const sellFee = !config.ratios_and_fees.sell_out_fee
-    ? zero
-    : Amounts.parseOrThrow(
-        `${balance.currency}:${config.ratios_and_fees.sell_out_fee}`,
-      );
-  const fiatCurrency = config.fiat_currency;
-
-  if (!sellRate || sellRate < 0) return <div>error rate</div>;
-
-  const amount = Amounts.parseOrThrow(
-    `${!form.isDebit ? fiatCurrency : balance.currency}:${
-      !form.amount ? "0" : form.amount
-    }`,
-  );
-
-  useEffect(() => {
-    if (form.isDebit) {
-      calculateFromDebit(amount, sellFee, sellRate)
-        .then((r) => {
-          setCalc(r);
-        })
-        .catch((error) => {
-          notify(
-            error instanceof RequestError
-              ? buildRequestErrorMessage(i18n, error.cause)
-              : {
-                  type: "error",
-                  title: i18n.str`Could not estimate the cashout`,
-                  description: error.message as TranslatedString
-                },
-          );
-        });
-    } else {
-      calculateFromCredit(amount, sellFee, sellRate)
-        .then((r) => {
-          setCalc(r);
-        })
-        .catch((error) => {
-          notify(
-            error instanceof RequestError
-              ? buildRequestErrorMessage(i18n, error.cause)
-              : {
-                  type: "error",
-                  title: i18n.str`Could not estimate the cashout`,
-                  description: error.message,
-                },
-          );
-        });
-    }
-  }, [form.amount, form.isDebit]);
-
-  const balanceAfter = Amounts.sub(balance, calc.debit).amount;
-
-  function updateForm(newForm: typeof form): void {
-    setForm(newForm);
-  }
-  const errors = undefinedIfEmpty<ErrorFrom<typeof form>>({
-    amount: !form.amount
-      ? i18n.str`required`
-      : !amount
-      ? i18n.str`could not be parsed`
-      : Amounts.cmp(limit, calc.debit) === -1
-      ? i18n.str`balance is not enough`
-      : Amounts.cmp(calc.beforeFee, sellFee) === -1
-      ? i18n.str`the total amount to transfer does not cover the fees`
-      : Amounts.isZero(calc.credit)
-      ? i18n.str`the total transfer at destination will be zero`
-      : undefined,
-    channel: !form.channel ? i18n.str`required` : undefined,
-  });
-
-  return (
-    <div>
-      <h1>New cashout</h1>
-      <form class="pure-form">
-        <fieldset>
-          <label>{i18n.str`Subject`}</label>
-          <input
-            value={form.subject ?? ""}
-            onChange={(e) => {
-              form.subject = e.currentTarget.value;
-              updateForm(structuredClone(form));
-            }}
-          />
-          <ShowInputErrorLabel
-            message={errors?.subject}
-            isDirty={form.subject !== undefined}
-          />
-        </fieldset>
-        <fieldset>
-          <label for="amount">
-            {form.isDebit
-              ? i18n.str`Amount to send`
-              : i18n.str`Amount to receive`}
-            
-          </label>
-          <div style={{ display: "flex" }}>
-            <Amount
-              name="amount"
-              currency={amount.currency}
-              value={form.amount}
-              onChange={(v) => {
-                form.amount = v;
-                updateForm(structuredClone(form));
-              }}
-              error={errors?.amount}
-            />
-            <label class="toggle" style={{ marginLeft: 4, marginTop: 0 }}>
-              <input
-                class="toggle-checkbox"
-                type="checkbox"
-                name="asd"
-                onChange={(e): void => {
-                  console.log("asdasd", form.isDebit);
-                  form.isDebit = !form.isDebit;
-                  updateForm(structuredClone(form));
-                }}
-              />
-              <div class="toggle-switch"></div>
-            </label>
-          </div>
-        </fieldset>
-        <fieldset>
-          <label>{i18n.str`Conversion rate`}</label>
-          <input value={sellRate} disabled />
-        </fieldset>
-        <fieldset>
-          <label for="balance-now">{i18n.str`Balance now`}</label>
-          <Amount
-            name="banace-now"
-            currency={balance.currency}
-            value={Amounts.stringifyValue(balance)}
-          />
-        </fieldset>
-        <fieldset>
-          <label for="total-cost"
-            style={{ fontWeight: "bold", color: "red" }}
-          >{i18n.str`Total cost`}</label>
-          <Amount
-            name="total-cost"
-            currency={balance.currency}
-            value={Amounts.stringifyValue(calc.debit)}
-          />
-        </fieldset>
-        <fieldset>
-          <label for="balance-after">{i18n.str`Balance after`}</label>
-          <Amount
-            name="balance-after"
-            currency={balance.currency}
-            value={balanceAfter ? Amounts.stringifyValue(balanceAfter) : ""}
-          />
-        </fieldset>{" "}
-        {Amounts.isZero(sellFee) ? undefined : (
-          <Fragment>
-            <fieldset>
-              <label for="amount-conversiojn">{i18n.str`Amount after conversion`}</label>
-              <Amount
-                name="amount-conversion"
-                currency={fiatCurrency}
-                value={Amounts.stringifyValue(calc.beforeFee)}
-              />
-            </fieldset>
-
-            <fieldset>
-              <label form="cashout-fee">{i18n.str`Cashout fee`}</label>
-              <Amount
-                name="cashout-fee"
-                currency={fiatCurrency}
-                value={Amounts.stringifyValue(sellFee)}
-              />
-            </fieldset>
-          </Fragment>
-        )}
-        <fieldset>
-          <label for="total"
-            style={{ fontWeight: "bold", color: "green" }}
-          >{i18n.str`Total cashout transfer`}</label>
-          <Amount
-            name="total"
-            currency={fiatCurrency}
-            value={Amounts.stringifyValue(calc.credit)}
-          />
-        </fieldset>
-        <fieldset>
-          <label>{i18n.str`Confirmation channel`}</label>
-
-          <div class="channel">
-            <input
-              class={
-                "pure-button content " +
-                (form.channel === TanChannel.EMAIL
-                  ? "pure-button-primary"
-                  : "pure-button-secondary")
-              }
-              type="submit"
-              value={i18n.str`Email`}
-              onClick={async (e) => {
-                e.preventDefault();
-                form.channel = TanChannel.EMAIL;
-                updateForm(structuredClone(form));
-              }}
-            />
-            <input
-              class={
-                "pure-button content " +
-                (form.channel === TanChannel.SMS
-                  ? "pure-button-primary"
-                  : "pure-button-secondary")
-              }
-              type="submit"
-              value={i18n.str`SMS`}
-              onClick={async (e) => {
-                e.preventDefault();
-                form.channel = TanChannel.SMS;
-                updateForm(structuredClone(form));
-              }}
-            />
-            <input
-              class={
-                "pure-button content " +
-                (form.channel === TanChannel.FILE
-                  ? "pure-button-primary"
-                  : "pure-button-secondary")
-              }
-              type="submit"
-              value={i18n.str`FILE`}
-              onClick={async (e) => {
-                e.preventDefault();
-                form.channel = TanChannel.FILE;
-                updateForm(structuredClone(form));
-              }}
-            />
-          </div>
-          <ShowInputErrorLabel
-            message={errors?.channel}
-            isDirty={form.channel !== undefined}
-          />
-        </fieldset>
-        <br />
-        <div style={{ display: "flex", justifyContent: "space-between" }}>
-          <button
-            class="pure-button pure-button-secondary btn-cancel"
-            onClick={(e) => {
-              e.preventDefault();
-              onCancel();
-            }}
-          >
-            {i18n.str`Cancel`}
-          </button>
-
-          <button
-            class="pure-button pure-button-primary btn-register"
-            type="submit"
-            disabled={!!errors}
-            onClick={async (e) => {
-              e.preventDefault();
-
-              if (errors) return;
-              try {
-                const res = await createCashout({
-                  amount_credit: Amounts.stringify(calc.credit),
-                  amount_debit: Amounts.stringify(calc.debit),
-                  subject: form.subject,
-                  tan_channel: form.channel,
-                });
-                onComplete(res.data.uuid);
-              } catch (error) {
-                if (error instanceof RequestError) {
-                  notify(
-                    buildRequestErrorMessage(i18n, error.cause, {
-                      onClientError: (status) =>
-                        status === HttpStatusCode.BadRequest
-                          ? i18n.str`The exchange rate was incorrectly applied`
-                          : status === HttpStatusCode.Forbidden
-                          ? i18n.str`A institutional user tried the operation`
-                          : status === HttpStatusCode.Conflict
-                          ? i18n.str`Need a contact data where to send the TAN`
-                          : status === HttpStatusCode.PreconditionFailed
-                          ? i18n.str`The account does not have sufficient funds`
-                          : undefined,
-                      onServerError: (status) =>
-                        status === HttpStatusCode.ServiceUnavailable
-                          ? i18n.str`The bank does not support the TAN channel for this operation`
-                          : undefined,
-                    }),
-                  );
-                } else {
-                  notifyError(
-                    i18n.str`Operation failed, please report`,
-                    (error instanceof Error
-                      ? error.message
-                      : JSON.stringify(error)) as TranslatedString
-                  )
-            }
-              }
-            }}
-          >
-            {i18n.str`Create`}
-          </button>
-        </div>
-      </form>
-    </div>
-  );
-}
-
-interface ShowCashoutProps {
-  id: string;
-  onCancel: () => void;
-  onLoadNotOk: <T>(
-    error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
-  ) => VNode;
-}
-export function ShowCashoutDetails({
-  id,
-  onCancel,
-  onLoadNotOk,
-}: ShowCashoutProps): VNode {
-  const { i18n } = useTranslationContext();
-  const result = useCashoutDetails(id);
-  const { abortCashout, confirmCashout } = useCircuitAccountAPI();
-  const [code, setCode] = useState<string | undefined>(undefined);
-  if (!result.ok) return onLoadNotOk(result);
-  const errors = undefinedIfEmpty({
-    code: !code ? i18n.str`required` : undefined,
-  });
-  const isPending = String(result.data.status).toUpperCase() === "PENDING";
-  return (
-    <div>
-      <h1>Cashout details {id}</h1>
-      <form class="pure-form">
-        <fieldset>
-          <label>
-            <i18n.Translate>Subject</i18n.Translate>
-          </label>
-          <input readOnly value={result.data.subject} />
-        </fieldset>
-        <fieldset>
-          <label>
-            <i18n.Translate>Created</i18n.Translate>
-          </label>
-          <input readOnly value={result.data.creation_time ?? ""} />
-        </fieldset>
-        <fieldset>
-          <label>
-            <i18n.Translate>Confirmed</i18n.Translate>
-          </label>
-          <input readOnly value={result.data.confirmation_time ?? ""} />
-        </fieldset>
-        <fieldset>
-          <label>
-            <i18n.Translate>Debited</i18n.Translate>
-          </label>
-          <input readOnly value={result.data.amount_debit} />
-        </fieldset>
-        <fieldset>
-          <label>
-            <i18n.Translate>Credit</i18n.Translate>
-          </label>
-          <input readOnly value={result.data.amount_credit} />
-        </fieldset>
-        <fieldset>
-          <label>
-            <i18n.Translate>Status</i18n.Translate>
-          </label>
-          <input readOnly value={result.data.status} />
-        </fieldset>
-        <fieldset>
-          <label>
-            <i18n.Translate>Destination</i18n.Translate>
-          </label>
-          <input readOnly value={result.data.cashout_address} />
-        </fieldset>
-        {isPending ? (
-          <fieldset>
-            <label>
-              <i18n.Translate>Code</i18n.Translate>
-            </label>
-            <input
-              value={code ?? ""}
-              onChange={(e) => {
-                setCode(e.currentTarget.value);
-              }}
-            />
-            <ShowInputErrorLabel
-              message={errors?.code}
-              isDirty={code !== undefined}
-            />
-          </fieldset>
-        ) : undefined}
-      </form>
-      <br />
-      <div style={{ display: "flex", justifyContent: "space-between" }}>
-        <button
-          class="pure-button pure-button-secondary btn-cancel"
-          onClick={(e) => {
-            e.preventDefault();
-            onCancel();
-          }}
-        >
-          {i18n.str`Back`}
-        </button>
-        {isPending ? (
-          <div>
-            <button
-              type="submit"
-              class="pure-button pure-button-primary button-error"
-              onClick={async (e) => {
-                e.preventDefault();
-                try {
-                  await abortCashout(id);
-                  onCancel();
-                } catch (error) {
-                  if (error instanceof RequestError) {
-                    notify(
-                      buildRequestErrorMessage(i18n, error.cause, {
-                        onClientError: (status) =>
-                          status === HttpStatusCode.NotFound
-                            ? i18n.str`Cashout not found. It may be also mean that it was already aborted.`
-                            : status === HttpStatusCode.PreconditionFailed
-                            ? i18n.str`Cashout was already confimed`
-                            : undefined,
-                      }),
-                    );
-                  } else {
-                    notifyError(
-                      i18n.str`Operation failed, please report`,
-                      (error instanceof Error
-                        ? error.message
-                        : JSON.stringify(error)) as TranslatedString
-                    )
-                }
-                }
-              }}
-            >
-              {i18n.str`Abort`}
-            </button>
-            &nbsp;
-            <button
-              type="submit"
-              disabled={!code}
-              class="pure-button pure-button-primary "
-              onClick={async (e) => {
-                e.preventDefault();
-                try {
-                  if (!code) return;
-                  const rest = await confirmCashout(id, {
-                    tan: code,
-                  });
-                } catch (error) {
-                  if (error instanceof RequestError) {
-                    notify(
-                      buildRequestErrorMessage(i18n, error.cause, {
-                        onClientError: (status) =>
-                          status === HttpStatusCode.NotFound
-                            ? i18n.str`Cashout not found. It may be also mean that it was already aborted.`
-                            : status === HttpStatusCode.PreconditionFailed
-                            ? i18n.str`Cashout was already confimed`
-                            : status === HttpStatusCode.Conflict
-                            ? i18n.str`Confirmation failed. Maybe the user changed their cash-out address between the creation and the confirmation`
-                            : status === HttpStatusCode.Forbidden
-                            ? i18n.str`Invalid code`
-                            : undefined,
-                      }),
-                    );
-                  } else {
-                    notifyError(
-                      i18n.str`Operation failed, please report`,
-                      (error instanceof Error
-                        ? error.message
-                        : JSON.stringify(error)) as TranslatedString
-                    )
-                }
-                }
-              }}
-            >
-              {i18n.str`Confirm`}
-            </button>
-          </div>
-        ) : (
-          <div />
-        )}
-      </div>
-    </div>
-  );
-}
-
-const MAX_AMOUNT_DIGIT = 2;
-/**
- * Truncate the amount of digits to display
- * in the form based on the fee calculations
- *
- * Backend must have the same truncation
- * @param a
- * @returns
- */
-function truncate(a: AmountJson): AmountJson {
-  const str = Amounts.stringify(a);
-  const idx = str.indexOf(".");
-  if (idx === -1) {
-    return a;
-  }
-  const truncated = str.substring(0, idx + 1 + MAX_AMOUNT_DIGIT);
-  return Amounts.parseOrThrow(truncated);
-}
-
-export function assertUnreachable(x: never): never {
-  throw new Error("Didn't expect to get here");
-}
diff --git a/packages/demobank-ui/src/pages/HomePage.tsx b/packages/demobank-ui/src/pages/HomePage.tsx
index e82e46eb2..a911f347c 100644
--- a/packages/demobank-ui/src/pages/HomePage.tsx
+++ b/packages/demobank-ui/src/pages/HomePage.tsx
@@ -35,7 +35,7 @@ import { useBackendContext } from "../context/backend.js";
 import { getInitialBackendBaseURL } from "../hooks/backend.js";
 import { useSettings } from "../hooks/settings.js";
 import { AccountPage } from "./AccountPage/index.js";
-import { AdminPage } from "./AdminPage.js";
+import { AdminHome } from "./admin/Home.js";
 import { LoginForm } from "./LoginForm.js";
 import { WithdrawalQRCode } from "./WithdrawalQRCode.js";
 import { error } from "console";
@@ -54,31 +54,24 @@ const logger = new Logger("AccountPage");
  */
 export function HomePage({
   onRegister,
+  account,
   onPendingOperationFound,
 }: {
+  account: string,
   onPendingOperationFound: (id: string) => void;
   onRegister: () => void;
 }): VNode {
-  const backend = useBackendContext();
   const [settings] = useSettings();
   const { i18n } = useTranslationContext();
 
-  if (backend.state.status === "loggedOut") {
-    return <LoginForm onRegister={onRegister} />;
-  }
-
   if (settings.currentWithdrawalOperationId) {
     onPendingOperationFound(settings.currentWithdrawalOperationId);
     return <Loading />;
   }
 
-  if (backend.state.isUserAdministrator) {
-    return <AdminPage onRegister={onRegister} />;
-  }
-
   return (
     <AccountPage
-      account={backend.state.username}
+      account={account}
       onLoadNotOk={handleNotOkResult(i18n, onRegister)}
     />
   );
@@ -105,8 +98,8 @@ export function WithdrawalOperationPage({
 
   if (!parsedUri) {
     notifyError(
-      i18n.str`The Withdrawal URI is not valid: "${uri}"`,
-      undefined
+      i18n.str`The Withdrawal URI is not valid`,
+      uri as TranslatedString
     );
     return <Loading />;
   }
diff --git a/packages/demobank-ui/src/pages/ShowAccountDetails.tsx b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
new file mode 100644
index 000000000..91b50b84c
--- /dev/null
+++ b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
@@ -0,0 +1,143 @@
+import { ErrorType, HttpResponsePaginated, RequestError, notify, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { VNode,h } from "preact";
+import { useAdminAccountAPI, useBusinessAccountDetails } from "../hooks/circuit.js";
+import { useState } from "preact/hooks";
+import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
+import { buildRequestErrorMessage } from "../utils.js";
+import { AccountForm } from "./admin/AccountForm.js";
+
+export function ShowAccountDetails({
+    account,
+    onClear,
+    onUpdateSuccess,
+    onLoadNotOk,
+    onChangePassword,
+  }: {
+    onLoadNotOk: <T>(
+      error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
+    ) => VNode;
+    onClear?: () => void;
+    onChangePassword: () => void;
+    onUpdateSuccess: () => void;
+    account: string;
+  }): VNode {
+    const { i18n } = useTranslationContext();
+    const result = useBusinessAccountDetails(account);
+    const { updateAccount } = useAdminAccountAPI();
+    const [update, setUpdate] = useState(false);
+    const [submitAccount, setSubmitAccount] = useState<
+      SandboxBackend.Circuit.CircuitAccountData | undefined
+    >();
+  
+    if (!result.ok) {
+      if (result.loading || result.type === ErrorType.TIMEOUT) {
+        return onLoadNotOk(result);
+      }
+      if (result.status === HttpStatusCode.NotFound) {
+        return <div>account not found</div>;
+      }
+      return onLoadNotOk(result);
+    }
+  
+    return (
+      <div>
+        <div>
+          <h1 class="nav welcome-text">
+            <i18n.Translate>Business account details</i18n.Translate>
+          </h1>
+        </div>
+        <div style={{ maxWidth: 600, overflowX: "hidden", margin: "auto" }}>
+          <AccountForm
+            template={result.data}
+            purpose={update ? "update" : "show"}
+            onChange={(a) => setSubmitAccount(a)}
+          />
+  
+          <p class="buttons-account">
+            <div
+              style={{
+                display: "flex",
+                justifyContent: "space-between",
+                flexFlow: "wrap-reverse",
+              }}
+            >
+              <div>
+                {onClear ? (
+                  <input
+                    class="pure-button"
+                    type="submit"
+                    value={i18n.str`Close`}
+                    onClick={async (e) => {
+                      e.preventDefault();
+                      onClear();
+                    }}
+                  />
+                ) : undefined}
+              </div>
+              <div style={{ display: "flex" }}>
+                <div>
+                  <input
+                    id="select-exchange"
+                    class="pure-button pure-button-primary content"
+                    disabled={update && !submitAccount}
+                    type="submit"
+                    value={i18n.str`Change password`}
+                    onClick={async (e) => {
+                      e.preventDefault();
+                      onChangePassword();
+                    }}
+                  />
+                </div>
+                <div>
+                  <input
+                    id="select-exchange"
+                    class="pure-button pure-button-primary content"
+                    disabled={update && !submitAccount}
+                    type="submit"
+                    value={update ? i18n.str`Confirm` : i18n.str`Update`}
+                    onClick={async (e) => {
+                      e.preventDefault();
+  
+                      if (!update) {
+                        setUpdate(true);
+                      } else {
+                        if (!submitAccount) return;
+                        try {
+                          await updateAccount(account, {
+                            cashout_address: submitAccount.cashout_address,
+                            contact_data: submitAccount.contact_data,
+                          });
+                          onUpdateSuccess();
+                        } catch (error) {
+                          if (error instanceof RequestError) {
+                            notify(
+                              buildRequestErrorMessage(i18n, error.cause, {
+                                onClientError: (status) =>
+                                  status === HttpStatusCode.Forbidden
+                                    ? i18n.str`The rights to change the account are not sufficient`
+                                    : status === HttpStatusCode.NotFound
+                                      ? i18n.str`The username was not found`
+                                      : undefined,
+                              }),
+                            );
+                          } else {
+                            notifyError(
+                              i18n.str`Operation failed, please report`,
+                              (error instanceof Error
+                                ? error.message
+                                : JSON.stringify(error)) as TranslatedString
+                            )
+                          }
+                        }
+                      }
+                    }}
+                  />
+                </div>
+              </div>
+            </div>
+          </p>
+        </div>
+      </div>
+    );
+  }
+  
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
new file mode 100644
index 000000000..084a5b643
--- /dev/null
+++ b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
@@ -0,0 +1,131 @@
+import { ErrorType, HttpResponsePaginated, RequestError, notify, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { useAdminAccountAPI, useBusinessAccountDetails } from "../hooks/circuit.js";
+import { useState } from "preact/hooks";
+import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
+import { VNode,h ,Fragment} from "preact";
+import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
+import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
+
+export function UpdateAccountPassword({
+    account,
+    onClear,
+    onUpdateSuccess,
+    onLoadNotOk,
+  }: {
+    onLoadNotOk: <T>(
+      error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
+    ) => VNode;
+    onClear: () => void;
+    onUpdateSuccess: () => void;
+    account: string;
+  }): VNode {
+    const { i18n } = useTranslationContext();
+    const result = useBusinessAccountDetails(account);
+    const { changePassword } = useAdminAccountAPI();
+    const [password, setPassword] = useState<string | undefined>();
+    const [repeat, setRepeat] = useState<string | undefined>();
+  
+    if (!result.ok) {
+      if (result.loading || result.type === ErrorType.TIMEOUT) {
+        return onLoadNotOk(result);
+      }
+      if (result.status === HttpStatusCode.NotFound) {
+        return <div>account not found</div>;
+      }
+      return onLoadNotOk(result);
+    }
+  
+    const errors = undefinedIfEmpty({
+      password: !password ? i18n.str`required` : undefined,
+      repeat: !repeat
+        ? i18n.str`required`
+        : password !== repeat
+          ? i18n.str`password doesn't match`
+          : undefined,
+    });
+  
+    return (
+      <div>
+        <div>
+          <h1 class="nav welcome-text">
+            <i18n.Translate>Update password for {account}</i18n.Translate>
+          </h1>
+        </div>
+  
+        <div style={{ maxWidth: 600, overflowX: "hidden", margin: "auto" }}>
+          <form class="pure-form">
+            <fieldset>
+              <label>{i18n.str`Password`}</label>
+              <input
+                type="password"
+                value={password ?? ""}
+                onChange={(e) => {
+                  setPassword(e.currentTarget.value);
+                }}
+              />
+              <ShowInputErrorLabel
+                message={errors?.password}
+                isDirty={password !== undefined}
+              />
+            </fieldset>
+            <fieldset>
+              <label>{i18n.str`Repeat password`}</label>
+              <input
+                type="password"
+                value={repeat ?? ""}
+                onChange={(e) => {
+                  setRepeat(e.currentTarget.value);
+                }}
+              />
+              <ShowInputErrorLabel
+                message={errors?.repeat}
+                isDirty={repeat !== undefined}
+              />
+            </fieldset>
+          </form>
+          <p>
+            <div style={{ display: "flex", justifyContent: "space-between" }}>
+              <div>
+                <input
+                  class="pure-button"
+                  type="submit"
+                  value={i18n.str`Close`}
+                  onClick={async (e) => {
+                    e.preventDefault();
+                    onClear();
+                  }}
+                />
+              </div>
+              <div>
+                <input
+                  id="select-exchange"
+                  class="pure-button pure-button-primary content"
+                  disabled={!!errors}
+                  type="submit"
+                  value={i18n.str`Confirm`}
+                  onClick={async (e) => {
+                    e.preventDefault();
+                    if (!!errors || !password) return;
+                    try {
+                      const r = await changePassword(account, {
+                        new_password: password,
+                      });
+                      onUpdateSuccess();
+                    } catch (error) {
+                      if (error instanceof RequestError) {
+                        notify(buildRequestErrorMessage(i18n, error.cause));
+                      } else {
+                        notifyError(i18n.str`Operation failed, please report`, (error instanceof Error
+                          ? error.message
+                          : JSON.stringify(error)) as TranslatedString)
+                      }
+                    }
+                  }}
+                />
+              </div>
+            </div>
+          </p>
+        </div>
+      </div>
+    );
+  }
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index ced152feb..30fcbdff7 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -317,7 +317,8 @@ export function WithdrawalConfirmationQuestion({
                   </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">Amount</dt>
-                    <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{Amounts.stringifyValue(details.amount)}</dd>
+                    <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">To be added</dd>
+                    {/* Amounts.stringifyValue(details.amount) */}
                   </div>
                 </dl>
               </div>
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index b48e3b1dc..2a3a1ec2c 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -100,10 +100,6 @@ export function WithdrawalQRCode({
   }
 
   if (data.confirmation_done) {
-    if (!settings.showWithdrawalSuccess) {
-      clearCurrentWithdrawal()
-      onContinue()
-    }
     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">
diff --git a/packages/demobank-ui/src/pages/admin/Account.tsx b/packages/demobank-ui/src/pages/admin/Account.tsx
new file mode 100644
index 000000000..8ab3e1323
--- /dev/null
+++ b/packages/demobank-ui/src/pages/admin/Account.tsx
@@ -0,0 +1,56 @@
+import { Amounts } from "@gnu-taler/taler-util";
+import { PaytoWireTransferForm } from "../PaytoWireTransferForm.js";
+import { handleNotOkResult } from "../HomePage.js";
+import { useAccountDetails } from "../../hooks/access.js";
+import { useBackendContext } from "../../context/backend.js";
+import { notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { Fragment, h, VNode } from "preact";
+
+export function AdminAccount({ onRegister }: { onRegister: () => void }): VNode {
+    const { i18n } = useTranslationContext();
+    const r = useBackendContext();
+    const account = r.state.status === "loggedIn" ? r.state.username : "admin";
+    const result = useAccountDetails(account);
+  
+    if (!result.ok) {
+      return handleNotOkResult(i18n, onRegister)(result);
+    }
+    const { data } = result;
+    const balance = Amounts.parseOrThrow(data.balance.amount);
+    const debitThreshold = Amounts.parseOrThrow(result.data.debitThreshold);
+    const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit";
+    const limit = balanceIsDebit
+      ? Amounts.sub(debitThreshold, balance).amount
+      : Amounts.add(balance, debitThreshold).amount;
+    if (!balance) return <Fragment />;
+    return (
+      <Fragment>
+        <section id="assets">
+          <div class="asset-summary">
+            <h2>{i18n.str`Bank account balance`}</h2>
+            {!balance ? (
+              <div class="large-amount" style={{ color: "gray" }}>
+                Waiting server response...
+              </div>
+            ) : (
+              <div class="large-amount amount">
+                {balanceIsDebit ? <b>-</b> : null}
+                <span class="value">{`${Amounts.stringifyValue(balance)}`}</span>
+                &nbsp;
+                <span class="currency">{`${balance.currency}`}</span>
+              </div>
+            )}
+          </div>
+        </section>
+        <PaytoWireTransferForm
+          focus
+          limit={limit}
+          onSuccess={() => {
+            notifyInfo(i18n.str`Wire transfer created!`);
+          }}
+          onCancel={undefined}
+        />
+      </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
new file mode 100644
index 000000000..9ca0323a1
--- /dev/null
+++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
@@ -0,0 +1,219 @@
+import { VNode,h  } from "preact";
+import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
+import { PartialButDefined, RecursivePartial, WithIntermediate, undefinedIfEmpty, validateIBAN } from "../../utils.js";
+import { useState } from "preact/hooks";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { parsePaytoUri } from "@gnu-taler/taler-util";
+
+const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/;
+const EMAIL_REGEX =
+  /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+const REGEX_JUST_NUMBERS_REGEX = /^\+[0-9 ]*$/;
+
+/**
+ * Create valid account object to update or create
+ * Take template as initial values for the form
+ * Purpose indicate if all field al read only (show), part of them (update)
+ * or none (create)
+ * @param param0
+ * @returns
+ */
+export function AccountForm({
+    template,
+    purpose,
+    onChange,
+  }: {
+    template: SandboxBackend.Circuit.CircuitAccountData | undefined;
+    onChange: (a: SandboxBackend.Circuit.CircuitAccountData | undefined) => void;
+    purpose: "create" | "update" | "show";
+  }): VNode {
+    const initial = initializeFromTemplate(template);
+    const [form, setForm] = useState(initial);
+    const [errors, setErrors] = useState<
+      RecursivePartial<typeof initial> | undefined
+    >(undefined);
+    const { i18n } = useTranslationContext();
+  
+    function updateForm(newForm: typeof initial): void {
+      const parsed = !newForm.cashout_address
+        ? undefined
+        : parsePaytoUri(newForm.cashout_address);
+  
+      const errors = undefinedIfEmpty<RecursivePartial<typeof initial>>({
+        cashout_address: !newForm.cashout_address
+          ? i18n.str`required`
+          : !parsed
+            ? i18n.str`does not follow the pattern`
+            : !parsed.isKnown || parsed.targetType !== "iban"
+              ? i18n.str`only "IBAN" target are supported`
+              : !IBAN_REGEX.test(parsed.iban)
+                ? i18n.str`IBAN should have just uppercased letters and numbers`
+                : validateIBAN(parsed.iban, i18n),
+        contact_data: undefinedIfEmpty({
+          email: !newForm.contact_data?.email
+            ? i18n.str`required`
+            : !EMAIL_REGEX.test(newForm.contact_data.email)
+              ? i18n.str`it should be an email`
+              : undefined,
+          phone: !newForm.contact_data?.phone
+            ? i18n.str`required`
+            : !newForm.contact_data.phone.startsWith("+")
+              ? i18n.str`should start with +`
+              : !REGEX_JUST_NUMBERS_REGEX.test(newForm.contact_data.phone)
+                ? i18n.str`phone number can't have other than numbers`
+                : undefined,
+        }),
+        iban: !newForm.iban
+          ? undefined //optional field
+          : !IBAN_REGEX.test(newForm.iban)
+            ? i18n.str`IBAN should have just uppercased letters and numbers`
+            : validateIBAN(newForm.iban, i18n),
+        name: !newForm.name ? i18n.str`required` : undefined,
+        username: !newForm.username ? i18n.str`required` : undefined,
+      });
+      setErrors(errors);
+      setForm(newForm);
+      onChange(errors === undefined ? (newForm as any) : undefined);
+    }
+  
+    return (
+      <form class="pure-form">
+        <fieldset>
+          <label for="username">
+            {i18n.str`Username`}
+            {purpose === "create" && <b style={{ color: "red" }}>*</b>}
+          </label>
+          <input
+            name="username"
+            type="text"
+            disabled={purpose !== "create"}
+            value={form.username}
+            onChange={(e) => {
+              form.username = e.currentTarget.value;
+              updateForm(structuredClone(form));
+            }}
+          />{" "}
+          <ShowInputErrorLabel
+            message={errors?.username}
+            isDirty={form.username !== undefined}
+          />
+        </fieldset>
+        <fieldset>
+          <label>
+            {i18n.str`Name`}
+            {purpose === "create" && <b style={{ color: "red" }}>*</b>}
+          </label>
+          <input
+            disabled={purpose !== "create"}
+            value={form.name ?? ""}
+            onChange={(e) => {
+              form.name = e.currentTarget.value;
+              updateForm(structuredClone(form));
+            }}
+          />
+          <ShowInputErrorLabel
+            message={errors?.name}
+            isDirty={form.name !== undefined}
+          />
+        </fieldset>
+        {purpose !== "create" && (
+          <fieldset>
+            <label>{i18n.str`Internal IBAN`}</label>
+            <input
+              disabled={true}
+              value={form.iban ?? ""}
+              onChange={(e) => {
+                form.iban = e.currentTarget.value;
+                updateForm(structuredClone(form));
+              }}
+            />
+            <ShowInputErrorLabel
+              message={errors?.iban}
+              isDirty={form.iban !== undefined}
+            />
+          </fieldset>
+        )}
+        <fieldset>
+          <label>
+            {i18n.str`Email`}
+            {purpose !== "show" && <b style={{ color: "red" }}>*</b>}
+          </label>
+          <input
+            disabled={purpose === "show"}
+            value={form.contact_data.email ?? ""}
+            onChange={(e) => {
+              form.contact_data.email = e.currentTarget.value;
+              updateForm(structuredClone(form));
+            }}
+          />
+          <ShowInputErrorLabel
+            message={errors?.contact_data?.email}
+            isDirty={form.contact_data.email !== undefined}
+          />
+        </fieldset>
+        <fieldset>
+          <label>
+            {i18n.str`Phone`}
+            {purpose !== "show" && <b style={{ color: "red" }}>*</b>}
+          </label>
+          <input
+            disabled={purpose === "show"}
+            value={form.contact_data.phone ?? ""}
+            onChange={(e) => {
+              form.contact_data.phone = e.currentTarget.value;
+              updateForm(structuredClone(form));
+            }}
+          />
+          <ShowInputErrorLabel
+            message={errors?.contact_data?.phone}
+            isDirty={form.contact_data?.phone !== undefined}
+          />
+        </fieldset>
+        <fieldset>
+          <label>
+            {i18n.str`Cashout address`}
+            {purpose !== "show" && <b style={{ color: "red" }}>*</b>}
+          </label>
+          <input
+            disabled={purpose === "show"}
+            value={(form.cashout_address ?? "").substring("payto://iban/".length)}
+            onChange={(e) => {
+              form.cashout_address = "payto://iban/" + e.currentTarget.value;
+              updateForm(structuredClone(form));
+            }}
+          />
+          <ShowInputErrorLabel
+            message={errors?.cashout_address}
+            isDirty={form.cashout_address !== undefined}
+          />
+        </fieldset>
+      </form>
+    );
+  }
+  
+  function initializeFromTemplate(
+    account: SandboxBackend.Circuit.CircuitAccountData | undefined,
+  ): WithIntermediate<SandboxBackend.Circuit.CircuitAccountData> {
+    const emptyAccount = {
+      cashout_address: undefined,
+      iban: undefined,
+      name: undefined,
+      username: undefined,
+      contact_data: undefined,
+    };
+    const emptyContact = {
+      email: undefined,
+      phone: undefined,
+    };
+  
+    const initial: PartialButDefined<SandboxBackend.Circuit.CircuitAccountData> =
+      structuredClone(account) ?? emptyAccount;
+    if (typeof initial.contact_data === "undefined") {
+      initial.contact_data = emptyContact;
+    }
+    initial.contact_data.email;
+    return initial as any;
+  }
+  
+  
+  
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/admin/AccountList.tsx b/packages/demobank-ui/src/pages/admin/AccountList.tsx
new file mode 100644
index 000000000..56b15818b
--- /dev/null
+++ b/packages/demobank-ui/src/pages/admin/AccountList.tsx
@@ -0,0 +1,120 @@
+import { h, VNode } from "preact";
+import { useBusinessAccounts } from "../../hooks/circuit.js";
+import { handleNotOkResult } from "../HomePage.js";
+import { AccountAction } from "./Home.js";
+import { Amounts } from "@gnu-taler/taler-util";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
+
+interface Props {
+    onAction: (type: AccountAction, account: string) => void;
+    account: string | undefined;
+    onRegister: () => void;
+
+}
+
+export function AccountList({ account, onAction, onRegister }: Props): VNode {
+    const result = useBusinessAccounts({ account });
+    const { i18n } = useTranslationContext();
+
+    if (result.loading) return <div />;
+    if (!result.ok) {
+        return handleNotOkResult(i18n, onRegister)(result);
+    }
+
+    const { customers } = result.data;
+    return <section
+        id="main"
+        style={{ width: 600, marginLeft: "auto", marginRight: "auto" }}
+    >
+        {!customers.length ? (
+            <div></div>
+        ) : (
+            <article>
+                <h2>{i18n.str`Accounts:`}</h2>
+                <div class="results">
+                    <table class="pure-table pure-table-striped">
+                        <thead>
+                            <tr>
+                                <th>{i18n.str`Username`}</th>
+                                <th>{i18n.str`Name`}</th>
+                                <th>{i18n.str`Balance`}</th>
+                                <th>{i18n.str`Actions`}</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            {customers.map((item, idx) => {
+                                const balance = !item.balance
+                                    ? undefined
+                                    : Amounts.parse(item.balance.amount);
+                                const balanceIsDebit =
+                                    item.balance &&
+                                    item.balance.credit_debit_indicator == "debit";
+                                return (
+                                    <tr key={idx}>
+                                        <td>
+                                            <a
+                                                href="#"
+                                                onClick={(e) => {
+                                                    e.preventDefault();
+                                                    onAction("show-details", item.username)
+                                                }}
+                                            >
+                                                {item.username}
+                                            </a>
+                                        </td>
+                                        <td>{item.name}</td>
+                                        <td>
+                                            {!balance ? (
+                                                i18n.str`unknown`
+                                            ) : (
+                                                <span class="amount">
+                                                    {balanceIsDebit ? <b>-</b> : null}
+                                                    <span class="value">{`${Amounts.stringifyValue(
+                                                        balance,
+                                                    )}`}</span>
+                                                    &nbsp;
+                                                    <span class="currency">{`${balance.currency}`}</span>
+                                                </span>
+                                            )}
+                                        </td>
+                                        <td>
+                                            <a
+                                                href="#"
+                                                onClick={(e) => {
+                                                    e.preventDefault();
+                                                    onAction("update-password", item.username)
+                                                }}
+                                            >
+                                                change password
+                                            </a>
+                                            &nbsp;
+                                            <a
+                                                href="#"
+                                                onClick={(e) => {
+                                                    e.preventDefault();
+                                                    onAction("show-cashout", item.username)
+                                                }}
+                                            >
+                                                cashouts
+                                            </a>
+                                            &nbsp;
+                                            <a
+                                                href="#"
+                                                onClick={(e) => {
+                                                    e.preventDefault();
+                                                    onAction("remove-account", item.username)
+                                                }}
+                                            >
+                                                remove
+                                            </a>
+                                        </td>
+                                    </tr>
+                                );
+                            })}
+                        </tbody>
+                    </table>
+                </div>
+            </article>
+        )}
+    </section>
+}
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
new file mode 100644
index 000000000..90835d52b
--- /dev/null
+++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
@@ -0,0 +1,107 @@
+import { RequestError, notify, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { VNode, h, Fragment } from "preact";
+import { useAdminAccountAPI } from "../../hooks/circuit.js";
+import { useState } from "preact/hooks";
+import { buildRequestErrorMessage } from "../../utils.js";
+import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
+import { getRandomPassword } from "../rnd.js";
+import { AccountForm } from "./AccountForm.js";
+
+export function CreateNewAccount({
+    onClose,
+    onCreateSuccess,
+}: {
+    onClose: () => void;
+    onCreateSuccess: (password: string) => void;
+}): VNode {
+    const { i18n } = useTranslationContext();
+    const { createAccount } = useAdminAccountAPI();
+    const [submitAccount, setSubmitAccount] = useState<
+        SandboxBackend.Circuit.CircuitAccountData | undefined
+    >();
+    return (
+        <div>
+            <div>
+                <h1 class="nav welcome-text">
+                    <i18n.Translate>New account</i18n.Translate>
+                </h1>
+            </div>
+
+            <div style={{ maxWidth: 600, overflowX: "hidden", margin: "auto" }}>
+                <AccountForm
+                    template={undefined}
+                    purpose="create"
+                    onChange={(a) => {
+                        setSubmitAccount(a);
+                    }}
+                />
+
+                <p>
+                    <div style={{ display: "flex", justifyContent: "space-between" }}>
+                        <div>
+                            <input
+                                class="pure-button"
+                                type="submit"
+                                value={i18n.str`Close`}
+                                onClick={async (e) => {
+                                    e.preventDefault();
+                                    onClose();
+                                }}
+                            />
+                        </div>
+                        <div>
+                            <input
+                                id="select-exchange"
+                                class="pure-button pure-button-primary content"
+                                disabled={!submitAccount}
+                                type="submit"
+                                value={i18n.str`Confirm`}
+                                onClick={async (e) => {
+                                    e.preventDefault();
+
+                                    if (!submitAccount) return;
+                                    try {
+                                        const account: SandboxBackend.Circuit.CircuitAccountRequest =
+                                        {
+                                            cashout_address: submitAccount.cashout_address,
+                                            contact_data: submitAccount.contact_data,
+                                            internal_iban: submitAccount.iban,
+                                            name: submitAccount.name,
+                                            username: submitAccount.username,
+                                            password: getRandomPassword(),
+                                        };
+
+                                        await createAccount(account);
+                                        onCreateSuccess(account.password);
+                                    } catch (error) {
+                                        if (error instanceof RequestError) {
+                                            notify(
+                                                buildRequestErrorMessage(i18n, error.cause, {
+                                                    onClientError: (status) =>
+                                                        status === HttpStatusCode.Forbidden
+                                                            ? i18n.str`The rights to perform the operation are not sufficient`
+                                                            : status === HttpStatusCode.BadRequest
+                                                                ? i18n.str`Input data was invalid`
+                                                                : status === HttpStatusCode.Conflict
+                                                                    ? i18n.str`At least one registration detail was not available`
+                                                                    : undefined,
+                                                }),
+                                            );
+                                        } else {
+                                            notifyError(
+                                                i18n.str`Operation failed, please report`,
+                                                (error instanceof Error
+                                                    ? error.message
+                                                    : JSON.stringify(error)) as TranslatedString
+                                            )
+                                        }
+                                    }
+                                }}
+                            />
+                        </div>
+                    </div>
+                </p>
+            </div>
+        </div>
+    );
+}
diff --git a/packages/demobank-ui/src/pages/admin/Home.tsx b/packages/demobank-ui/src/pages/admin/Home.tsx
new file mode 100644
index 000000000..e1ec6cfe0
--- /dev/null
+++ b/packages/demobank-ui/src/pages/admin/Home.tsx
@@ -0,0 +1,162 @@
+import { notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { Fragment, VNode, h } from "preact";
+import { useState } from "preact/hooks";
+import { Cashouts } from "../../components/Cashouts/index.js";
+import { ShowCashoutDetails } from "../business/Home.js";
+import { handleNotOkResult } from "../HomePage.js";
+import { ShowAccountDetails } from "../ShowAccountDetails.js";
+import { UpdateAccountPassword } from "../UpdateAccountPassword.js";
+import { AdminAccount } from "./Account.js";
+import { AccountList } from "./AccountList.js";
+import { CreateNewAccount } from "./CreateNewAccount.js";
+import { RemoveAccount } from "./RemoveAccount.js";
+
+/**
+ * Query account information and show QR code if there is pending withdrawal
+ */
+interface Props {
+  onRegister: () => void;
+}
+export type AccountAction = "show-details" | 
+  "show-cashout" | 
+  "update-password" | 
+  "remove-account" | 
+  "show-cashouts-details";
+
+export function AdminHome({ onRegister }: Props): VNode {
+  const [action, setAction] = useState<{
+    type: AccountAction,
+    account: string
+  }>()
+
+  const [createAccount, setCreateAccount] = useState(false);
+
+  const { i18n } = useTranslationContext();
+
+  if (action) {
+    switch (action.type) {
+      case "show-details": return <ShowCashoutDetails
+        id={action.account}
+        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
+        onCancel={() => {
+          setAction(undefined);
+        }}
+      />
+      case "show-cashout": return (
+        <div>
+          <div>
+            <h1 class="nav welcome-text">
+              <i18n.Translate>Cashout for account {action.account}</i18n.Translate>
+            </h1>
+          </div>
+          <Cashouts
+            account={action.account}
+            onSelected={(id) => {
+              setAction({
+                type: "show-cashouts-details",
+                account: action.account
+              });
+            }}
+          />
+          <p>
+            <input
+              class="pure-button"
+              type="submit"
+              value={i18n.str`Close`}
+              onClick={async (e) => {
+                e.preventDefault();
+                setAction(undefined);
+              }}
+            />
+          </p>
+        </div>
+      )
+      case "update-password": return <UpdateAccountPassword
+        account={action.account}
+        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
+        onUpdateSuccess={() => {
+          notifyInfo(i18n.str`Password changed`);
+          setAction(undefined);
+        }}
+        onClear={() => {
+          setAction(undefined);
+        }}
+      />
+      case "remove-account": return <RemoveAccount
+        account={action.account}
+        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
+        onUpdateSuccess={() => {
+          notifyInfo(i18n.str`Account removed`);
+          setAction(undefined);
+        }}
+        onClear={() => {
+          setAction(undefined);
+        }}
+      />
+      case "show-cashouts-details": return <ShowAccountDetails
+        account={action.account}
+        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
+        onChangePassword={() => {
+          setAction({
+            type: "update-password",
+            account: action.account,
+          })
+        }}
+        onUpdateSuccess={() => {
+          notifyInfo(i18n.str`Account updated`);
+          setAction(undefined);
+        }}
+        onClear={() => {
+          setAction(undefined);
+        }}
+      />
+    }
+  }
+
+  if (createAccount) {
+    return (
+      <CreateNewAccount
+        onClose={() => setCreateAccount(false)}
+        onCreateSuccess={(password) => {
+          notifyInfo(
+            i18n.str`Account created with password "${password}". The user must change the password on the next login.`,
+          );
+          setCreateAccount(false);
+        }}
+      />
+    );
+  }
+
+  return (
+    <Fragment>
+      <div>
+        <h1 class="nav welcome-text">
+          <i18n.Translate>Admin panel</i18n.Translate>
+        </h1>
+      </div>
+
+      <p>
+        <div style={{ display: "flex", justifyContent: "space-between" }}>
+          <div></div>
+          <div>
+            <input
+              class="pure-button pure-button-primary content"
+              type="submit"
+              value={i18n.str`Create account`}
+              onClick={async (e) => {
+                e.preventDefault();
+
+                setCreateAccount(true);
+              }}
+            />
+          </div>
+        </div>
+      </p>
+
+      <AdminAccount onRegister={onRegister} />
+
+      <AccountList account={undefined} onAction={(type,account) => setAction({account, type})} onRegister={onRegister}/>
+
+    </Fragment>
+  );
+}
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
new file mode 100644
index 000000000..2900db9d2
--- /dev/null
+++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
@@ -0,0 +1,112 @@
+import { ErrorType, HttpResponsePaginated, RequestError, notify, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { VNode,h,Fragment } from "preact";
+import { useAccountDetails } from "../../hooks/access.js";
+import { useAdminAccountAPI } from "../../hooks/circuit.js";
+import { Amounts, HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
+import { buildRequestErrorMessage } from "../../utils.js";
+
+export function RemoveAccount({
+    account,
+    onClear,
+    onUpdateSuccess,
+    onLoadNotOk,
+  }: {
+    onLoadNotOk: <T>(
+      error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
+    ) => VNode;
+    onClear: () => void;
+    onUpdateSuccess: () => void;
+    account: string;
+  }): VNode {
+    const { i18n } = useTranslationContext();
+    const result = useAccountDetails(account);
+    const { deleteAccount } = useAdminAccountAPI();
+  
+    if (!result.ok) {
+      if (result.loading || result.type === ErrorType.TIMEOUT) {
+        return onLoadNotOk(result);
+      }
+      if (result.status === HttpStatusCode.NotFound) {
+        return <div>account not found</div>;
+      }
+      return onLoadNotOk(result);
+    }
+  
+    const balance = Amounts.parse(result.data.balance.amount);
+    if (!balance) {
+      return <div>there was an error reading the balance</div>;
+    }
+    const isBalanceEmpty = Amounts.isZero(balance);
+    return (
+      <div>
+        <div>
+          <h1 class="nav welcome-text">
+            <i18n.Translate>Remove account: {account}</i18n.Translate>
+          </h1>
+        </div>
+        {/* {FXME: SHOW WARNING} */}
+        {/* {!isBalanceEmpty && (
+          <ErrorBannerFloat
+            error={{
+              title: i18n.str`Can't delete the account`,
+              description: i18n.str`Balance is not empty`,
+            }}
+            onClear={() => saveError(undefined)}
+          />
+        )} */}
+  
+        <p>
+          <div style={{ display: "flex", justifyContent: "space-between" }}>
+            <div>
+              <input
+                class="pure-button"
+                type="submit"
+                value={i18n.str`Cancel`}
+                onClick={async (e) => {
+                  e.preventDefault();
+                  onClear();
+                }}
+              />
+            </div>
+            <div>
+              <input
+                id="select-exchange"
+                class="pure-button pure-button-primary content"
+                disabled={!isBalanceEmpty}
+                type="submit"
+                value={i18n.str`Confirm`}
+                onClick={async (e) => {
+                  e.preventDefault();
+                  try {
+                    const r = await deleteAccount(account);
+                    onUpdateSuccess();
+                  } catch (error) {
+                    if (error instanceof RequestError) {
+                      notify(
+                        buildRequestErrorMessage(i18n, error.cause, {
+                          onClientError: (status) =>
+                            status === HttpStatusCode.Forbidden
+                              ? i18n.str`The administrator specified a institutional username`
+                              : status === HttpStatusCode.NotFound
+                                ? i18n.str`The username was not found`
+                                : status === HttpStatusCode.PreconditionFailed
+                                  ? i18n.str`Balance was not zero`
+                                  : undefined,
+                        }),
+                      );
+                    } else {
+                      notifyError(i18n.str`Operation failed, please report`,
+                        (error instanceof Error
+                            ? error.message
+                            : JSON.stringify(error)) as TranslatedString);
+                    }
+                  }
+                }}
+              />
+            </div>
+          </div>
+        </p>
+      </div>
+    );
+  }
+  
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/business/Home.tsx b/packages/demobank-ui/src/pages/business/Home.tsx
new file mode 100644
index 000000000..8beea640a
--- /dev/null
+++ b/packages/demobank-ui/src/pages/business/Home.tsx
@@ -0,0 +1,757 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+import {
+  AmountJson,
+  Amounts,
+  HttpStatusCode,
+  TranslatedString
+} from "@gnu-taler/taler-util";
+import {
+  HttpResponse,
+  HttpResponsePaginated,
+  RequestError,
+  notify,
+  notifyError,
+  notifyInfo,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
+import { Fragment, VNode, h } from "preact";
+import { useEffect, useState } from "preact/hooks";
+import { Cashouts } from "../../components/Cashouts/index.js";
+import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
+import { useBackendContext } from "../../context/backend.js";
+import { useAccountDetails } from "../../hooks/access.js";
+import {
+  useCashoutDetails,
+  useCircuitAccountAPI,
+  useEstimator,
+  useRatiosAndFeeConfig,
+} from "../../hooks/circuit.js";
+import {
+  TanChannel,
+  buildRequestErrorMessage,
+  undefinedIfEmpty,
+} from "../../utils.js";
+import { handleNotOkResult } from "../HomePage.js";
+import { LoginForm } from "../LoginForm.js";
+import { Amount } from "../PaytoWireTransferForm.js";
+import { ShowAccountDetails } from "../ShowAccountDetails.js";
+import { UpdateAccountPassword } from "../UpdateAccountPassword.js";
+
+interface Props {
+  account: string,
+  onClose: () => void;
+  onRegister: () => void;
+  onLoadNotOk: () => void;
+}
+export function BusinessAccount({
+  onClose,
+  account,
+  onLoadNotOk,
+  onRegister,
+}: Props): VNode {
+  const { i18n } = useTranslationContext();
+  const [updatePassword, setUpdatePassword] = useState(false);
+  const [newCashout, setNewcashout] = useState(false);
+  const [showCashoutDetails, setShowCashoutDetails] = useState<
+    string | undefined
+  >();
+
+
+  if (newCashout) {
+    return (
+      <CreateCashout
+        account={account}
+        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
+        onCancel={() => {
+          setNewcashout(false);
+        }}
+        onComplete={(id) => {
+          notifyInfo(
+            i18n.str`Cashout created. You need to confirm the operation to complete the transaction.`,
+          );
+          setNewcashout(false);
+          setShowCashoutDetails(id);
+        }}
+      />
+    );
+  }
+  if (showCashoutDetails) {
+    return (
+      <ShowCashoutDetails
+        id={showCashoutDetails}
+        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
+        onCancel={() => {
+          setShowCashoutDetails(undefined);
+        }}
+      />
+    );
+  }
+  if (updatePassword) {
+    return (
+      <UpdateAccountPassword
+        account={account}
+        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
+        onUpdateSuccess={() => {
+          notifyInfo(i18n.str`Password changed`);
+          setUpdatePassword(false);
+        }}
+        onClear={() => {
+          setUpdatePassword(false);
+        }}
+      />
+    );
+  }
+  return (
+    <div>
+      <ShowAccountDetails
+        account={account}
+        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
+        onUpdateSuccess={() => {
+          notifyInfo(i18n.str`Account updated`);
+        }}
+        onChangePassword={() => {
+          setUpdatePassword(true);
+        }}
+        onClear={onClose}
+      />
+      <section style={{ marginTop: "2em" }}>
+        <div class="active">
+          <h3>{i18n.str`Latest cashouts`}</h3>
+          <Cashouts
+            account={account}
+            onSelected={(id) => {
+              setShowCashoutDetails(id);
+            }}
+          />
+        </div>
+        <br />
+        <div style={{ display: "flex", justifyContent: "space-between" }}>
+          <div />
+          <input
+            class="pure-button pure-button-primary content"
+            type="submit"
+            value={i18n.str`New cashout`}
+            onClick={async (e) => {
+              e.preventDefault();
+              setNewcashout(true);
+            }}
+          />
+        </div>
+      </section>
+    </div>
+  );
+}
+
+interface PropsCashout {
+  account: string;
+  onComplete: (id: string) => void;
+  onCancel: () => void;
+  onLoadNotOk: <T>(
+    error:
+      | HttpResponsePaginated<T, SandboxBackend.SandboxError>
+      | HttpResponse<T, SandboxBackend.SandboxError>,
+  ) => VNode;
+}
+
+type FormType = {
+  isDebit: boolean;
+  amount: string;
+  subject: string;
+  channel: TanChannel;
+};
+type ErrorFrom<T> = {
+  [P in keyof T]+?: string;
+};
+
+// check #7719
+function useRatiosAndFeeConfigWithChangeDetection(): HttpResponse<
+  SandboxBackend.Circuit.Config & { hasChanged?: boolean },
+  SandboxBackend.SandboxError
+> {
+  const result = useRatiosAndFeeConfig();
+  const [oldResult, setOldResult] = useState<
+    SandboxBackend.Circuit.Config | undefined
+  >(undefined);
+  const dataFromBackend = result.ok ? result.data : undefined;
+  useEffect(() => {
+    // save only the first result of /config to the backend
+    if (!dataFromBackend || oldResult !== undefined) return;
+    setOldResult(dataFromBackend);
+  }, [dataFromBackend]);
+
+  if (!result.ok) return result;
+
+  const data = !oldResult ? result.data : oldResult;
+  const hasChanged =
+    oldResult &&
+    (result.data.name !== oldResult.name ||
+      result.data.version !== oldResult.version ||
+      result.data.ratios_and_fees.buy_at_ratio !==
+        oldResult.ratios_and_fees.buy_at_ratio ||
+      result.data.ratios_and_fees.buy_in_fee !==
+        oldResult.ratios_and_fees.buy_in_fee ||
+      result.data.ratios_and_fees.sell_at_ratio !==
+        oldResult.ratios_and_fees.sell_at_ratio ||
+      result.data.ratios_and_fees.sell_out_fee !==
+        oldResult.ratios_and_fees.sell_out_fee ||
+      result.data.fiat_currency !== oldResult.fiat_currency);
+
+  return {
+    ...result,
+    data: { ...data, hasChanged },
+  };
+}
+
+function CreateCashout({
+  account,
+  onComplete,
+  onCancel,
+  onLoadNotOk,
+}: PropsCashout): VNode {
+  const { i18n } = useTranslationContext();
+  const ratiosResult = useRatiosAndFeeConfig();
+  const result = useAccountDetails(account);
+  const {
+    estimateByCredit: calculateFromCredit,
+    estimateByDebit: calculateFromDebit,
+  } = useEstimator();
+  const [form, setForm] = useState<Partial<FormType>>({ isDebit: true });
+
+  const { createCashout } = useCircuitAccountAPI();
+  if (!result.ok) return onLoadNotOk(result);
+  if (!ratiosResult.ok) return onLoadNotOk(ratiosResult);
+  const config = ratiosResult.data;
+
+  const balance = Amounts.parseOrThrow(result.data.balance.amount);
+  const debitThreshold = Amounts.parseOrThrow(result.data.debitThreshold);
+  const zero = Amounts.zeroOfCurrency(balance.currency);
+  const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit";
+  const limit = balanceIsDebit
+    ? Amounts.sub(debitThreshold, balance).amount
+    : Amounts.add(balance, debitThreshold).amount;
+
+  const zeroCalc = { debit: zero, credit: zero, beforeFee: zero };
+  const [calc, setCalc] = useState(zeroCalc);
+  const sellRate = config.ratios_and_fees.sell_at_ratio;
+  const sellFee = !config.ratios_and_fees.sell_out_fee
+    ? zero
+    : Amounts.parseOrThrow(
+        `${balance.currency}:${config.ratios_and_fees.sell_out_fee}`,
+      );
+  const fiatCurrency = config.fiat_currency;
+
+  if (!sellRate || sellRate < 0) return <div>error rate</div>;
+
+  const amount = Amounts.parseOrThrow(
+    `${!form.isDebit ? fiatCurrency : balance.currency}:${
+      !form.amount ? "0" : form.amount
+    }`,
+  );
+
+  useEffect(() => {
+    if (form.isDebit) {
+      calculateFromDebit(amount, sellFee, sellRate)
+        .then((r) => {
+          setCalc(r);
+        })
+        .catch((error) => {
+          notify(
+            error instanceof RequestError
+              ? buildRequestErrorMessage(i18n, error.cause)
+              : {
+                  type: "error",
+                  title: i18n.str`Could not estimate the cashout`,
+                  description: error.message as TranslatedString
+                },
+          );
+        });
+    } else {
+      calculateFromCredit(amount, sellFee, sellRate)
+        .then((r) => {
+          setCalc(r);
+        })
+        .catch((error) => {
+          notify(
+            error instanceof RequestError
+              ? buildRequestErrorMessage(i18n, error.cause)
+              : {
+                  type: "error",
+                  title: i18n.str`Could not estimate the cashout`,
+                  description: error.message,
+                },
+          );
+        });
+    }
+  }, [form.amount, form.isDebit]);
+
+  const balanceAfter = Amounts.sub(balance, calc.debit).amount;
+
+  function updateForm(newForm: typeof form): void {
+    setForm(newForm);
+  }
+  const errors = undefinedIfEmpty<ErrorFrom<typeof form>>({
+    amount: !form.amount
+      ? i18n.str`required`
+      : !amount
+      ? i18n.str`could not be parsed`
+      : Amounts.cmp(limit, calc.debit) === -1
+      ? i18n.str`balance is not enough`
+      : Amounts.cmp(calc.beforeFee, sellFee) === -1
+      ? i18n.str`the total amount to transfer does not cover the fees`
+      : Amounts.isZero(calc.credit)
+      ? i18n.str`the total transfer at destination will be zero`
+      : undefined,
+    channel: !form.channel ? i18n.str`required` : undefined,
+  });
+
+  return (
+    <div>
+      <h1>New cashout</h1>
+      <form class="pure-form">
+        <fieldset>
+          <label>{i18n.str`Subject`}</label>
+          <input
+            value={form.subject ?? ""}
+            onChange={(e) => {
+              form.subject = e.currentTarget.value;
+              updateForm(structuredClone(form));
+            }}
+          />
+          <ShowInputErrorLabel
+            message={errors?.subject}
+            isDirty={form.subject !== undefined}
+          />
+        </fieldset>
+        <fieldset>
+          <label for="amount">
+            {form.isDebit
+              ? i18n.str`Amount to send`
+              : i18n.str`Amount to receive`}
+            
+          </label>
+          <div style={{ display: "flex" }}>
+            <Amount
+              name="amount"
+              currency={amount.currency}
+              value={form.amount}
+              onChange={(v) => {
+                form.amount = v;
+                updateForm(structuredClone(form));
+              }}
+              error={errors?.amount}
+            />
+            <label class="toggle" style={{ marginLeft: 4, marginTop: 0 }}>
+              <input
+                class="toggle-checkbox"
+                type="checkbox"
+                name="asd"
+                onChange={(e): void => {
+                  console.log("asdasd", form.isDebit);
+                  form.isDebit = !form.isDebit;
+                  updateForm(structuredClone(form));
+                }}
+              />
+              <div class="toggle-switch"></div>
+            </label>
+          </div>
+        </fieldset>
+        <fieldset>
+          <label>{i18n.str`Conversion rate`}</label>
+          <input value={sellRate} disabled />
+        </fieldset>
+        <fieldset>
+          <label for="balance-now">{i18n.str`Balance now`}</label>
+          <Amount
+            name="banace-now"
+            currency={balance.currency}
+            value={Amounts.stringifyValue(balance)}
+          />
+        </fieldset>
+        <fieldset>
+          <label for="total-cost"
+            style={{ fontWeight: "bold", color: "red" }}
+          >{i18n.str`Total cost`}</label>
+          <Amount
+            name="total-cost"
+            currency={balance.currency}
+            value={Amounts.stringifyValue(calc.debit)}
+          />
+        </fieldset>
+        <fieldset>
+          <label for="balance-after">{i18n.str`Balance after`}</label>
+          <Amount
+            name="balance-after"
+            currency={balance.currency}
+            value={balanceAfter ? Amounts.stringifyValue(balanceAfter) : ""}
+          />
+        </fieldset>{" "}
+        {Amounts.isZero(sellFee) ? undefined : (
+          <Fragment>
+            <fieldset>
+              <label for="amount-conversiojn">{i18n.str`Amount after conversion`}</label>
+              <Amount
+                name="amount-conversion"
+                currency={fiatCurrency}
+                value={Amounts.stringifyValue(calc.beforeFee)}
+              />
+            </fieldset>
+
+            <fieldset>
+              <label form="cashout-fee">{i18n.str`Cashout fee`}</label>
+              <Amount
+                name="cashout-fee"
+                currency={fiatCurrency}
+                value={Amounts.stringifyValue(sellFee)}
+              />
+            </fieldset>
+          </Fragment>
+        )}
+        <fieldset>
+          <label for="total"
+            style={{ fontWeight: "bold", color: "green" }}
+          >{i18n.str`Total cashout transfer`}</label>
+          <Amount
+            name="total"
+            currency={fiatCurrency}
+            value={Amounts.stringifyValue(calc.credit)}
+          />
+        </fieldset>
+        <fieldset>
+          <label>{i18n.str`Confirmation channel`}</label>
+
+          <div class="channel">
+            <input
+              class={
+                "pure-button content " +
+                (form.channel === TanChannel.EMAIL
+                  ? "pure-button-primary"
+                  : "pure-button-secondary")
+              }
+              type="submit"
+              value={i18n.str`Email`}
+              onClick={async (e) => {
+                e.preventDefault();
+                form.channel = TanChannel.EMAIL;
+                updateForm(structuredClone(form));
+              }}
+            />
+            <input
+              class={
+                "pure-button content " +
+                (form.channel === TanChannel.SMS
+                  ? "pure-button-primary"
+                  : "pure-button-secondary")
+              }
+              type="submit"
+              value={i18n.str`SMS`}
+              onClick={async (e) => {
+                e.preventDefault();
+                form.channel = TanChannel.SMS;
+                updateForm(structuredClone(form));
+              }}
+            />
+            <input
+              class={
+                "pure-button content " +
+                (form.channel === TanChannel.FILE
+                  ? "pure-button-primary"
+                  : "pure-button-secondary")
+              }
+              type="submit"
+              value={i18n.str`FILE`}
+              onClick={async (e) => {
+                e.preventDefault();
+                form.channel = TanChannel.FILE;
+                updateForm(structuredClone(form));
+              }}
+            />
+          </div>
+          <ShowInputErrorLabel
+            message={errors?.channel}
+            isDirty={form.channel !== undefined}
+          />
+        </fieldset>
+        <br />
+        <div style={{ display: "flex", justifyContent: "space-between" }}>
+          <button
+            class="pure-button pure-button-secondary btn-cancel"
+            onClick={(e) => {
+              e.preventDefault();
+              onCancel();
+            }}
+          >
+            {i18n.str`Cancel`}
+          </button>
+
+          <button
+            class="pure-button pure-button-primary btn-register"
+            type="submit"
+            disabled={!!errors}
+            onClick={async (e) => {
+              e.preventDefault();
+
+              if (errors) return;
+              try {
+                const res = await createCashout({
+                  amount_credit: Amounts.stringify(calc.credit),
+                  amount_debit: Amounts.stringify(calc.debit),
+                  subject: form.subject,
+                  tan_channel: form.channel,
+                });
+                onComplete(res.data.uuid);
+              } catch (error) {
+                if (error instanceof RequestError) {
+                  notify(
+                    buildRequestErrorMessage(i18n, error.cause, {
+                      onClientError: (status) =>
+                        status === HttpStatusCode.BadRequest
+                          ? i18n.str`The exchange rate was incorrectly applied`
+                          : status === HttpStatusCode.Forbidden
+                          ? i18n.str`A institutional user tried the operation`
+                          : status === HttpStatusCode.Conflict
+                          ? i18n.str`Need a contact data where to send the TAN`
+                          : status === HttpStatusCode.PreconditionFailed
+                          ? i18n.str`The account does not have sufficient funds`
+                          : undefined,
+                      onServerError: (status) =>
+                        status === HttpStatusCode.ServiceUnavailable
+                          ? i18n.str`The bank does not support the TAN channel for this operation`
+                          : undefined,
+                    }),
+                  );
+                } else {
+                  notifyError(
+                    i18n.str`Operation failed, please report`,
+                    (error instanceof Error
+                      ? error.message
+                      : JSON.stringify(error)) as TranslatedString
+                  )
+            }
+              }
+            }}
+          >
+            {i18n.str`Create`}
+          </button>
+        </div>
+      </form>
+    </div>
+  );
+}
+
+interface ShowCashoutProps {
+  id: string;
+  onCancel: () => void;
+  onLoadNotOk: <T>(
+    error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
+  ) => VNode;
+}
+export function ShowCashoutDetails({
+  id,
+  onCancel,
+  onLoadNotOk,
+}: ShowCashoutProps): VNode {
+  const { i18n } = useTranslationContext();
+  const result = useCashoutDetails(id);
+  const { abortCashout, confirmCashout } = useCircuitAccountAPI();
+  const [code, setCode] = useState<string | undefined>(undefined);
+  if (!result.ok) return onLoadNotOk(result);
+  const errors = undefinedIfEmpty({
+    code: !code ? i18n.str`required` : undefined,
+  });
+  const isPending = String(result.data.status).toUpperCase() === "PENDING";
+  return (
+    <div>
+      <h1>Cashout details {id}</h1>
+      <form class="pure-form">
+        <fieldset>
+          <label>
+            <i18n.Translate>Subject</i18n.Translate>
+          </label>
+          <input readOnly value={result.data.subject} />
+        </fieldset>
+        <fieldset>
+          <label>
+            <i18n.Translate>Created</i18n.Translate>
+          </label>
+          <input readOnly value={result.data.creation_time ?? ""} />
+        </fieldset>
+        <fieldset>
+          <label>
+            <i18n.Translate>Confirmed</i18n.Translate>
+          </label>
+          <input readOnly value={result.data.confirmation_time ?? ""} />
+        </fieldset>
+        <fieldset>
+          <label>
+            <i18n.Translate>Debited</i18n.Translate>
+          </label>
+          <input readOnly value={result.data.amount_debit} />
+        </fieldset>
+        <fieldset>
+          <label>
+            <i18n.Translate>Credit</i18n.Translate>
+          </label>
+          <input readOnly value={result.data.amount_credit} />
+        </fieldset>
+        <fieldset>
+          <label>
+            <i18n.Translate>Status</i18n.Translate>
+          </label>
+          <input readOnly value={result.data.status} />
+        </fieldset>
+        <fieldset>
+          <label>
+            <i18n.Translate>Destination</i18n.Translate>
+          </label>
+          <input readOnly value={result.data.cashout_address} />
+        </fieldset>
+        {isPending ? (
+          <fieldset>
+            <label>
+              <i18n.Translate>Code</i18n.Translate>
+            </label>
+            <input
+              value={code ?? ""}
+              onChange={(e) => {
+                setCode(e.currentTarget.value);
+              }}
+            />
+            <ShowInputErrorLabel
+              message={errors?.code}
+              isDirty={code !== undefined}
+            />
+          </fieldset>
+        ) : undefined}
+      </form>
+      <br />
+      <div style={{ display: "flex", justifyContent: "space-between" }}>
+        <button
+          class="pure-button pure-button-secondary btn-cancel"
+          onClick={(e) => {
+            e.preventDefault();
+            onCancel();
+          }}
+        >
+          {i18n.str`Back`}
+        </button>
+        {isPending ? (
+          <div>
+            <button
+              type="submit"
+              class="pure-button pure-button-primary button-error"
+              onClick={async (e) => {
+                e.preventDefault();
+                try {
+                  await abortCashout(id);
+                  onCancel();
+                } catch (error) {
+                  if (error instanceof RequestError) {
+                    notify(
+                      buildRequestErrorMessage(i18n, error.cause, {
+                        onClientError: (status) =>
+                          status === HttpStatusCode.NotFound
+                            ? i18n.str`Cashout not found. It may be also mean that it was already aborted.`
+                            : status === HttpStatusCode.PreconditionFailed
+                            ? i18n.str`Cashout was already confimed`
+                            : undefined,
+                      }),
+                    );
+                  } else {
+                    notifyError(
+                      i18n.str`Operation failed, please report`,
+                      (error instanceof Error
+                        ? error.message
+                        : JSON.stringify(error)) as TranslatedString
+                    )
+                }
+                }
+              }}
+            >
+              {i18n.str`Abort`}
+            </button>
+            &nbsp;
+            <button
+              type="submit"
+              disabled={!code}
+              class="pure-button pure-button-primary "
+              onClick={async (e) => {
+                e.preventDefault();
+                try {
+                  if (!code) return;
+                  const rest = await confirmCashout(id, {
+                    tan: code,
+                  });
+                } catch (error) {
+                  if (error instanceof RequestError) {
+                    notify(
+                      buildRequestErrorMessage(i18n, error.cause, {
+                        onClientError: (status) =>
+                          status === HttpStatusCode.NotFound
+                            ? i18n.str`Cashout not found. It may be also mean that it was already aborted.`
+                            : status === HttpStatusCode.PreconditionFailed
+                            ? i18n.str`Cashout was already confimed`
+                            : status === HttpStatusCode.Conflict
+                            ? i18n.str`Confirmation failed. Maybe the user changed their cash-out address between the creation and the confirmation`
+                            : status === HttpStatusCode.Forbidden
+                            ? i18n.str`Invalid code`
+                            : undefined,
+                      }),
+                    );
+                  } else {
+                    notifyError(
+                      i18n.str`Operation failed, please report`,
+                      (error instanceof Error
+                        ? error.message
+                        : JSON.stringify(error)) as TranslatedString
+                    )
+                }
+                }
+              }}
+            >
+              {i18n.str`Confirm`}
+            </button>
+          </div>
+        ) : (
+          <div />
+        )}
+      </div>
+    </div>
+  );
+}
+
+const MAX_AMOUNT_DIGIT = 2;
+/**
+ * Truncate the amount of digits to display
+ * in the form based on the fee calculations
+ *
+ * Backend must have the same truncation
+ * @param a
+ * @returns
+ */
+function truncate(a: AmountJson): AmountJson {
+  const str = Amounts.stringify(a);
+  const idx = str.indexOf(".");
+  if (idx === -1) {
+    return a;
+  }
+  const truncated = str.substring(0, idx + 1 + MAX_AMOUNT_DIGIT);
+  return Amounts.parseOrThrow(truncated);
+}
+
+export function assertUnreachable(x: never): never {
+  throw new Error("Didn't expect to get here");
+}
-- 
cgit v1.2.3

