gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] 02/02: more ui


From: gnunet
Subject: [taler-wallet-core] 02/02: more ui
Date: Sun, 22 Oct 2023 01:26:04 +0200

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

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

commit 2ac73949e7cb8de44e56f2fecae617efab15671e
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Sat Oct 21 20:25:38 2023 -0300

    more ui
---
 packages/demobank-ui/src/assets/lang.svg           |  48 +++
 .../demobank-ui/src/components/Cashouts/views.tsx  | 159 +++++++---
 packages/demobank-ui/src/components/CopyButton.tsx |  32 +-
 .../demobank-ui/src/components/LangSelector.tsx    |   3 +-
 packages/demobank-ui/src/components/Routing.tsx    | 208 +++++++++++--
 .../src/components/Transactions/views.tsx          |  12 +-
 packages/demobank-ui/src/components/app.tsx        |   3 +-
 packages/demobank-ui/src/context/config.ts         |  10 +-
 packages/demobank-ui/src/hooks/access.ts           |  24 +-
 packages/demobank-ui/src/hooks/circuit.ts          |  14 +-
 packages/demobank-ui/src/pages.ts                  |  44 +++
 .../demobank-ui/src/pages/AccountPage/views.tsx    |  29 --
 packages/demobank-ui/src/pages/BankFrame.tsx       | 122 ++++----
 packages/demobank-ui/src/pages/LoginForm.tsx       |  12 +-
 .../demobank-ui/src/pages/OperationState/state.ts  |  15 +-
 packages/demobank-ui/src/pages/PaymentOptions.tsx  |  24 +-
 .../src/pages/PaytoWireTransferForm.tsx            |  65 ++--
 .../demobank-ui/src/pages/ProfileNavigation.tsx    |  56 ++++
 .../demobank-ui/src/pages/RegistrationPage.tsx     |  16 +-
 .../demobank-ui/src/pages/ShowAccountDetails.tsx   | 123 +++-----
 .../src/pages/UpdateAccountPassword.tsx            | 259 ++++++++-------
 .../demobank-ui/src/pages/WithdrawalQRCode.tsx     |   1 +
 packages/demobank-ui/src/pages/admin/Account.tsx   |  10 +-
 .../demobank-ui/src/pages/admin/AccountForm.tsx    | 182 ++++++++---
 .../demobank-ui/src/pages/admin/AccountList.tsx    |  58 ++--
 packages/demobank-ui/src/pages/admin/AdminHome.tsx |  32 ++
 .../src/pages/admin/CashoutListForAccount.tsx      |  47 +++
 .../src/pages/admin/CreateNewAccount.tsx           |  24 +-
 packages/demobank-ui/src/pages/admin/Home.tsx      | 143 ---------
 .../demobank-ui/src/pages/admin/RemoveAccount.tsx  |   3 +-
 .../pages/business/{Home.tsx => CreateCashout.tsx} | 346 +--------------------
 .../src/pages/business/ShowCashoutDetails.tsx      | 237 ++++++++++++++
 packages/demobank-ui/src/route.ts                  | 167 ++++++++++
 33 files changed, 1526 insertions(+), 1002 deletions(-)

