gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] 06/20: admin refactor


From: gnunet
Subject: [taler-wallet-core] 06/20: admin refactor
Date: Mon, 25 Sep 2023 19:51:10 +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 062939d9cc016a186a282f7a48492c3e01cd740c
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Thu Sep 21 10:31:10 2023 -0300

    admin refactor
---
 packages/demobank-ui/src/components/Routing.tsx    |   13 +-
 packages/demobank-ui/src/pages/AdminPage.tsx       | 1042 --------------------
 packages/demobank-ui/src/pages/HomePage.tsx        |   19 +-
 .../demobank-ui/src/pages/ShowAccountDetails.tsx   |  143 +++
 .../src/pages/UpdateAccountPassword.tsx            |  131 +++
 .../src/pages/WithdrawalConfirmationQuestion.tsx   |    3 +-
 .../demobank-ui/src/pages/WithdrawalQRCode.tsx     |    4 -
 packages/demobank-ui/src/pages/admin/Account.tsx   |   56 ++
 .../demobank-ui/src/pages/admin/AccountForm.tsx    |  219 ++++
 .../demobank-ui/src/pages/admin/AccountList.tsx    |  120 +++
 .../src/pages/admin/CreateNewAccount.tsx           |  107 ++
 packages/demobank-ui/src/pages/admin/Home.tsx      |  162 +++
 .../demobank-ui/src/pages/admin/RemoveAccount.tsx  |  112 +++
 .../{BusinessAccount.tsx => business/Home.tsx}     |   35 +-
 14 files changed, 1083 insertions(+), 1083 deletions(-)

diff --git a/packages/demobank-ui/src/components/Routing.tsx 
b/packages/demobank-ui/src/components/Routing.tsx
index 890058a9b..ef11af76e 100644
--- a/packages/demobank-ui/src/components/Routing.tsx
+++ b/packages/demobank-ui/src/components/Routing.tsx
@@ -19,14 +19,14 @@ import { VNode, h } from "preact";
 import { Route, Router, route } from "preact-router";
 import { useEffect } from "preact/hooks";
 import { BankFrame } from "../pages/BankFrame.js";
-import { BusinessAccount } from "../pages/BusinessAccount.js";
+import { BusinessAccount } from "../pages/business/Home.js";
 import { HomePage, WithdrawalOperationPage } from "../pages/HomePage.js";
 import { PublicHistoriesPage } from "../pages/PublicHistoriesPage.js";
 import { RegistrationPage } from "../pages/RegistrationPage.js";
 import { Test } from "../pages/Test.js";
 import { useBackendContext } from "../context/backend.js";
 import { LoginForm } from "../pages/LoginForm.js";
