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

---
 .../demobank-ui/src/pages/WithdrawalQRCode.tsx     | 23 ++++++++++++++++++++--
 1 file changed, 21 insertions(+), 2 deletions(-)

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

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()
-- 
cgit v1.2.3


From 7d4c5a71aaa6c4e781af124fe821d8be4ed101ed Mon Sep 17 00:00:00 2001
From: Sebastian <sebasjm@gmail.com>
Date: Wed, 20 Sep 2023 16:10:32 -0300
Subject: more ui

---
 packages/demobank-ui/src/hooks/backend.ts          |  16 +-
 packages/demobank-ui/src/hooks/settings.ts         |  10 ++
 packages/demobank-ui/src/pages/BankFrame.tsx       |  50 +++++--
 packages/demobank-ui/src/pages/HomePage.tsx        |   9 +-
 .../src/pages/WithdrawalConfirmationQuestion.tsx   | 165 +++++++++++----------
 .../demobank-ui/src/pages/WithdrawalQRCode.tsx     | 117 ++++++++++-----
 6 files changed, 221 insertions(+), 146 deletions(-)

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

diff --git a/packages/demobank-ui/src/hooks/backend.ts b/packages/demobank-ui/src/hooks/backend.ts
index 4b60d1b6c..c05ab33e9 100644
--- a/packages/demobank-ui/src/hooks/backend.ts
+++ b/packages/demobank-ui/src/hooks/backend.ts
@@ -85,18 +85,26 @@ export function getInitialBackendBaseURL(): string {
     typeof localStorage !== "undefined"
       ? localStorage.getItem("bank-base-url")
       : undefined;
+  let result: string;
   if (!overrideUrl) {
     //normal path
     if (!bankUiSettings.backendBaseURL) {
       console.error(
         "ERROR: backendBaseURL was overridden by a setting file and missing. Setting value to 'window.origin'",
       );
-      return canonicalizeBaseUrl(window.origin);
+      result = window.origin
     }
-    return canonicalizeBaseUrl(bankUiSettings.backendBaseURL);
+    result = bankUiSettings.backendBaseURL;
+  } else {
+    // testing/development path
+    result = overrideUrl
+  }
+  try {
+    return canonicalizeBaseUrl(result)
+  } catch (e) {
+    //fall back
+    return canonicalizeBaseUrl(window.origin)
   }
-  // testing/development path
-  return canonicalizeBaseUrl(overrideUrl);
 }
 
 export const defaultState: BackendState = {
diff --git a/packages/demobank-ui/src/hooks/settings.ts b/packages/demobank-ui/src/hooks/settings.ts
index 46b31bf2a..43e803726 100644
--- a/packages/demobank-ui/src/hooks/settings.ts
+++ b/packages/demobank-ui/src/hooks/settings.ts
@@ -15,8 +15,12 @@
  */
 
 import {
+  AmountString,
   Codec,
   buildCodecForObject,
+  codecForAmountString,
+  codecForBoolean,
+  codecForNumber,
   codecForString,
   codecOptional,
 } from "@gnu-taler/taler-util";
@@ -24,15 +28,21 @@ import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser";
 
 interface Settings {
   currentWithdrawalOperationId: string | undefined;
+  showWithdrawalSuccess: boolean;
+  maxWithdrawalAmount: number;
 }
 
 export const codecForSettings = (): Codec<Settings> =>
   buildCodecForObject<Settings>()
     .property("currentWithdrawalOperationId", codecOptional(codecForString()))
+    .property("showWithdrawalSuccess", (codecForBoolean()))
+    .property("maxWithdrawalAmount", codecForNumber())
     .build("Settings");
 
 const defaultSettings: Settings = {
   currentWithdrawalOperationId: undefined,
+  showWithdrawalSuccess: true,
+  maxWithdrawalAmount: 25
 };
 
 const DEMOBANK_SETTINGS_KEY = buildStorageKey(
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx
index d234845a0..e682085ae 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -183,8 +183,28 @@ export function BankFrame({
                                       {/* <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>
+                                  <li>
+                                    <div class="flex items-center justify-between">
+                                      <span class="flex flex-grow flex-col">
+                                        <span class="text-sm text-black font-medium leading-6 " id="availability-label">
+                                          <i18n.Translate>Show withdrawal confirmation</i18n.Translate>
+                                        </span>
+                                      </span>
+                                      <button type="button" data-enabled={settings.showWithdrawalSuccess} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description"
+
+                                        onClick={() => {
+                                          console.log(settings.showWithdrawalSuccess)
+                                          updateSettings("showWithdrawalSuccess", !settings.showWithdrawalSuccess);
+                                        }}>
+                                        <span aria-hidden="true" data-enabled={settings.showWithdrawalSuccess} class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
+                                      </button>
+                                    </div>
+                                  </li>
                                 </ul>
                               </li>
+
+
+
                               <li class="sm:hidden">
                                 <div class="text-xs font-semibold leading-6 text-gray-400">
                                   <i18n.Translate>Sites</i18n.Translate>
@@ -343,14 +363,14 @@ function StatusBanner(): VNode {
         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>
+              <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) => {
@@ -363,15 +383,15 @@ function StatusBanner(): VNode {
                         <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>     
+                  </p>
+                </div>
               </div>
+              {n.message.description &&
+                <div class="mt-2 text-sm text-red-700">
+                  {n.message.description}
+                </div>
+              }
             </div>
-            {n.message.description &&
-              <div class="mt-2 text-sm text-red-700">
-                {n.message.description}
-              </div>
-            }
-          </div>
           case "info":
             return <div class="rounded-md bg-green-50 border-4 border-green-600 p-6">
               <div class="flex">
diff --git a/packages/demobank-ui/src/pages/HomePage.tsx b/packages/demobank-ui/src/pages/HomePage.tsx
index e00daf278..e82e46eb2 100644
--- a/packages/demobank-ui/src/pages/HomePage.tsx
+++ b/packages/demobank-ui/src/pages/HomePage.tsx
@@ -95,8 +95,9 @@ export function WithdrawalOperationPage({
 }): VNode {
   //FIXME: libeufin sandbox should return show to create the integration api endpoint
   //or return withdrawal uri from response
+  const baseUrl = getInitialBackendBaseURL()
   const uri = stringifyWithdrawUri({
-    bankIntegrationApiBaseUrl: `${getInitialBackendBaseURL()}/integration-api`,
+    bankIntegrationApiBaseUrl: `${baseUrl}/integration-api`,
     withdrawalOperationId: operationId,
   });
   const parsedUri = parseWithdrawUri(uri);
@@ -155,7 +156,7 @@ export function handleNotOkResult(
         }
         case ErrorType.SERVER: {
           notify({
-            type: "error", 
+            type: "error",
             title: i18n.str`Server returned with error`,
             description: result.payload.error.description as TranslatedString,
             debug: JSON.stringify(result.payload),
@@ -164,7 +165,7 @@ export function handleNotOkResult(
         }
         case ErrorType.UNREADABLE: {
           notify({
-            type:"error",
+            type: "error",
             title: i18n.str`Unexpected error.`,
             description: i18n.str`Response from ${result.info?.url} is unreadable, http status: ${result.status}`,
             debug: JSON.stringify(result),
@@ -173,7 +174,7 @@ export function handleNotOkResult(
         }
         case ErrorType.UNEXPECTED: {
           notify({
-            type:"error",
+            type: "error",
             title: i18n.str`Unexpected error.`,
             description: i18n.str`Diagnostic from ${result.info?.url} is "${result.message}"`,
             debug: JSON.stringify(result),
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index 28f00169d..80e7a78ac 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -20,24 +20,23 @@ import {
   HttpStatusCode,
   Logger,
   PaytoUri,
-  PaytoUriGeneric,
   PaytoUriIBAN,
   PaytoUriTalerBank,
   TranslatedString,
-  WithdrawUriResult,
+  WithdrawUriResult
 } from "@gnu-taler/taler-util";
 import {
   RequestError,
   notify,
   notifyError,
+  notifyInfo,
   useTranslationContext,
 } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useMemo, useState } from "preact/hooks";
+import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
 import { useAccessAnonAPI } from "../hooks/access.js";
 import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
-import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
-import { Amount } from "./PaytoWireTransferForm.js";
 
 const logger = new Logger("WithdrawalConfirmationQuestion");
 
@@ -71,6 +70,7 @@ export function WithdrawalConfirmationQuestion({
   const { confirmWithdrawal, abortWithdrawal } = useAccessAnonAPI();
   const [captchaAnswer, setCaptchaAnswer] = useState<string | undefined>();
   const answer = parseInt(captchaAnswer ?? "", 10);
+  const [busy, setBusy] = useState<Record<string, undefined>>()
   const errors = undefinedIfEmpty({
     answer: !captchaAnswer
       ? i18n.str`Answer the question before continue`
@@ -79,13 +79,15 @@ export function WithdrawalConfirmationQuestion({
         : answer !== captchaNumbers.a + captchaNumbers.b
           ? i18n.str`The answer "${answer}" to "${captchaNumbers.a} + ${captchaNumbers.b}" is wrong.`
           : undefined,
-  });
+  }) ?? busy;
 
   async function doTransfer() {
     try {
+      setBusy({})
       await confirmWithdrawal(
         withdrawUri.withdrawalOperationId,
       );
+      notifyInfo(i18n.str`Wire transfer completed!`)
     } catch (error) {
       if (error instanceof RequestError) {
         notify(
@@ -107,10 +109,12 @@ export function WithdrawalConfirmationQuestion({
         )
       }
     }
+    setBusy(undefined)
   }
 
   async function doCancel() {
     try {
+      setBusy({})
       await abortWithdrawal(withdrawUri.withdrawalOperationId);
       onAborted();
     } catch (error) {
@@ -132,6 +136,7 @@ export function WithdrawalConfirmationQuestion({
         )
       }
     }
+    setBusy(undefined)
   }
 
   return (
@@ -142,68 +147,6 @@ export function WithdrawalConfirmationQuestion({
             <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>
-
-                      }
-                    })()}
-                    <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"}>
@@ -285,36 +228,32 @@ export function WithdrawalConfirmationQuestion({
                         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 ?? ""}
+                        value={captchaAnswer ?? ""}
                         required
 
                         name="answer"
                         id="answer"
                         autocomplete="off"
-                      // value={value ?? ""}
-                      // disabled={!onChange}
-                      // onInput={(e): void => {
-                      //   if (onChange) {
-                      //     onChange(e.currentTarget.value);
-                      //   }
-                      // }}
+                        onChange={(e): void => {
+                          setCaptchaAnswer(e.currentTarget.value)
+                        }}
                       />
                     </div>
-                    <ShowInputErrorLabel message={errors?.answer} isDirty={false} />
+                    <ShowInputErrorLabel message={errors?.answer} isDirty={captchaAnswer !== undefined} />
                   </div>
                 </div>
                 <div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8">
                   <button type="button" class="text-sm font-semibold leading-6 text-gray-900"
-                  // onClick={onCancel}
+                    onClick={doCancel}
                   >
                     <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()
-                  // }}
+                    disabled={!!errors}
+                    onClick={(e) => {
+                      e.preventDefault()
+                      doTransfer()
+                    }}
                   >
                     <i18n.Translate>Transfer</i18n.Translate>
                   </button>
@@ -323,6 +262,68 @@ export function WithdrawalConfirmationQuestion({
               </form>
             </div>
           </div>
+          <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>
+
+                    }
+                  })()}
+                  <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>
       </div>
 
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 3b983c2d4..b48e3b1dc 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -62,9 +62,10 @@ export function WithdrawalQRCode({
       result.type === ErrorType.CLIENT &&
       result.status === HttpStatusCode.NotFound
     ) {
+      clearCurrentWithdrawal()
       return <div>operation not found</div>;
     }
-    onLoadNotOk();
+    // onLoadNotOk();
     return handleNotOkResult(i18n)(result);
   }
   const { data } = result;
@@ -85,12 +86,12 @@ export function WithdrawalQRCode({
           </i18n.Translate>
         </p>
         <a class="pure-button pure-button-primary"
-          style={{float:"right"}}
+          style={{ float: "right" }}
           onClick={async (e) => {
             e.preventDefault();
             clearCurrentWithdrawal()
             onContinue()
-        }}>
+          }}>
           {i18n.str`Continue`}
         </a>
 
@@ -99,49 +100,69 @@ export function WithdrawalQRCode({
   }
 
   if (data.confirmation_done) {
-    return <section id="main" class="content">
-      <h1 class="nav">{i18n.str`Operation completed`}</h1>
-
-      <section id="assets" style={{maxWidth: 400, marginLeft: "auto", marginRight:"auto"}}>
-        <p>
-          <i18n.Translate>
-            The wire transfer to the GNU Taler Exchange bank's account is completed, now the 
-            exchange will send the requested amount into your GNU Taler wallet.
-          </i18n.Translate>
-        </p>
-        <p>
-          <i18n.Translate>
-            You can close this page now or continue to the account page.
-          </i18n.Translate>
-        </p>
-        <div style={{textAlign:"center"}}>
+    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">
+          <svg class="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
+            <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
+          </svg>
+        </div>
+        <div class="mt-3 text-center sm:mt-5">
+          <h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title">
+            <i18n.Translate>Withdrawal OK</i18n.Translate>
+          </h3>
+          <div class="mt-2">
+            <p class="text-sm text-gray-500">
+              <i18n.Translate>
+                The wire transfer to the Taler exchange bank's account is completed, now the
+                exchange will send the requested amount into your GNU Taler wallet.
+              </i18n.Translate>
+            </p>
+          </div>
+          <div class="mt-2">
+            <p >
+              <i18n.Translate>
+                You can close this page now or continue to the account page.
+              </i18n.Translate>
+            </p>
+          </div>
+        </div>
+      </div>
+      <div class="mt-4">
+        <div class="flex items-center justify-between">
+          <span class="flex flex-grow flex-col">
+            <span class="text-sm text-black font-medium leading-6 " id="availability-label">
+              <i18n.Translate>Do not show this again</i18n.Translate>
+            </span>
+          </span>
+          <button type="button" data-enabled={!settings.showWithdrawalSuccess} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description"
 
-          <a class="pure-button pure-button-primary"
-            onClick={async (e) => {
-              e.preventDefault();
-              clearCurrentWithdrawal()
-              onContinue()
+            onClick={() => {
+              updateSettings("showWithdrawalSuccess", !settings.showWithdrawalSuccess);
             }}>
-            {i18n.str`Continue`}
-          </a>
+            <span aria-hidden="true" data-enabled={!settings.showWithdrawalSuccess} class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
+          </button>
         </div>
-      </section>
-    </section>
-  }
-  if (!data.selected_reserve_pub) {
-    return <div>
-      the exchange is selcted but no reserve pub
+      </div>
+      <div class="mt-5 sm:mt-6">
+        <button type="button"
+          class="inline-flex w-full justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
+          onClick={async (e) => {
+            e.preventDefault();
+            clearCurrentWithdrawal()
+            onContinue()
+          }}>
+          <i18n.Translate>Continue</i18n.Translate>
+        </button>
+      </div>
     </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 (
       <QrCodeSection
@@ -150,11 +171,25 @@ export function WithdrawalQRCode({
           notifyInfo(i18n.str`Operation canceled`);
           clearCurrentWithdrawal()
           onContinue()
-      }}
+        }}
       />
     );
   }
 
+  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>
+  }
+
   return (
     <WithdrawalConfirmationQuestion
       withdrawUri={withdrawUri}
@@ -167,7 +202,7 @@ export function WithdrawalQRCode({
         notifyInfo(i18n.str`Operation canceled`);
         clearCurrentWithdrawal()
         onContinue()
-  }}
+      }}
     />
   );
 }
\ No newline at end of file
-- 
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/WithdrawalQRCode.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


From 56a6f47c7daae088c2017c0d9781ddcf7cee175b Mon Sep 17 00:00:00 2001
From: Sebastian <sebasjm@gmail.com>
Date: Thu, 21 Sep 2023 15:44:17 -0300
Subject: more ui

---
 packages/demobank-ui/src/components/Routing.tsx    |  12 +-
 packages/demobank-ui/src/hooks/settings.ts         |   8 +-
 .../demobank-ui/src/pages/AccountPage/views.tsx    |  50 ++++++
 packages/demobank-ui/src/pages/BankFrame.tsx       | 133 ++++++++-------
 packages/demobank-ui/src/pages/HomePage.tsx        |  20 +--
 packages/demobank-ui/src/pages/PaymentOptions.tsx  | 137 +++++++--------
 packages/demobank-ui/src/pages/QrCodeSection.tsx   | 106 ++++++++++++
 .../demobank-ui/src/pages/WalletWithdrawForm.tsx   | 183 +++++++++++----------
 .../demobank-ui/src/pages/WithdrawalQRCode.tsx     | 159 +++++++++++++++---
 .../demobank-ui/src/pages/admin/RemoveAccount.tsx  |   2 +-
 10 files changed, 553 insertions(+), 257 deletions(-)

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

diff --git a/packages/demobank-ui/src/components/Routing.tsx b/packages/demobank-ui/src/components/Routing.tsx
index b8e39948b..e1fd93737 100644
--- a/packages/demobank-ui/src/components/Routing.tsx
+++ b/packages/demobank-ui/src/components/Routing.tsx
@@ -76,9 +76,9 @@ export function Routing(): VNode {
               onContinue={() => {
                 route("/account");
               }}
-              onLoadNotOk={() => {
-                route("/account");
-              }}
+              // onLoadNotOk={() => {
+              //   route("/account");
+              // }}
             />
           )}
         />
@@ -108,9 +108,9 @@ export function Routing(): VNode {
             } else {
               return <HomePage
                 account={username}
-                onPendingOperationFound={(wopid) => {
-                  route(`/operation/${wopid}`);
-                }}
+                // onPendingOperationFound={(wopid) => {
+                //   route(`/operation/${wopid}`);
+                // }}
                 goToBusinessAccount={() => {
                   route("/business");
                 }}
diff --git a/packages/demobank-ui/src/hooks/settings.ts b/packages/demobank-ui/src/hooks/settings.ts
index 43e803726..c2fd93a0c 100644
--- a/packages/demobank-ui/src/hooks/settings.ts
+++ b/packages/demobank-ui/src/hooks/settings.ts
@@ -29,20 +29,26 @@ import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser";
 interface Settings {
   currentWithdrawalOperationId: string | undefined;
   showWithdrawalSuccess: boolean;
+  showDemoDescription: boolean;
   maxWithdrawalAmount: number;
+  fastWithdrawal: boolean;
 }
 
 export const codecForSettings = (): Codec<Settings> =>
   buildCodecForObject<Settings>()
     .property("currentWithdrawalOperationId", codecOptional(codecForString()))
     .property("showWithdrawalSuccess", (codecForBoolean()))
+    .property("showDemoDescription", (codecForBoolean()))
+    .property("fastWithdrawal", (codecForBoolean()))
     .property("maxWithdrawalAmount", codecForNumber())
     .build("Settings");
 
 const defaultSettings: Settings = {
   currentWithdrawalOperationId: undefined,
   showWithdrawalSuccess: true,
-  maxWithdrawalAmount: 25
+  showDemoDescription: true,
+  maxWithdrawalAmount: 25,
+  fastWithdrawal: false,
 };
 
 const DEMOBANK_SETTINGS_KEY = buildStorageKey(
diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx b/packages/demobank-ui/src/pages/AccountPage/views.tsx
index abd14848f..0187989af 100644
--- a/packages/demobank-ui/src/pages/AccountPage/views.tsx
+++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx
@@ -23,6 +23,7 @@ import { State } from "./index.js";
 import { CopyButton } from "../../components/CopyButton.js";
 import { bankUiSettings } from "../../settings.js";
 import { useBusinessAccountDetails } from "../../hooks/circuit.js";
+import { useSettings } from "../../hooks/settings.js";
 
 export function InvalidIbanView({ error }: State.InvalidIban) {
   return (
@@ -78,9 +79,58 @@ function ImportantMessage(): VNode {
 
 }
 
+function ShowDemoInfo():VNode {
+  const { i18n } = useTranslationContext();
+  const [settings, updateSettings] = useSettings();
+  if (!settings.showDemoDescription) return <Fragment />
+  return <div class="rounded-md bg-blue-50 p-4">
+  <div class="flex">
+    <div class="flex-shrink-0">
+      <svg class="h-5 w-5 text-blue-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+        <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
+      </svg>
+    </div>
+    <div class="ml-3">
+      <h3 class="text-sm font-bold text-blue-800">
+        <i18n.Translate>This is a demo bank!</i18n.Translate>
+      </h3>
+      <div class="mt-2 text-sm text-blue-700">
+        <p>
+          <i18n.Translate>
+            This part of the demo shows how a bank that supports Taler
+            directly would work. In addition to using your own bank
+            account, you can also see the transaction history of some{" "}
+            <a href="/public-accounts">Public Accounts</a>.
+          </i18n.Translate>
+        </p>
+        <p class="mt-3 text-sm flex justify-end">
+          <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();
+              updateSettings("showDemoDescription", false);
+            }}
+          >
+            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>
+  </div>
+</div>
+}
+
 export function ReadyView({ account, limit, goToBusinessAccount }: State.Ready): VNode<{}> {
+  const { i18n } = useTranslationContext();
+
   return <Fragment>
     <MaybeBusinessButton account={account} onClick={goToBusinessAccount} />
+
+    <ShowDemoInfo />
+    
     <PaymentOptions limit={limit} />
     <Transactions account={account} />
   </Fragment>;
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx
index 4b23686d6..d1c94135b 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -27,7 +27,6 @@ import { CopyButton, CopyIcon } from "../components/CopyButton.js";
 import logo from "../assets/logo-2021.svg";
 import { useAccountDetails } from "../hooks/access.js";
 
-const IS_PUBLIC_ACCOUNT_ENABLED = false;
 const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined;
 const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined;
 
@@ -142,24 +141,40 @@ export function BankFrame({
                         <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>
-                                <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>
+                            <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 <li>
+                                    <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>
+                                  </li>
+                                })}
+                              </ul>
+                            </li>
+
+                            <li>
+                              <ul role="list" class="space-y-1">
+                                <li class="mt-2">
                                   <div class="flex items-center justify-between">
                                     <span class="flex flex-grow flex-col">
                                       <span class="text-sm text-black font-medium leading-6 " id="availability-label">
@@ -169,29 +184,43 @@ export function BankFrame({
                                     <button type="button" data-enabled={settings.showWithdrawalSuccess} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description"
 
                                       onClick={() => {
-                                        console.log(settings.showWithdrawalSuccess)
                                         updateSettings("showWithdrawalSuccess", !settings.showWithdrawalSuccess);
                                       }}>
                                       <span aria-hidden="true" data-enabled={settings.showWithdrawalSuccess} class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
                                     </button>
                                   </div>
                                 </li>
-                              </ul>
-                            </li>
-
-
+                                <li class="mt-2">
+                                  <div class="flex items-center justify-between">
+                                    <span class="flex flex-grow flex-col">
+                                      <span class="text-sm text-black font-medium leading-6 " id="availability-label">
+                                        <i18n.Translate>Show demo description</i18n.Translate>
+                                      </span>
+                                    </span>
+                                    <button type="button" data-enabled={settings.showDemoDescription} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description"
 
-                            <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>
-                                })}
+                                      onClick={() => {
+                                        updateSettings("showDemoDescription", !settings.showDemoDescription);
+                                      }}>
+                                      <span aria-hidden="true" data-enabled={settings.showDemoDescription} class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
+                                    </button>
+                                  </div>
+                                </li>
+                                <li class="mt-2">
+                                  <div class="flex items-center justify-between">
+                                    <span class="flex flex-grow flex-col">
+                                      <span class="text-sm text-black font-medium leading-6 " id="availability-label">
+                                        <i18n.Translate>Use fast withdrawal</i18n.Translate>
+                                      </span>
+                                    </span>
+                                    <button type="button" data-enabled={settings.fastWithdrawal} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description"
+                                      onClick={() => {
+                                        updateSettings("fastWithdrawal", !settings.fastWithdrawal);
+                                      }}>
+                                      <span aria-hidden="true" data-enabled={settings.fastWithdrawal} class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
+                                    </button>
+                                  </div>
+                                </li>
                               </ul>
                             </li>
                           </ul>
@@ -240,11 +269,6 @@ export function BankFrame({
     //   >
     //     <a href="#main" class="skip">{i18n.str`Skip to main content`}</a>
     //     <div style="max-width: 50em; margin-left: 2em; margin-right: 2em;">
-    //       <h1>
-    //         <span class="it">
-    //           <a href="/">{bankUiSettings.bankName}</a>
-    //         </span>
-    //       </h1>
     //       {maybeDemoContent(
     //         <p>
     //           {IS_PUBLIC_ACCOUNT_ENABLED ? (
@@ -298,33 +322,6 @@ export function BankFrame({
   );
 }
 
-// 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>
-//   );
-// }
 
 function StatusBanner(): VNode {
   const notifs = useNotifications()
@@ -469,5 +466,5 @@ function AccountBalance({ account }: { account: string }): VNode {
     {Amounts.currencyOf(result.data.balance.amount)}
     &nbsp;{result.data.balance.credit_debit_indicator === "debit" ? "-" : ""}
     {Amounts.stringifyValue(result.data.balance.amount)}
-    </div>
+  </div>
 }
diff --git a/packages/demobank-ui/src/pages/HomePage.tsx b/packages/demobank-ui/src/pages/HomePage.tsx
index 40cc147a6..2acfc9b57 100644
--- a/packages/demobank-ui/src/pages/HomePage.tsx
+++ b/packages/demobank-ui/src/pages/HomePage.tsx
@@ -52,21 +52,21 @@ const logger = new Logger("AccountPage");
 export function HomePage({
   onRegister,
   account,
-  onPendingOperationFound,
+  // onPendingOperationFound,
   goToBusinessAccount,
 }: {
   account: string,
-  onPendingOperationFound: (id: string) => void;
+  // onPendingOperationFound: (id: string) => void;
   onRegister: () => void;
   goToBusinessAccount: () => void;
 }): VNode {
   const [settings] = useSettings();
   const { i18n } = useTranslationContext();
 
-  if (settings.currentWithdrawalOperationId) {
-    onPendingOperationFound(settings.currentWithdrawalOperationId);
-    return <Loading />;
-  }
+  // if (settings.currentWithdrawalOperationId) {
+  //   onPendingOperationFound(settings.currentWithdrawalOperationId);
+  //   return <Loading />;
+  // }
 
   return (
     <AccountPage
@@ -79,11 +79,9 @@ export function HomePage({
 
 export function WithdrawalOperationPage({
   operationId,
-  onLoadNotOk,
   onContinue,
 }: {
   operationId: string;
-  onLoadNotOk: () => void;
   onContinue: () => void;
 }): VNode {
   //FIXME: libeufin sandbox should return show to create the integration api endpoint
@@ -95,6 +93,7 @@ export function WithdrawalOperationPage({
   });
   const parsedUri = parseWithdrawUri(uri);
   const { i18n } = useTranslationContext();
+  const [settings, updateSettings] = useSettings();
 
   if (!parsedUri) {
     notifyError(
@@ -107,8 +106,9 @@ export function WithdrawalOperationPage({
   return (
     <WithdrawalQRCode
       withdrawUri={parsedUri}
-      onContinue={onContinue}
-      onLoadNotOk={onLoadNotOk}
+      onClose={() => {
+        updateSettings("currentWithdrawalOperationId", undefined)
+      }}
     />
   );
 }
diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx b/packages/demobank-ui/src/pages/PaymentOptions.tsx
index 1728074a3..573f8c769 100644
--- a/packages/demobank-ui/src/pages/PaymentOptions.tsx
+++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx
@@ -34,80 +34,83 @@ export function PaymentOptions({ limit }: { limit: AmountJson }): VNode {
   // 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">
-        <i18n.Translate>Send money to</i18n.Translate>
-      </legend>
+    <div class="mt-2">
 
-      <div class="px-4 mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-2 sm:gap-x-4">
-        {/* <!-- Active: "border-indigo-600 ring-2 ring-indigo-600", Not Active: "border-gray-300" --> */}
-        <label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none" + (tab === "charge-wallet" ? "border-indigo-600 ring-2 ring-indigo-600" : "border-gray-300")}>
-          <input type="radio" name="project-type" value="Newsletter" class="sr-only" aria-labelledby="project-type-0-label" aria-describedby="project-type-0-description-0 project-type-0-description-1" onClick={() => {
-            setTab("charge-wallet")
-          }} />
-          <span class="flex flex-1">
-            <div class="text-lg mr-2">&#x1F4B5;</div>
-            <span class="flex flex-col">
-              <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>
+      <fieldset>
+        <legend class="px-4 text-base font-semibold leading-6 text-gray-900">
+          <i18n.Translate>Send money to</i18n.Translate>
+        </legend>
+
+        <div class="px-4 mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-2 sm:gap-x-4">
+          {/* <!-- Active: "border-indigo-600 ring-2 ring-indigo-600", Not Active: "border-gray-300" --> */}
+          <label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none" + (tab === "charge-wallet" ? "border-indigo-600 ring-2 ring-indigo-600" : "border-gray-300")}>
+            <input type="radio" name="project-type" value="Newsletter" class="sr-only" aria-labelledby="project-type-0-label" aria-describedby="project-type-0-description-0 project-type-0-description-1" onClick={() => {
+              setTab("charge-wallet")
+            }} />
+            <span class="flex flex-1">
+              <div class="text-lg mr-2">&#x1F4B5;</div>
+              <span class="flex flex-col">
+                <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>
-          </span>
-          <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>
+            <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>
 
 
-        <label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none" + (tab === "wire-transfer" ? "border-indigo-600 ring-2 ring-indigo-600" : "border-gray-300")}>
-          <input type="radio" name="project-type" value="Existing Customers" class="sr-only" aria-labelledby="project-type-1-label" aria-describedby="project-type-1-description-0 project-type-1-description-1" onClick={() => {
-            setTab("wire-transfer")
-          }} />
-          <span class="flex flex-1">
-          <div class="text-lg mr-2">&#x2194;</div>
-            <span class="flex flex-col">
-              <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">
-                <i18n.Translate>Make a wire transfer to an account which you know the address.</i18n.Translate>
+          <label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none" + (tab === "wire-transfer" ? "border-indigo-600 ring-2 ring-indigo-600" : "border-gray-300")}>
+            <input type="radio" name="project-type" value="Existing Customers" class="sr-only" aria-labelledby="project-type-1-label" aria-describedby="project-type-1-description-0 project-type-1-description-1" onClick={() => {
+              setTab("wire-transfer")
+            }} />
+            <span class="flex flex-1">
+              <div class="text-lg mr-2">&#x2194;</div>
+              <span class="flex flex-col">
+                <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">
+                  <i18n.Translate>Make a wire transfer to an account which you know the address.</i18n.Translate>
+                </span>
               </span>
             </span>
-          </span>
-          <svg class="h-5 w-5 text-indigo-600" style={{ visibility: tab === "wire-transfer" ? "visible" : "hidden" }} viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
-            <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
-          </svg>
-        </label>
-      </div>
-      {tab === "charge-wallet" && (
-        <WalletWithdrawForm
-          focus
-          limit={limit}
-          onSuccess={(id) => {
-            updateSettings("currentWithdrawalOperationId", id);
-          }}
-          onCancel={() => {
-            setTab(undefined)
-          }}
-        />
-      )}
-      {tab === "wire-transfer" && (
-        <PaytoWireTransferForm
-          focus
-          title={i18n.str`Transfer details`}
-          limit={limit}
-          onSuccess={() => {
-            notifyInfo(i18n.str`Wire transfer created!`);
-          }}
-          onCancel={() => {
-            setTab(undefined)
-          }}
-        />
-      )}
+            <svg class="h-5 w-5 text-indigo-600" style={{ visibility: tab === "wire-transfer" ? "visible" : "hidden" }} viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+              <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
+            </svg>
+          </label>
+        </div>
+        {tab === "charge-wallet" && (
+          <WalletWithdrawForm
+            focus
+            limit={limit}
+            onSuccess={(id) => {
+              updateSettings("currentWithdrawalOperationId", id);
+            }}
+            onCancel={() => {
+              setTab(undefined)
+            }}
+          />
+        )}
+        {tab === "wire-transfer" && (
+          <PaytoWireTransferForm
+            focus
+            title={i18n.str`Transfer details`}
+            limit={limit}
+            onSuccess={() => {
+              notifyInfo(i18n.str`Wire transfer created!`);
+            }}
+            onCancel={() => {
+              setTab(undefined)
+            }}
+          />
+        )}
 
-    </fieldset>
+      </fieldset>
+    </div>
   )
 }
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx b/packages/demobank-ui/src/pages/QrCodeSection.tsx
index 7c1b3bdc5..416c714e2 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx
@@ -135,3 +135,109 @@ export function QrCodeSection({
     </Fragment>
   );
 }
+
+
+export function QrCodeSectionSimpler({
+  withdrawUri,
+  onAborted,
+}: {
+  withdrawUri: WithdrawUriResult;
+  onAborted: () => void;
+}): VNode {
+  const { i18n } = useTranslationContext();
+  useEffect(() => {
+    //Taler Wallet WebExtension is listening to headers response and tab updates.
+    //In the SPA there is no header response with the Taler URI so
+    //this hack manually triggers the tab update after the QR is in the DOM.
+    // WebExtension will be using
+    // https://developer.chrome.com/docs/extensions/reference/tabs/#event-onUpdated
+    document.title = `${document.title} ${withdrawUri.withdrawalOperationId}`;
+  }, []);
+  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 (
+    <Fragment>
+      <div class="bg-white shadow-xl sm:rounded-lg">
+        <div class="p2 ">
+          <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>
+      </div>
+
+    </Fragment>
+  );
+}
+
diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
index 8c41f7576..08f706919 100644
--- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
+++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
@@ -20,6 +20,7 @@ import {
   HttpStatusCode,
   Logger,
   TranslatedString,
+  WithdrawUriResult,
   parseWithdrawUri,
 } from "@gnu-taler/taler-util";
 import {
@@ -34,6 +35,9 @@ import { useEffect, useRef, useState } from "preact/hooks";
 import { useAccessAPI } from "../hooks/access.js";
 import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
 import { Amount } from "./PaytoWireTransferForm.js";
+import { useSettings } from "../hooks/settings.js";
+import { WithdrawalOperationState } from "./WithdrawalQRCode.js";
+import { Loading } from "../components/Loading.js";
 
 const logger = new Logger("WalletWithdrawForm");
 const RefAmount = forwardRef(Amount);
@@ -51,8 +55,9 @@ export function WalletWithdrawForm({
 }): VNode {
   const { i18n } = useTranslationContext();
   const { createWithdrawal } = useAccessAPI();
+  const [settings, updateSettings] = useSettings()
 
-  const [amountStr, setAmountStr] = useState<string | undefined>("5.00");
+  const [amountStr, setAmountStr] = useState<string | undefined>(`${settings.maxWithdrawalAmount}`);
   const ref = useRef<HTMLInputElement>(null);
   useEffect(() => {
     if (focus) ref.current?.focus();
@@ -78,7 +83,6 @@ export function WalletWithdrawForm({
   async function doStart() {
     if (!parsedAmount) return;
     try {
-      console.log("ASDASD")
       const result = await createWithdrawal({
         amount: Amounts.stringify(parsedAmount),
       });
@@ -109,7 +113,6 @@ export function WalletWithdrawForm({
         )
       }
     }
-
   }
 
   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">
@@ -122,91 +125,103 @@ export function WalletWithdrawForm({
         <i18n.Translate>After using your wallet you will be redirected here to confirm or cancel the operation.</i18n.Translate>
       </p>
     </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">
-        <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-6 py-4 text-sm items-center rounded-l-md bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
-                onClick={(e) => {
-                  e.preventDefault();
-                  setAmountStr("50.00")
-                }}
-              >
-                50.00
-              </button>
-              <button type="button"
-                class="relative -ml-px -mr-px inline-flex px-6 py-4 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")
-                }}
-              >
-
-                25.00
-              </button>
-              <button type="button"
-                class="relative -ml-px -mr-px inline-flex px-6 py-4 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-6 py-4 text-sm items-center rounded-r-md bg-white  text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
-                onClick={(e) => {
-                  e.preventDefault();
-                  setAmountStr("5.00")
+
+    {!settings.fastWithdrawal ?
+      <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">
+            <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);
                 }}
-              >
-                5.00
-              </button>
-            </span>
-          </div>
+                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-6 py-4 text-sm items-center rounded-l-md bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
+                  onClick={(e) => {
+                    e.preventDefault();
+                    setAmountStr("50.00")
+                  }}
+                >
+                  50.00
+                </button>
+                <button type="button"
+                  class="relative -ml-px -mr-px inline-flex px-6 py-4 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")
+                  }}
+                >
+
+                  25.00
+                </button>
+                <button type="button"
+                  class="relative -ml-px -mr-px inline-flex px-6 py-4 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-6 py-4 text-sm items-center rounded-r-md bg-white  text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
+                  onClick={(e) => {
+                    e.preventDefault();
+                    setAmountStr("5.00")
+                  }}
+                >
+                  5.00
+                </button>
+              </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>
-      </div>
-      <div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8">
-        <button type="button" class="text-sm font-semibold leading-6 text-gray-900"
-          onClick={onCancel}
-        >
-          <i18n.Translate>Cancel</i18n.Translate></button>
-        <button type="submit"
-          class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
-          // disabled={isRawPayto ? !!errorsPayto : !!errorsWire}
-          onClick={(e) => {
-            e.preventDefault()
-            doStart()
-          }}
-        >
-          <i18n.Translate>Continue</i18n.Translate>
-        </button>
-      </div>
 
-    </form>
+      </form>
+      : settings.currentWithdrawalOperationId === undefined ?
+        <Loading /> :
+        <WithdrawalOperationState
+          currentOperation={settings.currentWithdrawalOperationId}
+          currency={limit.currency}
+          onClose={() => {
+            onCancel()
+          }}
+        />
+    }
   </div>
   );
 }
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 2a3a1ec2c..9976babdb 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -18,24 +18,29 @@ import {
   Amounts,
   HttpStatusCode,
   Logger,
+  TranslatedString,
   WithdrawUriResult,
   parsePaytoUri,
+  parseWithdrawUri,
+  stringifyWithdrawUri,
 } from "@gnu-taler/taler-util";
-import { ErrorType, notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { ErrorType, RequestError, notify, notifyError, 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 { useAccessAPI, useWithdrawalDetails } from "../hooks/access.js";
 import { useSettings } from "../hooks/settings.js";
 import { handleNotOkResult } from "./HomePage.js";
-import { QrCodeSection } from "./QrCodeSection.js";
+import { QrCodeSection, QrCodeSectionSimpler } from "./QrCodeSection.js";
 import { WithdrawalConfirmationQuestion } from "./WithdrawalConfirmationQuestion.js";
+import { useEffect, useState } from "preact/hooks";
+import { buildRequestErrorMessage } from "../utils.js";
+import { getInitialBackendBaseURL } from "../hooks/backend.js";
 
 const logger = new Logger("WithdrawalQRCode");
 
 interface Props {
   withdrawUri: WithdrawUriResult;
-  onContinue: () => void;
-  onLoadNotOk: () => void;
+  onClose: () => void;
 }
 /**
  * Offer the QR code (and a clickable taler://-link) to
@@ -44,14 +49,9 @@ interface Props {
  */
 export function WithdrawalQRCode({
   withdrawUri,
-  onContinue,
-  onLoadNotOk,
+  onClose,
 }: Props): VNode {
   const [settings, updateSettings] = useSettings();
-  function clearCurrentWithdrawal(): void {
-    updateSettings("currentWithdrawalOperationId", undefined);
-    onContinue();
-  }
   const { i18n } = useTranslationContext();
   const result = useWithdrawalDetails(withdrawUri.withdrawalOperationId);
   if (!result.ok) {
@@ -62,7 +62,7 @@ export function WithdrawalQRCode({
       result.type === ErrorType.CLIENT &&
       result.status === HttpStatusCode.NotFound
     ) {
-      clearCurrentWithdrawal()
+      onClose()
       return <div>operation not found</div>;
     }
     // onLoadNotOk();
@@ -89,8 +89,7 @@ export function WithdrawalQRCode({
           style={{ float: "right" }}
           onClick={async (e) => {
             e.preventDefault();
-            clearCurrentWithdrawal()
-            onContinue()
+            onClose()
           }}>
           {i18n.str`Continue`}
         </a>
@@ -149,8 +148,7 @@ export function WithdrawalQRCode({
           class="inline-flex w-full justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
           onClick={async (e) => {
             e.preventDefault();
-            clearCurrentWithdrawal()
-            onContinue()
+            onClose()
           }}>
           <i18n.Translate>Continue</i18n.Translate>
         </button>
@@ -165,8 +163,7 @@ export function WithdrawalQRCode({
         withdrawUri={withdrawUri}
         onAborted={() => {
           notifyInfo(i18n.str`Operation canceled`);
-          clearCurrentWithdrawal()
-          onContinue()
+          onClose()
         }}
       />
     );
@@ -196,9 +193,131 @@ export function WithdrawalQRCode({
       }}
       onAborted={() => {
         notifyInfo(i18n.str`Operation canceled`);
-        clearCurrentWithdrawal()
-        onContinue()
+        onClose()
       }}
     />
   );
+}
+
+
+export function WithdrawalOperationState({
+  currency,
+  currentOperation,
+  onClose,
+}: {currency:string, currentOperation: string, onClose: () => void}): VNode {
+  const { i18n } = useTranslationContext();
+  const [settings, updateSettings] = useSettings()
+  const { createWithdrawal } = useAccessAPI();
+
+  const amount = settings.maxWithdrawalAmount
+  async function doSilentStart() {
+    //FIXME: if amount is not enough use balance
+    const parsedAmount = Amounts.parseOrThrow(`${currency}:${amount}`)
+
+    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 {
+        updateSettings("currentWithdrawalOperationId", 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
+        )
+      }
+    }
+  }
+
+  useEffect(() => {
+    doSilentStart()
+  }, [settings.fastWithdrawal, amount])
+
+  const result = useWithdrawalDetails(currentOperation);
+  if (!result.ok) {
+    if (result.loading) {
+      return <Loading />;
+    }
+    if (
+      result.type === ErrorType.CLIENT &&
+      result.status === HttpStatusCode.NotFound
+    ) {
+      onClose()
+      return <div>operation not found</div>;
+    }
+    // onLoadNotOk();
+    return handleNotOkResult(i18n)(result);
+  }
+  const { data } = result;
+
+  const baseUrl = getInitialBackendBaseURL()
+  const uri = stringifyWithdrawUri({
+    bankIntegrationApiBaseUrl: `${baseUrl}/integration-api`,
+    withdrawalOperationId: currentOperation,
+  });
+  const parsedUri = parseWithdrawUri(uri);
+
+  if (data.aborted) {
+    return <div>
+      the operation was aborted, you can create another one
+    </div>
+  }
+
+  if (data.confirmation_done) {
+    return <div>
+      the wire transfer is made, you coin should arrive shortly
+    </div>
+  }
+  if (!parsedUri) {
+    return <div>
+      the operation is not valid, create another one
+    </div>
+  }
+  if (!data.selection_done) {
+    return (
+      <QrCodeSectionSimpler
+        withdrawUri={parsedUri}
+        onAborted={() => {
+          notifyInfo(i18n.str`Operation canceled`);
+          onClose()
+        }}
+      />
+    );
+  }
+
+  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 selected but no account
+    </div>
+  }
+
+  return <div>
+    the operation is wating for the question to be answered
+  </div>;
 }
\ 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
index 050f1fb8a..1e5370afc 100644
--- a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
@@ -61,7 +61,7 @@ export function RemoveAccount({
             </h3>
             <div class="mt-2 text-sm text-yellow-700">
               <p>
-                <i18n.Translate>The account can be delete while still holding some balance. First make sure that the owner make a complete cashout.</i18n.Translate>
+                <i18n.Translate>The account can't be delete while still holding some balance. First make sure that the owner make a complete cashout.</i18n.Translate>
               </p>
             </div>
           </div>
-- 
cgit v1.2.3


From a59df74fb2b4374fd58f68fd4abaffe623cd54d6 Mon Sep 17 00:00:00 2001
From: Sebastian <sebasjm@gmail.com>
Date: Fri, 22 Sep 2023 15:29:19 -0300
Subject: more ui

---
 packages/demobank-ui/src/components/Routing.tsx    |  27 +-
 packages/demobank-ui/src/hooks/settings.ts         |   3 +
 .../demobank-ui/src/pages/AccountPage/index.ts     |   2 +
 .../demobank-ui/src/pages/AccountPage/state.ts     |   3 +-
 .../demobank-ui/src/pages/AccountPage/views.tsx    |   4 +-
 packages/demobank-ui/src/pages/BankFrame.tsx       |  15 +
 packages/demobank-ui/src/pages/HomePage.tsx        |  17 +-
 .../demobank-ui/src/pages/OperationState/index.ts  |  10 +-
 .../demobank-ui/src/pages/OperationState/state.ts  | 109 ++++++-
 .../demobank-ui/src/pages/OperationState/views.tsx | 363 ++++++++++++++++++++-
 packages/demobank-ui/src/pages/PaymentOptions.tsx  |  17 +-
 packages/demobank-ui/src/pages/QrCodeSection.tsx   | 104 ------
 .../demobank-ui/src/pages/WalletWithdrawForm.tsx   | 273 ++++++++++------
 .../demobank-ui/src/pages/WithdrawalQRCode.tsx     | 163 +--------
 packages/demobank-ui/src/pages/business/Home.tsx   |   1 -
 15 files changed, 693 insertions(+), 418 deletions(-)

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

diff --git a/packages/demobank-ui/src/components/Routing.tsx b/packages/demobank-ui/src/components/Routing.tsx
index e1fd93737..90d2d4c48 100644
--- a/packages/demobank-ui/src/components/Routing.tsx
+++ b/packages/demobank-ui/src/components/Routing.tsx
@@ -45,6 +45,20 @@ export function Routing(): VNode {
             />
           )}
         />
+        <Route
+          path="/operation/:wopid"
+          component={({ wopid }: { wopid: string }) => (
+            <WithdrawalOperationPage
+              operationId={wopid}
+              onContinue={() => {
+                route("/account");
+              }}
+              // onLoadNotOk={() => {
+              //   route("/account");
+              // }}
+            />
+          )}
+        />
         <Route
           path="/register"
           component={() => (
@@ -64,10 +78,6 @@ export function Routing(): VNode {
   return (
     <BankFrame account={backend.state.username}>
       <Router history={history}>
-        <Route
-          path="/test"
-          component={Test}
-        />
         <Route
           path="/operation/:wopid"
           component={({ wopid }: { wopid: string }) => (
@@ -76,9 +86,6 @@ export function Routing(): VNode {
               onContinue={() => {
                 route("/account");
               }}
-              // onLoadNotOk={() => {
-              //   route("/account");
-              // }}
             />
           )}
         />
@@ -108,9 +115,9 @@ export function Routing(): VNode {
             } else {
               return <HomePage
                 account={username}
-                // onPendingOperationFound={(wopid) => {
-                //   route(`/operation/${wopid}`);
-                // }}
+                goToConfirmOperation={(wopid) => {
+                  route(`/operation/${wopid}`);
+                }}
                 goToBusinessAccount={() => {
                   route("/business");
                 }}
diff --git a/packages/demobank-ui/src/hooks/settings.ts b/packages/demobank-ui/src/hooks/settings.ts
index c2fd93a0c..5f004c6d4 100644
--- a/packages/demobank-ui/src/hooks/settings.ts
+++ b/packages/demobank-ui/src/hooks/settings.ts
@@ -30,6 +30,7 @@ interface Settings {
   currentWithdrawalOperationId: string | undefined;
   showWithdrawalSuccess: boolean;
   showDemoDescription: boolean;
+  showInstallWallet: boolean;
   maxWithdrawalAmount: number;
   fastWithdrawal: boolean;
 }
@@ -39,6 +40,7 @@ export const codecForSettings = (): Codec<Settings> =>
     .property("currentWithdrawalOperationId", codecOptional(codecForString()))
     .property("showWithdrawalSuccess", (codecForBoolean()))
     .property("showDemoDescription", (codecForBoolean()))
+    .property("showInstallWallet", (codecForBoolean()))
     .property("fastWithdrawal", (codecForBoolean()))
     .property("maxWithdrawalAmount", codecForNumber())
     .build("Settings");
@@ -47,6 +49,7 @@ const defaultSettings: Settings = {
   currentWithdrawalOperationId: undefined,
   showWithdrawalSuccess: true,
   showDemoDescription: true,
+  showInstallWallet: true,
   maxWithdrawalAmount: 25,
   fastWithdrawal: false,
 };
diff --git a/packages/demobank-ui/src/pages/AccountPage/index.ts b/packages/demobank-ui/src/pages/AccountPage/index.ts
index 128a6d30f..81eeb4a03 100644
--- a/packages/demobank-ui/src/pages/AccountPage/index.ts
+++ b/packages/demobank-ui/src/pages/AccountPage/index.ts
@@ -29,6 +29,7 @@ export interface Props {
     error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
   ) => VNode;
   goToBusinessAccount: () => void;
+  goToConfirmOperation: (id:string) => void;
 }
 
 export type State = State.Loading | State.LoadingError | State.Ready | State.InvalidIban | State.UserNotFound;
@@ -54,6 +55,7 @@ export namespace State {
     account: string, 
     limit: AmountJson,
     goToBusinessAccount: () => void;
+    goToConfirmOperation: (id:string) => void;
   }
 
   export interface InvalidIban {
diff --git a/packages/demobank-ui/src/pages/AccountPage/state.ts b/packages/demobank-ui/src/pages/AccountPage/state.ts
index a57e19901..1a1475c0d 100644
--- a/packages/demobank-ui/src/pages/AccountPage/state.ts
+++ b/packages/demobank-ui/src/pages/AccountPage/state.ts
@@ -20,7 +20,7 @@ import { useBackendContext } from "../../context/backend.js";
 import { useAccountDetails } from "../../hooks/access.js";
 import { Props, State } from "./index.js";
 
-export function useComponentState({ account, goToBusinessAccount }: Props): State {
+export function useComponentState({ account, goToBusinessAccount, goToConfirmOperation }: Props): State {
   const result = useAccountDetails(account);
   const backend = useBackendContext();
   const { i18n } = useTranslationContext();
@@ -75,6 +75,7 @@ export function useComponentState({ account, goToBusinessAccount }: Props): Stat
   return {
     status: "ready",
     goToBusinessAccount,
+    goToConfirmOperation,
     error: undefined,
     account,
     limit,
diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx b/packages/demobank-ui/src/pages/AccountPage/views.tsx
index 0187989af..23a815bd8 100644
--- a/packages/demobank-ui/src/pages/AccountPage/views.tsx
+++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx
@@ -123,7 +123,7 @@ function ShowDemoInfo():VNode {
 </div>
 }
 
-export function ReadyView({ account, limit, goToBusinessAccount }: State.Ready): VNode<{}> {
+export function ReadyView({ account, limit, goToBusinessAccount, goToConfirmOperation }: State.Ready): VNode<{}> {
   const { i18n } = useTranslationContext();
 
   return <Fragment>
@@ -131,7 +131,7 @@ export function ReadyView({ account, limit, goToBusinessAccount }: State.Ready):
 
     <ShowDemoInfo />
     
-    <PaymentOptions limit={limit} />
+    <PaymentOptions limit={limit} goToConfirmOperation={goToConfirmOperation} />
     <Transactions account={account} />
   </Fragment>;
 }
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx
index d1c94135b..5bfaa63ec 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -206,6 +206,21 @@ export function BankFrame({
                                     </button>
                                   </div>
                                 </li>
+                                <li class="mt-2">
+                                  <div class="flex items-center justify-between">
+                                    <span class="flex flex-grow flex-col">
+                                      <span class="text-sm text-black font-medium leading-6 " id="availability-label">
+                                        <i18n.Translate>Show install wallet first</i18n.Translate>
+                                      </span>
+                                    </span>
+                                    <button type="button" data-enabled={settings.showInstallWallet} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description"
+                                      onClick={() => {
+                                        updateSettings("showInstallWallet", !settings.showInstallWallet);
+                                      }}>
+                                      <span aria-hidden="true" data-enabled={settings.showInstallWallet} class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
+                                    </button>
+                                  </div>
+                                </li>
                                 <li class="mt-2">
                                   <div class="flex items-center justify-between">
                                     <span class="flex flex-grow flex-col">
diff --git a/packages/demobank-ui/src/pages/HomePage.tsx b/packages/demobank-ui/src/pages/HomePage.tsx
index 2acfc9b57..8d5e1f3b9 100644
--- a/packages/demobank-ui/src/pages/HomePage.tsx
+++ b/packages/demobank-ui/src/pages/HomePage.tsx
@@ -36,6 +36,7 @@ import { useSettings } from "../hooks/settings.js";
 import { AccountPage } from "./AccountPage/index.js";
 import { LoginForm } from "./LoginForm.js";
 import { WithdrawalQRCode } from "./WithdrawalQRCode.js";
+import { route } from "preact-router";
 
 const logger = new Logger("AccountPage");
 
@@ -52,25 +53,20 @@ const logger = new Logger("AccountPage");
 export function HomePage({
   onRegister,
   account,
-  // onPendingOperationFound,
+  goToConfirmOperation,
   goToBusinessAccount,
 }: {
   account: string,
-  // onPendingOperationFound: (id: string) => void;
   onRegister: () => void;
   goToBusinessAccount: () => void;
+  goToConfirmOperation: (id:string) => void;
 }): VNode {
-  const [settings] = useSettings();
   const { i18n } = useTranslationContext();
 
-  // if (settings.currentWithdrawalOperationId) {
-  //   onPendingOperationFound(settings.currentWithdrawalOperationId);
-  //   return <Loading />;
-  // }
-
   return (
     <AccountPage
       account={account}
+      goToConfirmOperation={goToConfirmOperation}
       goToBusinessAccount={goToBusinessAccount}
       onLoadNotOk={handleNotOkResult(i18n, onRegister)}
     />
@@ -102,12 +98,13 @@ export function WithdrawalOperationPage({
     );
     return <Loading />;
   }
-
+  
   return (
     <WithdrawalQRCode
       withdrawUri={parsedUri}
       onClose={() => {
         updateSettings("currentWithdrawalOperationId", undefined)
+        onContinue()
       }}
     />
   );
@@ -178,7 +175,7 @@ export function handleNotOkResult(
           assertUnreachable(result);
         }
       }
-
+      route("/")
       return <div>error</div>;
     }
     return <div />;
diff --git a/packages/demobank-ui/src/pages/OperationState/index.ts b/packages/demobank-ui/src/pages/OperationState/index.ts
index 254fcba5f..32302f272 100644
--- a/packages/demobank-ui/src/pages/OperationState/index.ts
+++ b/packages/demobank-ui/src/pages/OperationState/index.ts
@@ -26,6 +26,7 @@ import { ErrorLoading } from "../../components/ErrorLoading.js";
 export interface Props {
   currency: string;
   onClose: () => void;
+  goToConfirmOperation: (id: string) => void;
 }
 
 export type State = State.Loading |
@@ -57,26 +58,33 @@ export namespace State {
     error: undefined;
     uri: WithdrawUriResult,
     onClose: () => void;
+    onAbort: () => void;
   }
 
   export interface InvalidPayto {
     status: "invalid-payto",
     error: undefined;
     payto: string | null;
+    onClose: () => void;
   }
   export interface InvalidWithdrawal {
     status: "invalid-withdrawal",
     error: undefined;
+    onClose: () => void;
     uri: string,
   }
   export interface InvalidReserve {
     status: "invalid-reserve",
     error: undefined;
+    onClose: () => void;
     reserve: string | null;
   }
   export interface NeedConfirmation {
     status: "need-confirmation",
+    onAbort: () => void;
+    onConfirm: () => void;
     error: undefined;
+    busy: boolean,
   }
   export interface Aborted {
     status: "aborted",
@@ -111,7 +119,7 @@ const viewMapping: utils.StateViewMap<State> = {
   ready: ReadyView,
 };
 
-export const AccountPage = utils.compose(
+export const OperationState = utils.compose(
   (p: Props) => useComponentState(p),
   viewMapping,
 );
diff --git a/packages/demobank-ui/src/pages/OperationState/state.ts b/packages/demobank-ui/src/pages/OperationState/state.ts
index 6fb7bb28f..ae03ed529 100644
--- a/packages/demobank-ui/src/pages/OperationState/state.ts
+++ b/packages/demobank-ui/src/pages/OperationState/state.ts
@@ -15,21 +15,24 @@
  */
 
 import { Amounts, HttpStatusCode, TranslatedString, parsePaytoUri, parseWithdrawUri, stringifyWithdrawUri } from "@gnu-taler/taler-util";
-import { ErrorType, RequestError, notify, notifyError, useTranslationContext, utils } from "@gnu-taler/web-util/browser";
+import { ErrorType, RequestError, notify, notifyError, notifyInfo, useTranslationContext, utils } from "@gnu-taler/web-util/browser";
 import { useBackendContext } from "../../context/backend.js";
-import { useAccessAPI, useAccountDetails, useWithdrawalDetails } from "../../hooks/access.js";
+import { useAccessAPI, useAccessAnonAPI, useAccountDetails, useWithdrawalDetails } from "../../hooks/access.js";
 import { Props, State } from "./index.js";
 import { useSettings } from "../../hooks/settings.js";
-import { buildRequestErrorMessage } from "../../utils.js";
-import { useEffect } from "preact/hooks";
+import { buildRequestErrorMessage, undefinedIfEmpty } from "../../utils.js";
+import { useEffect, useMemo, useState } from "preact/hooks";
 import { getInitialBackendBaseURL } from "../../hooks/backend.js";
 
-export function useComponentState({ currency, onClose }: Props): utils.RecursiveState<State> {
+export function useComponentState({ currency, onClose,goToConfirmOperation }: Props): utils.RecursiveState<State> {
   const { i18n } = useTranslationContext();
   const [settings, updateSettings] = useSettings()
   const { createWithdrawal } = useAccessAPI();
+  const { abortWithdrawal, confirmWithdrawal } = useAccessAnonAPI();
+  const [busy, setBusy] = useState<Record<string, undefined>>()
 
   const amount = settings.maxWithdrawalAmount
+
   async function doSilentStart() {
     //FIXME: if amount is not enough use balance
     const parsedAmount = Amounts.parseOrThrow(`${currency}:${amount}`)
@@ -67,12 +70,14 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
     }
   }
 
+  const withdrawalOperationId = settings.currentWithdrawalOperationId
   useEffect(() => {
-    doSilentStart()
+    if (withdrawalOperationId === undefined) {
+      doSilentStart()
+    }
   }, [settings.fastWithdrawal, amount])
 
   const baseUrl = getInitialBackendBaseURL()
-  const withdrawalOperationId = settings.currentWithdrawalOperationId
 
   if (!withdrawalOperationId) {
     return {
@@ -81,6 +86,63 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
     }
   }
 
+  const wid = withdrawalOperationId
+
+  async function doAbort() {
+    try {
+      setBusy({})
+      await abortWithdrawal(wid);
+      onClose();
+    } 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
+        )
+      }
+    }
+    setBusy(undefined)
+  }
+
+  async function doConfirm() {
+    try {
+      setBusy({})
+      await confirmWithdrawal(wid);
+      notifyInfo(i18n.str`Wire transfer completed!`)
+    } 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
+        )
+      }
+    }
+    setBusy(undefined)
+  }  
   const bankIntegrationApiBaseUrl = `${baseUrl}/integration-api`
   const uri = stringifyWithdrawUri({
     bankIntegrationApiBaseUrl,
@@ -92,11 +154,13 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
       status: "invalid-withdrawal",
       error: undefined,
       uri,
+      onClose,
     }
   }
 
   return (): utils.RecursiveState<State> => {
     const result = useWithdrawalDetails(withdrawalOperationId);
+
     if (!result.ok) {
       if (result.loading) {
         return {
@@ -119,10 +183,17 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
     }
 
     if (data.confirmation_done) {
+      if (!settings.showWithdrawalSuccess) {
+        updateSettings("currentWithdrawalOperationId", undefined)
+        onClose()
+      }
       return {
         status: "confirmed",
         error: undefined,
-        onClose,
+        onClose: async () => {
+          updateSettings("currentWithdrawalOperationId", undefined)
+          onClose()
+        },
       }
     }
 
@@ -131,7 +202,12 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
         status: "ready",
         error: undefined,
         uri: parsedUri,
-        onClose
+        onClose: async () => {
+          await doAbort()
+          updateSettings("currentWithdrawalOperationId", undefined)
+          onClose()
+        },
+        onAbort: doAbort,
       }
     }
 
@@ -139,7 +215,8 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
       return {
         status: "invalid-reserve",
         error: undefined,
-        reserve: data.selected_reserve_pub
+        reserve: data.selected_reserve_pub,
+        onClose,
       }
     }
 
@@ -149,13 +226,23 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
       return {
         status: "invalid-payto",
         error: undefined,
-        payto: data.selected_exchange_account
+        payto: data.selected_exchange_account,
+        onClose,
       }
     }
 
+
+    // goToConfirmOperation(withdrawalOperationId)
     return {
       status: "need-confirmation",
       error: undefined,
+      onAbort: async () => {
+        await doAbort()
+        updateSettings("currentWithdrawalOperationId", undefined)
+        onClose()
+      },
+      busy: !!busy,
+      onConfirm: doConfirm
     }
   }
 
diff --git a/packages/demobank-ui/src/pages/OperationState/views.tsx b/packages/demobank-ui/src/pages/OperationState/views.tsx
index db25eaf61..17f1d8457 100644
--- a/packages/demobank-ui/src/pages/OperationState/views.tsx
+++ b/packages/demobank-ui/src/pages/OperationState/views.tsx
@@ -14,7 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { Amounts, stringifyPaytoUri } from "@gnu-taler/taler-util";
+import { Amounts, stringifyPaytoUri, stringifyWithdrawUri } from "@gnu-taler/taler-util";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { Fragment, h, VNode } from "preact";
 import { Transactions } from "../../components/Transactions/index.js";
@@ -24,42 +24,375 @@ import { CopyButton } from "../../components/CopyButton.js";
 import { bankUiSettings } from "../../settings.js";
 import { useBusinessAccountDetails } from "../../hooks/circuit.js";
 import { useSettings } from "../../hooks/settings.js";
+import { useEffect, useMemo, useState } from "preact/hooks";
+import { undefinedIfEmpty } from "../../utils.js";
+import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
+import { QR } from "../../components/QR.js";
 
-export function InvalidPaytoView({ error }: State.InvalidPayto) {
+export function InvalidPaytoView({ payto, onClose }: State.InvalidPayto) {
   return (
-    <div>Payto from server is not valid &quot;{error.data.paytoUri}&quot;</div>
+    <div>Payto from server is not valid &quot;{payto}&quot;</div>
   );
 }
-export function InvalidWithdrawalView({ error }: State.InvalidWithdrawal) {
+export function InvalidWithdrawalView({ uri, onClose }: State.InvalidWithdrawal) {
   return (
-    <div>Payto from server is not valid &quot;{error.data.paytoUri}&quot;</div>
+    <div>Withdrawal uri from server is not valid &quot;{uri}&quot;</div>
   );
 }
-export function InvalidReserveView({ error }: State.InvalidReserve) {
+export function InvalidReserveView({ reserve, onClose }: State.InvalidReserve) {
   return (
-    <div>Payto from server is not valid &quot;{error.data.paytoUri}&quot;</div>
+    <div>Reserve from server is not valid &quot;{reserve}&quot;</div>
   );
 }
 
-export function NeedConfirmationView({ error }: State.NeedConfirmation) {
+export function NeedConfirmationView({ error, onAbort, onConfirm, busy }: State.NeedConfirmation) {
+  const { i18n } = useTranslationContext()
+
+  const captchaNumbers = useMemo(() => {
+    return {
+      a: Math.floor(Math.random() * 10),
+      b: Math.floor(Math.random() * 10),
+    };
+  }, []);
+  const [captchaAnswer, setCaptchaAnswer] = useState<string | undefined>();
+  const answer = parseInt(captchaAnswer ?? "", 10);
+  const errors = undefinedIfEmpty({
+    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,
+  }) ?? (busy ? {} as Record<string, undefined> : undefined);
+
   return (
-    <div>Payto from server is not valid &quot;{error.data.paytoUri}&quot;</div>
+    <div class="bg-white shadow sm:rounded-lg">
+      <div class="px-4 py-5 sm:p-6">
+        <h3 class="text-base font-semibold 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 grid grid-cols-1 gap-y-6 sm:grid-cols-4 sm:gap-x-3">
+
+            <label class={"relative sm:col-span-2 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">
+
+          <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={captchaAnswer ?? ""}
+                    required
+
+                    name="answer"
+                    id="answer"
+                    autocomplete="off"
+                    onChange={(e): void => {
+                      setCaptchaAnswer(e.currentTarget.value)
+                    }}
+                  />
+                </div>
+                <ShowInputErrorLabel message={errors?.answer} isDirty={captchaAnswer !== undefined} />
+              </div>
+            </div>
+            <div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8">
+              <button type="button" class="text-sm font-semibold leading-6 text-gray-900"
+                onClick={onAbort}
+              >
+                <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={!!errors}
+                onClick={(e) => {
+                  e.preventDefault()
+                  onConfirm()
+                }}
+              >
+                <i18n.Translate>Transfer</i18n.Translate>
+              </button>
+            </div>
+
+          </form>
+        </div>
+        <div class="px-4 mt-4 ">
+          {/* <div class="w-full">
+          <div class="px-4 sm:px-0 text-sm">
+            <p><i18n.Translate>Wire transfer details</i18n.Translate></p>
+          </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>
+
+                }
+              })()}
+              <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 break-words">{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">To be added</dd>
+                // {/* Amounts.stringifyValue(details.amount) 
+              </div>
+            </dl>
+          </div>
+        </div> */}
+
+        </div>
+      </div>
+    </div>
+
   );
 }
-export function AbortedView({ error }: State.Aborted) {
+export function AbortedView({ error, onClose }: State.Aborted) {
   return (
-    <div>Payto from server is not valid &quot;{error.data.paytoUri}&quot;</div>
+    <div>aborted</div>
   );
 }
-export function ConfirmedView({ error }: State.Confirmed) {
+export function ConfirmedView({ error, onClose }: State.Confirmed) {
+  const { i18n } = useTranslationContext();
+  const [settings, updateSettings] = useSettings()
   return (
-    <div>Payto from server is not valid &quot;{error.data.paytoUri}&quot;</div>
+    <Fragment>
+
+      <div class="relative ml-auto mr-auto transform overflow-hidden rounded-lg bg-white p-4 text-left shadow-xl transition-all ">
+
+        <div class="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100">
+          <svg class="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
+            <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
+          </svg>
+        </div>
+        <div class="mt-3 text-center sm:mt-5">
+          <h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title">
+            <i18n.Translate>Withdrawal OK</i18n.Translate>
+          </h3>
+          <div class="mt-2">
+            <p class="text-sm text-gray-500">
+              <i18n.Translate>
+                The wire transfer to the Taler exchange bank's account is completed, now the
+                exchange will send the requested amount into your GNU Taler wallet.
+              </i18n.Translate>
+            </p>
+          </div>
+        </div>
+      </div>
+      <div class="mt-4">
+        <div class="flex items-center justify-between">
+          <span class="flex flex-grow flex-col">
+            <span class="text-sm text-black font-medium leading-6 " id="availability-label">
+              <i18n.Translate>Do not show this again</i18n.Translate>
+            </span>
+          </span>
+          <button type="button" data-enabled={!settings.showWithdrawalSuccess} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description"
+            onClick={() => {
+              updateSettings("showWithdrawalSuccess", !settings.showWithdrawalSuccess);
+            }}>
+            <span aria-hidden="true" data-enabled={!settings.showWithdrawalSuccess} class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
+          </button>
+        </div>
+      </div>
+      <div class="mt-5 sm:mt-6">
+        <button type="button"
+          class="inline-flex w-full justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
+          onClick={async (e) => {
+            e.preventDefault();
+            onClose()
+          }}>
+          <i18n.Translate>Close</i18n.Translate>
+        </button>
+      </div>
+    </Fragment>
+
   );
 }
 
-export function ReadyView({ account, limit, goToBusinessAccount }: State.Ready): VNode<{}> {
+export function ReadyView({ uri, onClose }: State.Ready): VNode<{}> {
   const { i18n } = useTranslationContext();
 
-  return <div />
+  useEffect(() => {
+    //Taler Wallet WebExtension is listening to headers response and tab updates.
+    //In the SPA there is no header response with the Taler URI so
+    //this hack manually triggers the tab update after the QR is in the DOM.
+    // WebExtension will be using
+    // https://developer.chrome.com/docs/extensions/reference/tabs/#event-onUpdated
+    document.title = `${document.title} ${uri.withdrawalOperationId}`;
+  }, []);
+  const talerWithdrawUri = stringifyWithdrawUri(uri);
+  const [show, setShow] = useState(false)
+  return <Fragment>
+
+    <div class="bg-white shadow sm:rounded-lg mt-4">
+      <div class="p-4">
+        <h3 class="text-base font-semibold leading-6 text-gray-900">
+          <i18n.Translate>On this device</i18n.Translate>
+        </h3>
+        <div class="mt-2 sm:flex sm:items-start sm:justify-between">
+          <div class="max-w-xl text-sm text-gray-500">
+            <p>
+              <i18n.Translate>If you are using a desktop browser you can open the popup now or click the link if you have the "Inject Taler support" option enabled.</i18n.Translate>
+            </p>
+          </div>
+          <div class="mt-5 sm:ml-6 sm:mt-0 sm:flex sm:flex-shrink-0 sm:items-center">
+            <a href={talerWithdrawUri}
+              class="inline-flex items-center  disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
+            >
+              <i18n.Translate>Start</i18n.Translate>
+            </a>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="bg-white shadow sm:rounded-lg mt-2">
+      <div class="p-4">
+        <h3 class="text-base font-semibold leading-6 text-gray-900">
+          <i18n.Translate>On a mobile phone</i18n.Translate>
+        </h3>
+        <div class="mt-2 sm:flex sm:items-start sm:justify-between">
+          <div class="max-w-xl text-sm text-gray-500">
+            <p>
+              <i18n.Translate>Scan the QR code with your mobile device.</i18n.Translate>
+            </p>
+          </div>
+          <div class="mt-5 sm:ml-6 sm:mt-0 sm:flex sm:flex-shrink-0 sm:items-center">
+            <button type="button"
+              class="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-500"
+              onClick={() => {
+                setShow(!show)
+              }}
+            >
+              {!show ?
+                <i18n.Translate>Show QR</i18n.Translate>
+                :
+                <i18n.Translate>Hide QR</i18n.Translate>
+              }
+            </button>
+          </div>
+        </div>
+        {show &&
+          <div class="mt-2 max-w-md ml-auto mr-auto">
+            <QR text={talerWithdrawUri} />
+          </div>
+        }
+      </div>
+    </div>
+
+    <div class="flex justify-end mt-4">
+      <button type="button"
+        class="inline-flex items-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500"
+        onClick={() => {
+          onClose()
+        }}
+      >
+        Cancel
+      </button>
+    </div>
+  </Fragment>
 
 }
diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx b/packages/demobank-ui/src/pages/PaymentOptions.tsx
index 573f8c769..2830f5c1e 100644
--- a/packages/demobank-ui/src/pages/PaymentOptions.tsx
+++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx
@@ -26,12 +26,11 @@ import { useSettings } from "../hooks/settings.js";
  * Let the user choose a payment option,
  * then specify the details trigger the action.
  */
-export function PaymentOptions({ limit }: { limit: AmountJson }): VNode {
+export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJson, goToConfirmOperation: (id: string) => void }): VNode {
   const { i18n } = useTranslationContext();
-  const [settings, updateSettings] = useSettings();
+  const [settings] = useSettings();
 
   const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>();
-  // const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>(undefined);
 
   return (
     <div class="mt-2">
@@ -56,6 +55,14 @@ export function PaymentOptions({ limit }: { limit: AmountJson }): VNode {
                 <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>
+                {!!settings.currentWithdrawalOperationId &&
+                  <span class="inline-flex items-center gap-x-1.5 rounded-full bg-green-100 px-1.5 py-0.5 text-xs font-medium text-green-700">
+                    <svg class="h-1.5 w-1.5 fill-green-500" viewBox="0 0 6 6" aria-hidden="true">
+                      <circle cx="3" cy="3" r="3" />
+                    </svg>
+                    <i18n.Translate>Operation in progress</i18n.Translate>
+                  </span>
+                }
               </span>
             </span>
             <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">
@@ -88,9 +95,7 @@ export function PaymentOptions({ limit }: { limit: AmountJson }): VNode {
           <WalletWithdrawForm
             focus
             limit={limit}
-            onSuccess={(id) => {
-              updateSettings("currentWithdrawalOperationId", id);
-            }}
+            goToConfirmOperation={goToConfirmOperation}
             onCancel={() => {
               setTab(undefined)
             }}
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx b/packages/demobank-ui/src/pages/QrCodeSection.tsx
index 416c714e2..0a5a386ae 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx
@@ -137,107 +137,3 @@ export function QrCodeSection({
 }
 
 
-export function QrCodeSectionSimpler({
-  withdrawUri,
-  onAborted,
-}: {
-  withdrawUri: WithdrawUriResult;
-  onAborted: () => void;
-}): VNode {
-  const { i18n } = useTranslationContext();
-  useEffect(() => {
-    //Taler Wallet WebExtension is listening to headers response and tab updates.
-    //In the SPA there is no header response with the Taler URI so
-    //this hack manually triggers the tab update after the QR is in the DOM.
-    // WebExtension will be using
-    // https://developer.chrome.com/docs/extensions/reference/tabs/#event-onUpdated
-    document.title = `${document.title} ${withdrawUri.withdrawalOperationId}`;
-  }, []);
-  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 (
-    <Fragment>
-      <div class="bg-white shadow-xl sm:rounded-lg">
-        <div class="p2 ">
-          <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>
-      </div>
-
-    </Fragment>
-  );
-}
-
diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
index 08f706919..8dbdd9da6 100644
--- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
+++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
@@ -36,33 +36,52 @@ import { useAccessAPI } from "../hooks/access.js";
 import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
 import { Amount } from "./PaytoWireTransferForm.js";
 import { useSettings } from "../hooks/settings.js";
-import { WithdrawalOperationState } from "./WithdrawalQRCode.js";
-import { Loading } from "../components/Loading.js";
+import { OperationState } from "./OperationState/index.js";
 
 const logger = new Logger("WalletWithdrawForm");
 const RefAmount = forwardRef(Amount);
 
-export function WalletWithdrawForm({
-  focus,
-  limit,
-  onSuccess,
-  onCancel,
-}: {
+
+function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
   limit: AmountJson;
   focus?: boolean;
-  onSuccess: (operationId: string) => void;
+  goToConfirmOperation: (operationId: string) => void;
   onCancel: () => void;
 }): VNode {
   const { i18n } = useTranslationContext();
-  const { createWithdrawal } = useAccessAPI();
   const [settings, updateSettings] = useSettings()
 
+  const { createWithdrawal } = useAccessAPI();
   const [amountStr, setAmountStr] = useState<string | undefined>(`${settings.maxWithdrawalAmount}`);
   const ref = useRef<HTMLInputElement>(null);
   useEffect(() => {
     if (focus) ref.current?.focus();
   }, [focus]);
 
+  if (!!settings.currentWithdrawalOperationId) {
+    return <div class="rounded-md bg-yellow-50 ring-yellow-2 p-4">
+      <div class="flex">
+        <div class="flex-shrink-0">
+          <svg class="h-5 w-5 text-yellow-300" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+            <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
+          </svg>
+        </div>
+        <div class="ml-3">
+          <h3 class="text-sm font-bold text-yellow-800">
+            <i18n.Translate>There is an operation already</i18n.Translate>
+          </h3>
+          <div class="mt-2 text-sm text-yellow-700">
+            <p>
+              <i18n.Translate>
+                To complete or cancel the operation click <a class="font-semibold text-yellow-700 hover:text-yellow-600" href={`#/operation/${settings.currentWithdrawalOperationId}`}>here</a>
+              </i18n.Translate>
+            </p>
+        </div>
+      </div>
+    </div>
+  </div >
+  }
+
   const trimmedAmountStr = amountStr?.trim();
 
   const parsedAmount = trimmedAmountStr
@@ -92,7 +111,8 @@ export function WalletWithdrawForm({
           i18n.str`Server responded with an invalid  withdraw URI`,
           i18n.str`Withdraw URI: ${result.data.taler_withdraw_uri}`);
       } else {
-        onSuccess(uri.withdrawalOperationId);
+        updateSettings("currentWithdrawalOperationId", uri.withdrawalOperationId)
+        goToConfirmOperation(uri.withdrawalOperationId);
       }
     } catch (error) {
       if (error instanceof RequestError) {
@@ -115,113 +135,168 @@ export function WalletWithdrawForm({
     }
   }
 
+  return <form
+    class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2 mt-4"
+    autoCapitalize="none"
+    autoCorrect="off"
+    onSubmit={e => {
+      e.preventDefault()
+    }}
+  >
+    <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-6 py-4 text-sm items-center rounded-l-md bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
+              onClick={(e) => {
+                e.preventDefault();
+                setAmountStr("50.00")
+              }}
+            >
+              50.00
+            </button>
+            <button type="button"
+              class="relative -ml-px -mr-px inline-flex px-6 py-4 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")
+              }}
+            >
+
+              25.00
+            </button>
+            <button type="button"
+              class="relative -ml-px -mr-px inline-flex px-6 py-4 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-6 py-4 text-sm items-center rounded-r-md bg-white  text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
+              onClick={(e) => {
+                e.preventDefault();
+                setAmountStr("5.00")
+              }}
+            >
+              5.00
+            </button>
+          </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>
+
+  </form>
+}
+
+
+export function WalletWithdrawForm({
+  focus,
+  limit,
+  onCancel,
+  goToConfirmOperation,
+}: {
+  limit: AmountJson;
+  focus?: boolean;
+  goToConfirmOperation: (operationId: string) => void;
+  onCancel: () => void;
+}): VNode {
+  const { i18n } = useTranslationContext();
+  const [settings, updateSettings] = useSettings()
+
   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 target="_blank" rel="noreferrer noopener" 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>
+        <i18n.Translate>After using your wallet you will confirm or cancel the operation.</i18n.Translate>
       </p>
     </div>
 
-    {!settings.fastWithdrawal ?
-      <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">
-            <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-6 py-4 text-sm items-center rounded-l-md bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
+    <div class="col-span-2">
+      {settings.showInstallWallet && <div class="rounded-md bg-blue-50 ring-blue-2 ring-2 p-4">
+        <div class="flex">
+          <div class="flex-shrink-0">
+            <svg class="h-5 w-5 text-blue-300" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+              <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
+            </svg>
+          </div>
+          <div class="ml-3">
+            <h3 class="text-sm font-bold text-blue-800">
+              <i18n.Translate>You need a GNU Taler Wallet</i18n.Translate>
+            </h3>
+            <div class="mt-2 text-sm text-blue-700">
+              <p>
+                <i18n.Translate>
+                  If you dont have one yet you can follow the instruction <a target="_blank" rel="noreferrer noopener" class="font-semibold text-blue-700 hover:text-blue-600" href="https://taler.net/en/wallet.html">here</a>
+                </i18n.Translate>
+              </p>
+              <p class="mt-3 text-sm flex justify-end">
+                <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();
-                    setAmountStr("50.00")
+                    updateSettings("showInstallWallet", false);
                   }}
                 >
-                  50.00
+                  I know
+                  <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>
-                <button type="button"
-                  class="relative -ml-px -mr-px inline-flex px-6 py-4 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")
-                  }}
-                >
+              </p>
 
-                  25.00
-                </button>
-                <button type="button"
-                  class="relative -ml-px -mr-px inline-flex px-6 py-4 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-6 py-4 text-sm items-center rounded-r-md bg-white  text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
-                  onClick={(e) => {
-                    e.preventDefault();
-                    setAmountStr("5.00")
-                  }}
-                >
-                  5.00
-                </button>
-              </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>
+      </div>}
 
-      </form>
-      : settings.currentWithdrawalOperationId === undefined ?
-        <Loading /> :
-        <WithdrawalOperationState
-          currentOperation={settings.currentWithdrawalOperationId}
+      {!settings.fastWithdrawal ?
+        <OldWithdrawalForm
+          focus={focus}
+          limit={limit}
+          onCancel={onCancel}
+          goToConfirmOperation={goToConfirmOperation}
+        />
+        :
+        <OperationState
           currency={limit.currency}
-          onClose={() => {
-            onCancel()
-          }}
+          onClose={onCancel}
+          goToConfirmOperation={goToConfirmOperation}
         />
-    }
+      }
+    </div>
   </div>
   );
 }
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 9976babdb..25c571e28 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -18,23 +18,17 @@ import {
   Amounts,
   HttpStatusCode,
   Logger,
-  TranslatedString,
   WithdrawUriResult,
-  parsePaytoUri,
-  parseWithdrawUri,
-  stringifyWithdrawUri,
+  parsePaytoUri
 } from "@gnu-taler/taler-util";
-import { ErrorType, RequestError, notify, notifyError, notifyInfo, 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 { useAccessAPI, useWithdrawalDetails } from "../hooks/access.js";
+import { useWithdrawalDetails } from "../hooks/access.js";
 import { useSettings } from "../hooks/settings.js";
 import { handleNotOkResult } from "./HomePage.js";
-import { QrCodeSection, QrCodeSectionSimpler } from "./QrCodeSection.js";
+import { QrCodeSection } from "./QrCodeSection.js";
 import { WithdrawalConfirmationQuestion } from "./WithdrawalConfirmationQuestion.js";
-import { useEffect, useState } from "preact/hooks";
-import { buildRequestErrorMessage } from "../utils.js";
-import { getInitialBackendBaseURL } from "../hooks/backend.js";
 
 const logger = new Logger("WithdrawalQRCode");
 
@@ -54,18 +48,11 @@ export function WithdrawalQRCode({
   const [settings, updateSettings] = useSettings();
   const { i18n } = useTranslationContext();
   const result = useWithdrawalDetails(withdrawUri.withdrawalOperationId);
+
   if (!result.ok) {
     if (result.loading) {
       return <Loading />;
     }
-    if (
-      result.type === ErrorType.CLIENT &&
-      result.status === HttpStatusCode.NotFound
-    ) {
-      onClose()
-      return <div>operation not found</div>;
-    }
-    // onLoadNotOk();
     return handleNotOkResult(i18n)(result);
   }
   const { data } = result;
@@ -127,22 +114,6 @@ export function WithdrawalQRCode({
           </div>
         </div>
       </div>
-      <div class="mt-4">
-        <div class="flex items-center justify-between">
-          <span class="flex flex-grow flex-col">
-            <span class="text-sm text-black font-medium leading-6 " id="availability-label">
-              <i18n.Translate>Do not show this again</i18n.Translate>
-            </span>
-          </span>
-          <button type="button" data-enabled={!settings.showWithdrawalSuccess} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description"
-
-            onClick={() => {
-              updateSettings("showWithdrawalSuccess", !settings.showWithdrawalSuccess);
-            }}>
-            <span aria-hidden="true" data-enabled={!settings.showWithdrawalSuccess} class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
-          </button>
-        </div>
-      </div>
       <div class="mt-5 sm:mt-6">
         <button type="button"
           class="inline-flex w-full justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
@@ -182,7 +153,6 @@ export function WithdrawalQRCode({
       the exchange is selcted but no account
     </div>
   }
-
   return (
     <WithdrawalConfirmationQuestion
       withdrawUri={withdrawUri}
@@ -198,126 +168,3 @@ export function WithdrawalQRCode({
     />
   );
 }
-
-
-export function WithdrawalOperationState({
-  currency,
-  currentOperation,
-  onClose,
-}: {currency:string, currentOperation: string, onClose: () => void}): VNode {
-  const { i18n } = useTranslationContext();
-  const [settings, updateSettings] = useSettings()
-  const { createWithdrawal } = useAccessAPI();
-
-  const amount = settings.maxWithdrawalAmount
-  async function doSilentStart() {
-    //FIXME: if amount is not enough use balance
-    const parsedAmount = Amounts.parseOrThrow(`${currency}:${amount}`)
-
-    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 {
-        updateSettings("currentWithdrawalOperationId", 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
-        )
-      }
-    }
-  }
-
-  useEffect(() => {
-    doSilentStart()
-  }, [settings.fastWithdrawal, amount])
-
-  const result = useWithdrawalDetails(currentOperation);
-  if (!result.ok) {
-    if (result.loading) {
-      return <Loading />;
-    }
-    if (
-      result.type === ErrorType.CLIENT &&
-      result.status === HttpStatusCode.NotFound
-    ) {
-      onClose()
-      return <div>operation not found</div>;
-    }
-    // onLoadNotOk();
-    return handleNotOkResult(i18n)(result);
-  }
-  const { data } = result;
-
-  const baseUrl = getInitialBackendBaseURL()
-  const uri = stringifyWithdrawUri({
-    bankIntegrationApiBaseUrl: `${baseUrl}/integration-api`,
-    withdrawalOperationId: currentOperation,
-  });
-  const parsedUri = parseWithdrawUri(uri);
-
-  if (data.aborted) {
-    return <div>
-      the operation was aborted, you can create another one
-    </div>
-  }
-
-  if (data.confirmation_done) {
-    return <div>
-      the wire transfer is made, you coin should arrive shortly
-    </div>
-  }
-  if (!parsedUri) {
-    return <div>
-      the operation is not valid, create another one
-    </div>
-  }
-  if (!data.selection_done) {
-    return (
-      <QrCodeSectionSimpler
-        withdrawUri={parsedUri}
-        onAborted={() => {
-          notifyInfo(i18n.str`Operation canceled`);
-          onClose()
-        }}
-      />
-    );
-  }
-
-  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 selected but no account
-    </div>
-  }
-
-  return <div>
-    the operation is wating for the question to be answered
-  </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
index 318a4cfda..f5f77a3ea 100644
--- a/packages/demobank-ui/src/pages/business/Home.tsx
+++ b/packages/demobank-ui/src/pages/business/Home.tsx
@@ -360,7 +360,6 @@ function CreateCashout({
                 type="checkbox"
                 name="asd"
                 onChange={(e): void => {
-                  console.log("asdasd", form.isDebit);
                   form.isDebit = !form.isDebit;
                   updateForm(structuredClone(form));
                 }}
-- 
cgit v1.2.3


From ea0738ccd585445d7e2080d9009025dde9cf22c5 Mon Sep 17 00:00:00 2001
From: Sebastian <sebasjm@gmail.com>
Date: Mon, 25 Sep 2023 14:49:47 -0300
Subject: better /config error

---
 .../demobank-ui/src/components/ErrorLoading.tsx    |  3 ++
 .../src/components/Transactions/state.ts           |  2 +-
 packages/demobank-ui/src/components/app.tsx        | 18 +++++++---
 packages/demobank-ui/src/declaration.d.ts          |  3 +-
 packages/demobank-ui/src/hooks/config.ts           | 40 +++++++++-------------
 .../demobank-ui/src/pages/AccountPage/state.ts     |  4 +--
 packages/demobank-ui/src/pages/BankFrame.tsx       | 14 ++++++--
 .../demobank-ui/src/pages/WithdrawalQRCode.tsx     |  1 -
 packages/web-util/src/hooks/index.ts               |  1 +
 packages/web-util/src/hooks/useNotifications.ts    | 11 ++++++
 10 files changed, 60 insertions(+), 37 deletions(-)

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

diff --git a/packages/demobank-ui/src/components/ErrorLoading.tsx b/packages/demobank-ui/src/components/ErrorLoading.tsx
index a4faa4d5d..f83b61234 100644
--- a/packages/demobank-ui/src/components/ErrorLoading.tsx
+++ b/packages/demobank-ui/src/components/ErrorLoading.tsx
@@ -32,6 +32,9 @@ export function ErrorLoading({ error }: { error: HttpError<SandboxBackend.Sandbo
           <p class="text-sm font-medium text-red-800">{error.message}</p>
         </div>
       </div>
+        <div class="ml-3 flex-1 md:flex md:justify-between">
+          <p class="text-sm font-medium text-red-800">Got status "{error.info.status}" on {error.info.url}</p>
+        </div>
     </div>
     </div>
   );
diff --git a/packages/demobank-ui/src/components/Transactions/state.ts b/packages/demobank-ui/src/components/Transactions/state.ts
index 30c48aa45..4b62b005e 100644
--- a/packages/demobank-ui/src/components/Transactions/state.ts
+++ b/packages/demobank-ui/src/components/Transactions/state.ts
@@ -44,7 +44,7 @@ export function useComponentState({ account }: Props): State {
         cp.targetType === "bitcoin" ? `${cp.targetPath.substring(0, 6)}...` : undefined) ??
         "unkown";
 
-      const when = AbsoluteTime.fromMilliseconds(tx.date / 1000);
+      const when = AbsoluteTime.fromProtocolTimestamp(tx.date);
       const amount = Amounts.parse(tx.amount);
       const subject = tx.subject;
       return {
diff --git a/packages/demobank-ui/src/components/app.tsx b/packages/demobank-ui/src/components/app.tsx
index ebda31035..a587c6f1e 100644
--- a/packages/demobank-ui/src/components/app.tsx
+++ b/packages/demobank-ui/src/components/app.tsx
@@ -29,6 +29,8 @@ import { useEffect, useState } from "preact/hooks";
 import { Loading } from "./Loading.js";
 import { getInitialBackendBaseURL } from "../hooks/backend.js";
 import { BANK_INTEGRATION_PROTOCOL_VERSION, useConfigState } from "../hooks/config.js";
+import { ErrorLoading } from "./ErrorLoading.js";
+import { BankFrame } from "../pages/BankFrame.js";
 const WITH_LOCAL_STORAGE_CACHE = false;
 
 /**
@@ -76,12 +78,18 @@ function VersionCheck({ children }: { children: ComponentChildren }): VNode {
   if (checked === undefined) {
     return <Loading />
   }
-  if (checked === false) {
-    return <div>
-      the bank backend is not supported. supported version "{BANK_INTEGRATION_PROTOCOL_VERSION}"
-    </div>
+  if (typeof checked ===  "string") {
+    return <BankFrame>
+      the bank backend is not supported. supported version "{BANK_INTEGRATION_PROTOCOL_VERSION}", server version "{checked}"
+    </BankFrame>
   }
-  return <Fragment>{children}</Fragment>
+  if (checked === true) {
+    return <Fragment>{children}</Fragment>
+  }
+  
+  return <BankFrame>
+    <ErrorLoading error={checked}/>
+  </BankFrame>
 }
 
 function localStorageProvider(): Map<unknown, unknown> {
diff --git a/packages/demobank-ui/src/declaration.d.ts b/packages/demobank-ui/src/declaration.d.ts
index 8d729c1f7..d3d9e02ef 100644
--- a/packages/demobank-ui/src/declaration.d.ts
+++ b/packages/demobank-ui/src/declaration.d.ts
@@ -205,8 +205,7 @@ namespace SandboxBackend {
       // Transaction unique ID.  Matches
       // $transaction_id from the URI.
       row_id: number;
-      date: number;
-      // date: Timestamp;
+      date: Timestamp;
     }
 
     interface CreateBankAccountTransactionCreate {
diff --git a/packages/demobank-ui/src/hooks/config.ts b/packages/demobank-ui/src/hooks/config.ts
index 4b22e8ad3..4cf677d35 100644
--- a/packages/demobank-ui/src/hooks/config.ts
+++ b/packages/demobank-ui/src/hooks/config.ts
@@ -1,5 +1,5 @@
 import { LibtoolVersion } from "@gnu-taler/taler-util";
-import { useApiContext } from "@gnu-taler/web-util/browser";
+import { ErrorType, HttpError, HttpResponseServerError, RequestError, useApiContext } from "@gnu-taler/web-util/browser";
 import { useEffect, useState } from "preact/hooks";
 import { getInitialBackendBaseURL } from "./backend.js";
 
@@ -12,38 +12,32 @@ export const BANK_INTEGRATION_PROTOCOL_VERSION = "0:0:0";
 
 async function getConfigState(
   request: ReturnType<typeof useApiContext>["request"],
-): Promise<SandboxBackend.Config | undefined> {
-  try {
-    const url = getInitialBackendBaseURL();
-    const result = await request<SandboxBackend.Config>(
-      url,
-      `config`,
-    );
-    return result.data;
-  } catch (error) {
-    return undefined;
-  }
+): Promise<SandboxBackend.Config> {
+  const url = getInitialBackendBaseURL();
+  const result = await request<SandboxBackend.Config>(url, `config`);
+  return result.data;
 }
 
-export function useConfigState(): boolean | undefined {
-  const [checked, setChecked] = useState<boolean>()
+export function useConfigState(): undefined | true | string | HttpError<SandboxBackend.SandboxError> {
+  const [checked, setChecked] = useState<true | string | HttpError<SandboxBackend.SandboxError>>()
   const { request } = useApiContext();
 
   useEffect(() => {
-    
     getConfigState(request)
-      .then((result) => {
-        if (!result) {
-          setChecked(false)
+      .then((s) => {
+        const r = LibtoolVersion.compare(BANK_INTEGRATION_PROTOCOL_VERSION, s.version)
+        if (r?.compatible) {
+          setChecked(true);
         } else {
-          const r = LibtoolVersion.compare(BANK_INTEGRATION_PROTOCOL_VERSION, result.version)
-          setChecked(r?.compatible);
+          setChecked(s.version)
         }
       })
-      .catch((error) => {
-        setChecked(false);
+      .catch((error: unknown) => {
+        if (error instanceof RequestError) {
+          setChecked(error.cause);
+        }
       });
-  });
+  }, []);
 
   return checked;
 }
diff --git a/packages/demobank-ui/src/pages/AccountPage/state.ts b/packages/demobank-ui/src/pages/AccountPage/state.ts
index c212e7484..ca7e1d447 100644
--- a/packages/demobank-ui/src/pages/AccountPage/state.ts
+++ b/packages/demobank-ui/src/pages/AccountPage/state.ts
@@ -75,9 +75,7 @@ export function useComponentState({ account, goToBusinessAccount, goToConfirmOpe
     };
   }
 
-  // FIXME: balance
-  const balanceIsDebit = true;
-  // data.balance.credit_debit_indicator == "debit";
+  const balanceIsDebit = data.balance.credit_debit_indicator == "debit";
   const limit = balanceIsDebit
     ? Amounts.sub(debitThreshold, balance).amount
     : Amounts.add(balance, debitThreshold).amount;
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx
index c4f872679..5c43d2c3e 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -15,7 +15,7 @@
  */
 
 import { Amounts, Logger, PaytoUriIBAN, TranslatedString, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
-import { notifyError, useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { notifyError, notifyException, useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser";
 import { ComponentChildren, Fragment, h, VNode } from "preact";
 import { StateUpdater, useEffect, useErrorBoundary, useState } from "preact/hooks";
 import { LangSelectorLikePy as LangSelector } from "../components/LangSelector.js";
@@ -54,7 +54,12 @@ export function BankFrame({
 
   useEffect(() => {
     if (error) {
-      notifyError(i18n.str`Internal error, please report.`, (error instanceof Error ? error.message : String(error)) as TranslatedString)
+      const desc = (error instanceof Error ? error.stack : String(error)) as TranslatedString
+      if (error instanceof Error) {
+        notifyException(i18n.str`Internal error, please report.`, error)
+      } else {
+        notifyError(i18n.str`Internal error, please report.`, String(error) as TranslatedString)
+      }
       resetError()
     }
   }, [error])
@@ -386,6 +391,11 @@ function StatusBanner(): VNode {
                   {n.message.description}
                 </div>
               }
+              {n.message.debug &&
+                <div class="mt-2 text-sm text-red-700 font-mono break-all">
+                    {n.message.debug}
+                </div>
+              }
             </div>
           case "info":
             return <div class="rounded-md bg-green-50 border-4 border-green-600 p-6">
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 25c571e28..8f4e175f6 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -45,7 +45,6 @@ export function WithdrawalQRCode({
   withdrawUri,
   onClose,
 }: Props): VNode {
-  const [settings, updateSettings] = useSettings();
   const { i18n } = useTranslationContext();
   const result = useWithdrawalDetails(withdrawUri.withdrawalOperationId);
 
diff --git a/packages/web-util/src/hooks/index.ts b/packages/web-util/src/hooks/index.ts
index c29de9023..cc3267dbd 100644
--- a/packages/web-util/src/hooks/index.ts
+++ b/packages/web-util/src/hooks/index.ts
@@ -4,6 +4,7 @@ export { useMemoryStorage } from "./useMemoryStorage.js";
 export {
   useNotifications,
   notifyError,
+  notifyException,
   notifyInfo,
   notify,
   ErrorNotification,
diff --git a/packages/web-util/src/hooks/useNotifications.ts b/packages/web-util/src/hooks/useNotifications.ts
index 2f9df24f9..792095b06 100644
--- a/packages/web-util/src/hooks/useNotifications.ts
+++ b/packages/web-util/src/hooks/useNotifications.ts
@@ -36,6 +36,17 @@ export function notifyError(
     debug,
   });
 }
+export function notifyException(
+  title: TranslatedString,
+  ex: Error,
+) {
+  notify({
+    type: "error" as const,
+    title,
+    description: ex.message as TranslatedString,
+    debug: ex.stack,
+  });
+}
 export function notifyInfo(title: TranslatedString) {
   notify({
     type: "info" as const,
-- 
cgit v1.2.3


From 1708d49a2d5da1f325173a030695223e5a24e75c Mon Sep 17 00:00:00 2001
From: Sebastian <sebasjm@gmail.com>
Date: Fri, 29 Sep 2023 16:02:15 -0300
Subject: more ui

---
 packages/demobank-ui/dev.mjs                       |   2 +-
 packages/demobank-ui/src/components/Attention.tsx  |  59 +++++
 .../demobank-ui/src/components/ErrorLoading.tsx    |  22 +-
 .../src/components/Transactions/views.tsx          |  51 ++--
 packages/demobank-ui/src/demobank-ui-settings.js   |  21 ++
 packages/demobank-ui/src/hooks/access.ts           |   3 +-
 packages/demobank-ui/src/hooks/circuit.ts          |   2 +-
 packages/demobank-ui/src/hooks/settings.ts         |   3 +
 .../demobank-ui/src/pages/AccountPage/views.tsx    |  62 ++---
 packages/demobank-ui/src/pages/BankFrame.tsx       | 104 ++++----
 packages/demobank-ui/src/pages/HomePage.tsx        |   6 +-
 packages/demobank-ui/src/pages/LoginForm.tsx       |   8 +-
 .../demobank-ui/src/pages/OperationState/state.ts  |   4 +-
 .../demobank-ui/src/pages/OperationState/views.tsx |   5 +-
 packages/demobank-ui/src/pages/PaymentOptions.tsx  |   5 +-
 .../src/pages/PaytoWireTransferForm.tsx            | 268 ++++++++++++---------
 packages/demobank-ui/src/pages/QrCodeSection.tsx   |   1 -
 .../src/pages/UpdateAccountPassword.tsx            |   8 +-
 .../demobank-ui/src/pages/WalletWithdrawForm.tsx   | 183 +++++---------
 .../src/pages/WithdrawalConfirmationQuestion.tsx   |  14 +-
 .../demobank-ui/src/pages/WithdrawalQRCode.tsx     |  12 +-
 .../demobank-ui/src/pages/admin/AccountForm.tsx    |   8 +-
 packages/demobank-ui/src/pages/admin/Home.tsx      |   2 +
 .../demobank-ui/src/pages/admin/RemoveAccount.tsx  |  65 +----
 24 files changed, 448 insertions(+), 470 deletions(-)
 create mode 100644 packages/demobank-ui/src/components/Attention.tsx
 create mode 100644 packages/demobank-ui/src/demobank-ui-settings.js

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

diff --git a/packages/demobank-ui/dev.mjs b/packages/demobank-ui/dev.mjs
index 9c09e5716..f29a05e49 100755
--- a/packages/demobank-ui/dev.mjs
+++ b/packages/demobank-ui/dev.mjs
@@ -18,7 +18,7 @@
 import { serve } from "@gnu-taler/web-util/node";
 import { initializeDev } from "@gnu-taler/web-util/build";
 
-const devEntryPoints = ["src/stories.tsx", "src/index.tsx"];
+const devEntryPoints = ["src/stories.tsx", "src/index.tsx", "src/demobank-ui-settings.js"];
 
 const build = initializeDev({
   type: "development",
diff --git a/packages/demobank-ui/src/components/Attention.tsx b/packages/demobank-ui/src/components/Attention.tsx
new file mode 100644
index 000000000..3313e5796
--- /dev/null
+++ b/packages/demobank-ui/src/components/Attention.tsx
@@ -0,0 +1,59 @@
+import { TranslatedString } from "@gnu-taler/taler-util";
+import { ComponentChildren, Fragment, VNode, h } from "preact";
+import { assertUnreachable } from "./Routing.js";
+
+interface Props { 
+  type?: "info" | "success" | "warning" | "danger", 
+  onClose?: () => void, 
+  title: TranslatedString, 
+  children?: ComponentChildren ,
+}
+export function Attention({ type = "info", title, children, onClose }: Props): VNode {
+  return <div class={`group attention-${type} mt-2`}>
+    <div class="rounded-md group-[.attention-info]:bg-blue-50 group-[.attention-warning]:bg-yellow-50 group-[.attention-danger]:bg-red-50 group-[.attention-success]:bg-green-50 p-4 shadow">
+      <div class="flex">
+        <div >
+          <svg xmlns="http://www.w3.org/2000/svg" stroke="none" viewBox="0 0 24 24" fill="currentColor" class="w-8 h-8 group-[.attention-info]:text-blue-400 group-[.attention-warning]:text-yellow-400 group-[.attention-danger]:text-red-400 group-[.attention-success]:text-green-400">
+            {(() => {
+              switch (type) {
+                case "info":
+                  return <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" />
+                case "warning":
+                  return <path fill-rule="evenodd" d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z" />
+                case "danger":
+                  return <path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z" />
+                case "success":
+                  return <path fill-rule="evenodd" d="M7.493 18.75c-.425 0-.82-.236-.975-.632A7.48 7.48 0 016 15.375c0-1.75.599-3.358 1.602-4.634.151-.192.373-.309.6-.397.473-.183.89-.514 1.212-.924a9.042 9.042 0 012.861-2.4c.723-.384 1.35-.956 1.653-1.715a4.498 4.498 0 00.322-1.672V3a.75.75 0 01.75-.75 2.25 2.25 0 012.25 2.25c0 1.152-.26 2.243-.723 3.218-.266.558.107 1.282.725 1.282h3.126c1.026 0 1.945.694 2.054 1.715.045.422.068.85.068 1.285a11.95 11.95 0 01-2.649 7.521c-.388.482-.987.729-1.605.729H14.23c-.483 0-.964-.078-1.423-.23l-3.114-1.04a4.501 4.501 0 00-1.423-.23h-.777zM2.331 10.977a11.969 11.969 0 00-.831 4.398 12 12 0 00.52 3.507c.26.85 1.084 1.368 1.973 1.368H4.9c.445 0 .72-.498.523-.898a8.963 8.963 0 01-.924-3.977c0-1.708.476-3.305 1.302-4.666.245-.403-.028-.959-.5-.959H4.25c-.832 0-1.612.453-1.918 1.227z" />
+                default:
+                  assertUnreachable(type)
+              }
+            })()}
+          </svg>
+        </div>
+        <div class="ml-3 w-full">
+          <h3 class="text-sm group-hover:text-white font-bold group-[.attention-info]:text-blue-800 group-[.attention-success]:text-green-800 group-[.attention-warning]:text-yellow-800 group-[.attention-danger]:text-red-800">
+            {title}
+          </h3>
+          <div class="mt-2 text-sm group-[.attention-info]:text-blue-700 group-[.attention-warning]:text-yellow-700 group-[.attention-danger]:text-red-700 group-[.attention-success]:text-green-700">
+            {children}
+          </div>
+        </div>
+        {onClose &&
+          <div>
+            <button type="button" class="font-semibold items-center rounded bg-transparent px-2 py-1 text-xs text-gray-900  hover:bg-gray-50"
+              onClick={(e) => {
+                e.preventDefault();
+                onClose();
+              }}
+            >
+              <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>
+          </div>
+        }
+      </div>
+    </div>
+
+  </div>
+}
diff --git a/packages/demobank-ui/src/components/ErrorLoading.tsx b/packages/demobank-ui/src/components/ErrorLoading.tsx
index f83b61234..ee62671ce 100644
--- a/packages/demobank-ui/src/components/ErrorLoading.tsx
+++ b/packages/demobank-ui/src/components/ErrorLoading.tsx
@@ -17,25 +17,13 @@
 
 import { HttpError, useTranslationContext } from "@gnu-taler/web-util/browser";
 import { h, VNode } from "preact";
+import { Attention } from "./Attention.js";
+import { TranslatedString } from "@gnu-taler/taler-util";
 
 export function ErrorLoading({ error }: { error: HttpError<SandboxBackend.SandboxError> }): VNode {
   const { i18n } = useTranslationContext()
-  return (
-    <div><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">{error.message}</p>
-        </div>
-      </div>
-        <div class="ml-3 flex-1 md:flex md:justify-between">
-          <p class="text-sm font-medium text-red-800">Got status "{error.info.status}" on {error.info.url}</p>
-        </div>
-    </div>
-    </div>
+  return (<Attention type="danger" title={error.message as TranslatedString}>
+    <p class="text-sm font-medium text-red-800">Got status "{error.info.status}" on {error.info.url}</p>
+  </Attention>
   );
 }
diff --git a/packages/demobank-ui/src/components/Transactions/views.tsx b/packages/demobank-ui/src/components/Transactions/views.tsx
index f8b2e3113..f92c874f3 100644
--- a/packages/demobank-ui/src/components/Transactions/views.tsx
+++ b/packages/demobank-ui/src/components/Transactions/views.tsx
@@ -19,6 +19,7 @@ import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { State } from "./index.js";
 import { format, isToday } from "date-fns";
 import { Amounts } from "@gnu-taler/taler-util";
+import { useEffect, useRef } from "preact/hooks";
 
 export function LoadingUriView({ error }: State.LoadingUriError): VNode {
   const { i18n } = useTranslationContext();
@@ -55,9 +56,9 @@ export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode
           <thead>
             <tr>
               <th scope="col" class="pl-2 py-3.5 text-left text-sm font-semibold text-gray-900 ">{i18n.str`Date`}</th>
-              <th scope="col" class="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>
+              <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm font-semibold text-gray-900 ">{i18n.str`Amount`}</th>
+              <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm font-semibold text-gray-900 ">{i18n.str`Counterpart`}</th>
+              <th scope="col" class="pl-2 py-3.5 text-left text-sm font-semibold text-gray-900 ">{i18n.str`Subject`}</th>
             </tr>
           </thead>
           <tbody>
@@ -69,22 +70,38 @@ export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode
                   </th>
                 </tr>
                 {txs.map(item => {
+                  const time = item.when.t_ms === "never" ? "" : format(item.when.t_ms, "HH:mm:ss")
+                  const amount = <Fragment>
+                    {item.negative ? "-" : ""}
+                    {item.amount ? (
+                      `${Amounts.stringifyValue(item.amount)} ${item.amount.currency
+                      }`
+                    ) : (
+                      <span style={{ color: "grey" }}>&lt;{i18n.str`invalid value`}&gt;</span>
+                    )}
+                  </Fragment>
                   return (<tr key={idx}>
                     <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, "HH:mm:ss")}</div>
+                      <div class="font-medium text-gray-900">{time}</div>
+                      <dl class="font-normal sm:hidden">
+                        <dt class="sr-only sm:hidden"><i18n.Translate>Amount</i18n.Translate></dt>
+                        <dd class="mt-1 truncate text-gray-700">
+                          {item.negative ? i18n.str`sent` : i18n.str`received`} {item.amount ? (
+                            `${Amounts.stringifyValue(item.amount)}`
+                          ) : (
+                            <span style={{ color: "grey" }}>&lt;{i18n.str`invalid value`}&gt;</span>
+                          )}</dd>
+                        <dt class="sr-only sm:hidden"><i18n.Translate>Counterpart</i18n.Translate></dt>
+                        <dd class="mt-1 truncate text-gray-500 sm:hidden">
+                        {item.negative ? i18n.str`to` : i18n.str`from`} {item.counterpart}
+                        </dd>
+                      </dl>
                     </td>
                     <td data-negative={item.negative ? "true" : "false"}
-                      class="px-3 py-3.5 text-sm text-gray-500 data-[negative=false]:text-green-600 data-[negative=true]:text-red-600">
-                      {item.negative ? "-" : ""}
-                      {item.amount ? (
-                        `${Amounts.stringifyValue(item.amount)} ${item.amount.currency
-                        }`
-                      ) : (
-                        <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>
+                      class="hidden sm:table-cell px-3 py-3.5 text-sm text-gray-500 data-[negative=false]:text-green-600 data-[negative=true]:text-red-600">
+                      {amount}
+                    </td>
+                    <td class="hidden sm:table-cell 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 break-all min-w-md">{item.subject}</td>
                   </tr>)
                 })}
@@ -94,8 +111,8 @@ export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode
           </tbody>
 
         </table>
-       
-       <nav class="flex items-center justify-between border-t border-gray-200 bg-white px-4 py-3 sm:px-6 rounded-lg" aria-label="Pagination">
+
+        <nav class="flex items-center justify-between border-t border-gray-200 bg-white px-4 py-3 sm:px-6 rounded-lg" aria-label="Pagination">
           <div class="flex flex-1 justify-between sm:justify-end">
             <button
               class="relative disabled:bg-gray-100 disabled:text-gray-500 inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline-offset-0"
diff --git a/packages/demobank-ui/src/demobank-ui-settings.js b/packages/demobank-ui/src/demobank-ui-settings.js
new file mode 100644
index 000000000..8a0961831
--- /dev/null
+++ b/packages/demobank-ui/src/demobank-ui-settings.js
@@ -0,0 +1,21 @@
+// Values for development environment
+
+/**
+ * Global settings for the demobank UI.
+ */
+localStorage.setItem("bank-base-url", "http://bank.taler.test/");
+
+globalThis.talerDemobankSettings = {
+  backendBaseURL: "http://bank.taler.test/",
+  allowRegistrations: true,
+  showDemoNav: true,
+  simplePasswordForRandomAccounts: true,
+  allowRandomAccountCreation: true,
+  bankName: "Taler DEVELOPMENT Bank",
+  // Names and links for other demo sites to show in the navbar
+  demoSites: [
+    ["Exchange", "https://Exchnage.taler.test/"],
+    ["Bank", "https://bank-ui.taler.test/"],
+    ["Merchant", "https://merchant.taler.test/"],
+  ],
+};
diff --git a/packages/demobank-ui/src/hooks/access.ts b/packages/demobank-ui/src/hooks/access.ts
index 20fd64bfa..154c43ae6 100644
--- a/packages/demobank-ui/src/hooks/access.ts
+++ b/packages/demobank-ui/src/hooks/access.ts
@@ -70,7 +70,7 @@ export function useAccessAPI(): AccessAPI {
         contentType: "json",
       },
     );
-    await mutateAll(/.*accounts\/.*\/transactions.*/);
+    await mutateAll(/.*accounts\/.*/);
     return res;
   };
   const deleteAccount = async (): Promise<HttpResponseOk<void>> => {
@@ -382,7 +382,6 @@ export function useTransactions(
     loadMore: () => {
       if (!afterData || isReachingEnd) return;
       // if (afterData.data.transactions.length < MAX_RESULT_SIZE) {
-      // console.log("load more", page)
       const l = afterData.data.transactions[afterData.data.transactions.length-1]
       setStart(String(l.row_id));
       // }
diff --git a/packages/demobank-ui/src/hooks/circuit.ts b/packages/demobank-ui/src/hooks/circuit.ts
index 82caafdf2..5dba60951 100644
--- a/packages/demobank-ui/src/hooks/circuit.ts
+++ b/packages/demobank-ui/src/hooks/circuit.ts
@@ -435,7 +435,7 @@ export function useBusinessAccounts(
     HttpResponseOk<SandboxBackend.Circuit.CircuitAccounts>,
     RequestError<SandboxBackend.SandboxError>
   >(
-    [`circuit-api/accounts`, args?.page, PAGE_SIZE, args?.account],
+    [`accounts`, args?.page, PAGE_SIZE, args?.account],
     sandboxAccountsFetcher,
     {
       refreshInterval: 0,
diff --git a/packages/demobank-ui/src/hooks/settings.ts b/packages/demobank-ui/src/hooks/settings.ts
index 5f004c6d4..ad853f9d7 100644
--- a/packages/demobank-ui/src/hooks/settings.ts
+++ b/packages/demobank-ui/src/hooks/settings.ts
@@ -33,6 +33,7 @@ interface Settings {
   showInstallWallet: boolean;
   maxWithdrawalAmount: number;
   fastWithdrawal: boolean;
+  showDebugInfo: boolean;
 }
 
 export const codecForSettings = (): Codec<Settings> =>
@@ -42,6 +43,7 @@ export const codecForSettings = (): Codec<Settings> =>
     .property("showDemoDescription", (codecForBoolean()))
     .property("showInstallWallet", (codecForBoolean()))
     .property("fastWithdrawal", (codecForBoolean()))
+    .property("showDebugInfo", (codecForBoolean()))
     .property("maxWithdrawalAmount", codecForNumber())
     .build("Settings");
 
@@ -52,6 +54,7 @@ const defaultSettings: Settings = {
   showInstallWallet: true,
   maxWithdrawalAmount: 25,
   fastWithdrawal: false,
+  showDebugInfo: false,
 };
 
 const DEMOBANK_SETTINGS_KEY = buildStorageKey(
diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx b/packages/demobank-ui/src/pages/AccountPage/views.tsx
index 83846be90..483cb579a 100644
--- a/packages/demobank-ui/src/pages/AccountPage/views.tsx
+++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx
@@ -16,10 +16,10 @@
 
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
+import { Attention } from "../../components/Attention.js";
 import { Transactions } from "../../components/Transactions/index.js";
 import { useBusinessAccountDetails } from "../../hooks/circuit.js";
 import { useSettings } from "../../hooks/settings.js";
-import { bankUiSettings } from "../../settings.js";
 import { PaymentOptions } from "../PaymentOptions.js";
 import { State } from "./index.js";
 
@@ -31,53 +31,27 @@ export function InvalidIbanView({ error }: State.InvalidIban) {
 
 const IS_PUBLIC_ACCOUNT_ENABLED = false
 
-
 function ShowDemoInfo(): VNode {
   const { i18n } = useTranslationContext();
   const [settings, updateSettings] = useSettings();
   if (!settings.showDemoDescription) return <Fragment />
-  return <div class="rounded-md bg-blue-50 p-4">
-    <div class="flex">
-      <div class="flex-shrink-0">
-        <svg class="h-5 w-5 text-blue-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
-          <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
-        </svg>
-      </div>
-      <div class="ml-3">
-        <h3 class="text-sm font-bold text-blue-800">
-          <i18n.Translate>This is a demo bank!</i18n.Translate>
-        </h3>
-        <div class="mt-2 text-sm text-blue-700">
-          {IS_PUBLIC_ACCOUNT_ENABLED ? (
-            <i18n.Translate>
-              This part of the demo shows how a bank that supports Taler
-              directly would work. In addition to using your own bank
-              account, you can also see the transaction history of some{" "}
-              <a href="/public-accounts">Public Accounts</a>.
-            </i18n.Translate>
-          ) : (
-            <i18n.Translate>
-              This part of the demo shows how a bank that supports Taler
-              directly would work.
-            </i18n.Translate>
-          )}
-          <p class="mt-3 text-sm flex justify-end">
-            <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();
-                updateSettings("showDemoDescription", false);
-              }}
-            >
-              <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>
-    </div>
-  </div>
+  return <Attention title={i18n.str`This is a demo bank`} onClose={() => {
+    updateSettings("showDemoDescription", false);
+  }}>
+    {IS_PUBLIC_ACCOUNT_ENABLED ? (
+      <i18n.Translate>
+        This part of the demo shows how a bank that supports Taler
+        directly would work. In addition to using your own bank
+        account, you can also see the transaction history of some{" "}
+        <a href="/public-accounts">Public Accounts</a>.
+      </i18n.Translate>
+    ) : (
+      <i18n.Translate>
+        This part of the demo shows how a bank that supports Taler
+        directly would work.
+      </i18n.Translate>
+    )}
+  </Attention>
 }
 
 export function ReadyView({ account, limit, goToBusinessAccount, goToConfirmOperation }: State.Ready): VNode<{}> {
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx
index 15ef8a036..29334cae4 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -15,7 +15,7 @@
  */
 
 import { Amounts, Logger, PaytoUriIBAN, TranslatedString, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
-import { notifyError, notifyException, useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { NotificationMessage, notifyError, notifyException, useNotifications, useTranslationContext } from "@gnu-taler/web-util/browser";
 import { ComponentChildren, Fragment, h, VNode } from "preact";
 import { StateUpdater, useEffect, useErrorBoundary, useState } from "preact/hooks";
 import { LangSelector } from "../components/LangSelector.js";
@@ -26,6 +26,7 @@ import { useSettings } from "../hooks/settings.js";
 import { CopyButton, CopyIcon } from "../components/CopyButton.js";
 import logo from "../assets/logo-2021.svg";
 import { useAccountDetails } from "../hooks/access.js";
+import { Attention } from "../components/Attention.js";
 
 const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined;
 const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined;
@@ -108,7 +109,7 @@ export function BankFrame({
                   setOpen(!open)
                 }}>
                 <span class="absolute -inset-0.5"></span>
-                <span class="sr-only">Open main menu</span>
+                <span class="sr-only">Open settings</span>
                 <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>
@@ -227,6 +228,22 @@ export function BankFrame({
                                     </button>
                                   </div>
                                 </li>
+                                <li class="mt-2">
+                                  <div class="flex items-center justify-between">
+                                    <span class="flex flex-grow flex-col">
+                                      <span class="text-sm text-black font-medium leading-6 " id="availability-label">
+                                        <i18n.Translate>Show debug info</i18n.Translate>
+                                      </span>
+                                    </span>
+                                    <button type="button" data-enabled={settings.showDebugInfo} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description"
+
+                                      onClick={() => {
+                                        updateSettings("showDebugInfo", !settings.showDebugInfo);
+                                      }}>
+                                      <span aria-hidden="true" data-enabled={settings.showDebugInfo} class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
+                                    </button>
+                                  </div>
+                                </li>
                                 <li class="mt-2">
                                   <div class="flex items-center justify-between">
                                     <span class="flex flex-grow flex-col">
@@ -286,10 +303,10 @@ export function BankFrame({
       }
     </div >
 
+    <StatusBanner />
     <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">
-          <StatusBanner />
           {children}
         </div>
       </div>
@@ -301,79 +318,46 @@ export function BankFrame({
   );
 }
 
+function MaybeShowDebugInfo({ info }: { info: any }): VNode {
+  const [settings] = useSettings()
+  if (settings.showDebugInfo) {
+    return <pre class="whitespace-break-spaces ">
+    {info}
+  </pre>
+  }
+  return <Fragment /> 
+}
+
 
 function StatusBanner(): VNode {
   const notifs = useNotifications()
-  return <div
-    class="fixed top-10 z-20 ml-4 mr-4"
-  > {
+  if (notifs.length === 0) return <Fragment />
+  return <div class="fixed z-20 w-full p-4"> {
       notifs.map(n => {
         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>
-                </div>
-                <div>
-                  <p class="text-sm">
-                    <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>
+            return <Attention type="danger" title={n.message.title} onClose={() => {
+              n.remove()
+            }}>
               {n.message.description &&
                 <div class="mt-2 text-sm text-red-700">
                   {n.message.description}
                 </div>
               }
+              <MaybeShowDebugInfo info={n.message.debug} />
+              {/* <a href="#" class="text-gray-500">
+                show debug info
+              </a>
               {n.message.debug &&
                 <div class="mt-2 text-sm text-red-700 font-mono break-all">
                   {n.message.debug}
                 </div>
-              }
-            </div>
+              } */}
+            </Attention>
           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();
-                      }}
-                    >
-                      <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>
+            return <Attention type="success" title={n.message.title} onClose={() => {
+              n.remove();
+            }} />
         }
       })}
   </div>
diff --git a/packages/demobank-ui/src/pages/HomePage.tsx b/packages/demobank-ui/src/pages/HomePage.tsx
index d945d80d1..95144f086 100644
--- a/packages/demobank-ui/src/pages/HomePage.tsx
+++ b/packages/demobank-ui/src/pages/HomePage.tsx
@@ -137,8 +137,8 @@ export function handleNotOkResult(
           const errorData = result.payload;
           notify({
             type: "error",
-            title: i18n.str`Could not load due to a client error`,
-            description: errorData?.error?.description as TranslatedString,
+            title: i18n.str`Could not load due to a request error`,
+            description: i18n.str`Request to url "${result.info.url}" returned ${result.info.status}`,
             debug: JSON.stringify(result),
           });
           break;
@@ -174,7 +174,7 @@ export function handleNotOkResult(
           assertUnreachable(result);
         }
       }
-      route("/")
+      // route("/")
       return <div>error</div>;
     }
     return <div />;
diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx
index 14d261622..3ea94b899 100644
--- a/packages/demobank-ui/src/pages/LoginForm.tsx
+++ b/packages/demobank-ui/src/pages/LoginForm.tsx
@@ -23,6 +23,7 @@ import { useBackendContext } from "../context/backend.js";
 import { useCredentialsChecker } from "../hooks/useCredentialsChecker.js";
 import { bankUiSettings } from "../settings.js";
 import { undefinedIfEmpty } from "../utils.js";
+import { doAutoFocus } from "./PaytoWireTransferForm.js";
 
 
 /**
@@ -98,8 +99,8 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
               });
             } else {
               saveError({
-                title: i18n.str`Could not load due to a client error`,
-                // description: cause.payload.error.description,
+                title: i18n.str`Could not load due to a request error`,
+                description: i18n.str`Request to url "${cause.info.url}" returned ${cause.info.status}`,
                 debug: JSON.stringify(cause.payload),
               });
             }
@@ -159,8 +160,7 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
             </label>
             <div class="mt-2">
               <input
-                ref={ref}
-                autoFocus
+                ref={doAutoFocus}
                 type="text"
                 name="username"
                 id="username"
diff --git a/packages/demobank-ui/src/pages/OperationState/state.ts b/packages/demobank-ui/src/pages/OperationState/state.ts
index 56e79f9ab..4be680377 100644
--- a/packages/demobank-ui/src/pages/OperationState/state.ts
+++ b/packages/demobank-ui/src/pages/OperationState/state.ts
@@ -118,7 +118,9 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
     try {
       setBusy({})
       await confirmWithdrawal(wid);
-      notifyInfo(i18n.str`Wire transfer completed!`)
+      if (!settings.showWithdrawalSuccess) {
+        notifyInfo(i18n.str`Wire transfer completed!`)
+      }
     } catch (error) {
       if (error instanceof RequestError) {
         notify(
diff --git a/packages/demobank-ui/src/pages/OperationState/views.tsx b/packages/demobank-ui/src/pages/OperationState/views.tsx
index 93b3694d7..2cb7385db 100644
--- a/packages/demobank-ui/src/pages/OperationState/views.tsx
+++ b/packages/demobank-ui/src/pages/OperationState/views.tsx
@@ -267,13 +267,12 @@ export function ConfirmedView({ error, onClose }: State.Confirmed) {
         </div>
         <div class="mt-3 text-center sm:mt-5">
           <h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title">
-            <i18n.Translate>Withdrawal OK</i18n.Translate>
+            <i18n.Translate>Withdrawal confirmed</i18n.Translate>
           </h3>
           <div class="mt-2">
             <p class="text-sm text-gray-500">
               <i18n.Translate>
-                The wire transfer to the Taler exchange bank's account is completed, now the
-                exchange will send the requested amount into your GNU Taler wallet.
+                The wire transfer to the Taler operator has been initiated. You will soon receive the requested amount in your Taler wallet.
               </i18n.Translate>
             </p>
           </div>
diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx b/packages/demobank-ui/src/pages/PaymentOptions.tsx
index 49419d0dc..fef272831 100644
--- a/packages/demobank-ui/src/pages/PaymentOptions.tsx
+++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx
@@ -30,7 +30,7 @@ export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJ
   const { i18n } = useTranslationContext();
   const [settings] = useSettings();
 
-  const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>();
+  const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>("wire-transfer");
 
   return (
     <div class="mt-2">
@@ -82,7 +82,7 @@ export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJ
                   <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">
-                  <i18n.Translate>Make a wire transfer to an account which you know the address.</i18n.Translate>
+                  <i18n.Translate>Make a wire transfer to an account which you know the bank account number</i18n.Translate>
                 </span>
               </span>
             </span>
@@ -108,6 +108,7 @@ export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJ
             limit={limit}
             onSuccess={() => {
               notifyInfo(i18n.str`Wire transfer created!`);
+              setTab(undefined)
             }}
             onCancel={() => {
               setTab(undefined)
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index 5f5a6ce3b..785dc4264 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -55,10 +55,11 @@ export function PaytoWireTransferForm({
   onCancel: (() => void) | undefined;
   limit: AmountJson;
 }): VNode {
-  const [isRawPayto, setIsRawPayto] = useState(false);
-  const [iban, setIban] = useState<string | undefined>(undefined);
-  const [subject, setSubject] = useState<string | undefined>(undefined);
-  const [amount, setAmount] = useState<string | undefined>(undefined);
+  const [isRawPayto, setIsRawPayto] = useState(true);
+  // FIXME: remove this
+  const [iban, setIban] = useState<string | undefined>("DE4745461198061");
+  const [subject, setSubject] = useState<string | undefined>("ASD");
+  const [amount, setAmount] = useState<string | undefined>("1.00001");
 
   const [rawPaytoInput, rawPaytoInputSetter] = useState<string | undefined>(
     undefined,
@@ -76,17 +77,17 @@ export function PaytoWireTransferForm({
 
   const errorsWire = undefinedIfEmpty({
     iban: !iban
-      ? i18n.str`Missing IBAN`
+      ? i18n.str`required`
       : !IBAN_REGEX.test(iban)
         ? i18n.str`IBAN should have just uppercased letters and numbers`
         : validateIBAN(iban, i18n),
-    subject: !subject ? i18n.str`Missing subject` : undefined,
+    subject: !subject ? i18n.str`required` : undefined,
     amount: !trimmedAmountStr
-      ? i18n.str`Missing amount`
+      ? i18n.str`required`
       : !parsedAmount
-        ? i18n.str`Amount is not valid`
+        ? i18n.str`not valid`
         : Amounts.isZero(parsedAmount)
-          ? i18n.str`Should be greater than 0`
+          ? i18n.str`should be greater than 0`
           : Amounts.cmp(limit, parsedAmount) === -1
             ? i18n.str`balance is not enough`
             : undefined,
@@ -101,14 +102,14 @@ export function PaytoWireTransferForm({
       ? i18n.str`required`
       : !parsed
         ? i18n.str`does not follow the pattern`
-        : !parsed.params.amount
-          ? i18n.str`use the "amount" parameter to specify the amount to be transferred`
-          : Amounts.parse(parsed.params.amount) === undefined
-            ? i18n.str`the amount is not valid`
-            : !parsed.params.message
-              ? i18n.str`use the "message" parameter to specify a reference text for the transfer`
-              : !parsed.isKnown || parsed.targetType !== "iban"
-                ? i18n.str`only "IBAN" target are supported`
+        : !parsed.isKnown || parsed.targetType !== "iban"
+          ? i18n.str`only "IBAN" target are supported`
+          : !parsed.params.amount
+            ? i18n.str`use the "amount" parameter to specify the amount to be transferred`
+            : Amounts.parse(parsed.params.amount) === undefined
+              ? i18n.str`the amount is not valid`
+              : !parsed.params.message
+                ? i18n.str`use the "message" parameter to specify a reference text for the transfer`
                 : !IBAN_REGEX.test(parsed.iban)
                   ? i18n.str`IBAN should have just uppercased letters and numbers`
                   : validateIBAN(parsed.iban, i18n),
@@ -159,6 +160,9 @@ export function PaytoWireTransferForm({
   }
 
   return (<div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 bg-gray-100 my-4 px-4 pb-4 rounded-lg">
+    {/**
+     * FIXME: Scan a qr code
+     */}
     <div class="px-4 sm:px-0">
       <h2 class="text-base font-semibold leading-7 text-gray-900">
         {title}
@@ -167,6 +171,17 @@ export function PaytoWireTransferForm({
         <div class="px-4 mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-1 sm:gap-x-4">
           <label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none" + (!isRawPayto ? "border-indigo-600 ring-2 ring-indigo-600" : "border-gray-300")}>
             <input type="radio" name="project-type" value="Newsletter" class="sr-only" aria-labelledby="project-type-0-label" aria-describedby="project-type-0-description-0 project-type-0-description-1" onChange={() => {
+              if (parsed && parsed.isKnown && parsed.targetType === "iban") {
+                setIban(parsed.iban)
+                const amount = Amounts.parse(parsed.params["amount"])
+                if (amount) {
+                  setAmount(Amounts.stringifyValue(amount))
+                }
+                const subject = parsed.params["subject"]
+                if (subject) {
+                  setSubject(subject)
+                }
+              }
               setIsRawPayto(false)
             }} />
             <span class="flex flex-1">
@@ -180,12 +195,22 @@ export function PaytoWireTransferForm({
 
           <label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none" + (isRawPayto ? "border-indigo-600 ring-2 ring-indigo-600" : "border-gray-300")}>
             <input type="radio" name="project-type" value="Existing Customers" class="sr-only" aria-labelledby="project-type-1-label" aria-describedby="project-type-1-description-0 project-type-1-description-1" onChange={() => {
+              if (iban) {
+                const payto = buildPayto("iban", iban, undefined)
+                if (parsedAmount) {
+                  payto.params["amount"] = Amounts.stringify(parsedAmount)
+                }
+                if (subject) {
+                  payto.params["message"] = subject
+                }
+                rawPaytoInputSetter(stringifyPaytoUri(payto))
+              }
               setIsRawPayto(true)
             }} />
             <span class="flex flex-1">
               <span class="flex flex-col">
                 <span class="block text-sm  font-medium text-gray-900">
-                  <i18n.Translate>using the payto:// format</i18n.Translate>
+                  <i18n.Translate>Import payto:// URI</i18n.Translate>
                 </span>
               </span>
             </span>
@@ -195,7 +220,7 @@ export function PaytoWireTransferForm({
     </div>
 
     <form
-      class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2"
+      class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2 w-fit mx-auto"
       autoCapitalize="none"
       autoCorrect="off"
       onSubmit={e => {
@@ -203,105 +228,106 @@ export function PaytoWireTransferForm({
       }}
     >
       <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-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"
-                    name="iban"
-                    id="iban"
-                    value={iban ?? ""}
-                    placeholder="CC0123456789"
-                    autocomplete="off"
-                    required
-                    pattern={ibanRegex}
-                    onInput={(e): void => {
-                      setIban(e.currentTarget.value);
-                    }}
-                  />
-                  <ShowInputErrorLabel
-                    message={errorsWire?.iban}
-                    isDirty={iban !== undefined}
-                  />
-                </div>
-                <p class="mt-2 text-sm text-gray-500" >the receiver of the money</p>
-              </div>
+        {!isRawPayto ?
+          <div class="grid max-w-xs grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
 
-              <div class="sm:col-span-5">
-                <label for="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
-                    onInput={(e): void => {
-                      setSubject(e.currentTarget.value);
-                    }}
-                  />
-                  <ShowInputErrorLabel
-                    message={errorsWire?.subject}
-                    isDirty={subject !== undefined}
-                  />
-                </div>
-                <p class="mt-2 text-sm text-gray-500" >some text to identify the transfer</p>
+            <div class="sm:col-span-5">
+              <label for="iban" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Recipient`}</label>
+              <div class="mt-2">
+                <input
+                  ref={focus ? doAutoFocus : undefined}
+                  type="text"
+                  class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+                  name="iban"
+                  id="iban"
+                  value={iban ?? ""}
+                  placeholder="CC0123456789"
+                  autocomplete="off"
+                  required
+                  pattern={ibanRegex}
+                  onInput={(e): void => {
+                    setIban(e.currentTarget.value.toUpperCase());
+                  }}
+                />
+                <ShowInputErrorLabel
+                  message={errorsWire?.iban}
+                  isDirty={iban !== undefined}
+                />
               </div>
+              <p class="mt-2 text-sm text-gray-500" >
+                <i18n.Translate>IBAN of the recipient's account</i18n.Translate>
+              </p>
+            </div>
 
-              <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)
+            <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
+                  onInput={(e): void => {
+                    setSubject(e.currentTarget.value);
                   }}
                 />
                 <ShowInputErrorLabel
                   message={errorsWire?.subject}
                   isDirty={subject !== undefined}
                 />
-                <p class="mt-2 text-sm text-gray-500" >amount to transfer</p>
               </div>
+              <p class="mt-2 text-sm text-gray-500" >some text to identify the transfer</p>
+            </div>
 
-            </Fragment> :
-            <Fragment>
-              <div class="sm:col-span-6">
-                <label for="address" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`payto URI:`}</label>
-                <div class="mt-2">
-                  <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}
-                    value={rawPaytoInput ?? ""}
-                    required
-                    placeholder={i18n.str`payto://iban/[receiver-iban]?message=[subject]&amount=[${limit.currency}:X.Y]`}
-                    onInput={(e): void => {
-                      rawPaytoInputSetter(e.currentTarget.value);
-                    }}
-                  />
-                  <ShowInputErrorLabel
-                    message={errorsPayto?.rawPaytoInput}
-                    isDirty={rawPaytoInput !== undefined}
-                  />
-                </div>
-              </div>
+            <div 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"
+                left
+                currency={limit.currency}
+                value={trimmedAmountStr}
+                onChange={(d) => {
+                  setAmount(d)
+                }}
+              />
+              <ShowInputErrorLabel
+                message={errorsWire?.amount}
+                isDirty={subject !== undefined}
+              />
+              <p class="mt-2 text-sm text-gray-500" >amount to transfer</p>
+            </div>
 
-            </Fragment>
-          }
-        </div>
+          </div> :
+          <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6 w-full">
+            <div class="sm:col-span-6">
+              <label for="address" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`payto URI:`}</label>
+              <div class="mt-2">
+                <textarea
+                  ref={focus ? doAutoFocus : undefined}
+                  name="address"
+                  id="address"
+                  type="textarea"
+                  rows={3}
+                  class="block overflow-hidden w-64 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+                  value={rawPaytoInput ?? ""}
+                  required
+                  placeholder={i18n.str`payto://iban/[receiver-iban]?message=[subject]&amount=[${limit.currency}:X.Y]`}
+                  onInput={(e): void => {
+                    rawPaytoInputSetter(e.currentTarget.value);
+                  }}
+                />
+                <ShowInputErrorLabel
+                  message={errorsPayto?.rawPaytoInput}
+                  isDirty={rawPaytoInput !== undefined}
+                />
+              </div>
+            </div>
+          </div>
+        }
       </div>
       <div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8">
         {onCancel ?
@@ -328,17 +354,37 @@ export function PaytoWireTransferForm({
   )
 
 }
+
+/**
+ * Show the element when the load ended
+ * @param element 
+ */
+export function doAutoFocus(element: HTMLElement | null) {
+  if (element) {
+    window.requestIdleCallback(() => {
+      element.focus()
+      element.scrollIntoView({
+        behavior: "smooth",
+        block: "center",
+        inline: "center"
+      })
+    })
+  }
+}
+
 export function Amount(
   {
     currency,
     name,
     value,
     error,
+    left,
     onChange,
   }: {
     error?: string;
     currency: string;
     name: string;
+    left?: boolean | undefined,
     value: string | undefined;
     onChange?: (s: string) => void;
   },
@@ -346,13 +392,16 @@ export function Amount(
 ): 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">
+      <div class="flex rounded-md shadow-sm border-0 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600">
+        <div
+          class="pointer-events-none inset-y-0 flex items-center px-3"
+        >
           <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"
+          data-left={left}
+          class="text-right rounded-md rounded-l-none data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900  placeholder:text-gray-400  sm:text-sm sm:leading-6"
           placeholder="0.00" aria-describedby="price-currency"
           ref={ref}
           name={name}
@@ -371,3 +420,4 @@ export function Amount(
     </div>
   );
 }
+
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx b/packages/demobank-ui/src/pages/QrCodeSection.tsx
index 0a5a386ae..6a50d4ef3 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx
@@ -86,7 +86,6 @@ export function QrCodeSection({
           </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>
diff --git a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
index d19c411f3..46f4fe0ef 100644
--- a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
+++ b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
@@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from "preact/hooks";
 import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
 import { useAdminAccountAPI, useBusinessAccountDetails } from "../hooks/circuit.js";
 import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
+import { doAutoFocus } from "./PaytoWireTransferForm.js";
 
 export function UpdateAccountPassword({
   account,
@@ -27,11 +28,6 @@ export function UpdateAccountPassword({
   const [password, setPassword] = useState<string | undefined>();
   const [repeat, setRepeat] = useState<string | undefined>();
 
-  const ref = useRef<HTMLInputElement>(null);
-  useEffect(() => {
-    if (focus) ref.current?.focus();
-  }, [focus]);
-
   if (!result.ok) {
     if (result.loading || result.type === ErrorType.TIMEOUT) {
       return onLoadNotOk(result);
@@ -96,7 +92,7 @@ export function UpdateAccountPassword({
               </label>
               <div class="mt-2">
                 <input
-                  ref={ref}
+                  ref={focus ? doAutoFocus : undefined}
                   type="password"
                   class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                   name="password"
diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
index 3c5ee34fd..7357223b7 100644
--- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
+++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
@@ -29,14 +29,15 @@ import {
   notifyError,
   useTranslationContext,
 } from "@gnu-taler/web-util/browser";
-import { VNode, h } from "preact";
+import { Fragment, VNode, h } from "preact";
 import { forwardRef } from "preact/compat";
 import { useEffect, useRef, useState } from "preact/hooks";
 import { useAccessAPI } from "../hooks/access.js";
 import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
-import { Amount } from "./PaytoWireTransferForm.js";
+import { Amount, doAutoFocus } from "./PaytoWireTransferForm.js";
 import { useSettings } from "../hooks/settings.js";
 import { OperationState } from "./OperationState/index.js";
+import { Attention } from "../components/Attention.js";
 
 const logger = new Logger("WalletWithdrawForm");
 const RefAmount = forwardRef(Amount);
@@ -53,47 +54,13 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
 
   const { createWithdrawal } = useAccessAPI();
   const [amountStr, setAmountStr] = useState<string | undefined>(`${settings.maxWithdrawalAmount}`);
-  const ref = useRef<HTMLInputElement>(null);
-  useEffect(() => {
-    if (focus) ref.current?.focus();
-  }, [focus]);
 
   if (!!settings.currentWithdrawalOperationId) {
-    return <div>
-
-      <div class="rounded-md bg-yellow-50 ring-yellow-2 p-4">
-        <div class="flex">
-          <div class="flex-shrink-0">
-            <svg class="h-5 w-5 text-yellow-300" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
-              <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
-            </svg>
-          </div>
-          <div class="ml-3">
-            <h3 class="text-sm font-bold text-yellow-800">
-              <i18n.Translate>There is an operation already</i18n.Translate>
-            </h3>
-            <div class="mt-2 text-sm text-yellow-700">
-              <p>
-                <i18n.Translate>
-                  To complete or cancel the operation click <a class="font-semibold text-yellow-700 hover:text-yellow-600" href={`#/operation/${settings.currentWithdrawalOperationId}`}>here</a>
-                </i18n.Translate>
-              </p>
-            </div>
-
-          </div>
-        </div>
-      </div >
-      <div class="flex 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 bg-white p-2 rounded-sm"
-          onClick={() => {
-            updateSettings("currentWithdrawalOperationId", undefined)
-            onCancel()
-          }}
-        >
-          <i18n.Translate>Cancel</i18n.Translate>
-        </button>
-      </div>
-    </div>
+    return <Attention type="warning" title={i18n.str`There is an operation already`}>
+      <i18n.Translate>
+        To complete or cancel the operation click <a class="font-semibold text-yellow-700 hover:text-yellow-600" href={`#/operation/${settings.currentWithdrawalOperationId}`}>here</a>
+      </i18n.Translate>
+    </Attention>
   }
 
   const trimmedAmountStr = amountStr?.trim();
@@ -157,8 +124,8 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
       e.preventDefault()
     }}
   >
-    <div class="px-4 py-6 sm:p-8">
-      <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
+    <div class="px-4 py-6 ">
+      <div class="grid max-w-xs grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
         <div class="sm:col-span-5">
           <label for="withdraw-amount">{i18n.str`Amount`}</label>
           <RefAmount
@@ -169,51 +136,53 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
               setAmountStr(v);
             }}
             error={errors?.amount}
-            ref={ref}
+            ref={focus ? doAutoFocus : undefined}
           />
         </div>
-        <div class="sm:col-span-5">
-          <span class="isolate inline-flex rounded-md shadow-sm">
-            <button type="button"
-              class="relative               inline-flex px-6 py-4 text-sm items-center rounded-l-md bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
-              onClick={(e) => {
-                e.preventDefault();
-                setAmountStr("50.00")
-              }}
-            >
-              50.00
-            </button>
-            <button type="button"
-              class="relative -ml-px -mr-px inline-flex px-6 py-4 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")
-              }}
-            >
+      </div>
+      <div class="mt-4">
+        <div class="sm:inline">
 
-              25.00
-            </button>
-            <button type="button"
-              class="relative -ml-px -mr-px inline-flex px-6 py-4 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-6 py-4 text-sm items-center rounded-r-md bg-white  text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
-              onClick={(e) => {
-                e.preventDefault();
-                setAmountStr("5.00")
-              }}
-            >
-              5.00
-            </button>
-          </span>
-        </div>
+          <button type="button"
+            class="               inline-flex px-6 py-4 text-sm items-center rounded-l-md bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
+            onClick={(e) => {
+              e.preventDefault();
+              setAmountStr("50.00")
+            }}
+          >
+            50.00
+          </button>
+          <button type="button"
+            class=" -ml-px -mr-px inline-flex px-6 py-4 text-sm items-center rounded-r-md sm:rounded-none             bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
+            onClick={(e) => {
+              e.preventDefault();
+              setAmountStr("25.00")
+            }}
+          >
 
+            25.00
+          </button>
+        </div>
+        <div class="mt-4 sm:inline">
+          <button type="button"
+            class=" -ml-px -mr-px inline-flex px-6 py-4 text-sm items-center rounded-l-md sm:rounded-none             bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
+            onClick={(e) => {
+              e.preventDefault();
+              setAmountStr("10.00")
+            }}
+          >
+            10.00
+          </button>
+          <button type="button"
+            class="               inline-flex px-6 py-4 text-sm items-center rounded-r-md bg-white  text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
+            onClick={(e) => {
+              e.preventDefault();
+              setAmountStr("5.00")
+            }}
+          >
+            5.00
+          </button>
+        </div>
       </div>
     </div>
     <div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8">
@@ -255,46 +224,20 @@ export function WalletWithdrawForm({
     <div class="px-4 sm:px-0">
       <h2 class="text-base font-semibold leading-7 text-gray-900"><i18n.Translate>Prepare your wallet</i18n.Translate></h2>
       <p class="mt-1 text-sm text-gray-500">
-        <i18n.Translate>After using your wallet you will confirm or cancel the operation.</i18n.Translate>
+        <i18n.Translate>After using your wallet you will need to confirm or cancel the operation on this site.</i18n.Translate>
       </p>
     </div>
 
     <div class="col-span-2">
-      {settings.showInstallWallet && <div class="rounded-md bg-blue-50 ring-blue-2 ring-2 p-4">
-        <div class="flex">
-          <div class="flex-shrink-0">
-            <svg class="h-5 w-5 text-blue-300" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
-              <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
-            </svg>
-          </div>
-          <div class="ml-3">
-            <h3 class="text-sm font-bold text-blue-800">
-              <i18n.Translate>You need a GNU Taler Wallet</i18n.Translate>
-            </h3>
-            <div class="mt-2 text-sm text-blue-700">
-              <p>
-                <i18n.Translate>
-                  If you dont have one yet you can follow the instruction <a target="_blank" rel="noreferrer noopener" class="font-semibold text-blue-700 hover:text-blue-600" href="https://taler.net/en/wallet.html">here</a>
-                </i18n.Translate>
-              </p>
-              <p class="mt-3 text-sm flex justify-end">
-                <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();
-                    updateSettings("showInstallWallet", false);
-                  }}
-                >
-                  I know
-                  <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>
-        </div>
-      </div>}
+      {settings.showInstallWallet &&
+        <Attention title={i18n.str`You need a GNU Taler Wallet`} onClose={() => {
+          updateSettings("showInstallWallet", false);
+        }}>
+          <i18n.Translate>
+            If you don't have one yet you can follow the instruction <a target="_blank" rel="noreferrer noopener" class="font-semibold text-blue-700 hover:text-blue-600" href="https://taler.net/en/wallet.html">here</a>
+          </i18n.Translate>
+        </Attention>
+      }
 
       {!settings.fastWithdrawal ?
         <OldWithdrawalForm
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index d160a88b3..208d4b859 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -37,6 +37,7 @@ import { useMemo, useState } from "preact/hooks";
 import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
 import { useAccessAnonAPI } from "../hooks/access.js";
 import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
+import { useSettings } from "../hooks/settings.js";
 
 const logger = new Logger("WithdrawalConfirmationQuestion");
 
@@ -59,6 +60,7 @@ export function WithdrawalConfirmationQuestion({
   withdrawUri,
 }: Props): VNode {
   const { i18n } = useTranslationContext();
+  const [settings, updateSettings] = useSettings()
 
   const captchaNumbers = useMemo(() => {
     return {
@@ -87,7 +89,9 @@ export function WithdrawalConfirmationQuestion({
       await confirmWithdrawal(
         withdrawUri.withdrawalOperationId,
       );
-      notifyInfo(i18n.str`Wire transfer completed!`)
+      if (!settings.showWithdrawalSuccess) {
+        notifyInfo(i18n.str`Wire transfer completed!`)
+      }
     } catch (error) {
       if (error instanceof RequestError) {
         notify(
@@ -203,7 +207,7 @@ export function WithdrawalConfirmationQuestion({
 
             <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 text-gray-900"><i18n.Translate>Answer the next question to authorize the wire transfer</i18n.Translate></h2>
+                <h2 class="text-base font-semibold 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"
@@ -311,14 +315,10 @@ export function WithdrawalConfirmationQuestion({
 
                     }
                   })()}
-                  <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 break-words">{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)}
+                      {Amounts.currencyOf(details.amount)} {Amounts.stringifyValue(details.amount)}
                     </dd>
                   </div>
                 </dl>
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 8f4e175f6..c8efc033b 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -94,20 +94,12 @@ export function WithdrawalQRCode({
         </div>
         <div class="mt-3 text-center sm:mt-5">
           <h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title">
-            <i18n.Translate>Withdrawal OK</i18n.Translate>
+            <i18n.Translate>Withdrawal confirmed</i18n.Translate>
           </h3>
           <div class="mt-2">
             <p class="text-sm text-gray-500">
               <i18n.Translate>
-                The wire transfer to the Taler exchange bank's account is completed, now the
-                exchange will send the requested amount into your GNU Taler wallet.
-              </i18n.Translate>
-            </p>
-          </div>
-          <div class="mt-2">
-            <p >
-              <i18n.Translate>
-                You can close this page now or continue to the account page.
+                The wire transfer to the Taler operator has been initiated. You will soon receive the requested amount in your Taler wallet.
               </i18n.Translate>
             </p>
           </div>
diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
index 02df824a2..ed8bf610d 100644
--- a/packages/demobank-ui/src/pages/admin/AccountForm.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
@@ -4,6 +4,7 @@ import { PartialButDefined, RecursivePartial, WithIntermediate, undefinedIfEmpty
 import { useEffect, useRef, useState } from "preact/hooks";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { buildPayto, parsePaytoUri } from "@gnu-taler/taler-util";
+import { doAutoFocus } from "../PaytoWireTransferForm.js";
 
 const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/;
 const EMAIL_REGEX =
@@ -37,10 +38,6 @@ export function AccountForm({
     RecursivePartial<typeof initial> | undefined
   >(undefined);
   const { i18n } = useTranslationContext();
-  const ref = useRef<HTMLInputElement>(null);
-  useEffect(() => {
-    if (focus) ref.current?.focus();
-  }, [focus]);
 
   function updateForm(newForm: typeof initial): void {
 
@@ -97,7 +94,6 @@ export function AccountForm({
       <div class="px-4 py-6 sm:p-8">
         <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
 
-
           <div class="sm:col-span-5">
             <label
               class="block text-sm font-medium leading-6 text-gray-900"
@@ -108,7 +104,7 @@ export function AccountForm({
             </label>
             <div class="mt-2">
               <input
-                ref={ref}
+                ref={focus ? doAutoFocus : undefined}
                 type="text"
                 class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                 name="username"
diff --git a/packages/demobank-ui/src/pages/admin/Home.tsx b/packages/demobank-ui/src/pages/admin/Home.tsx
index ffa559097..d50ff14b4 100644
--- a/packages/demobank-ui/src/pages/admin/Home.tsx
+++ b/packages/demobank-ui/src/pages/admin/Home.tsx
@@ -10,6 +10,7 @@ import { AdminAccount } from "./Account.js";
 import { AccountList } from "./AccountList.js";
 import { CreateNewAccount } from "./CreateNewAccount.js";
 import { RemoveAccount } from "./RemoveAccount.js";
+import { Transactions } from "../../components/Transactions/index.js";
 
 /**
  * Query account information and show QR code if there is pending withdrawal
@@ -141,6 +142,7 @@ export function AdminHome({ onRegister }: Props): VNode {
 
       <AdminAccount onRegister={onRegister} />
 
+      <Transactions account="admin"/>
     </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
index 1e5370afc..b323b0d01 100644
--- a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
@@ -6,6 +6,8 @@ import { Amounts, HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util
 import { buildRequestErrorMessage, undefinedIfEmpty } from "../../utils.js";
 import { useEffect, useRef, useState } from "preact/hooks";
 import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
+import { Attention } from "../../components/Attention.js";
+import { doAutoFocus } from "../PaytoWireTransferForm.js";
 
 export function RemoveAccount({
   account,
@@ -36,47 +38,15 @@ export function RemoveAccount({
     }
     return onLoadNotOk(result);
   }
-  const ref = useRef<HTMLInputElement>(null);
-  useEffect(() => {
-    if (focus) ref.current?.focus();
-  }, [focus]);
-
   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);
   if (!isBalanceEmpty) {
-    return <div>
-      <div class="rounded-md bg-yellow-50 p-4">
-        <div class="flex">
-          <div class="flex-shrink-0">
-            <svg class="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
-              <path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
-            </svg>
-          </div>
-          <div class="ml-3">
-            <h3 class="text-sm font-medium text-yellow-800">
-              <i18n.Translate>Can't delete the account</i18n.Translate>
-            </h3>
-            <div class="mt-2 text-sm text-yellow-700">
-              <p>
-                <i18n.Translate>The account can't be delete while still holding some balance. First make sure that the owner make a complete cashout.</i18n.Translate>
-              </p>
-            </div>
-          </div>
-
-        </div>
-      </div>
-      <div class="mt-2 flex justify-end">
-        <button type="button" class="rounded-md ring-1 ring-gray-400 bg-white px-3 py-2 text-sm font-semibold shadow-sm hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
-          onClick={() => {
-            onCancel()
-          }}>
-          <i18n.Translate>Go back</i18n.Translate>
-        </button>
-      </div>
-    </div>
+    return <Attention type="warning" title={i18n.str`Can't delete the account`} onClose={onCancel}>
+      <i18n.Translate>The account can't be delete while still holding some balance. First make sure that the owner make a complete cashout.</i18n.Translate>
+    </Attention>
   }
 
   async function doRemove() {
@@ -117,26 +87,9 @@ export function RemoveAccount({
 
   return (
     <div>
-      <div class="rounded-md bg-yellow-50 p-4">
-        <div class="flex">
-          <div class="flex-shrink-0">
-            <svg class="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
-              <path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
-            </svg>
-          </div>
-          <div class="ml-3">
-            <h3 class="text-sm font-bold text-yellow-800">
-              <i18n.Translate>You are going to remove the account</i18n.Translate>
-            </h3>
-            <div class="mt-2 text-sm text-yellow-700">
-              <p>
-                <i18n.Translate>This step can't be undone.</i18n.Translate>
-              </p>
-            </div>
-          </div>
-
-        </div>
-      </div>
+      <Attention type="warning" title={i18n.str`You are going to remove the account`}>
+        <i18n.Translate>This step can't be undone.</i18n.Translate>
+      </Attention>
 
       <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">
@@ -164,7 +117,7 @@ export function RemoveAccount({
                 </label>
                 <div class="mt-2">
                   <input
-                    ref={ref}
+                    ref={focus ? doAutoFocus : undefined}
                     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 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                     name="password"
-- 
cgit v1.2.3


From e84d2b6175da8c308835f1a7f27e177dcecd248a Mon Sep 17 00:00:00 2001
From: Sebastian <sebasjm@gmail.com>
Date: Tue, 3 Oct 2023 10:11:21 -0300
Subject: more ui, removed some testing values

---
 .../src/components/Transactions/views.tsx          |  4 +--
 .../src/pages/PaytoWireTransferForm.tsx            |  8 ++---
 packages/demobank-ui/src/pages/QrCodeSection.tsx   | 35 +++++++++++-----------
 .../demobank-ui/src/pages/WithdrawalQRCode.tsx     |  2 +-
 4 files changed, 23 insertions(+), 26 deletions(-)

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

diff --git a/packages/demobank-ui/src/components/Transactions/views.tsx b/packages/demobank-ui/src/components/Transactions/views.tsx
index 69f4369a2..696fb59f3 100644
--- a/packages/demobank-ui/src/components/Transactions/views.tsx
+++ b/packages/demobank-ui/src/components/Transactions/views.tsx
@@ -81,9 +81,7 @@ export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode
                       <dl class="font-normal sm:hidden">
                         <dt class="sr-only sm:hidden"><i18n.Translate>Amount</i18n.Translate></dt>
                         <dd class="mt-1 truncate text-gray-700">
-                          {item.negative ? i18n.str`sent` : i18n.str`received`}
-
-                          {item.amount ? (
+                          {item.negative ? i18n.str`sent` : i18n.str`received`} {item.amount ? (
                             <RenderAmount value={item.amount} />
                           ) : (
                             <span style={{ color: "grey" }}>&lt;{i18n.str`invalid value`}&gt;</span>
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index b3fd51670..69a9a07b2 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -59,9 +59,9 @@ export function PaytoWireTransferForm({
 }): VNode {
   const [isRawPayto, setIsRawPayto] = useState(false);
   // FIXME: remove this
-  const [iban, setIban] = useState<string | undefined>("DE4745461198061");
-  const [subject, setSubject] = useState<string | undefined>("ASD");
-  const [amount, setAmount] = useState<string | undefined>("1.00001");
+  const [iban, setIban] = useState<string | undefined>();
+  const [subject, setSubject] = useState<string | undefined>();
+  const [amount, setAmount] = useState<string | undefined>();
 
   const [rawPaytoInput, rawPaytoInputSetter] = useState<string | undefined>(
     undefined,
@@ -189,7 +189,7 @@ export function PaytoWireTransferForm({
             <span class="flex flex-1">
               <span class="flex flex-col">
                 <span class="block text-sm  font-medium text-gray-900">
-                  <i18n.Translate>using a form</i18n.Translate>
+                  <i18n.Translate>Using a form</i18n.Translate>
                 </span>
               </span>
             </span>
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx b/packages/demobank-ui/src/pages/QrCodeSection.tsx
index 6a50d4ef3..e07525ab4 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx
@@ -84,27 +84,25 @@ export function QrCodeSection({
           <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="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">
+          <div class="mt-4 mb-4 text-sm text-gray-500">
             <p><i18n.Translate>
               You will see the details of the operation in your wallet including the fees (if applies).
-              If you still 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>.
+              If you still don't have 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"
+              // class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md  px-3 py-2 text-sm font-semibold text-black shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600"
+              class="text-sm font-semibold leading-6 text-gray-900"
               onClick={doAbort}
             >
-              Cancel withdrawal
+              Cancel
             </button>
+            <a href={talerWithdrawUri}
+              class="inline-flex items-center  disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
+            >
+              <i18n.Translate>Withdraw</i18n.Translate>
+            </a>
           </div>
         </div>
       </div>
@@ -122,12 +120,13 @@ export function QrCodeSection({
           </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>
+        <button type="button"
+              // class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md  px-3 py-2 text-sm font-semibold text-black shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600"
+              class="text-sm font-semibold leading-6 text-gray-900"
+              onClick={doAbort}
+            >
+              Cancel
+            </button>
         </div>
       </div>
 
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index c8efc033b..2d90b410c 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -112,7 +112,7 @@ export function WithdrawalQRCode({
             e.preventDefault();
             onClose()
           }}>
-          <i18n.Translate>Continue</i18n.Translate>
+          <i18n.Translate>Done</i18n.Translate>
         </button>
       </div>
     </div>
-- 
cgit v1.2.3


From 851b2da39c3297ede3d267f3d2534cac213261c1 Mon Sep 17 00:00:00 2001
From: Sebastian <sebasjm@gmail.com>
Date: Wed, 4 Oct 2023 14:36:03 -0300
Subject: fixing issues reported by Christian, wip

---
 .../src/pages/PaytoWireTransferForm.tsx            |  4 +-
 .../demobank-ui/src/pages/WithdrawalQRCode.tsx     |  2 +-
 .../merchant-backoffice-ui/src/Application.tsx     |  4 +-
 .../merchant-backoffice-ui/src/InstanceRoutes.tsx  | 30 ++++++-------
 .../src/components/form/InputStock.tsx             |  5 +--
 .../src/components/menu/SideBar.tsx                | 29 ++++++++----
 .../src/components/menu/index.tsx                  |  8 ++--
 .../src/components/product/ProductForm.tsx         |  6 +--
 .../merchant-backoffice-ui/src/context/backend.ts  | 26 +++++++++--
 .../merchant-backoffice-ui/src/declaration.d.ts    |  4 +-
 packages/merchant-backoffice-ui/src/hooks/index.ts |  3 +-
 packages/merchant-backoffice-ui/src/hooks/otp.ts   |  4 +-
 .../merchant-backoffice-ui/src/hooks/product.ts    | 17 ++++++-
 .../src/paths/instance/products/list/Table.tsx     | 13 +++---
 .../src/paths/instance/products/list/index.tsx     | 52 +++++++++++++++++++++-
 .../paths/instance/reserves/create/CreatePage.tsx  | 27 +++++++----
 .../instance/validators/create/CreatePage.tsx      |  6 +--
 .../validators/create/CreatedSuccessfully.tsx      |  2 +-
 .../instance/validators/update/UpdatePage.tsx      |  2 +-
 .../src/paths/instance/validators/update/index.tsx |  2 +-
 .../src/paths/login/index.tsx                      | 17 +++----
 .../merchant-backoffice-ui/src/schemas/index.ts    |  3 +-
 .../merchant-backoffice-ui/src/utils/constants.ts  |  2 +-
 packages/web-util/src/index.build.ts               |  2 +-
 24 files changed, 185 insertions(+), 85 deletions(-)

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

diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index 69a9a07b2..52dbd4ff6 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -363,14 +363,14 @@ export function PaytoWireTransferForm({
  */
 export function doAutoFocus(element: HTMLElement | null) {
   if (element) {
-    window.requestIdleCallback(() => {
+    setTimeout(() => {
       element.focus()
       element.scrollIntoView({
         behavior: "smooth",
         block: "center",
         inline: "center"
       })
-    })
+    }, 100)
   }
 }
 
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 2d90b410c..91c5da718 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -150,7 +150,7 @@ export function WithdrawalQRCode({
       details={{
         account,
         reserve: data.selected_reserve_pub,
-        amount: Amounts.parseOrThrow("usd:10.00")
+        amount: Amounts.parseOrThrow(data.amount)
       }}
       onAborted={() => {
         notifyInfo(i18n.str`Operation canceled`);
diff --git a/packages/merchant-backoffice-ui/src/Application.tsx b/packages/merchant-backoffice-ui/src/Application.tsx
index 1a7617643..f0a7de53b 100644
--- a/packages/merchant-backoffice-ui/src/Application.tsx
+++ b/packages/merchant-backoffice-ui/src/Application.tsx
@@ -60,7 +60,7 @@ export function Application(): VNode {
  * @returns 
  */
 function ApplicationStatusRoutes(): VNode {
-  const { url: backendURL, updateToken, changeBackend } = useBackendContext();
+  const { changeBackend, selected: backendSelected } = useBackendContext();
   const result = useBackendConfig();
   const { i18n } = useTranslationContext();
 
@@ -69,7 +69,7 @@ function ApplicationStatusRoutes(): VNode {
     : { currency: "unknown", version: "unknown" };
   const ctx = useMemo(() => ({ currency, version }), [currency, version]);
 
-  if (!backendURL) {
+  if (!backendSelected) {
     return (
       <Fragment>
         <NotConnectedAppMenu title="Welcome!" />
diff --git a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
index c2a9d3b18..f5372db8d 100644
--- a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
+++ b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx
@@ -87,9 +87,9 @@ export enum InstancePaths {
   bank_update = "/bank/:bid/update",
   bank_new = "/bank/new",
 
-  product_list = "/products",
-  product_update = "/product/:pid/update",
-  product_new = "/product/new",
+  inventory_list = "/inventory",
+  inventory_update = "/inventory/:pid/update",
+  inventory_new = "/inventory/new",
 
   order_list = "/orders",
   order_new = "/order/new",
@@ -347,42 +347,42 @@ export function InstanceRoutes({
           onLoadError={ServerErrorRedirectTo(InstancePaths.error)}
         />
         {/**
-         * Product pages
+         * Inventory pages
          */}
         <Route
-          path={InstancePaths.product_list}
+          path={InstancePaths.inventory_list}
           component={ProductListPage}
           onUnauthorized={LoginPageAccessDenied}
           onLoadError={ServerErrorRedirectTo(InstancePaths.server)}
           onCreate={() => {
-            route(InstancePaths.product_new);
+            route(InstancePaths.inventory_new);
           }}
           onSelect={(id: string) => {
-            route(InstancePaths.product_update.replace(":pid", id));
+            route(InstancePaths.inventory_update.replace(":pid", id));
           }}
           onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
         />
         <Route
-          path={InstancePaths.product_update}
+          path={InstancePaths.inventory_update}
           component={ProductUpdatePage}
           onUnauthorized={LoginPageAccessDenied}
-          onLoadError={ServerErrorRedirectTo(InstancePaths.product_list)}
+          onLoadError={ServerErrorRedirectTo(InstancePaths.inventory_list)}
           onConfirm={() => {
-            route(InstancePaths.product_list);
+            route(InstancePaths.inventory_list);
           }}
           onBack={() => {
-            route(InstancePaths.product_list);
+            route(InstancePaths.inventory_list);
           }}
           onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
         />
         <Route
-          path={InstancePaths.product_new}
+          path={InstancePaths.inventory_new}
           component={ProductCreatePage}
           onConfirm={() => {
-            route(InstancePaths.product_list);
+            route(InstancePaths.inventory_list);
           }}
           onBack={() => {
-            route(InstancePaths.product_list);
+            route(InstancePaths.inventory_list);
           }}
         />
         {/**
@@ -405,7 +405,7 @@ export function InstanceRoutes({
           path={InstancePaths.bank_update}
           component={BankAccountUpdatePage}
           onUnauthorized={LoginPageAccessDenied}
-          onLoadError={ServerErrorRedirectTo(InstancePaths.product_list)}
+          onLoadError={ServerErrorRedirectTo(InstancePaths.inventory_list)}
           onConfirm={() => {
             route(InstancePaths.bank_list);
           }}
diff --git a/packages/merchant-backoffice-ui/src/components/form/InputStock.tsx b/packages/merchant-backoffice-ui/src/components/form/InputStock.tsx
index 012d14977..1d18685c5 100644
--- a/packages/merchant-backoffice-ui/src/components/form/InputStock.tsx
+++ b/packages/merchant-backoffice-ui/src/components/form/InputStock.tsx
@@ -212,10 +212,7 @@ export function InputStock<T>({
               withTimestampSupport
             />
 
-            <InputGroup<Entity>
-              name="address"
-              label={i18n.str`Delivery address`}
-            >
+            <InputGroup<Entity> name="address" label={i18n.str`Warehouse address`}>
               <InputLocation name="address" />
             </InputGroup>
           </FormProvider>
diff --git a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx
index 3d5f20c85..402134096 100644
--- a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx
+++ b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx
@@ -49,7 +49,7 @@ export function Sidebar({
   isPasswordOk
 }: Props): VNode {
   const config = useConfigContext();
-  const { url: backendURL } = useBackendContext()
+  const { url: backendURL, resetBackend } = useBackendContext()
   const { i18n } = useTranslationContext();
   const kycStatus = useInstanceKYCDetails();
   const needKYC = kycStatus.ok && kycStatus.data.type === "redirect";
@@ -80,7 +80,7 @@ export function Sidebar({
         </div>
       </div>
       <div class="menu is-menu-main">
-        {isPasswordOk && instance ? (
+        {instance ? (
           <Fragment>
             <ul class="menu-list">
               <li>
@@ -94,12 +94,12 @@ export function Sidebar({
                 </a>
               </li>
               <li>
-                <a href={"/products"} class="has-icon">
+                <a href={"/inventory"} class="has-icon">
                   <span class="icon">
                     <i class="mdi mdi-shopping" />
                   </span>
                   <span class="menu-item-label">
-                    <i18n.Translate>Products</i18n.Translate>
+                    <i18n.Translate>Inventory</i18n.Translate>
                   </span>
                 </a>
               </li>
@@ -243,7 +243,7 @@ export function Sidebar({
               </span>
             </div>
           </li>
-          {isPasswordOk && admin && !mimic && (
+          {admin && !mimic && (
             <Fragment>
               <p class="menu-label">
                 <i18n.Translate>Instances</i18n.Translate>
@@ -270,7 +270,7 @@ export function Sidebar({
               </li>
             </Fragment>
           )}
-          {isPasswordOk &&
+          {isPasswordOk ?
             <li>
               <a
                 class="has-icon is-state-info is-hoverable"
@@ -283,8 +283,21 @@ export function Sidebar({
                   <i18n.Translate>Log out</i18n.Translate>
                 </span>
               </a>
-            </li>
-          }
+            </li> :
+            <li>
+            <a
+              class="has-icon is-state-info is-hoverable"
+              onClick={(): void => resetBackend()}
+            >
+              <span class="icon">
+                <i class="mdi mdi-logout default" />
+              </span>
+              <span class="menu-item-label">
+                <i18n.Translate>Change server</i18n.Translate>
+              </span>
+            </a>
+          </li>
+        }
         </ul>
       </div>
     </aside>
diff --git a/packages/merchant-backoffice-ui/src/components/menu/index.tsx b/packages/merchant-backoffice-ui/src/components/menu/index.tsx
index cb318906f..b8ac2c9ab 100644
--- a/packages/merchant-backoffice-ui/src/components/menu/index.tsx
+++ b/packages/merchant-backoffice-ui/src/components/menu/index.tsx
@@ -30,11 +30,11 @@ function getInstanceTitle(path: string, id: string): string {
       return `${id}: Orders`;
     case InstancePaths.order_new:
       return `${id}: New order`;
-    case InstancePaths.product_list:
-      return `${id}: Products`;
-    case InstancePaths.product_new:
+    case InstancePaths.inventory_list:
+      return `${id}: Inventory`;
+    case InstancePaths.inventory_new:
       return `${id}: New product`;
-    case InstancePaths.product_update:
+    case InstancePaths.inventory_update:
       return `${id}: Update product`;
     case InstancePaths.reserves_new:
       return `${id}: New reserve`;
diff --git a/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx b/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx
index 8bebbd298..e91e8c876 100644
--- a/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx
+++ b/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx
@@ -146,9 +146,9 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) {
         />
         <InputNumber<Entity>
           name="minimum_age"
-          label={i18n.str`Age restricted`}
+          label={i18n.str`Age restriction`}
           tooltip={i18n.str`is this product restricted for customer below certain age?`}
-          help={i18n.str`can be overridden by the order configuration`}
+          help={i18n.str`minimum age of the buyer`}
         />
         <Input<Entity>
           name="unit"
@@ -165,7 +165,7 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) {
           name="stock"
           label={i18n.str`Stock`}
           alreadyExist={alreadyExist}
-          tooltip={i18n.str`product inventory for products with finite supply (for internal use only)`}
+          tooltip={i18n.str`inventory for products with finite supply (for internal use only)`}
         />
         <InputTaxes<Entity>
           name="taxes"
diff --git a/packages/merchant-backoffice-ui/src/context/backend.ts b/packages/merchant-backoffice-ui/src/context/backend.ts
index 056f9a192..d4a9abd5f 100644
--- a/packages/merchant-backoffice-ui/src/context/backend.ts
+++ b/packages/merchant-backoffice-ui/src/context/backend.ts
@@ -20,35 +20,55 @@
  */
 
 import { createContext, h, VNode } from "preact";
-import { useContext } from "preact/hooks";
+import { useContext, useState } from "preact/hooks";
 import { LoginToken } from "../declaration.js";
 import { useBackendDefaultToken, useBackendURL } from "../hooks/index.js";
+import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser";
+import { codecForBoolean } from "@gnu-taler/taler-util";
 
 interface BackendContextType {
   url: string,
+  selected: boolean;
   token?: LoginToken;
   updateToken: (token: LoginToken | undefined) => void;
   changeBackend: (url: string) => void;
+  resetBackend: () => void;
 }
 
 const BackendContext = createContext<BackendContextType>({
   url: "",
+  selected: false,
   token: undefined,
   updateToken: () => null,
   changeBackend: () => null,
+  resetBackend: () => null,
 });
 
+const BACKEND_SELECTED = buildStorageKey("backend-selected", codecForBoolean());
+
 function useBackendContextState(
   defaultUrl?: string,
 ): BackendContextType {
-  const [url, changeBackend] = useBackendURL(defaultUrl);
+  const [url, changeBackend2] = useBackendURL(defaultUrl);
   const [token, updateToken] = useBackendDefaultToken();
+  const {value, update} = useLocalStorage(BACKEND_SELECTED)
+
+  function changeBackend(s:string) {
+    changeBackend2(s)
+    update(true)
+  }
+
+  function resetBackend() {
+    update(false)
+  }
 
   return {
     url,
     token,
+    selected: value ?? false,
     updateToken,
-    changeBackend
+    changeBackend,
+    resetBackend
   };
 }
 
diff --git a/packages/merchant-backoffice-ui/src/declaration.d.ts b/packages/merchant-backoffice-ui/src/declaration.d.ts
index c3e6ea3da..dc53e3e83 100644
--- a/packages/merchant-backoffice-ui/src/declaration.d.ts
+++ b/packages/merchant-backoffice-ui/src/declaration.d.ts
@@ -1327,7 +1327,7 @@ export namespace MerchantBackend {
       otp_device_id: string;
 
       // Human-readable description for the device.
-      otp_description: string;
+      otp_device_description: string;
 
       // A base64-encoded key
       otp_key: string;
@@ -1341,7 +1341,7 @@ export namespace MerchantBackend {
 
     interface OtpDevicePatchDetails {
       // Human-readable description for the device.
-      otp_description: string;
+      otp_device_description: string;
 
       // A base64-encoded key
       otp_key: string | undefined;
diff --git a/packages/merchant-backoffice-ui/src/hooks/index.ts b/packages/merchant-backoffice-ui/src/hooks/index.ts
index ee696779f..498e4eb78 100644
--- a/packages/merchant-backoffice-ui/src/hooks/index.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/index.ts
@@ -31,7 +31,8 @@ const calculateRootPath = () => {
     typeof window !== undefined
       ? window.location.origin + window.location.pathname
       : "/";
-  return rootPath;
+
+  return rootPath.replace("webui/","");
 };
 
 const loginTokenCodec = buildCodecForObject<LoginToken>()
diff --git a/packages/merchant-backoffice-ui/src/hooks/otp.ts b/packages/merchant-backoffice-ui/src/hooks/otp.ts
index 3544b4881..93eefeea5 100644
--- a/packages/merchant-backoffice-ui/src/hooks/otp.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/otp.ts
@@ -30,13 +30,13 @@ const useSWR = _useSWR as unknown as SWRHook;
 
 const MOCKED_DEVICES: Record<string, MerchantBackend.OTP.OtpDeviceAddDetails> = {
   "1": {
-    otp_description: "first device",
+    otp_device_description: "first device",
     otp_algorithm: 1,
     otp_device_id: "1",
     otp_key: "123",
   },
   "2": {
-    otp_description: "second device",
+    otp_device_description: "second device",
     otp_algorithm: 0,
     otp_device_id: "2",
     otp_key: "456",
diff --git a/packages/merchant-backoffice-ui/src/hooks/product.ts b/packages/merchant-backoffice-ui/src/hooks/product.ts
index 8ecaefaa6..e06ea8ed8 100644
--- a/packages/merchant-backoffice-ui/src/hooks/product.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/product.ts
@@ -26,6 +26,9 @@ import _useSWR, { SWRHook, useSWRConfig } from "swr";
 const useSWR = _useSWR as unknown as SWRHook;
 
 export interface ProductAPI {
+  getProduct: (
+    id: string,  
+  ) => Promise<void>;
   createProduct: (
     data: MerchantBackend.Products.ProductAddDetail,
   ) => Promise<void>;
@@ -66,7 +69,7 @@ export function useProductAPI(): ProductAPI {
       data,
     });
 
-    return await mutateAll(/.*"\/private\/products.*/);
+    return await mutateAll(/.*\/private\/products.*/);
   };
 
   const deleteProduct = async (productId: string): Promise<void> => {
@@ -88,7 +91,17 @@ export function useProductAPI(): ProductAPI {
     return await mutateAll(/.*"\/private\/products.*/);
   };
 
-  return { createProduct, updateProduct, deleteProduct, lockProduct };
+  const getProduct = async (
+    productId: string,
+  ): Promise<void> => {
+    await request(`/private/products/${productId}`, {
+      method: "GET",
+    });
+
+    return
+  };
+
+  return { createProduct, updateProduct, deleteProduct, lockProduct, getProduct };
 }
 
 export function useInstanceProducts(): HttpResponse<
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx
index cbfe1d573..db73217ed 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx
@@ -66,7 +66,7 @@ export function CardTable({
           <span class="icon">
             <i class="mdi mdi-shopping" />
           </span>
-          <i18n.Translate>Products</i18n.Translate>
+          <i18n.Translate>Inventory</i18n.Translate>
         </p>
         <div class="card-header-icon" aria-label="more options">
           <span
@@ -142,7 +142,7 @@ function Table({
               <i18n.Translate>Taxes</i18n.Translate>
             </th>
             <th>
-              <i18n.Translate>Profit</i18n.Translate>
+              <i18n.Translate>Sales</i18n.Translate>
             </th>
             <th>
               <i18n.Translate>Stock</i18n.Translate>
@@ -190,18 +190,21 @@ function Table({
                       src={i.image ? i.image : emptyImage}
                       style={{
                         border: "solid black 1px",
-                        width: 100,
-                        height: 100,
+                        maxHeight: "2em",
+                        width: "auto",
+                        height: "auto",
                       }}
                     />
                   </td>
                   <td
+                    class="has-tooltip-right"                    
+                    data-tooltip={i.description}
                     onClick={() =>
                       rowSelection !== i.id && rowSelectionHandler(i.id)
                     }
                     style={{ cursor: "pointer" }}
                   >
-                    {i.description}
+                    {i.description.length > 30  ? i.description.substring(0, 30) + "..." : i.description}
                   </td>
                   <td
                     onClick={() =>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx
index 85c50e5ed..274a7c2ea 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx
@@ -53,7 +53,7 @@ export default function ProductList({
   onNotFound,
 }: Props): VNode {
   const result = useInstanceProducts();
-  const { deleteProduct, updateProduct } = useProductAPI();
+  const { deleteProduct, updateProduct, getProduct } = useProductAPI();
   const [deleting, setDeleting] =
     useState<MerchantBackend.Products.ProductDetail & WithId | null>(null);
   const [notif, setNotif] = useState<Notification | undefined>(undefined);
@@ -74,11 +74,61 @@ export default function ProductList({
       return onNotFound();
     return onLoadError(result);
   }
+  const [errorId, setErrorId] = useState<string | undefined>(
+    undefined,
+  );
+
+  const [productId, setProductId] = useState<string>()
+  async function testIfProductExistAndSelect(orderId: string | undefined): Promise<void> {
+    if (!orderId) {
+      setErrorId(i18n.str`Enter a product id`);
+      return;
+    }
+    try {
+      await getProduct(orderId);
+      onSelect(orderId);
+      setErrorId(undefined);
+    } catch {
+      setErrorId(i18n.str`product not found`);
+    }
+  }
 
   return (
     <section class="section is-main-section">
       <NotificationCard notification={notif} />
 
+      <div class="level">
+        <div class="level-left">
+          <div class="level-item">
+            <div class="field has-addons">
+              <div class="control">
+                <input
+                  class={errorId ? "input is-danger" : "input"}
+                  type="text"
+                  value={productId ?? ""}
+                  onChange={(e) => setProductId(e.currentTarget.value)}
+                  placeholder={i18n.str`product id`}
+                />
+                {errorId && <p class="help is-danger">{errorId}</p>}
+              </div>
+              <span
+                class="has-tooltip-bottom"
+                data-tooltip={i18n.str`jump to product with the given product ID`}
+              >
+                <button
+                  class="button"
+                  onClick={(e) => testIfProductExistAndSelect(productId)}
+                >
+                  <span class="icon">
+                    <i class="mdi mdi-arrow-right" />
+                  </span>
+                </button>
+              </span>
+            </div>
+          </div>
+        </div>
+      </div>
+
       <CardTable
         instances={result.data}
         onCreate={onCreate}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatePage.tsx
index 2201e75a5..0d2bb2c30 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatePage.tsx
@@ -19,7 +19,7 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { HttpError, RequestError, useApiContext, useTranslationContext } from "@gnu-taler/web-util/browser";
 import { Fragment, h, VNode } from "preact";
 import { StateUpdater, useEffect, useState } from "preact/hooks";
 import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
@@ -35,6 +35,7 @@ import {
   PAYTO_WIRE_METHOD_LOOKUP,
   URL_REGEX,
 } from "../../../../utils/constants.js";
+import { useBackendBaseRequest } from "../../../../hooks/backend.js";
 
 type Entity = MerchantBackend.Rewards.ReserveCreateRequest;
 
@@ -65,6 +66,7 @@ function ViewStep({
   setReserve,
 }: ViewProps): VNode {
   const { i18n } = useTranslationContext();
+  const {request} = useApiContext()
   const [wireMethods, setWireMethods] = useState<Array<string>>([]);
   const [exchangeQueryError, setExchangeQueryError] = useState<
     string | undefined
@@ -123,19 +125,26 @@ function ViewStep({
             <AsyncButton
               class="has-tooltip-left"
               onClick={() => {
-                return fetch(`${reserve.exchange_url}wire`)
-                  .then((r) => r.json())
+                if (!reserve.exchange_url) {
+                  return Promise.resolve();
+                }
+                
+                return request<any>(reserve.exchange_url, "keys") //</div>fetch(`${reserve.exchange_url}wire`)
                   .then((r) => {
-                    const wireMethods = r.accounts.map((a: any) => {
-                      const match = PAYTO_WIRE_METHOD_LOOKUP.exec(a.payto_uri);
-                      return (match && match[1]) || "";
-                    });
+                    if (r.loading) return;
+                    if (r.ok) {
+                      const wireMethods = r.data.accounts.map((a: any) => {
+                        const match = PAYTO_WIRE_METHOD_LOOKUP.exec(a.payto_uri);
+                        return (match && match[1]) || "";
+                      });                     
+                    }
                     setWireMethods(wireMethods);
                     setCurrentStep(Steps.WIRE_METHOD);
                     return;
                   })
-                  .catch((r: any) => {
-                    setExchangeQueryError(r.message);
+                  .catch((r: RequestError<{}>) => {
+                    console.log(r.cause)
+                    setExchangeQueryError(r.cause.message);
                   });
               }}
               data-tooltip={
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/validators/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/validators/create/CreatePage.tsx
index bdc86d226..cebc1ade6 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/validators/create/CreatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/validators/create/CreatePage.tsx
@@ -70,8 +70,8 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
         : state.otp_key.length !== 32
           ? i18n.str`size of the key should be 32`
           : undefined,
-    otp_description: !state.otp_description ? i18n.str`required`
-      : !/[a-zA-Z0-9]*/.test(state.otp_description)
+          otp_device_description: !state.otp_device_description ? i18n.str`required`
+      : !/[a-zA-Z0-9]*/.test(state.otp_device_description)
         ? i18n.str`no valid. only characters and numbers`
         : undefined,
 
@@ -103,7 +103,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
                 tooltip={i18n.str`Internal id on the system`}
               />
               <Input<Entity>
-                name="otp_description"
+                name="otp_device_description"
                 label={i18n.str`Descripiton`}
                 tooltip={i18n.str`Useful to identify the device physically`}
               />
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/validators/create/CreatedSuccessfully.tsx b/packages/merchant-backoffice-ui/src/paths/instance/validators/create/CreatedSuccessfully.tsx
index 22ae55677..db3842711 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/validators/create/CreatedSuccessfully.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/validators/create/CreatedSuccessfully.tsx
@@ -77,7 +77,7 @@ export function CreatedSuccessfully({
               <input
                 class="input"
                 readonly
-                value={entity.otp_description}
+                value={entity.otp_device_description}
               />
             </p>
           </div>
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/validators/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/validators/update/UpdatePage.tsx
index 585c12e11..79be9802f 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/validators/update/UpdatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/validators/update/UpdatePage.tsx
@@ -87,7 +87,7 @@ export function UpdatePage({ device, onUpdate, onBack }: Props): VNode {
                 errors={errors}
               >
                 <Input<Entity>
-                  name="otp_description"
+                  name="otp_device_description"
                   label={i18n.str`Description`}
                   tooltip={i18n.str`dddd`}
                 />
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/validators/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/validators/update/index.tsx
index 9a27ccfee..52f6c6c29 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/validators/update/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/validators/update/index.tsx
@@ -80,7 +80,7 @@ export default function UpdateValidator({
         device={{
           id: vid,
           otp_algorithm: result.data.otp_algorithm,
-          otp_description: result.data.device_description,
+          otp_device_description: result.data.device_description,
           otp_key: undefined,
           otp_ctr: result.data.otp_ctr
         }}
diff --git a/packages/merchant-backoffice-ui/src/paths/login/index.tsx b/packages/merchant-backoffice-ui/src/paths/login/index.tsx
index 9948307e4..a9e3c3a1b 100644
--- a/packages/merchant-backoffice-ui/src/paths/login/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/login/index.tsx
@@ -52,7 +52,7 @@ function cleanUp(s: string): string {
 }
 
 export function LoginPage({ onConfirm }: Props): VNode {
-  const { url: backendURL, changeBackend } = useBackendContext();
+  const { url: backendURL, changeBackend, resetBackend } = useBackendContext();
   const { admin, id } = useInstanceContext();
   const { requestNewLoginToken } = useCredentialsChecker();
   const [token, setToken] = useState("");
@@ -73,10 +73,9 @@ export function LoginPage({ onConfirm }: Props): VNode {
   }, [backendURL, id, token])
 
   async function changeServer() {
-    changeBackend("")
+    resetBackend()
   }
 
-  console.log(admin, id)
   if (admin && id !== "default") {
     //admin trying to access another instance
     return (<div class="columns is-centered" style={{ margin: "auto" }}>
@@ -211,10 +210,7 @@ export function LoginPage({ onConfirm }: Props): VNode {
               borderTop: 0,
             }}
           >
-            <AsyncButton
-
-              onClick={changeServer}
-            >
+            <AsyncButton onClick={changeServer}>
               <i18n.Translate>Change server</i18n.Translate>
             </AsyncButton>
 
@@ -304,11 +300,8 @@ export function ConnectionPage({ onConfirm }: { onConfirm: (s: string) => void }
               borderTop: 0,
             }}
           >
-            <AsyncButton
-              disabled={backendURL === url}
-              onClick={doConnect}
-            >
-              <i18n.Translate>Try again</i18n.Translate>
+            <AsyncButton onClick={doConnect}>
+              <i18n.Translate>Connect</i18n.Translate>
             </AsyncButton>
           </footer>
         </div>
diff --git a/packages/merchant-backoffice-ui/src/schemas/index.ts b/packages/merchant-backoffice-ui/src/schemas/index.ts
index 4be77595b..c97d41204 100644
--- a/packages/merchant-backoffice-ui/src/schemas/index.ts
+++ b/packages/merchant-backoffice-ui/src/schemas/index.ts
@@ -22,6 +22,7 @@
 import { isAfter, isFuture } from "date-fns";
 import * as yup from "yup";
 import { AMOUNT_REGEX, PAYTO_REGEX } from "../utils/constants.js";
+import { Amounts } from "@gnu-taler/taler-util";
 
 yup.setLocale({
   mixed: {
@@ -38,7 +39,7 @@ function listOfPayToUrisAreValid(values?: (string | undefined)[]): boolean {
 }
 
 function currencyWithAmountIsValid(value?: string): boolean {
-  return !!value && AMOUNT_REGEX.test(value);
+  return !!value && Amounts.parse(value) !== undefined;
 }
 function currencyGreaterThan0(value?: string) {
   if (value) {
diff --git a/packages/merchant-backoffice-ui/src/utils/constants.ts b/packages/merchant-backoffice-ui/src/utils/constants.ts
index fea9cb7e2..7c4e288b3 100644
--- a/packages/merchant-backoffice-ui/src/utils/constants.ts
+++ b/packages/merchant-backoffice-ui/src/utils/constants.ts
@@ -25,7 +25,7 @@ export const PAYTO_REGEX =
 export const PAYTO_WIRE_METHOD_LOOKUP =
   /payto:\/\/([a-zA-Z][a-zA-Z0-9-.]+)\/.*/;
 
-export const AMOUNT_REGEX = /^[a-zA-Z][a-zA-Z]*:[0-9][0-9,]*\.?[0-9,]*$/;
+export const AMOUNT_REGEX = /^[a-zA-Z][a-zA-Z]{1,11}:[0-9][0-9,]*\.?[0-9,]*$/;
 
 export const INSTANCE_ID_LOOKUP = /\/instances\/([^/]*)\/?$/;
 
diff --git a/packages/web-util/src/index.build.ts b/packages/web-util/src/index.build.ts
index 6ee1be20a..0e8e7cec3 100644
--- a/packages/web-util/src/index.build.ts
+++ b/packages/web-util/src/index.build.ts
@@ -54,7 +54,7 @@ if (GIT_ROOT === "/") {
   // eslint-disable-next-line no-undef
   process.exit(1);
 }
-const GIT_HASH = GIT_ROOT === "/" ? undefined : git_hash();
+const GIT_HASH = git_hash();
 
 const buf = fs.readFileSync(path.join(BASE, "package.json"));
 let _package = JSON.parse(buf.toString("utf-8"));
-- 
cgit v1.2.3