diff --git a/packages/demobank-ui/src/assets/lang.svg 
b/packages/demobank-ui/src/assets/lang.svg
new file mode 100644
index 000000000..dd72ce65e
--- /dev/null
+++ b/packages/demobank-ui/src/assets/lang.svg
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 
6.00 Build 0)  -->
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg"; 
xmlns:xlink="http://www.w3.org/1999/xlink"; x="0px" y="0px"
+        viewBox="0 0 2411.2 2794" style="enable-background:new 0 0 2411.2 
2794;" xml:space="preserve">
+<style type="text/css">
+       .st0{fill:#FFFFFF;}
+       .st1{fill-rule:evenodd;clip-rule:evenodd;}
+       .st2{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
+</style>
+<g id="Layer_2">
+</g>
+<g id="Layer_x5F_1_x5F_1">
+       <g>
+               <polygon points="1204.6,359.2 271.8,30 271.8,2060.1 
1204.6,1758.3               "/>
+               <polygon class="st0" points="1182.2,358.1 2150.6,29 2150.6,2059 
1182.2,1757.3           "/>
+               <polygon class="st0" points="30,2415.4 1182.2,2031.4 
1182.2,357.9 30,742                "/>
+               <polygon points="1707.2,2440.7 1870.5,2709.4 1956.6,2459.8      
        "/>
+               <g>
+                       <path 
d="M421.7,934.8c-6.1-6,8,49.1,27.6,68.9c34.8,35.1,61.9,39.6,76.4,40.2c32,1.3,71.5-8,94.9-17.8
+                               
c22.7-9.7,62.4-30,77.5-59.6c3.2-6.3,11.9-17,6.4-43.2c-4.2-20.2-17-27.3-32.7-26.2c-15.7,1.1-63.2,13.7-86.1,20.8
+                               
c-23,7-70.3,21.4-90.9,25.8C474.3,948.2,429,941.7,421.7,934.8z"/>
+                       <path 
d="M1003.1,1593.7c-9.1-3.3-196.9-81.1-223.6-93.9c-21.8-10.5-75.2-33.1-100.4-43.3c70.8-109.2,115.5-191.6,121.5-204.1
+                               
c11-23,86-169.6,87.7-178.7c1.7-9.1,3.8-42.9,2.2-51c-1.7-8.2-29.1,7.6-66.4,20.2c-37.4,12.6-108.4,58.8-135.8,64.6
+                               
c-27.5,5.7-115.5,39.1-160.5,54c-45,14.9-130.2,40.9-165.2,50.4c-35.1,9.5-65.7,10.2-85.3,16.2c0,0,2.6,27.5,7.8,35.7
+                               
c5.2,8.2,23.7,28.4,45.3,34.1c21.6,5.7,57.3,3.4,73.6-0.3c16.3-3.8,44.4-17.5,48.2-23.6c3.8-6.1-2-24.9,4.5-30.6
+                               
c6.5-5.6,92.2-25.7,124.6-35.4c32.4-10,156.3-52.6,173.1-50.5c-5.3,17.7-105,215.1-137.1,274c-32.1,58.9-218.6,318-258.3,363.6
+                               
c-30.1,34.7-103.2,123.5-128.5,143.6c6.4,1.8,51.6-2.1,59.9-7.2c51.3-31.6,136.9-138.1,164.4-170.5
+                               
c81.9-96,153.8-196.8,210.8-283.4h0.1c11.1,4.6,100.9,77.8,124.4,94c23.4,16.2,115.9,67.8,136,76.4c20,8.7,97.1,44.2,100.3,32.2
+                               C1029.4,1668,1012.2,1597.1,1003.1,1593.7z"/>
+               </g>
+               <path class="st1" 
d="M569,2572c18,11,35,20,54,29c38,19,81,39,122,54c56,21,112,38,168,51c31,7,65,13,98,18c3,0,92,11,110,11h90
+                       
c35-3,68-5,103-10c28-4,59-9,89-16c22-5,45-10,67-17c21-6,45-14,68-22c15-5,31-12,47-18c13-6,29-13,44-19c18-8,39-19,59-29
+                       
c16-8,34-18,51-28c13-7,43-30,59-30c18,0,30,16,30,30c0,29-39,38-57,51c-19,13-42,23-62,34c-40,21-81,39-120,54
+                       
c-51,19-107,37-157,49c-19,4-38,9-57,12c-10,2-114,18-143,18h-132c-35-3-72-7-107-12c-31-5-64-11-95-18c-24-5-50-12-73-19
+                       
c-40-11-79-25-117-40c-69-26-141-60-209-105c-12-8-13-16-13-25c0-15,11-29,29-29C531,2546,563,2569,569,2572z"/>
+               <path class="st1" d="M1151,2009L61,2372V764l1090-363V2009z 
M1212,354v1680c-1,5-3,10-7,15c-2,3-6,7-9,8c-25,10-1151,388-1166,388
+                       
c-12,0-23-8-29-21c0-1-1-2-1-4V739c2-5,3-12,7-16c8-11,22-13,31-16c17-6,1126-378,1142-378C1190,329,1212,336,1212,354z"/>
+               <path class="st1" d="M2120,2017l-907-282V380l907-308V2017z 
M2181,32v2023c-1,23-17,33-32,33c-13,0-107-32-123-37
+                       
c-126-39-253-78-378-117c-28-9-57-18-84-27c-24-7-50-15-74-23c-107-33-216-66-323-102c-4-1-14-15-14-18V351c2-5,4-11,9-15
+                       
c8-9,351-123,486-168c36-13,487-168,501-168C2167,0,2181,13,2181,32z"/>
+               <polygon points="2411.2,2440.7 1199.5,2054.5 1204.6,373.2 
2411.2,757.2          "/>
+               <g>
+                       <path class="st2" 
d="M1800.3,1124.6L1681.4,1412l218.6,66.3L1800.3,1124.6z 
M1729,853.2l156.1,47.3l284.4,1025l-160.3-48.7
+                               
l-57.6-210.4L1620.2,1566l-71.3,171.4l-160.4-48.7L1729,853.2z"/>
+               </g>
+       </g>
+</g>
+</svg>
diff --git a/packages/demobank-ui/src/components/Cashouts/views.tsx 
b/packages/demobank-ui/src/components/Cashouts/views.tsx
index 0602f507e..32fe0aa9e 100644
--- a/packages/demobank-ui/src/components/Cashouts/views.tsx
+++ b/packages/demobank-ui/src/components/Cashouts/views.tsx
@@ -14,7 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { h, VNode } from "preact";
+import { Fragment, h, VNode } from "preact";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { State } from "./index.js";
 import { format } from "date-fns";
@@ -33,55 +33,118 @@ export function LoadingUriView({ error }: 
State.LoadingUriError): VNode {
 
 export function ReadyView({ cashouts, onSelected }: State.Ready): VNode {
   const { i18n } = useTranslationContext();
-  if (!cashouts.length) {
-    return (
-      <div>
-        <i18n.Translate>No cashout at the moment</i18n.Translate>
-      </div>
-    );
-  }
+  if (!cashouts.length) return <div />
+  const txByDate = cashouts.reduce((prev, cur) => {
+    const d = cur.creation_time.t_s === "never"
+      ? ""
+      : format(cur.creation_time.t_s * 1000, "dd/MM/yyyy")
+    if (!prev[d]) {
+      prev[d] = []
+    }
+    prev[d].push(cur)
+    return prev
+  }, {} as Record<string, typeof cashouts>)
   return (
-    <div class="results">
-      <table class="pure-table pure-table-striped">
-        <thead>
-          <tr>
-            <th>{i18n.str`Created`}</th>
-            <th>{i18n.str`Confirmed`}</th>
-            <th>{i18n.str`Total debit`}</th>
-            <th>{i18n.str`Total credit`}</th>
-            <th>{i18n.str`Status`}</th>
-            <th>{i18n.str`Subject`}</th>
-          </tr>
-        </thead>
-        <tbody>
-          {cashouts.map((item, idx) => {
-            return (
-              <tr key={idx}>
-                <td>{item.creation_time.t_s === "never" ? i18n.str`never` : 
format(item.creation_time.t_s, "dd/MM/yyyy HH:mm:ss")}</td>
-                <td>
-                  {item.confirmation_time
+    <div class="px-4 mt-4">
+      <div class="sm:flex sm:items-center">
+        <div class="sm:flex-auto">
+          <h1 class="text-base font-semibold leading-6 
text-gray-900"><i18n.Translate>Latest cashouts</i18n.Translate></h1>
+        </div>
+      </div>
+      <div class="-mx-4 mt-5 ring-1 ring-gray-300 sm:mx-0 rounded-lg min-w-fit 
bg-white">
+        <table class="min-w-full divide-y divide-gray-300">
+          <thead>
+            <tr>
+              <th scope="col" class="                     pl-2 py-3.5 
text-left text-sm font-semibold text-gray-900">{i18n.str`Created`}</th>
+              <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 
text-left text-sm font-semibold text-gray-900">{i18n.str`Confirmed`}</th>
+              <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 
text-left text-sm font-semibold text-gray-900">{i18n.str`Total debit`}</th>
+              <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 
text-left text-sm font-semibold text-gray-900">{i18n.str`Total credit`}</th>
+              <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 
text-left text-sm font-semibold text-gray-900">{i18n.str`Status`}</th>
+              <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 
text-left text-sm font-semibold text-gray-900">{i18n.str`Subject`}</th>
+            </tr>
+          </thead>
+          <tbody>
+            {Object.entries(txByDate).map(([date, txs], idx) => {
+              return <Fragment key={idx}>
+                <tr class="border-t border-gray-200">
+                  <th colSpan={4} scope="colgroup" class="bg-gray-50 py-2 pl-4 
pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-3">
+                    {date}
+                  </th>
+                </tr>
+                {txs.map(item => {
+                  const creationTime = item.creation_time.t_s === "never" ? "" 
: format(item.creation_time.t_s * 1000, "HH:mm:ss")
+                  const confirmationTime = item.confirmation_time
                     ? item.confirmation_time.t_s === "never" ? i18n.str`never` 
: format(item.confirmation_time.t_s, "dd/MM/yyyy HH:mm:ss")
-                    : "-"}
-                </td>
-                <td><RenderAmount 
value={Amounts.parseOrThrow(item.amount_debit)} /></td>
-                <td><RenderAmount 
value={Amounts.parseOrThrow(item.amount_credit)} /></td>
-                <td>{item.status}</td>
-                <td>
-                  <a
-                    href="#"
-                    onClick={(e) => {
-                      e.preventDefault();
-                      onSelected(item.id);
-                    }}
-                  >
-                    {item.subject}
-                  </a>
-                </td>
-              </tr>
-            );
-          })}
-        </tbody>
-      </table>
+                    : "-"
+                  return (<tr key={idx} class="border-b border-gray-200 
last:border-none">
+
+                    <td class="relative py-2 pl-2 pr-2 text-sm ">
+                      <div class="font-medium 
text-gray-900">{creationTime}</div>
+                      {/* <dl class="font-normal sm:hidden">
+                        <dt class="sr-only 
sm:hidden"><i18n.Translate>Amount</i18n.Translate></dt>
+                        <dd class="mt-1 truncate text-gray-700">
+                          {item.negative ? i18n.str`sent` : 
i18n.str`received`} {item.amount ? (
+                            <span data-negative={item.negative ? "true" : 
"false"} class="data-[negative=false]:text-green-600 
data-[negative=true]:text-red-600">
+                              <RenderAmount value={item.amount} />
+                            </span>
+                          ) : (
+                            <span style={{ color: "grey" 
}}>&lt;{i18n.str`invalid value`}&gt;</span>
+                          )}</dd>
+
+                        <dt class="sr-only 
sm:hidden"><i18n.Translate>Counterpart</i18n.Translate></dt>
+                        <dd class="mt-1 truncate text-gray-500 sm:hidden">
+                          {item.negative ? i18n.str`to` : i18n.str`from`} 
{item.counterpart}
+                        </dd>
+                        <dd class="mt-1 text-gray-500 sm:hidden" >
+                          <pre class="break-words w-56 whitespace-break-spaces 
p-2 rounded-md mx-auto my-2 bg-gray-100">
+                            {item.subject}
+                          </pre>
+                        </dd>
+                      </dl> */}
+                    </td>
+                    <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500">{confirmationTime}</td>
+                    <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-red-600"><RenderAmount value={Amounts.parseOrThrow(item.amount_debit)} 
/></td>
+                    <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-green-600"><RenderAmount value={Amounts.parseOrThrow(item.amount_credit)} 
/></td>
+
+                    <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500">{item.status}</td>
+                    <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500 break-all min-w-md">
+                      <a href="#" onClick={(e) => {
+                        e.preventDefault();
+                        onSelected(item.id);
+                      }}>
+                        {item.subject}
+                      </a>
+                    </td>
+                  </tr>)
+                })}
+              </Fragment>
+
+            })}
+          </tbody>
+
+        </table>
+
+        {/* <nav class="flex items-center justify-between border-t 
border-gray-200 bg-white px-4 py-3 sm:px-6 rounded-lg" aria-label="Pagination">
+          <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"
+              disabled={!onPrev}
+              onClick={onPrev}
+            >
+              <i18n.Translate>First page</i18n.Translate>
+            </button>
+            <button
+              class="relative disabled:bg-gray-100 disabled:text-gray-500 ml-3 
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"
+              disabled={!onNext}
+              onClick={onNext}
+            >
+              <i18n.Translate>Next</i18n.Translate>
+            </button>
+          </div>
+        </nav> */}
+      </div>
     </div>
   );
+  // }
+
 }
diff --git a/packages/demobank-ui/src/components/CopyButton.tsx 
b/packages/demobank-ui/src/components/CopyButton.tsx
index b36de770e..ca1ceaa8a 100644
--- a/packages/demobank-ui/src/components/CopyButton.tsx
+++ b/packages/demobank-ui/src/components/CopyButton.tsx
@@ -5,31 +5,21 @@ import { useEffect, useState } from "preact/hooks";
 
 export function CopyIcon(): VNode {
   return (
-    <svg height="16" viewBox="0 0 16 16" width="16" stroke="currentColor" 
strokeWidth="1.5">
-      <path
-        fill-rule="evenodd"
-        d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 
00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 
0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z"
-      />
-      <path
-        fill-rule="evenodd"
-        d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 
1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25v-7.5zm1.75-.25a.25.25 0 
00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 
00-.25-.25h-7.5z"
-      />
+    <svg xmlns="http://www.w3.org/2000/svg"; fill="none" viewBox="0 0 24 24" 
stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
+      <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 
17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 
01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 
011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 
1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 
00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 
1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 
00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3. [...]
     </svg>
   )
 };
 
 export function CopiedIcon(): VNode {
   return (
-    <svg height="16" viewBox="0 0 16 16" width="16" stroke="currentColor" 
strokeWidth="1.5">
-      <path
-        fill-rule="evenodd"
-        d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 
9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"
-      />
+    <svg xmlns="http://www.w3.org/2000/svg"; fill="none" viewBox="0 0 24 24" 
stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
+      <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 
9-13.5" />
     </svg>
   )
 };
 
-export function CopyButton({ getContent }: { getContent: () => string }): 
VNode {
+export function CopyButton({ class: clazz, getContent }: { class: string, 
getContent: () => string }): VNode {
   const [copied, setCopied] = useState(false);
   function copyText(): void {
     navigator.clipboard.writeText(getContent() || "");
@@ -45,16 +35,14 @@ export function CopyButton({ getContent }: { getContent: () 
=> string }): VNode
 
   if (!copied) {
     return (
-      <button class="text-white" onClick={copyText} style={{ width: 16, 
height: 16, fontSize: "initial" }}>
+      <button class={clazz} onClick={copyText} >
         <CopyIcon />
       </button>
     );
   }
   return (
-    <div class="text-white" content="Copied" style={{ display: "inline-block" 
}}>
-      <button disabled style={{ width: 16, height: 16, fontSize: "initial" }}>
-        <CopiedIcon />
-      </button>
-    </div>
+    <button class={clazz} disabled>
+      <CopiedIcon />
+    </button>
   );
-}
\ No newline at end of file
+}
diff --git a/packages/demobank-ui/src/components/LangSelector.tsx 
b/packages/demobank-ui/src/components/LangSelector.tsx
index c1d0f64ef..7cf0300df 100644
--- a/packages/demobank-ui/src/components/LangSelector.tsx
+++ b/packages/demobank-ui/src/components/LangSelector.tsx
@@ -23,6 +23,7 @@ import { Fragment, h, VNode } from "preact";
 import { useEffect, useState } from "preact/hooks";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { strings as messages } from "../i18n/strings.js";
+import langIcon from "../assets/lang.svg";
 
 type LangsNames = {
   [P in keyof typeof messages]: string;
@@ -69,7 +70,7 @@ export function LangSelector(): VNode {
             setHidden((h) => !h);
           }}>
           <span class="flex items-center">
-            <img src="https://taler.net/images/languageicon.svg"; alt="" 
class="h-5 w-5 flex-shrink-0 rounded-full" />
+            <img src={langIcon} alt="" class="h-5 w-5 flex-shrink-0 
rounded-full" />
             <span class="ml-3 block truncate">{getLangName(lang)}</span>
           </span>
           <span class="pointer-events-none absolute inset-y-0 right-0 flex 
items-center pr-2">
diff --git a/packages/demobank-ui/src/components/Routing.tsx 
b/packages/demobank-ui/src/components/Routing.tsx
index 04cf96190..1d587fe32 100644
--- a/packages/demobank-ui/src/components/Routing.tsx
+++ b/packages/demobank-ui/src/components/Routing.tsx
@@ -19,19 +19,26 @@ import { createHashHistory } from "history";
 import { Fragment, VNode, h } from "preact";
 import { Route, Router, route } from "preact-router";
 import { useEffect } from "preact/hooks";
-import { useBackendContext } from "../context/backend.js";
+import { useBackendState } from "../hooks/backend.js";
 import { BankFrame } from "../pages/BankFrame.js";
 import { HomePage, WithdrawalOperationPage } from "../pages/HomePage.js";
 import { LoginForm } from "../pages/LoginForm.js";
 import { PublicHistoriesPage } from "../pages/PublicHistoriesPage.js";
 import { RegistrationPage } from "../pages/RegistrationPage.js";
-import { AdminHome } from "../pages/admin/Home.js";
-import { BusinessAccount } from "../pages/business/Home.js";
+import { AdminHome } from "../pages/admin/AdminHome.js";
+import { CreateCashout } from "../pages/business/CreateCashout.js";
 import { bankUiSettings } from "../settings.js";
+import { ShowAccountDetails } from "../pages/ShowAccountDetails.js";
+import { UpdateAccountPassword } from "../pages/UpdateAccountPassword.js";
+import { RemoveAccount } from "../pages/admin/RemoveAccount.js";
+import { CreateNewAccount } from "../pages/admin/CreateNewAccount.js";
+import { CashoutListForAccount } from 
"../pages/admin/CashoutListForAccount.js";
+import { ShowCashoutDetails } from "../pages/business/ShowCashoutDetails.js";
+import { WireTransfer } from "../pages/admin/Account.js";
 
 export function Routing(): VNode {
   const history = createHashHistory();
-  const backend = useBackendContext();
+  const backend = useBackendState();
   const { i18n } = useTranslationContext();
 
   if (backend.state.status === "loggedOut") {
@@ -90,7 +97,7 @@ export function Routing(): VNode {
   const { isUserAdministrator, username } = backend.state
 
   return (
-    <BankFrame account={backend.state.username}>
+    <BankFrame account={username}>
       <Router history={history}>
         <Route
           path="/operation/:wopid"
@@ -107,6 +114,167 @@ export function Routing(): VNode {
           path="/public-accounts"
           component={() => <PublicHistoriesPage />}
         />
+
+        <Route
+          path="/new-account"
+          component={() => <CreateNewAccount
+            onCancel={() => {
+              route("/account")
+            }}
+            onCreateSuccess={() => {
+              route("/account")
+            }}
+          />}
+        />
+
+        <Route
+          path="/profile/:account/details"
+          component={({ account }: { account: string }) => (
+            <ShowAccountDetails
+              account={account}
+              onUpdateSuccess={() => {
+                route("/account")
+              }}
+              onClear={() => {
+                route("/account")
+              }}
+            />
+          )}
+        />
+
+        <Route
+          path="/profile/:account/change-password"
+          component={({ account }: { account: string }) => (
+            <UpdateAccountPassword
+              focus
+              account={account}
+              onUpdateSuccess={() => {
+                route("/account")
+              }}
+              onCancel={() => {
+                route("/account")
+              }}
+            />
+          )}
+        />
+        <Route
+          path="/profile/:account/delete"
+          component={({ account }: { account: string }) => (
+            <RemoveAccount
+              account={account}
+              onUpdateSuccess={() => {
+                route("/account")
+              }}
+              onCancel={() => {
+                route("/account")
+              }}
+            />
+          )}
+        />
+
+        <Route
+          path="/profile/:account/cashouts"
+          component={({ account }: { account: string }) => (
+            <CashoutListForAccount
+              account={account}
+              onSelected={(cid) => {
+                route(`/cashout/${cid}`)
+              }}
+              onClose={() => {
+                route("/account")
+              }}
+            />
+          )}
+        />
+
+        <Route
+          path="/my-profile"
+          component={() => (
+            <ShowAccountDetails
+              account={username}
+              onUpdateSuccess={() => {
+                route("/account")
+              }}
+              onClear={() => {
+                route("/account")
+              }}
+            />
+          )}
+        />
+        <Route
+          path="/my-password"
+          component={() => (
+            <UpdateAccountPassword
+              focus
+              account={username}
+              onUpdateSuccess={() => {
+                route("/account")
+              }}
+              onCancel={() => {
+                route("/account")
+              }}
+            />
+          )}
+        />
+
+        <Route
+          path="/my-cashouts"
+          component={() => (
+            <CashoutListForAccount
+              account={username}
+              onSelected={(cid) => {
+                route(`/cashout/${cid}`)
+              }}
+              onClose={() => {
+                route("/account");
+              }}
+            />
+          )}
+        />
+
+        <Route
+          path="/new-cashout"
+          component={() => (
+            <CreateCashout
+              account={username}
+              onComplete={(cid) => {
+                route(`/cashout/${cid}`);
+              }}
+              onCancel={() => {
+                route("/account");
+              }}
+            />
+          )}
+        />
+
+        <Route
+          path="/cashout/:cid"
+          component={({ cid }: { cid: string }) => (
+            <ShowCashoutDetails
+              id={cid}
+              onCancel={() => {
+                route("/account");
+              }}
+            />
+          )}
+        />
+
+
+        <Route
+          path="/wire-transfer/:dest"
+          component={({ dest }: { dest: string }) => (
+            <WireTransfer
+              toAccount={dest}
+              onCancel={() => {
+                route("/account")
+              }}
+              onSuccess={() => {
+                route("/account")
+              }}
+            />
+          )}
+        />
+
         <Route
           path="/account"
           component={() => {
@@ -115,6 +283,22 @@ export function Routing(): VNode {
                 onRegister={() => {
                   route("/register");
                 }}
+                onCreateAccount={() => {
+                  route("/new-account")
+                }}
+                onShowAccountDetails={(aid) => {
+                  route(`/profile/${aid}/details`)
+                }}
+                onRemoveAccount={(aid) => {
+                  route(`/profile/${aid}/delete`)
+                }}
+                onShowCashoutForAccount={(aid) => {
+                  route(`/profile/${aid}/cashouts`)
+                }}
+                onUpdateAccountPassword={(aid) => {
+                  route(`/profile/${aid}/change-password`)
+
+                }}
               />;
             } else {
               return <HomePage
@@ -132,20 +316,6 @@ export function Routing(): VNode {
             }
           }}
         />
-        <Route
-          path="/business"
-          component={() => (
-            <BusinessAccount
-              account={username}
-              onClose={() => {
-                route("/account");
-              }}
-              onRegister={() => {
-                route("/register");
-              }}
-            />
-          )}
-        />
         <Route default component={Redirect} to="/account" />
       </Router>
     </BankFrame>
diff --git a/packages/demobank-ui/src/components/Transactions/views.tsx 
b/packages/demobank-ui/src/components/Transactions/views.tsx
index 5cdb47a0c..47daf8963 100644
--- a/packages/demobank-ui/src/components/Transactions/views.tsx
+++ b/packages/demobank-ui/src/components/Transactions/views.tsx
@@ -86,11 +86,13 @@ export function ReadyView({ transactions, onNext, onPrev }: 
State.Ready): VNode
 
                         <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}
+                          {item.negative ? i18n.str`to` : i18n.str`from`} <a 
href={`#/wire-transfer/${item.counterpart}`} class="text-indigo-600 
hover:text-indigo-900">
+                            {item.counterpart}
+                          </a>
                         </dd>
                         <dd class="mt-1 text-gray-500 sm:hidden" >
                           <pre class="break-words w-56 whitespace-break-spaces 
p-2 rounded-md mx-auto my-2 bg-gray-100">
-                          {item.subject}
+                            {item.subject}
                           </pre>
                         </dd>
                       </dl>
@@ -102,7 +104,11 @@ export function ReadyView({ transactions, onNext, onPrev 
}: State.Ready): VNode
                         <span style={{ color: "grey" }}>&lt;{i18n.str`invalid 
value`}&gt;</span>
                       )}
                     </td>
-                    <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500">{item.counterpart}</td>
+                    <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500">
+                      <a href={`#/wire-transfer/${item.counterpart}`} 
class="text-indigo-600 hover:text-indigo-900">
+                        {item.counterpart}
+                      </a>
+                    </td>
                     <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500 break-all min-w-md">{item.subject}</td>
                   </tr>)
                 })}
diff --git a/packages/demobank-ui/src/components/app.tsx 
b/packages/demobank-ui/src/components/app.tsx
index beb24da57..55e1178fe 100644
--- a/packages/demobank-ui/src/components/app.tsx
+++ b/packages/demobank-ui/src/components/app.tsx
@@ -27,6 +27,7 @@ import { BankCoreApiProvider } from "../context/config.js";
 import { strings } from "../i18n/strings.js";
 import { bankUiSettings } from "../settings.js";
 import { Routing } from "./Routing.js";
+import { BankFrame } from "../pages/BankFrame.js";
 const WITH_LOCAL_STORAGE_CACHE = false;
 
 const App: FunctionalComponent = () => {
@@ -34,7 +35,7 @@ const App: FunctionalComponent = () => {
   return (
     <TranslationProvider source={strings}>
       <BackendStateProvider>
-        <BankCoreApiProvider baseUrl={baseUrl}>
+        <BankCoreApiProvider baseUrl={baseUrl} frameOnError={BankFrame}>
           <SWRConfig
             value={{
               provider: WITH_LOCAL_STORAGE_CACHE
diff --git a/packages/demobank-ui/src/context/config.ts 
b/packages/demobank-ui/src/context/config.ts
index 013d8922e..a31d914b8 100644
--- a/packages/demobank-ui/src/context/config.ts
+++ b/packages/demobank-ui/src/context/config.ts
@@ -16,7 +16,7 @@
 
 import { TalerCorebankApi, TalerCoreBankHttpClient, TalerError } from 
"@gnu-taler/taler-util";
 import { BrowserHttpLib, useTranslationContext } from 
"@gnu-taler/web-util/browser";
-import { ComponentChildren, createContext, h, VNode } from "preact";
+import { ComponentChildren, createContext, FunctionComponent, h, VNode } from 
"preact";
 import { useContext, useEffect, useState } from "preact/hooks";
 import { ErrorLoading } from "../components/ErrorLoading.js";
 
@@ -43,9 +43,11 @@ export type ConfigResult = undefined
 export const BankCoreApiProvider = ({
   baseUrl,
   children,
+  frameOnError,
 }: {
   baseUrl: string,
   children: ComponentChildren;
+  frameOnError: FunctionComponent<{ children: ComponentChildren }>,
 }): VNode => {
   const [checked, setChecked] = useState<ConfigResult>()
   const { i18n } = useTranslationContext();
@@ -68,13 +70,13 @@ export const BankCoreApiProvider = ({
   }, []);
 
   if (checked === undefined) {
-    return h("div", {}, "loading...")
+    return h(frameOnError, { children: h("div", {}, "loading...") })
   }
   if (checked.type === "error") {
-    return h(ErrorLoading, { error: checked.error, showDetail: true })
+    return h(frameOnError, { children: h(ErrorLoading, { error: checked.error, 
showDetail: true }) })
   }
   if (checked.type === "incompatible") {
-    return h("div", {}, i18n.str`the bank backend is not supported. supported 
version "${checked.supported}", server version "${checked.result.version}"`)
+    return h(frameOnError, { children: h("div", {}, i18n.str`the bank backend 
is not supported. supported version "${checked.supported}", server version 
"${checked.result.version}"`) })
   }
   const value: Type = {
     url, config: checked.config, api
diff --git a/packages/demobank-ui/src/hooks/access.ts 
b/packages/demobank-ui/src/hooks/access.ts
index 7023b8803..da9812ffa 100644
--- a/packages/demobank-ui/src/hooks/access.ts
+++ b/packages/demobank-ui/src/hooks/access.ts
@@ -61,7 +61,7 @@ export function useWithdrawalDetails(wid: string) {
   // const { state: credentials } = useBackendState();
   const { api } = useBankCoreApiContext();
 
-  async function fetcher(wid: string) {
+  async function fetcher([wid]: [string]) {
     return await api.getWithdrawalById(wid)
   }
 
@@ -114,7 +114,7 @@ export function usePublicAccounts(initial?: number) {
   const [offset, setOffset] = useState<number | undefined>(initial);
   const { api } = useBankCoreApiContext();
 
-  async function fetcher(txid: number | undefined) {
+  async function fetcher([txid]: [number | undefined]) {
     return await api.getPublicAccounts({
       limit: MAX_RESULT_SIZE,
       offset: txid ? String(txid) : undefined,
@@ -124,16 +124,16 @@ export function usePublicAccounts(initial?: number) {
 
   const { data, error } = 
useSWR<TalerCoreBankResultByMethod<"getPublicAccounts">, TalerHttpError>(
     [offset, "getPublicAccounts"], fetcher, {
-      refreshInterval: 0,
-      refreshWhenHidden: false,
-      revalidateOnFocus: false,
-      revalidateOnReconnect: false,
-      refreshWhenOffline: false,
-      errorRetryCount: 0,
-      errorRetryInterval: 1,
-      shouldRetryOnError: false,
-      keepPreviousData: true,  
-    });
+    refreshInterval: 0,
+    refreshWhenHidden: false,
+    revalidateOnFocus: false,
+    revalidateOnReconnect: false,
+    refreshWhenOffline: false,
+    errorRetryCount: 0,
+    errorRetryInterval: 1,
+    shouldRetryOnError: false,
+    keepPreviousData: true,
+  });
 
   const isLastPage =
     data && data.body.public_accounts.length < PAGE_SIZE;
diff --git a/packages/demobank-ui/src/hooks/circuit.ts 
b/packages/demobank-ui/src/hooks/circuit.ts
index 0f7af5fe5..06e068d6d 100644
--- a/packages/demobank-ui/src/hooks/circuit.ts
+++ b/packages/demobank-ui/src/hooks/circuit.ts
@@ -15,16 +15,15 @@
  */
 
 import { useState } from "preact/hooks";
-import { useBackendContext } from "../context/backend.js";
 import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js";
 import { useBackendState } from "./backend.js";
 
-// FIX default import https://github.com/microsoft/TypeScript/issues/49189
 import { AccessToken, AmountJson, Amounts, OperationOk, 
TalerCoreBankResultByMethod, TalerCorebankApi, TalerError, TalerHttpError } 
from "@gnu-taler/taler-util";
 import _useSWR, { SWRHook } from "swr";
 import { useBankCoreApiContext } from "../context/config.js";
 import { assertUnreachable } from "../pages/HomePage.js";
 
+// FIX default import https://github.com/microsoft/TypeScript/issues/49189
 const useSWR = _useSWR as unknown as SWRHook;
 
 export type TransferCalculation = {
@@ -44,12 +43,8 @@ type CashoutEstimators = {
 };
 
 export function useEstimator(): CashoutEstimators {
-  const { state } = useBackendContext();
+  const { state } = useBackendState();
   const { api } = useBankCoreApiContext();
-  const creds =
-    state.status !== "loggedIn"
-      ? undefined
-      : state.token;
   return {
     estimateByCredit: async (amount, fee, rate) => {
       const resp = await api.getCashoutRate({
@@ -101,13 +96,14 @@ export function useEstimator(): CashoutEstimators {
 }
 
 export function useRatiosAndFeeConfig() {
-  const { api } = useBankCoreApiContext();
+  const { api, config } = useBankCoreApiContext();
+
   function fetcher() {
     return api.getConversionRates()
   }
 
   const { data, error } = 
useSWR<TalerCoreBankResultByMethod<"getConversionRates">, TalerHttpError>(
-    [, "getConversionRates"], fetcher, {
+    !config.have_cashout || !config.fiat_currency ? false : [, 
"getConversionRates"], fetcher, {
     refreshInterval: 60 * 1000,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
diff --git a/packages/demobank-ui/src/pages.ts 
b/packages/demobank-ui/src/pages.ts
new file mode 100644
index 000000000..c78240a02
--- /dev/null
+++ b/packages/demobank-ui/src/pages.ts
@@ -0,0 +1,44 @@
+import { WithdrawalOperationPage } from "./pages/HomePage.js";
+import { PageEntry, pageDefinition } from "./route.js";
+
+// const operationById: PageEntry<{ operationId: string }> = {
+//   url: pageDefinition("#/operation/:operationId"),
+//   view: WithdrawalOperationPage,
+// };
+
+
+// const home: PageEntry = {
+//   url: "#/",
+//   view: Home,
+// };
+// const cases: PageEntry = {
+//   url: "#/cases",
+//   view: Cases,
+// };
+
+// const newFormEntry: PageEntry<{ account?: string; type?: string }> = {
+//   url: pageDefinition("#/account/:account/new/:type?"),
+//   view: NewFormEntry,
+// };
+
+// const settings: PageEntry = {
+//   url: "#/settings",
+//   view: Settings,
+// };
+// const officer: PageEntry = {
+//   url: "#/officer",
+//   view: Officer,
+// };
+// const welcome: PageEntry<{ asd?: string; name?: string }> = {
+//   url: pageDefinition("#/welcome/:name?"),
+//   view: Welcome,
+// };
+// const form: PageEntry<{ number?: string }> = {
+//   url: pageDefinition("#/form/:number?"),
+//   view: AntiMoneyLaunderingForm,
+// };
+
+export const Pages = {
+  // operationById,
+
+};
diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx 
b/packages/demobank-ui/src/pages/AccountPage/views.tsx
index 0604001e3..00643ec3e 100644
--- a/packages/demobank-ui/src/pages/AccountPage/views.tsx
+++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx
@@ -54,40 +54,11 @@ function ShowDemoInfo(): VNode {
 }
 
 export function ReadyView({ account, limit, goToBusinessAccount, 
goToConfirmOperation }: State.Ready): VNode<{}> {
-  const { i18n } = useTranslationContext();
 
   return <Fragment>
-    <MaybeBusinessButton account={account} onClick={goToBusinessAccount} />
-
     <ShowDemoInfo />
-
     <PaymentOptions limit={limit} goToConfirmOperation={goToConfirmOperation} 
/>
     <Transactions account={account} />
   </Fragment>;
 }
 
-function MaybeBusinessButton({
-  account,
-  onClick,
-}: {
-  account: string;
-  onClick: () => void;
-}): VNode {
-  const { i18n } = useTranslationContext();
-  return <Fragment />
-  // const result = useBusinessAccountDetails(account);
-  // if (!result.ok) return <Fragment />;
-  // return (
-  //   <div class="w-full flex justify-end">
-  //     <button
-  //       class="disabled:opacity-50 disabled:cursor-default cursor-pointer 
rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
-  //       onClick={(e) => {
-  //         e.preventDefault()
-  //         onClick()
-  //       }}
-  //     >
-  //       <i18n.Translate>Business Profile</i18n.Translate>
-  //     </button>
-  //   </div>
-  // );
-}
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx 
b/packages/demobank-ui/src/pages/BankFrame.tsx
index 96ce9c317..c0babd0c9 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -23,8 +23,8 @@ import { Attention } from "../components/Attention.js";
 import { CopyButton } from "../components/CopyButton.js";
 import { LangSelector } from "../components/LangSelector.js";
 import { Loading } from "../components/Loading.js";
-import { useBackendContext } from "../context/backend.js";
 import { useAccountDetails } from "../hooks/access.js";
+import { useBackendState } from "../hooks/backend.js";
 import { getAllBooleanSettings, getLabelForSetting, useSettings } from 
"../hooks/settings.js";
 import { bankUiSettings } from "../settings.js";
 import { RenderAmount } from "./PaytoWireTransferForm.js";
@@ -49,7 +49,7 @@ export function BankFrame({
   children: ComponentChildren;
 }): VNode {
   const { i18n } = useTranslationContext();
-  const backend = useBackendContext();
+  const backend = useBackendState();
   const [settings, updateSettings] = useSettings();
   const [open, setOpen] = useState(false)
 
@@ -80,9 +80,9 @@ export function BankFrame({
   return (<div class="min-h-full flex flex-col m-0" style="min-height: 100vh;">
     <div class="bg-indigo-600 pb-32">
       <nav class="">
-        <div class="mx-auto max-w-7xl px-2 sm:px-4 lg:px-8">
+        <div class="mx-auto max-w-7xl px-2 ">
           <div class="relative flex h-16 items-center justify-between ">
-            <div class="flex items-center px-2 lg:px-0">
+            <div class="flex items-center px-2">
               <div class="flex-shrink-0 bg-white rounded-lg">
                 <a href={bankUiSettings.iconLinkURL ?? "#"}>
                   <img
@@ -94,7 +94,7 @@ export function BankFrame({
                 </a>
               </div>
               {bankUiSettings.demoSites &&
-                <div class="hidden sm:block lg:ml-10 ">
+                <div class="hidden sm:block ml-6 ">
                   <div class="flex space-x-4">
                     {/* <!-- Current: "bg-indigo-700 text-white", Default: 
"text-white hover:bg-indigo-500 hover:bg-opacity-75" --> */}
                     {bankUiSettings.demoSites.map(([name, url]) => {
@@ -161,41 +161,26 @@ export function BankFrame({
                       <div class="relative mt-6 flex-1 px-4 sm:px-6">
                         <nav class="flex flex-1 flex-col" aria-label="Sidebar">
                           <ul role="list" class="flex flex-1 flex-col gap-y-7">
-                            <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>
-                                <i18n.Translate>Log out</i18n.Translate>
-                              </a>
-                            </li>
+                            {backend.state.status === "loggedIn" ?
+                              <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>
+                                  <i18n.Translate>Log out</i18n.Translate>
+                                </a>
+                              </li>
+                              : undefined}
                             <li>
                               <LangSelector />
                             </li>
-                            {bankUiSettings.demoSites &&
-                              <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="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>
                               <div class="text-xs font-semibold leading-6 
text-gray-400">
                                 <i18n.Translate>Preferences</i18n.Translate>
@@ -220,6 +205,23 @@ export function BankFrame({
                                 })}
                               </ul>
                             </li>
+                            {bankUiSettings.demoSites &&
+                              <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="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>
+                            }
                           </ul>
                         </nav>
                       </div>
@@ -342,28 +344,30 @@ function Footer() {
 
 function WelcomeAccount({ account: accountName }: { account: string }): VNode {
   const { i18n } = useTranslationContext();
-
-  const result = useAccountDetails(accountName);
-  if (!result) {
-    return <Loading />
-  }
-  if (result instanceof TalerError) {
-    return <div />
-  }
-
-  const payto = result.type === "fail" ? undefined : 
parsePaytoUri(result.body.payto_uri)
-  const info = !payto || !payto.isKnown ? undefined
-    : payto.targetType === "iban" ? { account: payto.iban, uri: 
stringifyPaytoUri(payto) }
-      : payto.targetType === "x-taler-bank" ? { account: payto.account, uri: 
stringifyPaytoUri(payto) }
-        : undefined;
-
-  return <i18n.Translate>
-    Welcome,  <span class="whitespace-nowrap">{accountName}</span> {info !== 
undefined ?
-      <small class="whitespace-nowrap">
-        (<a href={info.uri}>{info.account}</a> <CopyButton getContent={() => 
info.uri} />)
-      </small>
-      : <Fragment />}!
-  </i18n.Translate>
+  return <a href="#/my-profile" class="underline underline-offset-2">
+    <i18n.Translate>Welcome,  <span 
class="whitespace-nowrap">{accountName}</span></i18n.Translate>
+  </a>
+  // const result = useAccountDetails(accountName);
+  // if (!result) {
+  //   return <Loading />
+  // }
+  // if (result instanceof TalerError) {
+  //   return <div />
+  // }
+
+  // const payto = result.type === "fail" ? undefined : 
parsePaytoUri(result.body.payto_uri)
+  // const info = !payto || !payto.isKnown ? undefined
+  //   : payto.targetType === "iban" ? { account: payto.iban, uri: 
stringifyPaytoUri(payto) }
+  //     : payto.targetType === "x-taler-bank" ? { account: payto.account, 
uri: stringifyPaytoUri(payto) }
+  //       : undefined;
+
+  // return <i18n.Translate>
+  //   Welcome,  <span class="whitespace-nowrap">{accountName}</span> {info 
!== undefined ?
+  //     <small class="whitespace-nowrap">
+  //       (<a href={info.uri}>{info.account}</a> <CopyButton getContent={() 
=> info.uri} />)
+  //     </small>
+  //     : <Fragment />}!
+  // </i18n.Translate>
 
 }
 
diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx 
b/packages/demobank-ui/src/pages/LoginForm.tsx
index 981b0f880..6d1d35288 100644
--- a/packages/demobank-ui/src/pages/LoginForm.tsx
+++ b/packages/demobank-ui/src/pages/LoginForm.tsx
@@ -14,24 +14,24 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { HttpStatusCode, TalerAuthentication, TranslatedString } from 
"@gnu-taler/taler-util";
-import { ErrorType, notify, notifyError, useTranslationContext } from 
"@gnu-taler/web-util/browser";
+import { TranslatedString } from "@gnu-taler/taler-util";
+import { notify, useTranslationContext } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useEffect, useRef, useState } from "preact/hooks";
 import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
-import { useBackendContext } from "../context/backend.js";
+import { useBankCoreApiContext } from "../context/config.js";
+import { useBackendState } from "../hooks/backend.js";
 import { bankUiSettings } from "../settings.js";
 import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
-import { doAutoFocus } from "./PaytoWireTransferForm.js";
-import { useBankCoreApiContext } from "../context/config.js";
 import { assertUnreachable } from "./HomePage.js";
+import { doAutoFocus } from "./PaytoWireTransferForm.js";
 
 
 /**
  * Collect and submit login data.
  */
 export function LoginForm({ reason, onRegister }: { reason?: "not-found" | 
"forbidden", onRegister?: () => void }): VNode {
-  const backend = useBackendContext();
+  const backend = useBackendState();
   const currentUser = backend.state.status !== "loggedOut" ? 
backend.state.username : undefined
   const [username, setUsername] = useState<string | undefined>(currentUser);
   const [password, setPassword] = useState<string | undefined>();
diff --git a/packages/demobank-ui/src/pages/OperationState/state.ts 
b/packages/demobank-ui/src/pages/OperationState/state.ts
index c9c1fa238..9e34a846b 100644
--- a/packages/demobank-ui/src/pages/OperationState/state.ts
+++ b/packages/demobank-ui/src/pages/OperationState/state.ts
@@ -86,10 +86,10 @@ export function useComponentState({ currency, onClose }: 
Props): utils.Recursive
   const wid = withdrawalOperationId
 
   async function doAbort() {
-    setBusy({})
     await withRuntimeErrorHandling(i18n, async () => {
       const resp = await api.abortWithdrawalById(wid);
       if (resp.type === "ok") {
+        updateSettings("currentWithdrawalOperationId", undefined)
         onClose();
       } else {
         switch (resp.case) {
@@ -103,7 +103,6 @@ export function useComponentState({ currency, onClose }: 
Props): utils.Recursive
         }
       }
     })
-    setBusy(undefined)
   }
 
   async function doConfirm() {
@@ -220,11 +219,7 @@ export function useComponentState({ currency, onClose }: 
Props): utils.Recursive
         status: "ready",
         error: undefined,
         uri: parsedUri,
-        onClose: async () => {
-          await doAbort()
-          updateSettings("currentWithdrawalOperationId", undefined)
-          onClose()
-        },
+        onClose: doAbort,
         onAbort: doAbort,
       }
     }
@@ -252,11 +247,7 @@ export function useComponentState({ currency, onClose }: 
Props): utils.Recursive
     return {
       status: "need-confirmation",
       error: undefined,
-      onAbort: async () => {
-        await doAbort()
-        updateSettings("currentWithdrawalOperationId", undefined)
-        onClose()
-      },
+      onAbort: doAbort,
       busy: !!busy,
       onConfirm: doConfirm
     }
diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx 
b/packages/demobank-ui/src/pages/PaymentOptions.tsx
index c2d87d0e6..d0ee0243d 100644
--- a/packages/demobank-ui/src/pages/PaymentOptions.tsx
+++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx
@@ -30,8 +30,8 @@ export function PaymentOptions({ limit, goToConfirmOperation 
}: { limit: AmountJ
   const { i18n } = useTranslationContext();
   const [settings] = useSettings();
 
-  const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | 
undefined>("wire-transfer");
-
+  const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | 
undefined>();
+  console.log("patment", tab)
   return (
     <div class="mt-2">
 
@@ -48,9 +48,9 @@ export function PaymentOptions({ limit, goToConfirmOperation 
}: { limit: AmountJ
             }} />
             <div class="flex flex-col">
               <span class="flex">
-              <div class="text-4xl mr-4 my-auto">&#x1F4B5;</div>
+                <div class="text-4xl mr-4 my-auto">&#x1F4B5;</div>
                 <span class="grow self-center text-lg text-gray-900 
align-middle text-center">
-                <i18n.Translate>a <b>Taler</b> wallet</i18n.Translate>
+                  <i18n.Translate>a <b>Taler</b> wallet</i18n.Translate>
                 </span>
                 <svg class="self-center flex-none h-5 w-5 text-indigo-600" 
style={{ visibility: tab === "charge-wallet" ? "visible" : "hidden" }} 
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
                   <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 
16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 
1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
@@ -59,14 +59,14 @@ export function PaymentOptions({ limit, 
goToConfirmOperation }: { limit: AmountJ
               <div class="mt-1 flex items-center text-sm text-gray-500">
                 <i18n.Translate>Withdraw digital money into your mobile wallet 
or browser extension</i18n.Translate>
               </div>
-                {!!settings.currentWithdrawalOperationId &&
-                  <span class="flex items-center gap-x-1.5 w-fit rounded-md 
bg-green-100 px-2 py-1 text-xs font-medium text-green-700 whitespace-pre">
-                    <svg class="h-1.5 w-1.5 fill-green-500" viewBox="0 0 6 6" 
aria-hidden="true">
-                      <circle cx="3" cy="3" r="3" />
-                    </svg>
-                    <i18n.Translate>operation ready</i18n.Translate>
-                  </span>
-                }
+              {!!settings.currentWithdrawalOperationId &&
+                <span class="flex items-center gap-x-1.5 w-fit rounded-md 
bg-green-100 px-2 py-1 text-xs font-medium text-green-700 whitespace-pre">
+                  <svg class="h-1.5 w-1.5 fill-green-500" viewBox="0 0 6 6" 
aria-hidden="true">
+                    <circle cx="3" cy="3" r="3" />
+                  </svg>
+                  <i18n.Translate>operation ready</i18n.Translate>
+                </span>
+              }
             </div>
           </label>
 
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx 
b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index e713324c5..d859c10d7 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -51,20 +51,25 @@ const logger = new Logger("PaytoWireTransferForm");
 export function PaytoWireTransferForm({
   focus,
   title,
+  toAccount,
   onSuccess,
   onCancel,
   limit,
 }: {
   title: TranslatedString,
   focus?: boolean;
+  toAccount?: string,
   onSuccess: () => void;
   onCancel: (() => void) | undefined;
   limit: AmountJson;
 }): VNode {
-  const [isRawPayto, setIsRawPayto] = useState(true);
+  const [isRawPayto, setIsRawPayto] = useState(false);
   const { state: credentials } = useBackendState()
   const { api } = useBankCoreApiContext();
-  const [iban, setIban] = useState<string | undefined>();
+
+  const sendingToFixedAccount = toAccount !== undefined
+  //FIXME: support other destination that just IBAN
+  const [iban, setIban] = useState<string | undefined>(toAccount);
   const [subject, setSubject] = useState<string | undefined>();
   const [amount, setAmount] = useState<string | undefined>();
 
@@ -163,7 +168,7 @@ export function PaytoWireTransferForm({
       setAmount(undefined);
       setIban(undefined);
       setSubject(undefined);
-      rawPaytoInputSetter(undefined)      
+      rawPaytoInputSetter(undefined)
     })
   }
 
@@ -181,9 +186,12 @@ export function PaytoWireTransferForm({
             <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 amountStr = parsed.params["amount"]
+                if (amountStr) {
+                  const amount = Amounts.parse(parsed.params["amount"])
+                  if (amount) {
+                    setAmount(Amounts.stringifyValue(amount))
+                  }
                 }
                 const subject = parsed.params["message"]
                 if (subject) {
@@ -201,28 +209,30 @@ export function PaytoWireTransferForm({
             </span>
           </label>
 
-          <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
+          {sendingToFixedAccount ? undefined :
+            <label class={"relative flex cursor-pointer rounded-lg border 
bg-white p-4 shadow-sm focus:outline-none" + (isRawPayto ? "border-indigo-600 
ring-2 ring-indigo-600" : "border-gray-300")}>
+              <input type="radio" name="project-type" value="Existing 
Customers" class="sr-only" aria-labelledby="project-type-1-label" 
aria-describedby="project-type-1-description-0 project-type-1-description-1" 
onChange={() => {
+                if (iban) {
+                  const payto = buildPayto("iban", iban, undefined)
+                  if (parsedAmount) {
+                    payto.params["amount"] = Amounts.stringify(parsedAmount)
+                  }
+                  if (subject) {
+                    payto.params["message"] = subject
+                  }
+                  rawPaytoInputSetter(stringifyPaytoUri(payto))
                 }
-                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>Import payto:// URI</i18n.Translate>
+                setIsRawPayto(true)
+              }} />
+              <span class="flex flex-1">
+                <span class="flex flex-col">
+                  <span class="block text-sm font-medium text-gray-900">
+                    <i18n.Translate>Import payto:// URI</i18n.Translate>
+                  </span>
                 </span>
               </span>
-            </span>
-          </label>
+            </label>
+          }
         </div>
       </div>
     </div>
@@ -244,9 +254,10 @@ export function PaytoWireTransferForm({
                 <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"
+                  class="block w-full disabled:bg-gray-200 rounded-md border-0 
py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 
sm:text-sm sm:leading-6"
                   name="iban"
                   id="iban"
+                  disabled={sendingToFixedAccount}
                   value={iban ?? ""}
                   placeholder="CC0123456789"
                   autocomplete="off"
@@ -369,7 +380,7 @@ export function PaytoWireTransferForm({
 export function doAutoFocus(element: HTMLElement | null) {
   if (element) {
     setTimeout(() => {
-      element.focus()
+      element.focus({ preventScroll: true })
       element.scrollIntoView({
         behavior: "smooth",
         block: "center",
diff --git a/packages/demobank-ui/src/pages/ProfileNavigation.tsx 
b/packages/demobank-ui/src/pages/ProfileNavigation.tsx
new file mode 100644
index 000000000..c061c9742
--- /dev/null
+++ b/packages/demobank-ui/src/pages/ProfileNavigation.tsx
@@ -0,0 +1,56 @@
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { Fragment, VNode, h } from "preact";
+import { useBankCoreApiContext } from "../context/config.js";
+import { assertUnreachable } from "./HomePage.js";
+
+export function ProfileNavigation({ current }: { current: "details" | 
"credentials" | "cashouts" }): VNode {
+  const { i18n } = useTranslationContext()
+  const { config } = useBankCoreApiContext()
+  return <div>
+    <div class="sm:hidden">
+      <label for="tabs" class="sr-only"><i18n.Translate>Select a 
section</i18n.Translate></label>
+      <select id="tabs" name="tabs" class="block w-full rounded-md 
border-gray-300 focus:border-indigo-500 focus:ring-indigo-500" onChange={(e) => 
{
+        const op = e.currentTarget.value as typeof current
+        switch (op) {
+          case "details": {
+            window.location.href = "#/my-profile";
+            return;
+          }
+          case "credentials": {
+            window.location.href = "#/my-password";
+            return;
+          }
+          case "cashouts": {
+            window.location.href = "#/my-cashouts";
+            return;
+          }
+          default: assertUnreachable(op)
+        }
+      }}>
+        <option value="details" selected={current == 
"details"}><i18n.Translate>Details</i18n.Translate></option>
+        <option value="credentials" selected={current == 
"credentials"}><i18n.Translate>Credentials</i18n.Translate></option>
+        {config.have_cashout ?
+          <option value="cashouts" selected={current == 
"cashouts"}><i18n.Translate>Cashouts</i18n.Translate></option>
+          : undefined}
+      </select>
+    </div>
+    <div class="hidden sm:block">
+      <nav class="isolate flex divide-x divide-gray-200 rounded-lg shadow" 
aria-label="Tabs">
+        <a href="#/my-profile" data-selected={current == "details"} 
class="rounded-l-lg text-gray-500 hover:text-gray-700 
data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 
overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium 
hover:bg-gray-50 focus:z-10" >
+          <span><i18n.Translate>Details</i18n.Translate></span>
+          <span aria-hidden="true" data-selected={current == "details"} 
class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 
bottom-0 h-0.5"></span>
+        </a>
+        <a href="#/my-password" data-selected={current == "credentials"} 
aria-current="page" class="             text-gray-500 hover:text-gray-700 
data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 
overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium 
hover:bg-gray-50 focus:z-10">
+          <span><i18n.Translate>Credentials</i18n.Translate></span>
+          <span aria-hidden="true" data-selected={current == "credentials"} 
class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 
bottom-0 h-0.5"></span>
+        </a>
+        {config.have_cashout ?
+          <a href="#/my-cashouts" data-selected={current == "cashouts"} 
class="rounded-r-lg text-gray-500 hover:text-gray-700 
data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 
overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium 
hover:bg-gray-50 focus:z-10">
+            <span>Cashouts</span>
+            <span aria-hidden="true" data-selected={current == "cashouts"} 
class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 
bottom-0 h-0.5"></span>
+          </a>
+          : undefined}
+      </nav>
+    </div>
+  </div>
+}
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx 
b/packages/demobank-ui/src/pages/RegistrationPage.tsx
index ce38a9fb8..3520405c5 100644
--- a/packages/demobank-ui/src/pages/RegistrationPage.tsx
+++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx
@@ -13,21 +13,19 @@
  You should have received a copy of the GNU General Public License along with
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
-import { AccessToken, HttpStatusCode, Logger, TalerError, TranslatedString } 
from "@gnu-taler/taler-util";
+import { AccessToken, Logger, TranslatedString } from "@gnu-taler/taler-util";
 import {
-  RequestError,
   notify,
-  notifyError,
-  useTranslationContext,
+  useTranslationContext
 } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
-import { useBackendContext } from "../context/backend.js";
-import { bankUiSettings } from "../settings.js";
-import { buildRequestErrorMessage, undefinedIfEmpty, withRuntimeErrorHandling 
} from "../utils.js";
 import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
-import { getRandomPassword, getRandomUsername } from "./rnd.js";
 import { useBankCoreApiContext } from "../context/config.js";
+import { useBackendState } from "../hooks/backend.js";
+import { bankUiSettings } from "../settings.js";
+import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
+import { getRandomPassword, getRandomUsername } from "./rnd.js";
 
 const logger = new Logger("RegistrationPage");
 
@@ -55,7 +53,7 @@ export const EMAIL_REGEX = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/;
  * Collect and submit registration data.
  */
 function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, 
onCancel: () => void }): VNode {
-  const backend = useBackendContext();
+  const backend = useBackendState();
   const [username, setUsername] = useState<string | undefined>();
   const [name, setName] = useState<string | undefined>();
   const [password, setPassword] = useState<string | undefined>();
diff --git a/packages/demobank-ui/src/pages/ShowAccountDetails.tsx 
b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
index c65b90503..21724474a 100644
--- a/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
+++ b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
@@ -1,25 +1,24 @@
-import { HttpStatusCode, TalerCorebankApi, TalerError, TranslatedString } from 
"@gnu-taler/taler-util";
-import { HttpResponsePaginated, RequestError, notify, notifyError, 
useTranslationContext } from "@gnu-taler/web-util/browser";
-import { VNode, h } from "preact";
+import { TalerCorebankApi, TalerError, TranslatedString } from 
"@gnu-taler/taler-util";
+import { notify, notifyInfo, useTranslationContext } from 
"@gnu-taler/web-util/browser";
+import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { ErrorLoading } from "../components/ErrorLoading.js";
 import { Loading } from "../components/Loading.js";
 import { useBankCoreApiContext } from "../context/config.js";
 import { useAccountDetails } from "../hooks/access.js";
 import { useBackendState } from "../hooks/backend.js";
-import { buildRequestErrorMessage, undefinedIfEmpty, withRuntimeErrorHandling 
} from "../utils.js";
+import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
 import { assertUnreachable } from "./HomePage.js";
 import { LoginForm } from "./LoginForm.js";
 import { AccountForm } from "./admin/AccountForm.js";
+import { ProfileNavigation } from "./ProfileNavigation.js";
 
 export function ShowAccountDetails({
   account,
   onClear,
   onUpdateSuccess,
-  onChangePassword,
 }: {
   onClear?: () => void;
-  onChangePassword: () => void;
   onUpdateSuccess: () => void;
   account: string;
 }): VNode {
@@ -27,6 +26,8 @@ export function ShowAccountDetails({
   const { state: credentials } = useBackendState();
   const creds = credentials.status !== "loggedIn" ? undefined : credentials
   const { api } = useBankCoreApiContext()
+  const accountIsTheCurrentUser = credentials.status === "loggedIn" ?
+    credentials.username === account : false
 
   const [update, setUpdate] = useState(false);
   const [submitAccount, setSubmitAccount] = 
useState<TalerCorebankApi.AccountData | undefined>();
@@ -47,11 +48,7 @@ export function ShowAccountDetails({
   }
 
   async function doUpdate() {
-    if (!update) {
-      setUpdate(true);
-      return;
-    }
-    if (!submitAccount || !creds) return;
+    if (!update || !submitAccount || !creds) return;
     await withRuntimeErrorHandling(i18n, async () => {
       const resp = await api.updateAccount(creds, {
         cashout_address: submitAccount.cashout_payto_uri,
@@ -62,8 +59,9 @@ export function ShowAccountDetails({
         is_exchange: false,
         name: submitAccount.name,
       });
-      
+
       if (resp.type === "ok") {
+        notifyInfo(i18n.str`Account updated`);
         onUpdateSuccess();
       } else {
         switch (resp.case) {
@@ -87,21 +85,23 @@ export function ShowAccountDetails({
   }
 
   return (
-    <div>
+    <Fragment>
+      {accountIsTheCurrentUser ?
+        <ProfileNavigation current="details" />
+        :
+        <h1 class="text-base font-semibold leading-6 text-gray-900">
+          <i18n.Translate>Account "{account}"</i18n.Translate>
+        </h1>
+
+      }
+
       <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 
bg-gray-100 my-4 px-4 pb-4 rounded-lg">
         <div class="px-4 sm:px-0">
           <h2 class="text-base font-semibold leading-7 text-gray-900">
-            {update ?
-              <i18n.Translate>Update account</i18n.Translate>
-              :
-              <i18n.Translate>Account details</i18n.Translate>
-            }
-          </h2>
-          <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>change the account details</i18n.Translate>
+                <span class="text-sm text-black font-semibold leading-6 " 
id="availability-label">
+                  <i18n.Translate>Change details</i18n.Translate>
                 </span>
               </span>
               <button type="button" data-enabled={!update} 
class="bg-indigo-600 data-[enabled=true]:bg-gray-200 relative inline-flex h-5 
w-10 flex-shrink-0 cursor-pointer rounded-full ring-2 border-gray-600 
transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 
focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" 
aria-labelledby="availability-label" aria-describedby="availability-description"
@@ -111,69 +111,36 @@ export function ShowAccountDetails({
                 <span aria-hidden="true" data-enabled={!update} 
class="translate-x-5 data-[enabled=true]:translate-x-0 pointer-events-none 
inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition 
duration-200 ease-in-out"></span>
               </button>
             </div>
-          </div>
-
+          </h2>
         </div>
+
         <AccountForm
+          focus={update}
+          username={account}
           template={result.body}
           purpose={update ? "update" : "show"}
           onChange={(a) => setSubmitAccount(a)}
         >
-
-        </AccountForm>
-
-        <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();
-                    doUpdate()
-                  }}
-                />
-              </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">
+            {onClear ?
+              <button type="button" class="text-sm font-semibold leading-6 
text-gray-900"
+                onClick={onClear}
+              >
+                <i18n.Translate>Cancel</i18n.Translate>
+              </button>
+              : <div />
+            }
+            <button type="submit"
+              class="disabled:opacity-50 disabled:cursor-default 
cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold 
text-white shadow-sm hover:bg-indigo-500 focus-visible:outline 
focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
+              disabled={!update || !submitAccount}
+              onClick={doUpdate}
+            >
+              <i18n.Translate>Update</i18n.Translate>
+            </button>
           </div>
-        </p>
+        </AccountForm>
       </div>
-    </div>
+    </Fragment>
   );
 }
+
diff --git a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx 
b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
index d82dac4b1..e3f0de8cc 100644
--- a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
+++ b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
@@ -1,16 +1,16 @@
-import { TalerError, TranslatedString } from "@gnu-taler/taler-util";
-import { HttpResponsePaginated, RequestError, notify, notifyError, 
useTranslationContext } from "@gnu-taler/web-util/browser";
+import { notify, notifyInfo, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
-import { buildRequestErrorMessage, undefinedIfEmpty, withRuntimeErrorHandling 
} from "../utils.js";
-import { doAutoFocus } from "./PaytoWireTransferForm.js";
 import { useBankCoreApiContext } from "../context/config.js";
-import { assertUnreachable } from "./HomePage.js";
 import { useBackendState } from "../hooks/backend.js";
+import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
+import { assertUnreachable } from "./HomePage.js";
+import { doAutoFocus } from "./PaytoWireTransferForm.js";
+import { ProfileNavigation } from "./ProfileNavigation.js";
 
 export function UpdateAccountPassword({
-  account,
+  account: accountName,
   onCancel,
   onUpdateSuccess,
   focus,
@@ -22,13 +22,18 @@ export function UpdateAccountPassword({
 }): VNode {
   const { i18n } = useTranslationContext();
   const { state: credentials } = useBackendState();
-  const creds = credentials.status !== "loggedIn" ? undefined : credentials
+  const token = credentials.status !== "loggedIn" ? undefined : 
credentials.token
   const { api } = useBankCoreApiContext();
 
+  const [current, setCurrent] = useState<string | undefined>();
   const [password, setPassword] = useState<string | undefined>();
   const [repeat, setRepeat] = useState<string | undefined>();
 
+  const accountIsTheCurrentUser = credentials.status === "loggedIn" ?
+    credentials.username === accountName : false
+
   const errors = undefinedIfEmpty({
+    current: !accountIsTheCurrentUser ? undefined : !current ? 
i18n.str`required` : undefined,
     password: !password ? i18n.str`required` : undefined,
     repeat: !repeat
       ? i18n.str`required`
@@ -37,13 +42,16 @@ export function UpdateAccountPassword({
         : undefined,
   });
 
+
   async function doChangePassword() {
-    if (!!errors || !password || !creds) return;
+    if (!!errors || !password || !token) return;
     await withRuntimeErrorHandling(i18n, async () => {
-      const resp = await api.updatePassword(creds, {
+      const resp = await api.updatePassword({ username: accountName, token }, {
+        // old_password: current,
         new_password: password,
       });
       if (resp.type === "ok") {
+        notifyInfo(i18n.str`Password changed`);
         onUpdateSuccess();
       } else {
         switch (resp.case) {
@@ -51,6 +59,10 @@ export function UpdateAccountPassword({
             type: "error",
             title: i18n.str`Not authorized to change the password, maybe the 
session is invalid.`
           })
+          case "no-rights": return notify({
+            type: "error",
+            title: i18n.str`This user have no right on to change the password.`
+          })
           case "not-found": return notify({
             type: "error",
             title: i18n.str`Account not found`
@@ -62,112 +74,147 @@ export function UpdateAccountPassword({
   }
 
   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>Update password for account 
"{account}"</i18n.Translate>
-        </h2>
-      </div>
-      <form
-        class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl 
md:col-span-2"
-        autoCapitalize="none"
-        autoCorrect="off"
-        onSubmit={e => {
-          e.preventDefault()
-        }}
-      >
-        <div class="px-4 py-6 sm:p-8">
-          <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 
sm:grid-cols-6">
+    <Fragment>
+      {accountIsTheCurrentUser ?
+        <ProfileNavigation current="credentials" /> :
+        <h1 class="text-base font-semibold leading-6 text-gray-900">
+          <i18n.Translate>Account "{accountName}"</i18n.Translate>
+        </h1>
 
-            <div class="sm:col-span-5">
-              <label
-                class="block text-sm font-medium leading-6 text-gray-900"
-                for="password"
-              >
-                {i18n.str`New password`}
-              </label>
-              <div class="mt-2">
-                <input
-                  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"
-                  id="password"
-                  data-error={!!errors?.password && password !== undefined}
-                  value={password ?? ""}
-                  onChange={(e) => {
-                    setPassword(e.currentTarget.value)
-                  }}
-                  // placeholder=""
-                  autocomplete="off"
-                />
-                <ShowInputErrorLabel
-                  message={errors?.password}
-                  isDirty={password !== undefined}
-                />
-              </div>
-              {/* <p class="mt-2 text-sm text-gray-500" >
-              <i18n.Translate>user </i18n.Translate>
-            </p> */}
-            </div>
+      }
 
-            <div class="sm:col-span-5">
-              <label
-                class="block text-sm font-medium leading-6 text-gray-900"
-                for="repeat"
-              >
-                {i18n.str`Type it again`}
-              </label>
-              <div class="mt-2">
-                <input
-                  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="repeat"
-                  id="repeat"
-                  data-error={!!errors?.repeat && repeat !== undefined}
-                  value={repeat ?? ""}
-                  onChange={(e) => {
-                    setRepeat(e.currentTarget.value)
-                  }}
-                  // placeholder=""
-                  autocomplete="off"
-                />
-                <ShowInputErrorLabel
-                  message={errors?.repeat}
-                  isDirty={repeat !== undefined}
-                />
+      <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>Update password</i18n.Translate>
+          </h2>
+        </div>
+        <form
+          class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl 
md:col-span-2"
+          autoCapitalize="none"
+          autoCorrect="off"
+          onSubmit={e => {
+            e.preventDefault()
+          }}
+        >
+          <div class="px-4 py-6 sm:p-8">
+            <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"
+                  for="password"
+                >
+                  {i18n.str`New password`}
+                </label>
+                <div class="mt-2">
+                  <input
+                    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"
+                    id="password"
+                    data-error={!!errors?.password && password !== undefined}
+                    value={password ?? ""}
+                    onChange={(e) => {
+                      setPassword(e.currentTarget.value)
+                    }}
+                    autocomplete="off"
+                  />
+                  <ShowInputErrorLabel
+                    message={errors?.password}
+                    isDirty={password !== undefined}
+                  />
+                </div>
               </div>
-              <p class="mt-2 text-sm text-gray-500" >
-                <i18n.Translate>repeat the same password</i18n.Translate>
-              </p>
-            </div>
 
+              <div class="sm:col-span-5">
+                <label
+                  class="block text-sm font-medium leading-6 text-gray-900"
+                  for="repeat"
+                >
+                  {i18n.str`Type it again`}
+                </label>
+                <div class="mt-2">
+                  <input
+                    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="repeat"
+                    id="repeat"
+                    data-error={!!errors?.repeat && repeat !== undefined}
+                    value={repeat ?? ""}
+                    onChange={(e) => {
+                      setRepeat(e.currentTarget.value)
+                    }}
+                    // placeholder=""
+                    autocomplete="off"
+                  />
+                  <ShowInputErrorLabel
+                    message={errors?.repeat}
+                    isDirty={repeat !== undefined}
+                  />
+                </div>
+                <p class="mt-2 text-sm text-gray-500" >
+                  <i18n.Translate>repeat the same password</i18n.Translate>
+                </p>
+              </div>
 
+              {accountIsTheCurrentUser ?
+                <div class="sm:col-span-5">
+                  <label
+                    class="block text-sm font-medium leading-6 text-gray-900"
+                    for="password"
+                  >
+                    {i18n.str`Current password`}
+                  </label>
+                  <div class="mt-2">
+                    <input
+                      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="current"
+                      id="current-password"
+                      data-error={!!errors?.current && current !== undefined}
+                      value={current ?? ""}
+                      onChange={(e) => {
+                        setCurrent(e.currentTarget.value)
+                      }}
+                      autocomplete="off"
+                    />
+                    <ShowInputErrorLabel
+                      message={errors?.current}
+                      isDirty={current !== undefined}
+                    />
+                  </div>
+                  <p class="mt-2 text-sm text-gray-500" >
+                    <i18n.Translate>your current password, for 
security</i18n.Translate>
+                  </p>
+                </div>
+                : undefined}
 
+            </div>
           </div>
-        </div>
-        <div class="flex items-center justify-between gap-x-6 border-t 
border-gray-900/10 px-4 py-4 sm:px-8">
-          {onCancel ?
-            <button type="button" class="text-sm font-semibold leading-6 
text-gray-900"
-              onClick={onCancel}
+          <div class="flex items-center justify-between gap-x-6 border-t 
border-gray-900/10 px-4 py-4 sm:px-8">
+            {onCancel ?
+              <button type="button" class="text-sm font-semibold leading-6 
text-gray-900"
+                onClick={onCancel}
+              >
+                <i18n.Translate>Cancel</i18n.Translate>
+              </button>
+              : <div />
+            }
+            <button type="submit"
+              class="disabled:opacity-50 disabled:cursor-default 
cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold 
text-white shadow-sm hover:bg-indigo-500 focus-visible:outline 
focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
+              disabled={!!errors}
+              onClick={(e) => {
+                e.preventDefault()
+                doChangePassword()
+              }}
             >
-              <i18n.Translate>Cancel</i18n.Translate>
+              <i18n.Translate>Change</i18n.Translate>
             </button>
-            : <div />
-          }
-          <button type="submit"
-            class="disabled:opacity-50 disabled:cursor-default cursor-pointer 
rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
-            disabled={!!errors}
-            onClick={(e) => {
-              e.preventDefault()
-              doChangePassword()
-            }}
-          >
-            <i18n.Translate>Change</i18n.Translate>
-          </button>
-        </div>
-      </form>
-    </div>
+          </div>
+        </form>
+      </div>
+    </Fragment>
 
   );
 }
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx 
b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 7266e4de4..51edbc95f 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -30,6 +30,7 @@ import { assertUnreachable } from "./HomePage.js";
 import { QrCodeSection } from "./QrCodeSection.js";
 import { WithdrawalConfirmationQuestion } from 
"./WithdrawalConfirmationQuestion.js";
 import { Attention } from "../components/Attention.js";
+import { Pages } from "../pages.js";
 
 const logger = new Logger("WithdrawalQRCode");
 
diff --git a/packages/demobank-ui/src/pages/admin/Account.tsx 
b/packages/demobank-ui/src/pages/admin/Account.tsx
index bf2fa86f0..103747414 100644
--- a/packages/demobank-ui/src/pages/admin/Account.tsx
+++ b/packages/demobank-ui/src/pages/admin/Account.tsx
@@ -3,15 +3,15 @@ import { notifyInfo, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { ErrorLoading } from "../../components/ErrorLoading.js";
 import { Loading } from "../../components/Loading.js";
-import { useBackendContext } from "../../context/backend.js";
 import { useAccountDetails } from "../../hooks/access.js";
 import { assertUnreachable } from "../HomePage.js";
 import { LoginForm } from "../LoginForm.js";
 import { PaytoWireTransferForm } from "../PaytoWireTransferForm.js";
+import { useBackendState } from "../../hooks/backend.js";
 
-export function AdminAccount({ onRegister }: { onRegister: () => void }): 
VNode {
+export function WireTransfer({ toAccount, onRegister, onCancel, onSuccess }: { 
onSuccess?: () => void; toAccount?: string, onCancel?: () => void, onRegister?: 
() => void }): VNode {
   const { i18n } = useTranslationContext();
-  const r = useBackendContext();
+  const r = useBackendState();
   const account = r.state.status !== "loggedOut" ? r.state.username : "admin";
   const result = useAccountDetails(account);
 
@@ -41,11 +41,13 @@ export function AdminAccount({ onRegister }: { onRegister: 
() => void }): VNode
   return (
     <PaytoWireTransferForm
       title={i18n.str`Make a wire transfer`}
+      toAccount={toAccount}
       limit={limit}
       onSuccess={() => {
         notifyInfo(i18n.str`Wire transfer created!`);
+        if (onSuccess) onSuccess()
       }}
-      onCancel={undefined}
+      onCancel={onCancel}
     />
   );
 }
diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx 
b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
index 8470930bf..bce089560 100644
--- a/packages/demobank-ui/src/pages/admin/AccountForm.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
@@ -1,16 +1,20 @@
-import { ComponentChildren, VNode, h } from "preact";
+import { ComponentChildren, Fragment, VNode, h } from "preact";
 import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
 import { PartialButDefined, RecursivePartial, WithIntermediate, 
undefinedIfEmpty, validateIBAN } from "../../utils.js";
 import { useEffect, useRef, useState } from "preact/hooks";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { TalerCorebankApi, buildPayto, parsePaytoUri } from 
"@gnu-taler/taler-util";
 import { doAutoFocus } from "../PaytoWireTransferForm.js";
+import { CopyButton } from "../../components/CopyButton.js";
+import { assertUnreachable } from "../HomePage.js";
 
 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 ]*$/;
 
+export type AccountFormData = TalerCorebankApi.AccountData & { username: 
string }
+
 /**
  * Create valid account object to update or create
  * Take template as initial values for the form
@@ -21,6 +25,7 @@ const REGEX_JUST_NUMBERS_REGEX = /^\+[0-9 ]*$/;
  */
 export function AccountForm({
   template,
+  username,
   purpose,
   onChange,
   focus,
@@ -28,11 +33,12 @@ export function AccountForm({
 }: {
   focus?: boolean,
   children: ComponentChildren,
+  username?: string,
   template: TalerCorebankApi.AccountData | undefined;
-  onChange: (a: TalerCorebankApi.AccountData | undefined) => void;
+  onChange: (a: AccountFormData | undefined) => void;
   purpose: "create" | "update" | "show";
 }): VNode {
-  const initial = initializeFromTemplate(template);
+  const initial = initializeFromTemplate(username, template);
   const [form, setForm] = useState(initial);
   const [errors, setErrors] = useState<
     RecursivePartial<typeof initial> | undefined
@@ -69,14 +75,8 @@ export function AccountForm({
               ? 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,
+      username: !newForm.username ? i18n.str`required` : undefined,
     });
     setErrors(errors);
     setForm(newForm);
@@ -95,7 +95,7 @@ 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">
+          <div class="sm:col-span-5">
             <label
               class="block text-sm font-medium leading-6 text-gray-900"
               for="username"
@@ -105,7 +105,7 @@ export function AccountForm({
             </label>
             <div class="mt-2">
               <input
-                ref={focus ? doAutoFocus : undefined}
+                ref={focus && purpose === "create" ? 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"
@@ -128,7 +128,7 @@ export function AccountForm({
             <p class="mt-2 text-sm text-gray-500" >
               <i18n.Translate>account identification in the 
bank</i18n.Translate>
             </p>
-          </div> */}
+          </div>
 
           <div class="sm:col-span-5">
             <label
@@ -165,27 +165,7 @@ export function AccountForm({
           </div>
 
 
-          {purpose !== "create" && (<div class="sm:col-span-5">
-            <label
-              class="block text-sm font-medium leading-6 text-gray-900"
-              for="internal-iban"
-            >
-              {i18n.str`Internal IBAN`}
-            </label>
-            <div class="mt-2">
-              <input
-                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="internal-iban"
-                id="internal-iban"
-                disabled={true}
-                value={form.payto_uri ?? ""}
-              />
-            </div>
-            <p class="mt-2 text-sm text-gray-500" >
-              <i18n.Translate>international bank account 
number</i18n.Translate>
-            </p>
-          </div>)}
+          {purpose !== "create" && (<RenderPaytoDisabledField 
paytoURI={form.payto_uri} />)}
 
           <div class="sm:col-span-5">
             <label
@@ -264,6 +244,7 @@ export function AccountForm({
             <div class="mt-2">
               <input
                 type="text"
+                ref={focus && purpose === "update" ? doAutoFocus : undefined}
                 data-error={!!errors?.cashout_payto_uri && 
form.cashout_payto_uri !== undefined}
                 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="cashout"
@@ -294,8 +275,9 @@ export function AccountForm({
 }
 
 function initializeFromTemplate(
+  username: string | undefined,
   account: TalerCorebankApi.AccountData | undefined,
-): WithIntermediate<TalerCorebankApi.AccountData> {
+): WithIntermediate<AccountFormData> {
   const emptyAccount = {
     cashout_payto_uri: undefined,
     contact_data: undefined,
@@ -314,8 +296,136 @@ function initializeFromTemplate(
   if (typeof initial.contact_data === "undefined") {
     initial.contact_data = emptyContact;
   }
-  // initial.contact_data.email;
+  const result: WithIntermediate<AccountFormData> = initial as any // FIXME: 
check types
+  result.username = username
+
   return initial as any;
 }
 
 
+function RenderPaytoDisabledField({ paytoURI }: { paytoURI: string | undefined 
}): VNode {
+  const { i18n } = useTranslationContext()
+  const payto = parsePaytoUri(paytoURI ?? "");
+  if (payto?.isKnown) {
+    if (payto.targetType === "iban") {
+      const value = payto.iban;
+      return <div class="sm:col-span-5">
+        <label
+          class="block text-sm font-medium leading-6 text-gray-900"
+          for="internal-iban"
+        >
+          {i18n.str`Internal IBAN`}
+        </label>
+        <div class="mt-2">
+          <div class="flex justify-between">
+            <input
+              type="text"
+              class="mr-4 w-full block-inline  disabled:bg-gray-100 rounded-md 
border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 
focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+              name="internal-iban"
+              id="internal-iban"
+              disabled={true}
+              value={value ?? ""}
+            />
+            <CopyButton
+              class="p-2 rounded-full  text-black shadow-sm  
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
+              getContent={() => value ?? ""}
+            />
+          </div>
+        </div>
+        <p class="mt-2 text-sm text-gray-500" >
+          <i18n.Translate>international bank account number</i18n.Translate>
+        </p>
+      </div>
+    }
+    if (payto.targetType === "x-taler-bank") {
+      const value = payto.account;
+      return <div class="sm:col-span-5">
+        <label
+          class="block text-sm font-medium leading-6 text-gray-900"
+          for="account-id"
+        >
+          {i18n.str`Account ID`}
+        </label>
+        <div class="mt-2">
+          <div class="flex justify-between">
+            <input
+              type="text"
+              class="mr-4 w-full block-inline  disabled:bg-gray-100 rounded-md 
border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 
focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+              name="account-id"
+              id="account-id"
+              disabled={true}
+              value={value ?? ""}
+            />
+            <CopyButton
+              class="p-2 rounded-full  text-black shadow-sm  
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
+              getContent={() => value ?? ""}
+            />
+          </div>
+        </div>
+        <p class="mt-2 text-sm text-gray-500" >
+          <i18n.Translate>internal account id</i18n.Translate>
+        </p>
+      </div>
+    }
+    if (payto.targetType === "bitcoin") {
+      const value = payto.targetPath;
+      return <div class="sm:col-span-5">
+        <label
+          class="block text-sm font-medium leading-6 text-gray-900"
+          for="account-id"
+        >
+          {i18n.str`Bitcoin address`}
+        </label>
+        <div class="mt-2">
+          <div class="flex justify-between">
+            <input
+              type="text"
+              class="mr-4 w-full block-inline  disabled:bg-gray-100 rounded-md 
border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 
focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+              name="account-id"
+              id="account-id"
+              disabled={true}
+              value={value ?? ""}
+            />
+            <CopyButton
+              class="p-2 rounded-full  text-black shadow-sm  
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
+              getContent={() => value ?? ""}
+            />
+          </div>
+        </div>
+        <p class="mt-2 text-sm text-gray-500" >
+          <i18n.Translate>bitcoin address</i18n.Translate>
+        </p>
+      </div>
+    }
+    assertUnreachable(payto)
+  }
+
+  const value = paytoURI ?? ""
+  return <div class="sm:col-span-5">
+    <label
+      class="block text-sm font-medium leading-6 text-gray-900"
+      for="internal-payto"
+    >
+      {i18n.str`Internal account`}
+    </label>
+    <div class="mt-2">
+      <div class="flex justify-between">
+        <input
+          type="text"
+          class="mr-4 w-full block-inline  disabled:bg-gray-100 rounded-md 
border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 
focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+          name="internal-payto"
+          id="internal-payto"
+          disabled={true}
+          value={value ?? ""}
+        />
+        <CopyButton
+          class="p-2 rounded-full  text-black shadow-sm  focus-visible:outline 
focus-visible:outline-2 focus-visible:outline-offset-2 "
+          getContent={() => value ?? ""}
+        />
+      </div>
+    </div>
+    <p class="mt-2 text-sm text-gray-500" >
+      <i18n.Translate>generic payto URI</i18n.Translate>
+    </p>
+  </div>
+}
diff --git a/packages/demobank-ui/src/pages/admin/AccountList.tsx 
b/packages/demobank-ui/src/pages/admin/AccountList.tsx
index 8a1e8294a..39b43b9b1 100644
--- a/packages/demobank-ui/src/pages/admin/AccountList.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountList.tsx
@@ -1,22 +1,26 @@
 import { Amounts, TalerError } from "@gnu-taler/taler-util";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { VNode, h } from "preact";
+import { Fragment, VNode, h } from "preact";
 import { ErrorLoading } from "../../components/ErrorLoading.js";
 import { Loading } from "../../components/Loading.js";
 import { useBusinessAccounts } from "../../hooks/circuit.js";
 import { assertUnreachable } from "../HomePage.js";
 import { RenderAmount } from "../PaytoWireTransferForm.js";
-import { AccountAction } from "./Home.js";
+import { useBankCoreApiContext } from "../../context/config.js";
 
 interface Props {
-  onAction: (type: AccountAction, account: string) => void;
-  account: string | undefined;
   onCreateAccount: () => void;
+
+  onShowAccountDetails: (aid: string) => void;
+  onRemoveAccount: (aid: string) => void;
+  onUpdateAccountPassword: (aid: string) => void;
+  onShowCashoutForAccount: (aid: string) => void;
 }
 
-export function AccountList({ account, onAction, onCreateAccount }: Props): 
VNode {
+export function AccountList({ onRemoveAccount, onShowAccountDetails, 
onUpdateAccountPassword, onShowCashoutForAccount, onCreateAccount }: Props): 
VNode {
   const result = useBusinessAccounts();
   const { i18n } = useTranslationContext();
+  const { config } = useBankCoreApiContext()
 
   if (!result) {
     return <Loading />
@@ -74,6 +78,7 @@ export function AccountList({ account, onAction, 
onCreateAccount }: Props): VNod
                   const balance = !item.balance
                     ? undefined
                     : Amounts.parse(item.balance.amount);
+                  const noBalance = Amounts.isZero(item.balance.amount)
                   const balanceIsDebit =
                     item.balance &&
                     item.balance.credit_debit_indicator == "debit";
@@ -83,7 +88,7 @@ export function AccountList({ account, onAction, 
onCreateAccount }: Props): VNod
                       <a href="#" class="text-indigo-600 hover:text-indigo-900"
                         onClick={(e) => {
                           e.preventDefault();
-                          onAction("show-details", item.username)
+                          onShowAccountDetails(item.username)
                         }}
                       >
                         {item.username}
@@ -94,7 +99,7 @@ export function AccountList({ account, onAction, 
onCreateAccount }: Props): VNod
                     <td class="whitespace-nowrap px-3 py-4 text-sm 
text-gray-500">
                       {item.name}
                     </td>
-                    <td class="whitespace-nowrap px-3 py-4 text-sm 
text-gray-500">
+                    <td data-negative={noBalance ? undefined : balanceIsDebit 
? "true" : "false"} class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 
data-[negative=false]:text-green-600 data-[negative=true]:text-red-600 ">
                       {!balance ? (
                         i18n.str`unknown`
                       ) : (
@@ -107,27 +112,34 @@ export function AccountList({ account, onAction, 
onCreateAccount }: Props): VNod
                       <a href="#" class="text-indigo-600 hover:text-indigo-900"
                         onClick={(e) => {
                           e.preventDefault();
-                          onAction("update-password", item.username)
+                          onUpdateAccountPassword(item.username)
                         }}
                       >
                         change password
                       </a>
                       <br />
-                      <a href="#" class="text-indigo-600 
hover:text-indigo-900" onClick={(e) => {
-                        e.preventDefault();
-                        onAction("show-cashout", item.username)
-                      }}
-                      >
-                        cashouts
-                      </a>
-                      <br />
-                      <a href="#" class="text-indigo-600 
hover:text-indigo-900" onClick={(e) => {
-                        e.preventDefault();
-                        onAction("remove-account", item.username)
-                      }}
-                      >
-                        remove
-                      </a>
+                      {config.have_cashout ?
+                        <Fragment>
+
+                          <a href="#" class="text-indigo-600 
hover:text-indigo-900" onClick={(e) => {
+                            e.preventDefault();
+                            onShowCashoutForAccount(item.username)
+                          }}
+                          >
+                            cashouts
+                          </a>
+                          <br />
+                        </Fragment>
+                        : undefined}
+                      {noBalance ?
+                        <a href="#" class="text-indigo-600 
hover:text-indigo-900" onClick={(e) => {
+                          e.preventDefault();
+                          onRemoveAccount(item.username)
+                        }}
+                        >
+                          remove
+                        </a>
+                        : undefined}
                     </td>
                   </tr>
                 })}
diff --git a/packages/demobank-ui/src/pages/admin/AdminHome.tsx 
b/packages/demobank-ui/src/pages/admin/AdminHome.tsx
new file mode 100644
index 000000000..01f9f6dbd
--- /dev/null
+++ b/packages/demobank-ui/src/pages/admin/AdminHome.tsx
@@ -0,0 +1,32 @@
+import { Fragment, VNode, h } from "preact";
+import { Transactions } from "../../components/Transactions/index.js";
+import { WireTransfer } from "./Account.js";
+import { AccountList } from "./AccountList.js";
+
+/**
+ * Query account information and show QR code if there is pending withdrawal
+ */
+interface Props {
+  onRegister: () => void;
+
+  onCreateAccount: () => void;
+  onShowAccountDetails: (aid: string) => void;
+  onRemoveAccount: (aid: string) => void;
+  onUpdateAccountPassword: (aid: string) => void;
+  onShowCashoutForAccount: (aid: string) => void;
+}
+export function AdminHome({ onCreateAccount, onRegister, onRemoveAccount, 
onShowAccountDetails, onShowCashoutForAccount, onUpdateAccountPassword }: 
Props): VNode {
+  return <Fragment>
+    <AccountList
+      onCreateAccount={onCreateAccount}
+      onRemoveAccount={onRemoveAccount}
+      onShowCashoutForAccount={onShowCashoutForAccount}
+      onShowAccountDetails={onShowAccountDetails}
+      onUpdateAccountPassword={onUpdateAccountPassword}
+    />
+
+    <WireTransfer onRegister={onRegister} />
+
+    <Transactions account="admin" />
+  </Fragment>
+}
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/admin/CashoutListForAccount.tsx 
b/packages/demobank-ui/src/pages/admin/CashoutListForAccount.tsx
new file mode 100644
index 000000000..466dc1a4b
--- /dev/null
+++ b/packages/demobank-ui/src/pages/admin/CashoutListForAccount.tsx
@@ -0,0 +1,47 @@
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { Fragment, VNode, h } from "preact";
+import { Cashouts } from "../../components/Cashouts/index.js";
+import { useBackendState } from "../../hooks/backend.js";
+import { ProfileNavigation } from "../ProfileNavigation.js";
+
+interface Props {
+  account: string,
+  onClose: () => void,
+  onSelected: (cid: string) => void
+}
+
+export function CashoutListForAccount({ account, onSelected, onClose }: 
Props): VNode {
+  const { i18n } = useTranslationContext();
+
+  const { state: credentials } = useBackendState();
+  const token = credentials.status !== "loggedIn" ? undefined : 
credentials.token
+
+  const accountIsTheCurrentUser = credentials.status === "loggedIn" ?
+    credentials.username === account : false
+
+  return <Fragment>
+    {accountIsTheCurrentUser ?
+      <ProfileNavigation current="cashouts" />
+      :
+      <h1 class="text-base font-semibold leading-6 text-gray-900">
+        <i18n.Translate>Cashout for account {account}</i18n.Translate>
+      </h1>
+    }
+    <Cashouts
+      account={account}
+      onSelected={onSelected}
+    />
+    <p>
+      <input
+        class="pure-button"
+        type="submit"
+        value={i18n.str`Close`}
+        onClick={async (e) => {
+          e.preventDefault();
+          onClose();
+        }}
+      />
+    </p>
+  </Fragment>
+}
+
diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx 
b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
index e10c3ad41..772ea6e84 100644
--- a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
@@ -1,21 +1,22 @@
 import { HttpStatusCode, TalerCorebankApi, TalerError, TranslatedString } from 
"@gnu-taler/taler-util";
-import { RequestError, notify, notifyError, useTranslationContext } from 
"@gnu-taler/web-util/browser";
+import { RequestError, notify, notifyError, notifyInfo, useTranslationContext 
} from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { buildRequestErrorMessage, withRuntimeErrorHandling } from 
"../../utils.js";
 import { getRandomPassword } from "../rnd.js";
-import { AccountForm } from "./AccountForm.js";
+import { AccountForm, AccountFormData } from "./AccountForm.js";
 import { useBackendState } from "../../hooks/backend.js";
 import { useBankCoreApiContext } from "../../context/config.js";
 import { assertUnreachable } from "../HomePage.js";
 import { mutate } from "swr";
+import { Attention } from "../../components/Attention.js";
 
 export function CreateNewAccount({
   onCancel,
   onCreateSuccess,
 }: {
   onCancel: () => void;
-  onCreateSuccess: (password: string) => void;
+  onCreateSuccess: () => void;
 }): VNode {
   const { i18n } = useTranslationContext();
   // const { createAccount } = useAdminAccountAPI();
@@ -23,9 +24,7 @@ export function CreateNewAccount({
   const token = credentials.status !== "loggedIn" ? undefined : 
credentials.token
   const { api } = useBankCoreApiContext();
 
-  const [submitAccount, setSubmitAccount] = useState<
-    TalerCorebankApi.AccountData | undefined
-  >();
+  const [submitAccount, setSubmitAccount] = useState<AccountFormData | 
undefined>();
 
   async function doCreate() {
     if (!submitAccount || !token) return;
@@ -35,14 +34,17 @@ export function CreateNewAccount({
         challenge_contact_data: submitAccount.contact_data,
         internal_payto_uri: submitAccount.payto_uri,
         name: submitAccount.name,
-        username: "",//FIXME: not in account data
+        username: submitAccount.username,//FIXME: not in account data
         password: getRandomPassword(),
       };
 
       const resp = await api.createAccount(token, account);
       if (resp.type === "ok") {
         mutate(() => true)// clean account list
-        onCreateSuccess(account.password);
+        notifyInfo(
+          i18n.str`Account created with password "${account.password}". The 
user must change the password on the next login.`,
+        );
+        onCreateSuccess();
       } else {
         switch (resp.case) {
           case "invalid-input": return notify({
@@ -75,6 +77,12 @@ export function CreateNewAccount({
     })
   }
 
+  if (!(credentials.status === "loggedIn" && credentials.isUserAdministrator)) 
{
+    return <Attention type="warning" title={i18n.str`Can't create accounts`} 
onClose={onCancel}>
+      <i18n.Translate>Only system admin can create accounts.</i18n.Translate>
+    </Attention>
+  }
+
   return (
     <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">
diff --git a/packages/demobank-ui/src/pages/admin/Home.tsx 
b/packages/demobank-ui/src/pages/admin/Home.tsx
deleted file mode 100644
index 71ea8ce1b..000000000
--- a/packages/demobank-ui/src/pages/admin/Home.tsx
+++ /dev/null
@@ -1,143 +0,0 @@
-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 { Transactions } from "../../components/Transactions/index.js";
-import { ShowAccountDetails } from "../ShowAccountDetails.js";
-import { UpdateAccountPassword } from "../UpdateAccountPassword.js";
-import { ShowCashoutDetails } from "../business/Home.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
-  } | undefined>()
-
-  const [createAccount, setCreateAccount] = useState(false);
-
-  const { i18n } = useTranslationContext();
-
-  if (action) {
-    switch (action.type) {
-      case "show-cashouts-details": return <ShowCashoutDetails
-        id={action.account}
-        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}
-        onUpdateSuccess={() => {
-          notifyInfo(i18n.str`Password changed`);
-          setAction(undefined);
-        }}
-        onCancel={() => {
-          setAction(undefined);
-        }}
-      />
-      case "remove-account": return <RemoveAccount
-        account={action.account}
-        onUpdateSuccess={() => {
-          notifyInfo(i18n.str`Account removed`);
-          setAction(undefined);
-        }}
-        onCancel={() => {
-          setAction(undefined);
-        }}
-      />
-      case "show-details": return <ShowAccountDetails
-        account={action.account}
-        onChangePassword={() => {
-          setAction({
-            type: "update-password",
-            account: action.account,
-          })
-        }}
-        onUpdateSuccess={() => {
-          notifyInfo(i18n.str`Account updated`);
-          setAction(undefined);
-        }}
-        onClear={() => {
-          setAction(undefined);
-        }}
-      />
-    }
-  }
-
-  if (createAccount) {
-    return (
-      <CreateNewAccount
-        onCancel={() => 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>
-
-      <AccountList
-        onCreateAccount={() => {
-          setCreateAccount(true);
-        }}
-        account={undefined}
-        onAction={(type, account) => setAction({ account, type })}
-
-      />
-
-      <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 9a212ebd0..88961c2cb 100644
--- a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
@@ -1,5 +1,5 @@
 import { Amounts, HttpStatusCode, TalerError, TranslatedString } from 
"@gnu-taler/taler-util";
-import { HttpResponsePaginated, RequestError, notify, notifyError, 
useTranslationContext } from "@gnu-taler/web-util/browser";
+import { HttpResponsePaginated, RequestError, notify, notifyError, notifyInfo, 
useTranslationContext } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { Attention } from "../../components/Attention.js";
@@ -63,6 +63,7 @@ export function RemoveAccount({
     await withRuntimeErrorHandling(i18n, async () => {
       const resp = await api.deleteAccount({ username: account, token });
       if (resp.type === "ok") {
+        notifyInfo(i18n.str`Account removed`);
         onUpdateSuccess();
       } else {
         switch (resp.case) {
diff --git a/packages/demobank-ui/src/pages/business/Home.tsx 
b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
similarity index 57%
rename from packages/demobank-ui/src/pages/business/Home.tsx
rename to packages/demobank-ui/src/pages/business/CreateCashout.tsx
index d7beda01d..4696c899e 100644
--- a/packages/demobank-ui/src/pages/business/Home.tsx
+++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
@@ -21,15 +21,12 @@ import {
 } from "@gnu-taler/taler-util";
 import {
   notify,
-  notifyError,
-  notifyInfo,
   useTranslationContext
 } from "@gnu-taler/web-util/browser";
 import { format } from "date-fns";
 import { Fragment, VNode, h } from "preact";
 import { useEffect, useState } from "preact/hooks";
 import { mutate } from "swr";
-import { Cashouts } from "../../components/Cashouts/index.js";
 import { ErrorLoading } from "../../components/ErrorLoading.js";
 import { Loading } from "../../components/Loading.js";
 import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
@@ -43,114 +40,15 @@ import {
 } from "../../hooks/circuit.js";
 import {
   TanChannel,
-  buildRequestErrorMessage,
   undefinedIfEmpty,
-  withRuntimeErrorHandling,
+  withRuntimeErrorHandling
 } from "../../utils.js";
 import { LoginForm } from "../LoginForm.js";
 import { InputAmount } from "../PaytoWireTransferForm.js";
-import { ShowAccountDetails } from "../ShowAccountDetails.js";
-import { UpdateAccountPassword } from "../UpdateAccountPassword.js";
+import { assertUnreachable } from "../HomePage.js";
+import { Attention } from "../../components/Attention.js";
 
 interface Props {
-  account: string,
-  onClose: () => void;
-  onRegister: () => void;
-}
-export function BusinessAccount({
-  onClose,
-  account,
-  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}
-        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}
-        onCancel={() => {
-          setShowCashoutDetails(undefined);
-        }}
-      />
-    );
-  }
-  if (updatePassword) {
-    return (
-      <UpdateAccountPassword
-        account={account}
-        onUpdateSuccess={() => {
-          notifyInfo(i18n.str`Password changed`);
-          setUpdatePassword(false);
-        }}
-        onCancel={() => {
-          setUpdatePassword(false);
-        }}
-      />
-    );
-  }
-  return (
-    <div>
-      <ShowAccountDetails
-        account={account}
-        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;
@@ -167,11 +65,11 @@ type ErrorFrom<T> = {
 };
 
 
-function CreateCashout({
+export function CreateCashout({
   account: accountName,
   onComplete,
   onCancel,
-}: PropsCashout): VNode {
+}: Props): VNode {
   const { i18n } = useTranslationContext();
   const resultRatios = useRatiosAndFeeConfig();
   const resultAccount = useAccountDetails(accountName);
@@ -184,6 +82,17 @@ function CreateCashout({
   const { api, config } = useBankCoreApiContext()
   const [form, setForm] = useState<Partial<FormType>>({ isDebit: true });
 
+  if (!config.have_cashout) {
+    return <Attention type="warning" title={i18n.str`Unable to create a 
cashout`} onClose={onCancel}>
+      <i18n.Translate>The bank configuration does not support cashout 
operations.</i18n.Translate>
+    </Attention>
+  }
+  if (!config.fiat_currency) {
+    return <Attention type="warning" title={i18n.str`Unable to create a 
cashout`} onClose={onCancel}>
+      <i18n.Translate>The bank configuration support cashout operations but 
there is no fiat currency.</i18n.Translate>
+    </Attention>
+  }
+
   if (!resultAccount || !resultRatios) {
     return <Loading />
   }
@@ -207,9 +116,6 @@ function CreateCashout({
       default: assertUnreachable(resultRatios.case)
     }
   }
-  if (!config.fiat_currency) {
-    return <div>cashout operations are not supported</div>
-  }
 
   const ratio = resultRatios.body
 
@@ -514,223 +420,3 @@ function CreateCashout({
     </div>
   );
 }
-
-interface ShowCashoutProps {
-  id: string;
-  onCancel: () => void;
-}
-export function ShowCashoutDetails({
-  id,
-  onCancel,
-}: ShowCashoutProps): VNode {
-  const { i18n } = useTranslationContext();
-  const { state } = useBackendState();
-  const creds = state.status !== "loggedIn" ? undefined : state
-  const { api } = useBankCoreApiContext()
-  const result = useCashoutDetails(id);
-  const [code, setCode] = useState<string | undefined>(undefined);
-
-  if (!result) {
-    return <Loading />
-  }
-  if (result instanceof TalerError) {
-    return <ErrorLoading error={result} />
-  }
-  if (result.type === "fail") {
-    switch (result.case) {
-      case "already-aborted": return <div>this cashout is already aborted</div>
-      default: assertUnreachable(result.case)
-    }
-  }
-  const errors = undefinedIfEmpty({
-    code: !code ? i18n.str`required` : undefined,
-  });
-  const isPending = String(result.body.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.body.subject} />
-        </fieldset>
-        <fieldset>
-          <label>
-            <i18n.Translate>Created</i18n.Translate>
-          </label>
-          <input readOnly value={result.body.creation_time.t_s === "never" ? 
i18n.str`never` : format(result.body.creation_time.t_s, "dd/MM/yyyy HH:mm:ss")} 
/>
-        </fieldset>
-        <fieldset>
-          <label>
-            <i18n.Translate>Confirmed</i18n.Translate>
-          </label>
-          <input readOnly value={result.body.confirmation_time === undefined ? 
"-" :
-            (result.body.confirmation_time.t_s === "never" ?
-              i18n.str`never` :
-              format(result.body.confirmation_time.t_s, "dd/MM/yyyy HH:mm:ss"))
-          } />
-        </fieldset>
-        <fieldset>
-          <label>
-            <i18n.Translate>Debited</i18n.Translate>
-          </label>
-          <input readOnly value={result.body.amount_debit} />
-        </fieldset>
-        <fieldset>
-          <label>
-            <i18n.Translate>Credit</i18n.Translate>
-          </label>
-          <input readOnly value={result.body.amount_credit} />
-        </fieldset>
-        <fieldset>
-          <label>
-            <i18n.Translate>Status</i18n.Translate>
-          </label>
-          <input readOnly value={result.body.status} />
-        </fieldset>
-        <fieldset>
-          <label>
-            <i18n.Translate>Destination</i18n.Translate>
-          </label>
-          <input readOnly value={result.body.credit_payto_uri} />
-        </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();
-                if (!creds) return;
-                await withRuntimeErrorHandling(i18n, async () => {
-                  const resp = await api.abortCashoutById(creds, id);
-                  if (resp.type === "ok") {
-                    onCancel();
-                  } else {
-                    switch (resp.case) {
-                      case "not-found": return notify({
-                        type: "error",
-                        title: i18n.str`Cashout not found. It may be also mean 
that it was already aborted.`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      })
-                      case "already-confirmed": return notify({
-                        type: "error",
-                        title: i18n.str`Cashout was already confimed.`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      })
-                      default: {
-                        assertUnreachable(resp)
-                      }
-                    }
-                  }
-                })
-              }}
-            >
-              {i18n.str`Abort`}
-            </button>
-            &nbsp;
-            <button
-              type="submit"
-              disabled={!code}
-              class="pure-button pure-button-primary "
-              onClick={async (e) => {
-                e.preventDefault();
-                if (!creds || !code) return;
-                await withRuntimeErrorHandling(i18n, async () => {
-                  const resp = await api.confirmCashoutById(creds, id, {
-                    tan: code,
-                  });
-                  if (resp.type === "ok") {
-                    mutate(() => true)//clean cashout state
-                  } else {
-                    switch (resp.case) {
-                      case "not-found": return notify({
-                        type: "error",
-                        title: i18n.str`Cashout not found. It may be also mean 
that it was already aborted.`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      })
-                      case "wrong-tan-or-credential": return notify({
-                        type: "error",
-                        title: i18n.str`Invalid code or credentials.`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      })
-                      case "cashout-address-changed": return notify({
-                        type: "error",
-                        title: i18n.str`The cash-out address between the 
creation and the confirmation changed.`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      })
-                      default: assertUnreachable(resp)
-                    }
-                  }                  
-                })
-              }}
-            >
-              {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/business/ShowCashoutDetails.tsx 
b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
new file mode 100644
index 000000000..a8e34e4b9
--- /dev/null
+++ b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
@@ -0,0 +1,237 @@
+/*
+ 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 {
+  TalerError,
+  TranslatedString
+} from "@gnu-taler/taler-util";
+import {
+  notify,
+  useTranslationContext
+} from "@gnu-taler/web-util/browser";
+import { format } from "date-fns";
+import { Fragment, VNode, h } from "preact";
+import { useState } from "preact/hooks";
+import { mutate } from "swr";
+import { ErrorLoading } from "../../components/ErrorLoading.js";
+import { Loading } from "../../components/Loading.js";
+import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
+import { useBankCoreApiContext } from "../../context/config.js";
+import { useBackendState } from "../../hooks/backend.js";
+import {
+  useCashoutDetails
+} from "../../hooks/circuit.js";
+import {
+  undefinedIfEmpty,
+  withRuntimeErrorHandling
+} from "../../utils.js";
+import { assertUnreachable } from "../HomePage.js";
+
+interface Props {
+  id: string;
+  onCancel: () => void;
+}
+export function ShowCashoutDetails({
+  id,
+  onCancel,
+}: Props): VNode {
+  const { i18n } = useTranslationContext();
+  const { state } = useBackendState();
+  const creds = state.status !== "loggedIn" ? undefined : state
+  const { api } = useBankCoreApiContext()
+  const result = useCashoutDetails(id);
+  const [code, setCode] = useState<string | undefined>(undefined);
+
+  if (!result) {
+    return <Loading />
+  }
+  if (result instanceof TalerError) {
+    return <ErrorLoading error={result} />
+  }
+  if (result.type === "fail") {
+    switch (result.case) {
+      case "already-aborted": return <div>this cashout is already aborted</div>
+      default: assertUnreachable(result.case)
+    }
+  }
+  const errors = undefinedIfEmpty({
+    code: !code ? i18n.str`required` : undefined,
+  });
+  const isPending = String(result.body.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.body.subject} />
+        </fieldset>
+        <fieldset>
+          <label>
+            <i18n.Translate>Created</i18n.Translate>
+          </label>
+          <input readOnly value={result.body.creation_time.t_s === "never" ? 
i18n.str`never` : format(result.body.creation_time.t_s, "dd/MM/yyyy HH:mm:ss")} 
/>
+        </fieldset>
+        <fieldset>
+          <label>
+            <i18n.Translate>Confirmed</i18n.Translate>
+          </label>
+          <input readOnly value={result.body.confirmation_time === undefined ? 
"-" :
+            (result.body.confirmation_time.t_s === "never" ?
+              i18n.str`never` :
+              format(result.body.confirmation_time.t_s, "dd/MM/yyyy HH:mm:ss"))
+          } />
+        </fieldset>
+        <fieldset>
+          <label>
+            <i18n.Translate>Debited</i18n.Translate>
+          </label>
+          <input readOnly value={result.body.amount_debit} />
+        </fieldset>
+        <fieldset>
+          <label>
+            <i18n.Translate>Credit</i18n.Translate>
+          </label>
+          <input readOnly value={result.body.amount_credit} />
+        </fieldset>
+        <fieldset>
+          <label>
+            <i18n.Translate>Status</i18n.Translate>
+          </label>
+          <input readOnly value={result.body.status} />
+        </fieldset>
+        <fieldset>
+          <label>
+            <i18n.Translate>Destination</i18n.Translate>
+          </label>
+          <input readOnly value={result.body.credit_payto_uri} />
+        </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();
+                if (!creds) return;
+                await withRuntimeErrorHandling(i18n, async () => {
+                  const resp = await api.abortCashoutById(creds, id);
+                  if (resp.type === "ok") {
+                    onCancel();
+                  } else {
+                    switch (resp.case) {
+                      case "not-found": return notify({
+                        type: "error",
+                        title: i18n.str`Cashout not found. It may be also mean 
that it was already aborted.`,
+                        description: resp.detail.hint as TranslatedString,
+                        debug: resp.detail,
+                      })
+                      case "already-confirmed": return notify({
+                        type: "error",
+                        title: i18n.str`Cashout was already confimed.`,
+                        description: resp.detail.hint as TranslatedString,
+                        debug: resp.detail,
+                      })
+                      default: {
+                        assertUnreachable(resp)
+                      }
+                    }
+                  }
+                })
+              }}
+            >
+              {i18n.str`Abort`}
+            </button>
+            &nbsp;
+            <button
+              type="submit"
+              disabled={!code}
+              class="pure-button pure-button-primary "
+              onClick={async (e) => {
+                e.preventDefault();
+                if (!creds || !code) return;
+                await withRuntimeErrorHandling(i18n, async () => {
+                  const resp = await api.confirmCashoutById(creds, id, {
+                    tan: code,
+                  });
+                  if (resp.type === "ok") {
+                    mutate(() => true)//clean cashout state
+                  } else {
+                    switch (resp.case) {
+                      case "not-found": return notify({
+                        type: "error",
+                        title: i18n.str`Cashout not found. It may be also mean 
that it was already aborted.`,
+                        description: resp.detail.hint as TranslatedString,
+                        debug: resp.detail,
+                      })
+                      case "wrong-tan-or-credential": return notify({
+                        type: "error",
+                        title: i18n.str`Invalid code or credentials.`,
+                        description: resp.detail.hint as TranslatedString,
+                        debug: resp.detail,
+                      })
+                      case "cashout-address-changed": return notify({
+                        type: "error",
+                        title: i18n.str`The cash-out address between the 
creation and the confirmation changed.`,
+                        description: resp.detail.hint as TranslatedString,
+                        debug: resp.detail,
+                      })
+                      default: assertUnreachable(resp)
+                    }
+                  }
+                })
+              }}
+            >
+              {i18n.str`Confirm`}
+            </button>
+          </div>
+        ) : (
+          <div />
+        )}
+      </div>
+    </div>
+  );
+}
diff --git a/packages/demobank-ui/src/route.ts 
b/packages/demobank-ui/src/route.ts
new file mode 100644
index 000000000..d54f9be83
--- /dev/null
+++ b/packages/demobank-ui/src/route.ts
@@ -0,0 +1,167 @@
+import { createHashHistory } from "history";
+import { h as create, VNode } from "preact";
+import { useEffect, useState } from "preact/hooks";
+const history = createHashHistory();
+
+type PageDefinition<DynamicPart extends Record<string, string>> = {
+  pattern: string;
+  (params: DynamicPart): string;
+};
+
+function replaceAll(
+  pattern: string,
+  vars: Record<string, string>,
+  values: Record<string, string>,
+): string {
+  let result = pattern;
+  for (const v in vars) {
+    result = result.replace(vars[v], !values[v] ? "" : values[v]);
+  }
+  return result;
+}
+
+export function pageDefinition<T extends Record<string, string>>(
+  pattern: string,
+): PageDefinition<T> {
+  const patternParams = pattern.match(/(:[\w?]*)/g);
+  if (!patternParams)
+    throw Error(
+      `page definition pattern ${pattern} doesn't have any parameter`,
+    );
+
+  const vars = patternParams.reduce((prev, cur) => {
+    const pName = cur.match(/(\w+)/g);
+
+    //skip things like :? in the path pattern
+    if (!pName || !pName[0]) return prev;
+    const name = pName[0];
+    return { ...prev, [name]: cur };
+  }, {} as Record<string, string>);
+
+  const f = (values: T): string => replaceAll(pattern, vars, values);
+  f.pattern = pattern;
+  return f;
+}
+
+export type PageEntry<T = unknown> = T extends Record<string, string>
+  ? {
+      url: PageDefinition<T>;
+      view: (props: T) => VNode;
+    }
+  : T extends unknown
+  ? {
+      url: string;
+      view: (props: {}) => VNode;
+    }
+  : never;
+
+export function Router({
+  pageList,
+  onNotFound,
+}: {
+  pageList: Array<PageEntry<any>>;
+  onNotFound: () => VNode;
+}): VNode {
+  const current = useCurrentLocation(pageList);
+  if (current !== undefined) {
+    return create(current.page.view, current.values);
+  }
+  return onNotFound();
+}
+
+type Location = {
+  page: PageEntry<any>;
+  path: string;
+  values: Record<string, string>;
+};
+export function useCurrentLocation(pageList: Array<PageEntry<any>>) {
+  const [currentLocation, setCurrentLocation] = useState<Location>();
+  /**
+   * Search path in the pageList
+   * get the values from the path found
+   * add params from searchParams
+   *
+   * @param path
+   * @param params
+   */
+  function doSync(path: string, params: URLSearchParams) {
+    let result: typeof currentLocation;
+    for (let idx = 0; idx < pageList.length; idx++) {
+      const page = pageList[idx];
+      if (typeof page.url === "string") {
+        if (page.url === path) {
+          const values: Record<string, string> = {};
+          params.forEach((v, k) => {
+            values[k] = v;
+          });
+          result = { page, values, path };
+          break;
+        }
+      } else {
+        const values = doestUrlMatchToRoute(path, page.url.pattern);
+        if (values !== undefined) {
+          params.forEach((v, k) => {
+            values[k] = v;
+          });
+          result = { page, values, path };
+          break;
+        }
+      }
+    }
+    setCurrentLocation(result);
+  }
+  useEffect(() => {
+    doSync(window.location.hash, new URLSearchParams(window.location.search));
+    return history.listen(() => {
+      doSync(window.location.hash, new 
URLSearchParams(window.location.search));
+    });
+  }, []);
+  return currentLocation;
+}
+
+function doestUrlMatchToRoute(
+  url: string,
+  route: string,
+): undefined | Record<string, string> {
+  const paramsPattern = /(?:\?([^#]*))?$/;
+  // const paramsPattern = /(?:\?([^#]*))?(#.*)?$/;
+  const params = url.match(paramsPattern);
+  const urlWithoutParams = url.replace(paramsPattern, "");
+
+  const result: Record<string, string> = {};
+  if (params && params[1]) {
+    const paramList = params[1].split("&");
+    for (let i = 0; i < paramList.length; i++) {
+      const idx = paramList[i].indexOf("=");
+      const name = paramList[i].substring(0, idx);
+      const value = paramList[i].substring(idx + 1);
+      result[decodeURIComponent(name)] = decodeURIComponent(value);
+    }
+  }
+  const urlSeg = urlWithoutParams.split("/");
+  const routeSeg = route.split("/");
+  let max = Math.max(urlSeg.length, routeSeg.length);
+  for (let i = 0; i < max; i++) {
+    if (routeSeg[i] && routeSeg[i].charAt(0) === ":") {
+      const param = routeSeg[i].replace(/(^:|[+*?]+$)/g, "");
+
+      const flags = (routeSeg[i].match(/[+*?]+$/) || EMPTY)[0] || "";
+      const plus = ~flags.indexOf("+");
+      const star = ~flags.indexOf("*");
+      const val = urlSeg[i] || "";
+
+      if (!val && !star && (flags.indexOf("?") < 0 || plus)) {
+        return undefined;
+      }
+      result[param] = decodeURIComponent(val);
+      if (plus || star) {
+        result[param] = urlSeg.slice(i).map(decodeURIComponent).join("/");
+        break;
+      }
+    } else if (routeSeg[i] !== urlSeg[i]) {
+      return undefined;
+    }
+  }
+  return result;
+}
+const EMPTY: Record<string, string> = {};

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



reply via email to

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