-import { AdminPage } from "../pages/AdminPage.js";
+import { AdminHome } from "../pages/admin/Home.js";
 
 export function Routing(): VNode {
   const history = createHashHistory();
@@ -34,6 +34,7 @@ export function Routing(): VNode {
 
   if (backend.state.status === "loggedOut") {
     return <BankFrame
+      account={undefined}
       goToBusinessAccount={() => {
         route("/business");
       }}
@@ -63,7 +64,7 @@ export function Routing(): VNode {
       </Router>
     </BankFrame>
   }
-  const isAdmin = backend.state.isUserAdministrator
+  const { isUserAdministrator, username } = backend.state
 
   return (
     <BankFrame
@@ -108,14 +109,15 @@ export function Routing(): VNode {
         <Route
           path="/account"
           component={() => {
-            if (isAdmin) {
-              return <AdminPage
+            if (isUserAdministrator) {
+              return <AdminHome
                 onRegister={() => {
                   route("/register");
                 }}
               />;
             } else {
               return <HomePage
+                account={username}
                 onPendingOperationFound={(wopid) => {
                   route(`/operation/${wopid}`);
                 }}
@@ -130,6 +132,7 @@ export function Routing(): VNode {
           path="/business"
           component={() => (
             <BusinessAccount
+              account={username}
               onClose={() => {
                 route("/account");
               }}
diff --git a/packages/demobank-ui/src/pages/AdminPage.tsx 
b/packages/demobank-ui/src/pages/AdminPage.tsx
deleted file mode 100644
index 18462bdc3..000000000
--- a/packages/demobank-ui/src/pages/AdminPage.tsx
+++ /dev/null
@@ -1,1042 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-import { Amounts, HttpStatusCode, TranslatedString, parsePaytoUri } from 
"@gnu-taler/taler-util";
-import {
-  ErrorType,
-  HttpResponsePaginated,
-  RequestError,
-  notify,
-  notifyError,
-  notifyInfo,
-  useTranslationContext,
-} from "@gnu-taler/web-util/browser";
-import { Fragment, h, VNode } from "preact";
-import { useState } from "preact/hooks";
-import { Cashouts } from "../components/Cashouts/index.js";
-import { useBackendContext } from "../context/backend.js";
-import { useAccountDetails } from "../hooks/access.js";
-import {
-  useAdminAccountAPI,
-  useBusinessAccountDetails,
-  useBusinessAccounts,
-} from "../hooks/circuit.js";
-import {
-  buildRequestErrorMessage,
-  PartialButDefined,
-  RecursivePartial,
-  undefinedIfEmpty,
-  validateIBAN,
-  WithIntermediate,
-} from "../utils.js";
-import { ShowCashoutDetails } from "./BusinessAccount.js";
-import { handleNotOkResult } from "./HomePage.js";
-import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js";
-import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
-
-const charset =
-  "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
-const upperIdx = charset.indexOf("A");
-
-function randomPassword(): string {
-  const random = Array.from({ length: 16 }).map(() => {
-    return charset.charCodeAt(Math.random() * charset.length);
-  });
-  // first char can't be upper
-  const charIdx = charset.indexOf(String.fromCharCode(random[0]));
-  random[0] =
-    charIdx > upperIdx ? charset.charCodeAt(charIdx - upperIdx) : random[0];
-  return String.fromCharCode(...random);
-}
-
-interface Props {
-  onRegister: () => void;
-}
-/**
- * Query account information and show QR code if there is pending withdrawal
- */
-export function AdminPage({ onRegister }: Props): VNode {
-  const [account, setAccount] = useState<string | undefined>();
-  const [showDetails, setShowDetails] = useState<string | undefined>();
-  const [showCashouts, setShowCashouts] = useState<string | undefined>();
-  const [updatePassword, setUpdatePassword] = useState<string | undefined>();
-  const [removeAccount, setRemoveAccount] = useState<string | undefined>();
-  const [showCashoutDetails, setShowCashoutDetails] = useState<
-    string | undefined
-  >();
-
-  const [createAccount, setCreateAccount] = useState(false);
-
-  const result = useBusinessAccounts({ account });
-  const { i18n } = useTranslationContext();
-
-  if (result.loading) return <div />;
-  if (!result.ok) {
-    return handleNotOkResult(i18n, onRegister)(result);
-  }
-
-  const { customers } = result.data;
-
-  if (showCashoutDetails) {
-    return (
-      <ShowCashoutDetails
-        id={showCashoutDetails}
-        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
-        onCancel={() => {
-          setShowCashoutDetails(undefined);
-        }}
-      />
-    );
-  }
-
-  if (showCashouts) {
-    return (
-      <div>
-        <div>
-          <h1 class="nav welcome-text">
-            <i18n.Translate>Cashout for account {showCashouts}</i18n.Translate>
-          </h1>
-        </div>
-        <Cashouts
-          account={showCashouts}
-          onSelected={(id) => {
-            setShowCashouts(id);
-            setShowCashouts(undefined);
-          }}
-        />
-        <p>
-          <input
-            class="pure-button"
-            type="submit"
-            value={i18n.str`Close`}
-            onClick={async (e) => {
-              e.preventDefault();
-              setShowCashouts(undefined);
-            }}
-          />
-        </p>
-      </div>
-    );
-  }
-
-  if (showDetails) {
-    return (
-      <ShowAccountDetails
-        account={showDetails}
-        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
-        onChangePassword={() => {
-          setUpdatePassword(showDetails);
-          setShowDetails(undefined);
-        }}
-        onUpdateSuccess={() => {
-          notifyInfo(i18n.str`Account updated`);
-          setShowDetails(undefined);
-        }}
-        onClear={() => {
-          setShowDetails(undefined);
-        }}
-      />
-    );
-  }
-  if (removeAccount) {
-    return (
-      <RemoveAccount
-        account={removeAccount}
-        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
-        onUpdateSuccess={() => {
-          notifyInfo(i18n.str`Account removed`);
-          setRemoveAccount(undefined);
-        }}
-        onClear={() => {
-          setRemoveAccount(undefined);
-        }}
-      />
-    );
-  }
-  if (updatePassword) {
-    return (
-      <UpdateAccountPassword
-        account={updatePassword}
-        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
-        onUpdateSuccess={() => {
-          notifyInfo(i18n.str`Password changed`);
-          setUpdatePassword(undefined);
-        }}
-        onClear={() => {
-          setUpdatePassword(undefined);
-        }}
-      />
-    );
-  }
-  if (createAccount) {
-    return (
-      <CreateNewAccount
-        onClose={() => setCreateAccount(false)}
-        onCreateSuccess={(password) => {
-          notifyInfo(
-            i18n.str`Account created with password "${password}". The user 
must change the password on the next login.`,
-          );
-          setCreateAccount(false);
-        }}
-      />
-    );
-  }
-
-  return (
-    <Fragment>
-      <div>
-        <h1 class="nav welcome-text">
-          <i18n.Translate>Admin panel</i18n.Translate>
-        </h1>
-      </div>
-
-      <p>
-        <div style={{ display: "flex", justifyContent: "space-between" }}>
-          <div></div>
-          <div>
-            <input
-              class="pure-button pure-button-primary content"
-              type="submit"
-              value={i18n.str`Create account`}
-              onClick={async (e) => {
-                e.preventDefault();
-
-                setCreateAccount(true);
-              }}
-            />
-          </div>
-        </div>
-      </p>
-
-      <AdminAccount onRegister={onRegister} />
-      <section
-        id="main"
-        style={{ width: 600, marginLeft: "auto", marginRight: "auto" }}
-      >
-        {!customers.length ? (
-          <div></div>
-        ) : (
-          <article>
-            <h2>{i18n.str`Accounts:`}</h2>
-            <div class="results">
-              <table class="pure-table pure-table-striped">
-                <thead>
-                  <tr>
-                    <th>{i18n.str`Username`}</th>
-                    <th>{i18n.str`Name`}</th>
-                    <th>{i18n.str`Balance`}</th>
-                    <th>{i18n.str`Actions`}</th>
-                  </tr>
-                </thead>
-                <tbody>
-                  {customers.map((item, idx) => {
-                    const balance = !item.balance
-                      ? undefined
-                      : Amounts.parse(item.balance.amount);
-                    const balanceIsDebit =
-                      item.balance &&
-                      item.balance.credit_debit_indicator == "debit";
-                    return (
-                      <tr key={idx}>
-                        <td>
-                          <a
-                            href="#"
-                            onClick={(e) => {
-                              e.preventDefault();
-                              setShowDetails(item.username);
-                            }}
-                          >
-                            {item.username}
-                          </a>
-                        </td>
-                        <td>{item.name}</td>
-                        <td>
-                          {!balance ? (
-                            i18n.str`unknown`
-                          ) : (
-                            <span class="amount">
-                              {balanceIsDebit ? <b>-</b> : null}
-                              <span class="value">{`${Amounts.stringifyValue(
-                                balance,
-                              )}`}</span>
-                              &nbsp;
-                              <span 
class="currency">{`${balance.currency}`}</span>
-                            </span>
-                          )}
-                        </td>
-                        <td>
-                          <a
-                            href="#"
-                            onClick={(e) => {
-                              e.preventDefault();
-                              setUpdatePassword(item.username);
-                            }}
-                          >
-                            change password
-                          </a>
-                          &nbsp;
-                          <a
-                            href="#"
-                            onClick={(e) => {
-                              e.preventDefault();
-                              setShowCashouts(item.username);
-                            }}
-                          >
-                            cashouts
-                          </a>
-                          &nbsp;
-                          <a
-                            href="#"
-                            onClick={(e) => {
-                              e.preventDefault();
-                              setRemoveAccount(item.username);
-                            }}
-                          >
-                            remove
-                          </a>
-                        </td>
-                      </tr>
-                    );
-                  })}
-                </tbody>
-              </table>
-            </div>
-          </article>
-        )}
-      </section>
-    </Fragment>
-  );
-}
-
-function AdminAccount({ onRegister }: { onRegister: () => void }): VNode {
-  const { i18n } = useTranslationContext();
-  const r = useBackendContext();
-  const account = r.state.status === "loggedIn" ? r.state.username : "admin";
-  const result = useAccountDetails(account);
-
-  if (!result.ok) {
-    return handleNotOkResult(i18n, onRegister)(result);
-  }
-  const { data } = result;
-  const balance = Amounts.parseOrThrow(data.balance.amount);
-  const debitThreshold = Amounts.parseOrThrow(result.data.debitThreshold);
-  const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit";
-  const limit = balanceIsDebit
-    ? Amounts.sub(debitThreshold, balance).amount
-    : Amounts.add(balance, debitThreshold).amount;
-  if (!balance) return <Fragment />;
-  return (
-    <Fragment>
-      <section id="assets">
-        <div class="asset-summary">
-          <h2>{i18n.str`Bank account balance`}</h2>
-          {!balance ? (
-            <div class="large-amount" style={{ color: "gray" }}>
-              Waiting server response...
-            </div>
-          ) : (
-            <div class="large-amount amount">
-              {balanceIsDebit ? <b>-</b> : null}
-              <span class="value">{`${Amounts.stringifyValue(balance)}`}</span>
-              &nbsp;
-              <span class="currency">{`${balance.currency}`}</span>
-            </div>
-          )}
-        </div>
-      </section>
-      <PaytoWireTransferForm
-        focus
-        limit={limit}
-        onSuccess={() => {
-          notifyInfo(i18n.str`Wire transfer created!`);
-        }}
-        onCancel={undefined}
-      />
-    </Fragment>
-  );
-}
-
-const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/;
-const EMAIL_REGEX =
-  
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
-const REGEX_JUST_NUMBERS_REGEX = /^\+[0-9 ]*$/;
-
-function initializeFromTemplate(
-  account: SandboxBackend.Circuit.CircuitAccountData | undefined,
-): WithIntermediate<SandboxBackend.Circuit.CircuitAccountData> {
-  const emptyAccount = {
-    cashout_address: undefined,
-    iban: undefined,
-    name: undefined,
-    username: undefined,
-    contact_data: undefined,
-  };
-  const emptyContact = {
-    email: undefined,
-    phone: undefined,
-  };
-
-  const initial: PartialButDefined<SandboxBackend.Circuit.CircuitAccountData> =
-    structuredClone(account) ?? emptyAccount;
-  if (typeof initial.contact_data === "undefined") {
-    initial.contact_data = emptyContact;
-  }
-  initial.contact_data.email;
-  return initial as any;
-}
-
-export function UpdateAccountPassword({
-  account,
-  onClear,
-  onUpdateSuccess,
-  onLoadNotOk,
-}: {
-  onLoadNotOk: <T>(
-    error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
-  ) => VNode;
-  onClear: () => void;
-  onUpdateSuccess: () => void;
-  account: string;
-}): VNode {
-  const { i18n } = useTranslationContext();
-  const result = useBusinessAccountDetails(account);
-  const { changePassword } = useAdminAccountAPI();
-  const [password, setPassword] = useState<string | undefined>();
-  const [repeat, setRepeat] = useState<string | undefined>();
-
-  if (!result.ok) {
-    if (result.loading || result.type === ErrorType.TIMEOUT) {
-      return onLoadNotOk(result);
-    }
-    if (result.status === HttpStatusCode.NotFound) {
-      return <div>account not found</div>;
-    }
-    return onLoadNotOk(result);
-  }
-
-  const errors = undefinedIfEmpty({
-    password: !password ? i18n.str`required` : undefined,
-    repeat: !repeat
-      ? i18n.str`required`
-      : password !== repeat
-        ? i18n.str`password doesn't match`
-        : undefined,
-  });
-
-  return (
-    <div>
-      <div>
-        <h1 class="nav welcome-text">
-          <i18n.Translate>Update password for {account}</i18n.Translate>
-        </h1>
-      </div>
-
-      <div style={{ maxWidth: 600, overflowX: "hidden", margin: "auto" }}>
-        <form class="pure-form">
-          <fieldset>
-            <label>{i18n.str`Password`}</label>
-            <input
-              type="password"
-              value={password ?? ""}
-              onChange={(e) => {
-                setPassword(e.currentTarget.value);
-              }}
-            />
-            <ShowInputErrorLabel
-              message={errors?.password}
-              isDirty={password !== undefined}
-            />
-          </fieldset>
-          <fieldset>
-            <label>{i18n.str`Repeat password`}</label>
-            <input
-              type="password"
-              value={repeat ?? ""}
-              onChange={(e) => {
-                setRepeat(e.currentTarget.value);
-              }}
-            />
-            <ShowInputErrorLabel
-              message={errors?.repeat}
-              isDirty={repeat !== undefined}
-            />
-          </fieldset>
-        </form>
-        <p>
-          <div style={{ display: "flex", justifyContent: "space-between" }}>
-            <div>
-              <input
-                class="pure-button"
-                type="submit"
-                value={i18n.str`Close`}
-                onClick={async (e) => {
-                  e.preventDefault();
-                  onClear();
-                }}
-              />
-            </div>
-            <div>
-              <input
-                id="select-exchange"
-                class="pure-button pure-button-primary content"
-                disabled={!!errors}
-                type="submit"
-                value={i18n.str`Confirm`}
-                onClick={async (e) => {
-                  e.preventDefault();
-                  if (!!errors || !password) return;
-                  try {
-                    const r = await changePassword(account, {
-                      new_password: password,
-                    });
-                    onUpdateSuccess();
-                  } catch (error) {
-                    if (error instanceof RequestError) {
-                      notify(buildRequestErrorMessage(i18n, error.cause));
-                    } else {
-                      notifyError(i18n.str`Operation failed, please report`, 
(error instanceof Error
-                        ? error.message
-                        : JSON.stringify(error)) as TranslatedString)
-                    }
-                  }
-                }}
-              />
-            </div>
-          </div>
-        </p>
-      </div>
-    </div>
-  );
-}
-
-function CreateNewAccount({
-  onClose,
-  onCreateSuccess,
-}: {
-  onClose: () => void;
-  onCreateSuccess: (password: string) => void;
-}): VNode {
-  const { i18n } = useTranslationContext();
-  const { createAccount } = useAdminAccountAPI();
-  const [submitAccount, setSubmitAccount] = useState<
-    SandboxBackend.Circuit.CircuitAccountData | undefined
-  >();
-  return (
-    <div>
-      <div>
-        <h1 class="nav welcome-text">
-          <i18n.Translate>New account</i18n.Translate>
-        </h1>
-      </div>
-
-      <div style={{ maxWidth: 600, overflowX: "hidden", margin: "auto" }}>
-        <AccountForm
-          template={undefined}
-          purpose="create"
-          onChange={(a) => {
-            setSubmitAccount(a);
-          }}
-        />
-
-        <p>
-          <div style={{ display: "flex", justifyContent: "space-between" }}>
-            <div>
-              <input
-                class="pure-button"
-                type="submit"
-                value={i18n.str`Close`}
-                onClick={async (e) => {
-                  e.preventDefault();
-                  onClose();
-                }}
-              />
-            </div>
-            <div>
-              <input
-                id="select-exchange"
-                class="pure-button pure-button-primary content"
-                disabled={!submitAccount}
-                type="submit"
-                value={i18n.str`Confirm`}
-                onClick={async (e) => {
-                  e.preventDefault();
-
-                  if (!submitAccount) return;
-                  try {
-                    const account: 
SandboxBackend.Circuit.CircuitAccountRequest =
-                    {
-                      cashout_address: submitAccount.cashout_address,
-                      contact_data: submitAccount.contact_data,
-                      internal_iban: submitAccount.iban,
-                      name: submitAccount.name,
-                      username: submitAccount.username,
-                      password: randomPassword(),
-                    };
-
-                    await createAccount(account);
-                    onCreateSuccess(account.password);
-                  } catch (error) {
-                    if (error instanceof RequestError) {
-                      notify(
-                        buildRequestErrorMessage(i18n, error.cause, {
-                          onClientError: (status) =>
-                            status === HttpStatusCode.Forbidden
-                              ? i18n.str`The rights to perform the operation 
are not sufficient`
-                              : status === HttpStatusCode.BadRequest
-                                ? i18n.str`Input data was invalid`
-                                : status === HttpStatusCode.Conflict
-                                  ? i18n.str`At least one registration detail 
was not available`
-                                  : undefined,
-                        }),
-                      );
-                    } else {
-                      notifyError(
-                        i18n.str`Operation failed, please report`,
-                        (error instanceof Error
-                          ? error.message
-                          : JSON.stringify(error)) as TranslatedString
-                      )
-                    }
-                  }
-                }}
-              />
-            </div>
-          </div>
-        </p>
-      </div>
-    </div>
-  );
-}
-
-export function ShowAccountDetails({
-  account,
-  onClear,
-  onUpdateSuccess,
-  onLoadNotOk,
-  onChangePassword,
-}: {
-  onLoadNotOk: <T>(
-    error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
-  ) => VNode;
-  onClear?: () => void;
-  onChangePassword: () => void;
-  onUpdateSuccess: () => void;
-  account: string;
-}): VNode {
-  const { i18n } = useTranslationContext();
-  const result = useBusinessAccountDetails(account);
-  const { updateAccount } = useAdminAccountAPI();
-  const [update, setUpdate] = useState(false);
-  const [submitAccount, setSubmitAccount] = useState<
-    SandboxBackend.Circuit.CircuitAccountData | undefined
-  >();
-
-  if (!result.ok) {
-    if (result.loading || result.type === ErrorType.TIMEOUT) {
-      return onLoadNotOk(result);
-    }
-    if (result.status === HttpStatusCode.NotFound) {
-      return <div>account not found</div>;
-    }
-    return onLoadNotOk(result);
-  }
-
-  return (
-    <div>
-      <div>
-        <h1 class="nav welcome-text">
-          <i18n.Translate>Business account details</i18n.Translate>
-        </h1>
-      </div>
-      <div style={{ maxWidth: 600, overflowX: "hidden", margin: "auto" }}>
-        <AccountForm
-          template={result.data}
-          purpose={update ? "update" : "show"}
-          onChange={(a) => setSubmitAccount(a)}
-        />
-
-        <p class="buttons-account">
-          <div
-            style={{
-              display: "flex",
-              justifyContent: "space-between",
-              flexFlow: "wrap-reverse",
-            }}
-          >
-            <div>
-              {onClear ? (
-                <input
-                  class="pure-button"
-                  type="submit"
-                  value={i18n.str`Close`}
-                  onClick={async (e) => {
-                    e.preventDefault();
-                    onClear();
-                  }}
-                />
-              ) : undefined}
-            </div>
-            <div style={{ display: "flex" }}>
-              <div>
-                <input
-                  id="select-exchange"
-                  class="pure-button pure-button-primary content"
-                  disabled={update && !submitAccount}
-                  type="submit"
-                  value={i18n.str`Change password`}
-                  onClick={async (e) => {
-                    e.preventDefault();
-                    onChangePassword();
-                  }}
-                />
-              </div>
-              <div>
-                <input
-                  id="select-exchange"
-                  class="pure-button pure-button-primary content"
-                  disabled={update && !submitAccount}
-                  type="submit"
-                  value={update ? i18n.str`Confirm` : i18n.str`Update`}
-                  onClick={async (e) => {
-                    e.preventDefault();
-
-                    if (!update) {
-                      setUpdate(true);
-                    } else {
-                      if (!submitAccount) return;
-                      try {
-                        await updateAccount(account, {
-                          cashout_address: submitAccount.cashout_address,
-                          contact_data: submitAccount.contact_data,
-                        });
-                        onUpdateSuccess();
-                      } catch (error) {
-                        if (error instanceof RequestError) {
-                          notify(
-                            buildRequestErrorMessage(i18n, error.cause, {
-                              onClientError: (status) =>
-                                status === HttpStatusCode.Forbidden
-                                  ? i18n.str`The rights to change the account 
are not sufficient`
-                                  : status === HttpStatusCode.NotFound
-                                    ? i18n.str`The username was not found`
-                                    : undefined,
-                            }),
-                          );
-                        } else {
-                          notifyError(
-                            i18n.str`Operation failed, please report`,
-                            (error instanceof Error
-                              ? error.message
-                              : JSON.stringify(error)) as TranslatedString
-                          )
-                        }
-                      }
-                    }
-                  }}
-                />
-              </div>
-            </div>
-          </div>
-        </p>
-      </div>
-    </div>
-  );
-}
-
-function RemoveAccount({
-  account,
-  onClear,
-  onUpdateSuccess,
-  onLoadNotOk,
-}: {
-  onLoadNotOk: <T>(
-    error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
-  ) => VNode;
-  onClear: () => void;
-  onUpdateSuccess: () => void;
-  account: string;
-}): VNode {
-  const { i18n } = useTranslationContext();
-  const result = useAccountDetails(account);
-  const { deleteAccount } = useAdminAccountAPI();
-
-  if (!result.ok) {
-    if (result.loading || result.type === ErrorType.TIMEOUT) {
-      return onLoadNotOk(result);
-    }
-    if (result.status === HttpStatusCode.NotFound) {
-      return <div>account not found</div>;
-    }
-    return onLoadNotOk(result);
-  }
-
-  const balance = Amounts.parse(result.data.balance.amount);
-  if (!balance) {
-    return <div>there was an error reading the balance</div>;
-  }
-  const isBalanceEmpty = Amounts.isZero(balance);
-  return (
-    <div>
-      <div>
-        <h1 class="nav welcome-text">
-          <i18n.Translate>Remove account: {account}</i18n.Translate>
-        </h1>
-      </div>
-      {/* {FXME: SHOW WARNING} */}
-      {/* {!isBalanceEmpty && (
-        <ErrorBannerFloat
-          error={{
-            title: i18n.str`Can't delete the account`,
-            description: i18n.str`Balance is not empty`,
-          }}
-          onClear={() => saveError(undefined)}
-        />
-      )} */}
-
-      <p>
-        <div style={{ display: "flex", justifyContent: "space-between" }}>
-          <div>
-            <input
-              class="pure-button"
-              type="submit"
-              value={i18n.str`Cancel`}
-              onClick={async (e) => {
-                e.preventDefault();
-                onClear();
-              }}
-            />
-          </div>
-          <div>
-            <input
-              id="select-exchange"
-              class="pure-button pure-button-primary content"
-              disabled={!isBalanceEmpty}
-              type="submit"
-              value={i18n.str`Confirm`}
-              onClick={async (e) => {
-                e.preventDefault();
-                try {
-                  const r = await deleteAccount(account);
-                  onUpdateSuccess();
-                } catch (error) {
-                  if (error instanceof RequestError) {
-                    notify(
-                      buildRequestErrorMessage(i18n, error.cause, {
-                        onClientError: (status) =>
-                          status === HttpStatusCode.Forbidden
-                            ? i18n.str`The administrator specified a 
institutional username`
-                            : status === HttpStatusCode.NotFound
-                              ? i18n.str`The username was not found`
-                              : status === HttpStatusCode.PreconditionFailed
-                                ? i18n.str`Balance was not zero`
-                                : undefined,
-                      }),
-                    );
-                  } else {
-                    notifyError(i18n.str`Operation failed, please report`,
-                      (error instanceof Error
-                          ? error.message
-                          : JSON.stringify(error)) as TranslatedString);
-                  }
-                }
-              }}
-            />
-          </div>
-        </div>
-      </p>
-    </div>
-  );
-}
-/**
- * Create valid account object to update or create
- * Take template as initial values for the form
- * Purpose indicate if all field al read only (show), part of them (update)
- * or none (create)
- * @param param0
- * @returns
- */
-function AccountForm({
-  template,
-  purpose,
-  onChange,
-}: {
-  template: SandboxBackend.Circuit.CircuitAccountData | undefined;
-  onChange: (a: SandboxBackend.Circuit.CircuitAccountData | undefined) => void;
-  purpose: "create" | "update" | "show";
-}): VNode {
-  const initial = initializeFromTemplate(template);
-  const [form, setForm] = useState(initial);
-  const [errors, setErrors] = useState<
-    RecursivePartial<typeof initial> | undefined
-  >(undefined);
-  const { i18n } = useTranslationContext();
-
-  function updateForm(newForm: typeof initial): void {
-    const parsed = !newForm.cashout_address
-      ? undefined
-      : parsePaytoUri(newForm.cashout_address);
-
-    const errors = undefinedIfEmpty<RecursivePartial<typeof initial>>({
-      cashout_address: !newForm.cashout_address
-        ? i18n.str`required`
-        : !parsed
-          ? i18n.str`does not follow the pattern`
-          : !parsed.isKnown || parsed.targetType !== "iban"
-            ? i18n.str`only "IBAN" target are supported`
-            : !IBAN_REGEX.test(parsed.iban)
-              ? i18n.str`IBAN should have just uppercased letters and numbers`
-              : validateIBAN(parsed.iban, i18n),
-      contact_data: undefinedIfEmpty({
-        email: !newForm.contact_data?.email
-          ? i18n.str`required`
-          : !EMAIL_REGEX.test(newForm.contact_data.email)
-            ? i18n.str`it should be an email`
-            : undefined,
-        phone: !newForm.contact_data?.phone
-          ? i18n.str`required`
-          : !newForm.contact_data.phone.startsWith("+")
-            ? i18n.str`should start with +`
-            : !REGEX_JUST_NUMBERS_REGEX.test(newForm.contact_data.phone)
-              ? i18n.str`phone number can't have other than numbers`
-              : undefined,
-      }),
-      iban: !newForm.iban
-        ? undefined //optional field
-        : !IBAN_REGEX.test(newForm.iban)
-          ? i18n.str`IBAN should have just uppercased letters and numbers`
-          : validateIBAN(newForm.iban, i18n),
-      name: !newForm.name ? i18n.str`required` : undefined,
-      username: !newForm.username ? i18n.str`required` : undefined,
-    });
-    setErrors(errors);
-    setForm(newForm);
-    onChange(errors === undefined ? (newForm as any) : undefined);
-  }
-
-  return (
-    <form class="pure-form">
-      <fieldset>
-        <label for="username">
-          {i18n.str`Username`}
-          {purpose === "create" && <b style={{ color: "red" }}>*</b>}
-        </label>
-        <input
-          name="username"
-          type="text"
-          disabled={purpose !== "create"}
-          value={form.username}
-          onChange={(e) => {
-            form.username = e.currentTarget.value;
-            updateForm(structuredClone(form));
-          }}
-        />{" "}
-        <ShowInputErrorLabel
-          message={errors?.username}
-          isDirty={form.username !== undefined}
-        />
-      </fieldset>
-      <fieldset>
-        <label>
-          {i18n.str`Name`}
-          {purpose === "create" && <b style={{ color: "red" }}>*</b>}
-        </label>
-        <input
-          disabled={purpose !== "create"}
-          value={form.name ?? ""}
-          onChange={(e) => {
-            form.name = e.currentTarget.value;
-            updateForm(structuredClone(form));
-          }}
-        />
-        <ShowInputErrorLabel
-          message={errors?.name}
-          isDirty={form.name !== undefined}
-        />
-      </fieldset>
-      {purpose !== "create" && (
-        <fieldset>
-          <label>{i18n.str`Internal IBAN`}</label>
-          <input
-            disabled={true}
-            value={form.iban ?? ""}
-            onChange={(e) => {
-              form.iban = e.currentTarget.value;
-              updateForm(structuredClone(form));
-            }}
-          />
-          <ShowInputErrorLabel
-            message={errors?.iban}
-            isDirty={form.iban !== undefined}
-          />
-        </fieldset>
-      )}
-      <fieldset>
-        <label>
-          {i18n.str`Email`}
-          {purpose !== "show" && <b style={{ color: "red" }}>*</b>}
-        </label>
-        <input
-          disabled={purpose === "show"}
-          value={form.contact_data.email ?? ""}
-          onChange={(e) => {
-            form.contact_data.email = e.currentTarget.value;
-            updateForm(structuredClone(form));
-          }}
-        />
-        <ShowInputErrorLabel
-          message={errors?.contact_data?.email}
-          isDirty={form.contact_data.email !== undefined}
-        />
-      </fieldset>
-      <fieldset>
-        <label>
-          {i18n.str`Phone`}
-          {purpose !== "show" && <b style={{ color: "red" }}>*</b>}
-        </label>
-        <input
-          disabled={purpose === "show"}
-          value={form.contact_data.phone ?? ""}
-          onChange={(e) => {
-            form.contact_data.phone = e.currentTarget.value;
-            updateForm(structuredClone(form));
-          }}
-        />
-        <ShowInputErrorLabel
-          message={errors?.contact_data?.phone}
-          isDirty={form.contact_data?.phone !== undefined}
-        />
-      </fieldset>
-      <fieldset>
-        <label>
-          {i18n.str`Cashout address`}
-          {purpose !== "show" && <b style={{ color: "red" }}>*</b>}
-        </label>
-        <input
-          disabled={purpose === "show"}
-          value={(form.cashout_address ?? 
"").substring("payto://iban/".length)}
-          onChange={(e) => {
-            form.cashout_address = "payto://iban/" + e.currentTarget.value;
-            updateForm(structuredClone(form));
-          }}
-        />
-        <ShowInputErrorLabel
-          message={errors?.cashout_address}
-          isDirty={form.cashout_address !== undefined}
-        />
-      </fieldset>
-    </form>
-  );
-}
diff --git a/packages/demobank-ui/src/pages/HomePage.tsx 
b/packages/demobank-ui/src/pages/HomePage.tsx
index e82e46eb2..a911f347c 100644
--- a/packages/demobank-ui/src/pages/HomePage.tsx
+++ b/packages/demobank-ui/src/pages/HomePage.tsx
@@ -35,7 +35,7 @@ import { useBackendContext } from "../context/backend.js";
 import { getInitialBackendBaseURL } from "../hooks/backend.js";
 import { useSettings } from "../hooks/settings.js";
 import { AccountPage } from "./AccountPage/index.js";
-import { AdminPage } from "./AdminPage.js";
+import { AdminHome } from "./admin/Home.js";
 import { LoginForm } from "./LoginForm.js";
 import { WithdrawalQRCode } from "./WithdrawalQRCode.js";
 import { error } from "console";
@@ -54,31 +54,24 @@ const logger = new Logger("AccountPage");
  */
 export function HomePage({
   onRegister,
+  account,
   onPendingOperationFound,
 }: {
+  account: string,
   onPendingOperationFound: (id: string) => void;
   onRegister: () => void;
 }): VNode {
-  const backend = useBackendContext();
   const [settings] = useSettings();
   const { i18n } = useTranslationContext();
 
-  if (backend.state.status === "loggedOut") {
-    return <LoginForm onRegister={onRegister} />;
-  }
-
   if (settings.currentWithdrawalOperationId) {
     onPendingOperationFound(settings.currentWithdrawalOperationId);
     return <Loading />;
   }
 
-  if (backend.state.isUserAdministrator) {
-    return <AdminPage onRegister={onRegister} />;
-  }
-
   return (
     <AccountPage
-      account={backend.state.username}
+      account={account}
       onLoadNotOk={handleNotOkResult(i18n, onRegister)}
     />
   );
@@ -105,8 +98,8 @@ export function WithdrawalOperationPage({
 
   if (!parsedUri) {
     notifyError(
-      i18n.str`The Withdrawal URI is not valid: "${uri}"`,
-      undefined
+      i18n.str`The Withdrawal URI is not valid`,
+      uri as TranslatedString
     );
     return <Loading />;
   }
diff --git a/packages/demobank-ui/src/pages/ShowAccountDetails.tsx 
b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
new file mode 100644
index 000000000..91b50b84c
--- /dev/null
+++ b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
@@ -0,0 +1,143 @@
+import { ErrorType, HttpResponsePaginated, RequestError, notify, notifyError, 
useTranslationContext } from "@gnu-taler/web-util/browser";
+import { VNode,h } from "preact";
+import { useAdminAccountAPI, useBusinessAccountDetails } from 
"../hooks/circuit.js";
+import { useState } from "preact/hooks";
+import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
+import { buildRequestErrorMessage } from "../utils.js";
+import { AccountForm } from "./admin/AccountForm.js";
+
+export function ShowAccountDetails({
+    account,
+    onClear,
+    onUpdateSuccess,
+    onLoadNotOk,
+    onChangePassword,
+  }: {
+    onLoadNotOk: <T>(
+      error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
+    ) => VNode;
+    onClear?: () => void;
+    onChangePassword: () => void;
+    onUpdateSuccess: () => void;
+    account: string;
+  }): VNode {
+    const { i18n } = useTranslationContext();
+    const result = useBusinessAccountDetails(account);
+    const { updateAccount } = useAdminAccountAPI();
+    const [update, setUpdate] = useState(false);
+    const [submitAccount, setSubmitAccount] = useState<
+      SandboxBackend.Circuit.CircuitAccountData | undefined
+    >();
+  
+    if (!result.ok) {
+      if (result.loading || result.type === ErrorType.TIMEOUT) {
+        return onLoadNotOk(result);
+      }
+      if (result.status === HttpStatusCode.NotFound) {
+        return <div>account not found</div>;
+      }
+      return onLoadNotOk(result);
+    }
+  
+    return (
+      <div>
+        <div>
+          <h1 class="nav welcome-text">
+            <i18n.Translate>Business account details</i18n.Translate>
+          </h1>
+        </div>
+        <div style={{ maxWidth: 600, overflowX: "hidden", margin: "auto" }}>
+          <AccountForm
+            template={result.data}
+            purpose={update ? "update" : "show"}
+            onChange={(a) => setSubmitAccount(a)}
+          />
+  
+          <p class="buttons-account">
+            <div
+              style={{
+                display: "flex",
+                justifyContent: "space-between",
+                flexFlow: "wrap-reverse",
+              }}
+            >
+              <div>
+                {onClear ? (
+                  <input
+                    class="pure-button"
+                    type="submit"
+                    value={i18n.str`Close`}
+                    onClick={async (e) => {
+                      e.preventDefault();
+                      onClear();
+                    }}
+                  />
+                ) : undefined}
+              </div>
+              <div style={{ display: "flex" }}>
+                <div>
+                  <input
+                    id="select-exchange"
+                    class="pure-button pure-button-primary content"
+                    disabled={update && !submitAccount}
+                    type="submit"
+                    value={i18n.str`Change password`}
+                    onClick={async (e) => {
+                      e.preventDefault();
+                      onChangePassword();
+                    }}
+                  />
+                </div>
+                <div>
+                  <input
+                    id="select-exchange"
+                    class="pure-button pure-button-primary content"
+                    disabled={update && !submitAccount}
+                    type="submit"
+                    value={update ? i18n.str`Confirm` : i18n.str`Update`}
+                    onClick={async (e) => {
+                      e.preventDefault();
+  
+                      if (!update) {
+                        setUpdate(true);
+                      } else {
+                        if (!submitAccount) return;
+                        try {
+                          await updateAccount(account, {
+                            cashout_address: submitAccount.cashout_address,
+                            contact_data: submitAccount.contact_data,
+                          });
+                          onUpdateSuccess();
+                        } catch (error) {
+                          if (error instanceof RequestError) {
+                            notify(
+                              buildRequestErrorMessage(i18n, error.cause, {
+                                onClientError: (status) =>
+                                  status === HttpStatusCode.Forbidden
+                                    ? i18n.str`The rights to change the 
account are not sufficient`
+                                    : status === HttpStatusCode.NotFound
+                                      ? i18n.str`The username was not found`
+                                      : undefined,
+                              }),
+                            );
+                          } else {
+                            notifyError(
+                              i18n.str`Operation failed, please report`,
+                              (error instanceof Error
+                                ? error.message
+                                : JSON.stringify(error)) as TranslatedString
+                            )
+                          }
+                        }
+                      }
+                    }}
+                  />
+                </div>
+              </div>
+            </div>
+          </p>
+        </div>
+      </div>
+    );
+  }
+  
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx 
b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
new file mode 100644
index 000000000..084a5b643
--- /dev/null
+++ b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
@@ -0,0 +1,131 @@
+import { ErrorType, HttpResponsePaginated, RequestError, notify, notifyError, 
useTranslationContext } from "@gnu-taler/web-util/browser";
+import { useAdminAccountAPI, useBusinessAccountDetails } from 
"../hooks/circuit.js";
+import { useState } from "preact/hooks";
+import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
+import { VNode,h ,Fragment} from "preact";
+import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
+import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
+
+export function UpdateAccountPassword({
+    account,
+    onClear,
+    onUpdateSuccess,
+    onLoadNotOk,
+  }: {
+    onLoadNotOk: <T>(
+      error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
+    ) => VNode;
+    onClear: () => void;
+    onUpdateSuccess: () => void;
+    account: string;
+  }): VNode {
+    const { i18n } = useTranslationContext();
+    const result = useBusinessAccountDetails(account);
+    const { changePassword } = useAdminAccountAPI();
+    const [password, setPassword] = useState<string | undefined>();
+    const [repeat, setRepeat] = useState<string | undefined>();
+  
+    if (!result.ok) {
+      if (result.loading || result.type === ErrorType.TIMEOUT) {
+        return onLoadNotOk(result);
+      }
+      if (result.status === HttpStatusCode.NotFound) {
+        return <div>account not found</div>;
+      }
+      return onLoadNotOk(result);
+    }
+  
+    const errors = undefinedIfEmpty({
+      password: !password ? i18n.str`required` : undefined,
+      repeat: !repeat
+        ? i18n.str`required`
+        : password !== repeat
+          ? i18n.str`password doesn't match`
+          : undefined,
+    });
+  
+    return (
+      <div>
+        <div>
+          <h1 class="nav welcome-text">
+            <i18n.Translate>Update password for {account}</i18n.Translate>
+          </h1>
+        </div>
+  
+        <div style={{ maxWidth: 600, overflowX: "hidden", margin: "auto" }}>
+          <form class="pure-form">
+            <fieldset>
+              <label>{i18n.str`Password`}</label>
+              <input
+                type="password"
+                value={password ?? ""}
+                onChange={(e) => {
+                  setPassword(e.currentTarget.value);
+                }}
+              />
+              <ShowInputErrorLabel
+                message={errors?.password}
+                isDirty={password !== undefined}
+              />
+            </fieldset>
+            <fieldset>
+              <label>{i18n.str`Repeat password`}</label>
+              <input
+                type="password"
+                value={repeat ?? ""}
+                onChange={(e) => {
+                  setRepeat(e.currentTarget.value);
+                }}
+              />
+              <ShowInputErrorLabel
+                message={errors?.repeat}
+                isDirty={repeat !== undefined}
+              />
+            </fieldset>
+          </form>
+          <p>
+            <div style={{ display: "flex", justifyContent: "space-between" }}>
+              <div>
+                <input
+                  class="pure-button"
+                  type="submit"
+                  value={i18n.str`Close`}
+                  onClick={async (e) => {
+                    e.preventDefault();
+                    onClear();
+                  }}
+                />
+              </div>
+              <div>
+                <input
+                  id="select-exchange"
+                  class="pure-button pure-button-primary content"
+                  disabled={!!errors}
+                  type="submit"
+                  value={i18n.str`Confirm`}
+                  onClick={async (e) => {
+                    e.preventDefault();
+                    if (!!errors || !password) return;
+                    try {
+                      const r = await changePassword(account, {
+                        new_password: password,
+                      });
+                      onUpdateSuccess();
+                    } catch (error) {
+                      if (error instanceof RequestError) {
+                        notify(buildRequestErrorMessage(i18n, error.cause));
+                      } else {
+                        notifyError(i18n.str`Operation failed, please report`, 
(error instanceof Error
+                          ? error.message
+                          : JSON.stringify(error)) as TranslatedString)
+                      }
+                    }
+                  }}
+                />
+              </div>
+            </div>
+          </p>
+        </div>
+      </div>
+    );
+  }
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx 
b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index ced152feb..30fcbdff7 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -317,7 +317,8 @@ export function WithdrawalConfirmationQuestion({
                   </div>
                   <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
                     <dt class="text-sm font-medium leading-6 
text-gray-900">Amount</dt>
-                    <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">{Amounts.stringifyValue(details.amount)}</dd>
+                    <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">To be added</dd>
+                    {/* Amounts.stringifyValue(details.amount) */}
                   </div>
                 </dl>
               </div>
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx 
b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index b48e3b1dc..2a3a1ec2c 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -100,10 +100,6 @@ export function WithdrawalQRCode({
   }
 
   if (data.confirmation_done) {
-    if (!settings.showWithdrawalSuccess) {
-      clearCurrentWithdrawal()
-      onContinue()
-    }
     return <div class="relative ml-auto mr-auto transform overflow-hidden 
rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 
sm:w-full sm:max-w-sm sm:p-6">
       <div>
         <div class="mx-auto flex h-12 w-12 items-center justify-center 
rounded-full bg-green-100">
diff --git a/packages/demobank-ui/src/pages/admin/Account.tsx 
b/packages/demobank-ui/src/pages/admin/Account.tsx
new file mode 100644
index 000000000..8ab3e1323
--- /dev/null
+++ b/packages/demobank-ui/src/pages/admin/Account.tsx
@@ -0,0 +1,56 @@
+import { Amounts } from "@gnu-taler/taler-util";
+import { PaytoWireTransferForm } from "../PaytoWireTransferForm.js";
+import { handleNotOkResult } from "../HomePage.js";
+import { useAccountDetails } from "../../hooks/access.js";
+import { useBackendContext } from "../../context/backend.js";
+import { notifyInfo, useTranslationContext } from 
"@gnu-taler/web-util/browser";
+import { Fragment, h, VNode } from "preact";
+
+export function AdminAccount({ onRegister }: { onRegister: () => void }): 
VNode {
+    const { i18n } = useTranslationContext();
+    const r = useBackendContext();
+    const account = r.state.status === "loggedIn" ? r.state.username : "admin";
+    const result = useAccountDetails(account);
+  
+    if (!result.ok) {
+      return handleNotOkResult(i18n, onRegister)(result);
+    }
+    const { data } = result;
+    const balance = Amounts.parseOrThrow(data.balance.amount);
+    const debitThreshold = Amounts.parseOrThrow(result.data.debitThreshold);
+    const balanceIsDebit = result.data.balance.credit_debit_indicator == 
"debit";
+    const limit = balanceIsDebit
+      ? Amounts.sub(debitThreshold, balance).amount
+      : Amounts.add(balance, debitThreshold).amount;
+    if (!balance) return <Fragment />;
+    return (
+      <Fragment>
+        <section id="assets">
+          <div class="asset-summary">
+            <h2>{i18n.str`Bank account balance`}</h2>
+            {!balance ? (
+              <div class="large-amount" style={{ color: "gray" }}>
+                Waiting server response...
+              </div>
+            ) : (
+              <div class="large-amount amount">
+                {balanceIsDebit ? <b>-</b> : null}
+                <span 
class="value">{`${Amounts.stringifyValue(balance)}`}</span>
+                &nbsp;
+                <span class="currency">{`${balance.currency}`}</span>
+              </div>
+            )}
+          </div>
+        </section>
+        <PaytoWireTransferForm
+          focus
+          limit={limit}
+          onSuccess={() => {
+            notifyInfo(i18n.str`Wire transfer created!`);
+          }}
+          onCancel={undefined}
+        />
+      </Fragment>
+    );
+  }
+  
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx 
b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
new file mode 100644
index 000000000..9ca0323a1
--- /dev/null
+++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
@@ -0,0 +1,219 @@
+import { VNode,h  } from "preact";
+import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
+import { PartialButDefined, RecursivePartial, WithIntermediate, 
undefinedIfEmpty, validateIBAN } from "../../utils.js";
+import { useState } from "preact/hooks";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { parsePaytoUri } from "@gnu-taler/taler-util";
+
+const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/;
+const EMAIL_REGEX =
+  
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+const REGEX_JUST_NUMBERS_REGEX = /^\+[0-9 ]*$/;
+
+/**
+ * Create valid account object to update or create
+ * Take template as initial values for the form
+ * Purpose indicate if all field al read only (show), part of them (update)
+ * or none (create)
+ * @param param0
+ * @returns
+ */
+export function AccountForm({
+    template,
+    purpose,
+    onChange,
+  }: {
+    template: SandboxBackend.Circuit.CircuitAccountData | undefined;
+    onChange: (a: SandboxBackend.Circuit.CircuitAccountData | undefined) => 
void;
+    purpose: "create" | "update" | "show";
+  }): VNode {
+    const initial = initializeFromTemplate(template);
+    const [form, setForm] = useState(initial);
+    const [errors, setErrors] = useState<
+      RecursivePartial<typeof initial> | undefined
+    >(undefined);
+    const { i18n } = useTranslationContext();
+  
+    function updateForm(newForm: typeof initial): void {
+      const parsed = !newForm.cashout_address
+        ? undefined
+        : parsePaytoUri(newForm.cashout_address);
+  
+      const errors = undefinedIfEmpty<RecursivePartial<typeof initial>>({
+        cashout_address: !newForm.cashout_address
+          ? i18n.str`required`
+          : !parsed
+            ? i18n.str`does not follow the pattern`
+            : !parsed.isKnown || parsed.targetType !== "iban"
+              ? i18n.str`only "IBAN" target are supported`
+              : !IBAN_REGEX.test(parsed.iban)
+                ? i18n.str`IBAN should have just uppercased letters and 
numbers`
+                : validateIBAN(parsed.iban, i18n),
+        contact_data: undefinedIfEmpty({
+          email: !newForm.contact_data?.email
+            ? i18n.str`required`
+            : !EMAIL_REGEX.test(newForm.contact_data.email)
+              ? i18n.str`it should be an email`
+              : undefined,
+          phone: !newForm.contact_data?.phone
+            ? i18n.str`required`
+            : !newForm.contact_data.phone.startsWith("+")
+              ? i18n.str`should start with +`
+              : !REGEX_JUST_NUMBERS_REGEX.test(newForm.contact_data.phone)
+                ? i18n.str`phone number can't have other than numbers`
+                : undefined,
+        }),
+        iban: !newForm.iban
+          ? undefined //optional field
+          : !IBAN_REGEX.test(newForm.iban)
+            ? i18n.str`IBAN should have just uppercased letters and numbers`
+            : validateIBAN(newForm.iban, i18n),
+        name: !newForm.name ? i18n.str`required` : undefined,
+        username: !newForm.username ? i18n.str`required` : undefined,
+      });
+      setErrors(errors);
+      setForm(newForm);
+      onChange(errors === undefined ? (newForm as any) : undefined);
+    }
+  
+    return (
+      <form class="pure-form">
+        <fieldset>
+          <label for="username">
+            {i18n.str`Username`}
+            {purpose === "create" && <b style={{ color: "red" }}>*</b>}
+          </label>
+          <input
+            name="username"
+            type="text"
+            disabled={purpose !== "create"}
+            value={form.username}
+            onChange={(e) => {
+              form.username = e.currentTarget.value;
+              updateForm(structuredClone(form));
+            }}
+          />{" "}
+          <ShowInputErrorLabel
+            message={errors?.username}
+            isDirty={form.username !== undefined}
+          />
+        </fieldset>
+        <fieldset>
+          <label>
+            {i18n.str`Name`}
+            {purpose === "create" && <b style={{ color: "red" }}>*</b>}
+          </label>
+          <input
+            disabled={purpose !== "create"}
+            value={form.name ?? ""}
+            onChange={(e) => {
+              form.name = e.currentTarget.value;
+              updateForm(structuredClone(form));
+            }}
+          />
+          <ShowInputErrorLabel
+            message={errors?.name}
+            isDirty={form.name !== undefined}
+          />
+        </fieldset>
+        {purpose !== "create" && (
+          <fieldset>
+            <label>{i18n.str`Internal IBAN`}</label>
+            <input
+              disabled={true}
+              value={form.iban ?? ""}
+              onChange={(e) => {
+                form.iban = e.currentTarget.value;
+                updateForm(structuredClone(form));
+              }}
+            />
+            <ShowInputErrorLabel
+              message={errors?.iban}
+              isDirty={form.iban !== undefined}
+            />
+          </fieldset>
+        )}
+        <fieldset>
+          <label>
+            {i18n.str`Email`}
+            {purpose !== "show" && <b style={{ color: "red" }}>*</b>}
+          </label>
+          <input
+            disabled={purpose === "show"}
+            value={form.contact_data.email ?? ""}
+            onChange={(e) => {
+              form.contact_data.email = e.currentTarget.value;
+              updateForm(structuredClone(form));
+            }}
+          />
+          <ShowInputErrorLabel
+            message={errors?.contact_data?.email}
+            isDirty={form.contact_data.email !== undefined}
+          />
+        </fieldset>
+        <fieldset>
+          <label>
+            {i18n.str`Phone`}
+            {purpose !== "show" && <b style={{ color: "red" }}>*</b>}
+          </label>
+          <input
+            disabled={purpose === "show"}
+            value={form.contact_data.phone ?? ""}
+            onChange={(e) => {
+              form.contact_data.phone = e.currentTarget.value;
+              updateForm(structuredClone(form));
+            }}
+          />
+          <ShowInputErrorLabel
+            message={errors?.contact_data?.phone}
+            isDirty={form.contact_data?.phone !== undefined}
+          />
+        </fieldset>
+        <fieldset>
+          <label>
+            {i18n.str`Cashout address`}
+            {purpose !== "show" && <b style={{ color: "red" }}>*</b>}
+          </label>
+          <input
+            disabled={purpose === "show"}
+            value={(form.cashout_address ?? 
"").substring("payto://iban/".length)}
+            onChange={(e) => {
+              form.cashout_address = "payto://iban/" + e.currentTarget.value;
+              updateForm(structuredClone(form));
+            }}
+          />
+          <ShowInputErrorLabel
+            message={errors?.cashout_address}
+            isDirty={form.cashout_address !== undefined}
+          />
+        </fieldset>
+      </form>
+    );
+  }
+  
+  function initializeFromTemplate(
+    account: SandboxBackend.Circuit.CircuitAccountData | undefined,
+  ): WithIntermediate<SandboxBackend.Circuit.CircuitAccountData> {
+    const emptyAccount = {
+      cashout_address: undefined,
+      iban: undefined,
+      name: undefined,
+      username: undefined,
+      contact_data: undefined,
+    };
+    const emptyContact = {
+      email: undefined,
+      phone: undefined,
+    };
+  
+    const initial: 
PartialButDefined<SandboxBackend.Circuit.CircuitAccountData> =
+      structuredClone(account) ?? emptyAccount;
+    if (typeof initial.contact_data === "undefined") {
+      initial.contact_data = emptyContact;
+    }
+    initial.contact_data.email;
+    return initial as any;
+  }
+  
+  
+  
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/admin/AccountList.tsx 
b/packages/demobank-ui/src/pages/admin/AccountList.tsx
new file mode 100644
index 000000000..56b15818b
--- /dev/null
+++ b/packages/demobank-ui/src/pages/admin/AccountList.tsx
@@ -0,0 +1,120 @@
+import { h, VNode } from "preact";
+import { useBusinessAccounts } from "../../hooks/circuit.js";
+import { handleNotOkResult } from "../HomePage.js";
+import { AccountAction } from "./Home.js";
+import { Amounts } from "@gnu-taler/taler-util";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
+
+interface Props {
+    onAction: (type: AccountAction, account: string) => void;
+    account: string | undefined;
+    onRegister: () => void;
+
+}
+
+export function AccountList({ account, onAction, onRegister }: Props): VNode {
+    const result = useBusinessAccounts({ account });
+    const { i18n } = useTranslationContext();
+
+    if (result.loading) return <div />;
+    if (!result.ok) {
+        return handleNotOkResult(i18n, onRegister)(result);
+    }
+
+    const { customers } = result.data;
+    return <section
+        id="main"
+        style={{ width: 600, marginLeft: "auto", marginRight: "auto" }}
+    >
+        {!customers.length ? (
+            <div></div>
+        ) : (
+            <article>
+                <h2>{i18n.str`Accounts:`}</h2>
+                <div class="results">
+                    <table class="pure-table pure-table-striped">
+                        <thead>
+                            <tr>
+                                <th>{i18n.str`Username`}</th>
+                                <th>{i18n.str`Name`}</th>
+                                <th>{i18n.str`Balance`}</th>
+                                <th>{i18n.str`Actions`}</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            {customers.map((item, idx) => {
+                                const balance = !item.balance
+                                    ? undefined
+                                    : Amounts.parse(item.balance.amount);
+                                const balanceIsDebit =
+                                    item.balance &&
+                                    item.balance.credit_debit_indicator == 
"debit";
+                                return (
+                                    <tr key={idx}>
+                                        <td>
+                                            <a
+                                                href="#"
+                                                onClick={(e) => {
+                                                    e.preventDefault();
+                                                    onAction("show-details", 
item.username)
+                                                }}
+                                            >
+                                                {item.username}
+                                            </a>
+                                        </td>
+                                        <td>{item.name}</td>
+                                        <td>
+                                            {!balance ? (
+                                                i18n.str`unknown`
+                                            ) : (
+                                                <span class="amount">
+                                                    {balanceIsDebit ? <b>-</b> 
: null}
+                                                    <span 
class="value">{`${Amounts.stringifyValue(
+                                                        balance,
+                                                    )}`}</span>
+                                                    &nbsp;
+                                                    <span 
class="currency">{`${balance.currency}`}</span>
+                                                </span>
+                                            )}
+                                        </td>
+                                        <td>
+                                            <a
+                                                href="#"
+                                                onClick={(e) => {
+                                                    e.preventDefault();
+                                                    
onAction("update-password", item.username)
+                                                }}
+                                            >
+                                                change password
+                                            </a>
+                                            &nbsp;
+                                            <a
+                                                href="#"
+                                                onClick={(e) => {
+                                                    e.preventDefault();
+                                                    onAction("show-cashout", 
item.username)
+                                                }}
+                                            >
+                                                cashouts
+                                            </a>
+                                            &nbsp;
+                                            <a
+                                                href="#"
+                                                onClick={(e) => {
+                                                    e.preventDefault();
+                                                    onAction("remove-account", 
item.username)
+                                                }}
+                                            >
+                                                remove
+                                            </a>
+                                        </td>
+                                    </tr>
+                                );
+                            })}
+                        </tbody>
+                    </table>
+                </div>
+            </article>
+        )}
+    </section>
+}
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx 
b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
new file mode 100644
index 000000000..90835d52b
--- /dev/null
+++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
@@ -0,0 +1,107 @@
+import { RequestError, notify, notifyError, useTranslationContext } from 
"@gnu-taler/web-util/browser";
+import { VNode, h, Fragment } from "preact";
+import { useAdminAccountAPI } from "../../hooks/circuit.js";
+import { useState } from "preact/hooks";
+import { buildRequestErrorMessage } from "../../utils.js";
+import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
+import { getRandomPassword } from "../rnd.js";
+import { AccountForm } from "./AccountForm.js";
+
+export function CreateNewAccount({
+    onClose,
+    onCreateSuccess,
+}: {
+    onClose: () => void;
+    onCreateSuccess: (password: string) => void;
+}): VNode {
+    const { i18n } = useTranslationContext();
+    const { createAccount } = useAdminAccountAPI();
+    const [submitAccount, setSubmitAccount] = useState<
+        SandboxBackend.Circuit.CircuitAccountData | undefined
+    >();
+    return (
+        <div>
+            <div>
+                <h1 class="nav welcome-text">
+                    <i18n.Translate>New account</i18n.Translate>
+                </h1>
+            </div>
+
+            <div style={{ maxWidth: 600, overflowX: "hidden", margin: "auto" 
}}>
+                <AccountForm
+                    template={undefined}
+                    purpose="create"
+                    onChange={(a) => {
+                        setSubmitAccount(a);
+                    }}
+                />
+
+                <p>
+                    <div style={{ display: "flex", justifyContent: 
"space-between" }}>
+                        <div>
+                            <input
+                                class="pure-button"
+                                type="submit"
+                                value={i18n.str`Close`}
+                                onClick={async (e) => {
+                                    e.preventDefault();
+                                    onClose();
+                                }}
+                            />
+                        </div>
+                        <div>
+                            <input
+                                id="select-exchange"
+                                class="pure-button pure-button-primary content"
+                                disabled={!submitAccount}
+                                type="submit"
+                                value={i18n.str`Confirm`}
+                                onClick={async (e) => {
+                                    e.preventDefault();
+
+                                    if (!submitAccount) return;
+                                    try {
+                                        const account: 
SandboxBackend.Circuit.CircuitAccountRequest =
+                                        {
+                                            cashout_address: 
submitAccount.cashout_address,
+                                            contact_data: 
submitAccount.contact_data,
+                                            internal_iban: submitAccount.iban,
+                                            name: submitAccount.name,
+                                            username: submitAccount.username,
+                                            password: getRandomPassword(),
+                                        };
+
+                                        await createAccount(account);
+                                        onCreateSuccess(account.password);
+                                    } catch (error) {
+                                        if (error instanceof RequestError) {
+                                            notify(
+                                                buildRequestErrorMessage(i18n, 
error.cause, {
+                                                    onClientError: (status) =>
+                                                        status === 
HttpStatusCode.Forbidden
+                                                            ? i18n.str`The 
rights to perform the operation are not sufficient`
+                                                            : status === 
HttpStatusCode.BadRequest
+                                                                ? 
i18n.str`Input data was invalid`
+                                                                : status === 
HttpStatusCode.Conflict
+                                                                    ? 
i18n.str`At least one registration detail was not available`
+                                                                    : 
undefined,
+                                                }),
+                                            );
+                                        } else {
+                                            notifyError(
+                                                i18n.str`Operation failed, 
please report`,
+                                                (error instanceof Error
+                                                    ? error.message
+                                                    : JSON.stringify(error)) 
as TranslatedString
+                                            )
+                                        }
+                                    }
+                                }}
+                            />
+                        </div>
+                    </div>
+                </p>
+            </div>
+        </div>
+    );
+}
diff --git a/packages/demobank-ui/src/pages/admin/Home.tsx 
b/packages/demobank-ui/src/pages/admin/Home.tsx
new file mode 100644
index 000000000..e1ec6cfe0
--- /dev/null
+++ b/packages/demobank-ui/src/pages/admin/Home.tsx
@@ -0,0 +1,162 @@
+import { notifyInfo, useTranslationContext } from 
"@gnu-taler/web-util/browser";
+import { Fragment, VNode, h } from "preact";
+import { useState } from "preact/hooks";
+import { Cashouts } from "../../components/Cashouts/index.js";
+import { ShowCashoutDetails } from "../business/Home.js";
+import { handleNotOkResult } from "../HomePage.js";
+import { ShowAccountDetails } from "../ShowAccountDetails.js";
+import { UpdateAccountPassword } from "../UpdateAccountPassword.js";
+import { AdminAccount } from "./Account.js";
+import { AccountList } from "./AccountList.js";
+import { CreateNewAccount } from "./CreateNewAccount.js";
+import { RemoveAccount } from "./RemoveAccount.js";
+
+/**
+ * Query account information and show QR code if there is pending withdrawal
+ */
+interface Props {
+  onRegister: () => void;
+}
+export type AccountAction = "show-details" | 
+  "show-cashout" | 
+  "update-password" | 
+  "remove-account" | 
+  "show-cashouts-details";
+
+export function AdminHome({ onRegister }: Props): VNode {
+  const [action, setAction] = useState<{
+    type: AccountAction,
+    account: string
+  }>()
+
+  const [createAccount, setCreateAccount] = useState(false);
+
+  const { i18n } = useTranslationContext();
+
+  if (action) {
+    switch (action.type) {
+      case "show-details": return <ShowCashoutDetails
+        id={action.account}
+        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
+        onCancel={() => {
+          setAction(undefined);
+        }}
+      />
+      case "show-cashout": return (
+        <div>
+          <div>
+            <h1 class="nav welcome-text">
+              <i18n.Translate>Cashout for account 
{action.account}</i18n.Translate>
+            </h1>
+          </div>
+          <Cashouts
+            account={action.account}
+            onSelected={(id) => {
+              setAction({
+                type: "show-cashouts-details",
+                account: action.account
+              });
+            }}
+          />
+          <p>
+            <input
+              class="pure-button"
+              type="submit"
+              value={i18n.str`Close`}
+              onClick={async (e) => {
+                e.preventDefault();
+                setAction(undefined);
+              }}
+            />
+          </p>
+        </div>
+      )
+      case "update-password": return <UpdateAccountPassword
+        account={action.account}
+        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
+        onUpdateSuccess={() => {
+          notifyInfo(i18n.str`Password changed`);
+          setAction(undefined);
+        }}
+        onClear={() => {
+          setAction(undefined);
+        }}
+      />
+      case "remove-account": return <RemoveAccount
+        account={action.account}
+        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
+        onUpdateSuccess={() => {
+          notifyInfo(i18n.str`Account removed`);
+          setAction(undefined);
+        }}
+        onClear={() => {
+          setAction(undefined);
+        }}
+      />
+      case "show-cashouts-details": return <ShowAccountDetails
+        account={action.account}
+        onLoadNotOk={handleNotOkResult(i18n, onRegister)}
+        onChangePassword={() => {
+          setAction({
+            type: "update-password",
+            account: action.account,
+          })
+        }}
+        onUpdateSuccess={() => {
+          notifyInfo(i18n.str`Account updated`);
+          setAction(undefined);
+        }}
+        onClear={() => {
+          setAction(undefined);
+        }}
+      />
+    }
+  }
+
+  if (createAccount) {
+    return (
+      <CreateNewAccount
+        onClose={() => setCreateAccount(false)}
+        onCreateSuccess={(password) => {
+          notifyInfo(
+            i18n.str`Account created with password "${password}". The user 
must change the password on the next login.`,
+          );
+          setCreateAccount(false);
+        }}
+      />
+    );
+  }
+
+  return (
+    <Fragment>
+      <div>
+        <h1 class="nav welcome-text">
+          <i18n.Translate>Admin panel</i18n.Translate>
+        </h1>
+      </div>
+
+      <p>
+        <div style={{ display: "flex", justifyContent: "space-between" }}>
+          <div></div>
+          <div>
+            <input
+              class="pure-button pure-button-primary content"
+              type="submit"
+              value={i18n.str`Create account`}
+              onClick={async (e) => {
+                e.preventDefault();
+
+                setCreateAccount(true);
+              }}
+            />
+          </div>
+        </div>
+      </p>
+
+      <AdminAccount onRegister={onRegister} />
+
+      <AccountList account={undefined} onAction={(type,account) => 
setAction({account, type})} onRegister={onRegister}/>
+
+    </Fragment>
+  );
+}
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx 
b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
new file mode 100644
index 000000000..2900db9d2
--- /dev/null
+++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
@@ -0,0 +1,112 @@
+import { ErrorType, HttpResponsePaginated, RequestError, notify, notifyError, 
useTranslationContext } from "@gnu-taler/web-util/browser";
+import { VNode,h,Fragment } from "preact";
+import { useAccountDetails } from "../../hooks/access.js";
+import { useAdminAccountAPI } from "../../hooks/circuit.js";
+import { Amounts, HttpStatusCode, TranslatedString } from 
"@gnu-taler/taler-util";
+import { buildRequestErrorMessage } from "../../utils.js";
+
+export function RemoveAccount({
+    account,
+    onClear,
+    onUpdateSuccess,
+    onLoadNotOk,
+  }: {
+    onLoadNotOk: <T>(
+      error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
+    ) => VNode;
+    onClear: () => void;
+    onUpdateSuccess: () => void;
+    account: string;
+  }): VNode {
+    const { i18n } = useTranslationContext();
+    const result = useAccountDetails(account);
+    const { deleteAccount } = useAdminAccountAPI();
+  
+    if (!result.ok) {
+      if (result.loading || result.type === ErrorType.TIMEOUT) {
+        return onLoadNotOk(result);
+      }
+      if (result.status === HttpStatusCode.NotFound) {
+        return <div>account not found</div>;
+      }
+      return onLoadNotOk(result);
+    }
+  
+    const balance = Amounts.parse(result.data.balance.amount);
+    if (!balance) {
+      return <div>there was an error reading the balance</div>;
+    }
+    const isBalanceEmpty = Amounts.isZero(balance);
+    return (
+      <div>
+        <div>
+          <h1 class="nav welcome-text">
+            <i18n.Translate>Remove account: {account}</i18n.Translate>
+          </h1>
+        </div>
+        {/* {FXME: SHOW WARNING} */}
+        {/* {!isBalanceEmpty && (
+          <ErrorBannerFloat
+            error={{
+              title: i18n.str`Can't delete the account`,
+              description: i18n.str`Balance is not empty`,
+            }}
+            onClear={() => saveError(undefined)}
+          />
+        )} */}
+  
+        <p>
+          <div style={{ display: "flex", justifyContent: "space-between" }}>
+            <div>
+              <input
+                class="pure-button"
+                type="submit"
+                value={i18n.str`Cancel`}
+                onClick={async (e) => {
+                  e.preventDefault();
+                  onClear();
+                }}
+              />
+            </div>
+            <div>
+              <input
+                id="select-exchange"
+                class="pure-button pure-button-primary content"
+                disabled={!isBalanceEmpty}
+                type="submit"
+                value={i18n.str`Confirm`}
+                onClick={async (e) => {
+                  e.preventDefault();
+                  try {
+                    const r = await deleteAccount(account);
+                    onUpdateSuccess();
+                  } catch (error) {
+                    if (error instanceof RequestError) {
+                      notify(
+                        buildRequestErrorMessage(i18n, error.cause, {
+                          onClientError: (status) =>
+                            status === HttpStatusCode.Forbidden
+                              ? i18n.str`The administrator specified a 
institutional username`
+                              : status === HttpStatusCode.NotFound
+                                ? i18n.str`The username was not found`
+                                : status === HttpStatusCode.PreconditionFailed
+                                  ? i18n.str`Balance was not zero`
+                                  : undefined,
+                        }),
+                      );
+                    } else {
+                      notifyError(i18n.str`Operation failed, please report`,
+                        (error instanceof Error
+                            ? error.message
+                            : JSON.stringify(error)) as TranslatedString);
+                    }
+                  }
+                }}
+              />
+            </div>
+          </div>
+        </p>
+      </div>
+    );
+  }
+  
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/BusinessAccount.tsx 
b/packages/demobank-ui/src/pages/business/Home.tsx
similarity index 96%
rename from packages/demobank-ui/src/pages/BusinessAccount.tsx
rename to packages/demobank-ui/src/pages/business/Home.tsx
index ec71ceca6..8beea640a 100644
--- a/packages/demobank-ui/src/pages/BusinessAccount.tsx
+++ b/packages/demobank-ui/src/pages/business/Home.tsx
@@ -30,52 +30,51 @@ import {
 } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useEffect, useState } from "preact/hooks";
-import { Cashouts } from "../components/Cashouts/index.js";
-import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
-import { useBackendContext } from "../context/backend.js";
-import { useAccountDetails } from "../hooks/access.js";
+import { Cashouts } from "../../components/Cashouts/index.js";
+import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
+import { useBackendContext } from "../../context/backend.js";
+import { useAccountDetails } from "../../hooks/access.js";
 import {
   useCashoutDetails,
   useCircuitAccountAPI,
   useEstimator,
   useRatiosAndFeeConfig,
-} from "../hooks/circuit.js";
+} from "../../hooks/circuit.js";
 import {
   TanChannel,
   buildRequestErrorMessage,
   undefinedIfEmpty,
-} from "../utils.js";
-import { ShowAccountDetails, UpdateAccountPassword } from "./AdminPage.js";
-import { handleNotOkResult } from "./HomePage.js";
-import { LoginForm } from "./LoginForm.js";
-import { Amount } from "./PaytoWireTransferForm.js";
+} from "../../utils.js";
+import { handleNotOkResult } from "../HomePage.js";
+import { LoginForm } from "../LoginForm.js";
+import { Amount } from "../PaytoWireTransferForm.js";
+import { ShowAccountDetails } from "../ShowAccountDetails.js";
+import { UpdateAccountPassword } from "../UpdateAccountPassword.js";
 
 interface Props {
+  account: string,
   onClose: () => void;
   onRegister: () => void;
   onLoadNotOk: () => void;
 }
 export function BusinessAccount({
   onClose,
+  account,
   onLoadNotOk,
   onRegister,
 }: Props): VNode {
   const { i18n } = useTranslationContext();
-  const backend = useBackendContext();
   const [updatePassword, setUpdatePassword] = useState(false);
   const [newCashout, setNewcashout] = useState(false);
   const [showCashoutDetails, setShowCashoutDetails] = useState<
     string | undefined
   >();
 
-  if (backend.state.status === "loggedOut") {
-    return <LoginForm onRegister={onRegister} />;
-  }
 
   if (newCashout) {
     return (
       <CreateCashout
-        account={backend.state.username}
+        account={account}
         onLoadNotOk={handleNotOkResult(i18n, onRegister)}
         onCancel={() => {
           setNewcashout(false);
@@ -104,7 +103,7 @@ export function BusinessAccount({
   if (updatePassword) {
     return (
       <UpdateAccountPassword
-        account={backend.state.username}
+        account={account}
         onLoadNotOk={handleNotOkResult(i18n, onRegister)}
         onUpdateSuccess={() => {
           notifyInfo(i18n.str`Password changed`);
@@ -119,7 +118,7 @@ export function BusinessAccount({
   return (
     <div>
       <ShowAccountDetails
-        account={backend.state.username}
+        account={account}
         onLoadNotOk={handleNotOkResult(i18n, onRegister)}
         onUpdateSuccess={() => {
           notifyInfo(i18n.str`Account updated`);
@@ -133,7 +132,7 @@ export function BusinessAccount({
         <div class="active">
           <h3>{i18n.str`Latest cashouts`}</h3>
           <Cashouts
-            account={backend.state.username}
+            account={account}
             onSelected={(id) => {
               setShowCashoutDetails(id);
             }}

-- 
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]