gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] 02/02: business account


From: gnunet
Subject: [taler-wallet-core] 02/02: business account
Date: Fri, 10 Feb 2023 13:55:04 +0100

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

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

commit ba8b40c9150586982e42e76d364d76202627bd6f
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Fri Feb 10 09:51:37 2023 -0300

    business account
---
 .../demobank-ui/src/components/Cashouts/index.ts   |   2 +-
 .../demobank-ui/src/components/Cashouts/state.ts   |  17 +-
 .../src/components/Cashouts/stories.tsx            |  18 +-
 .../demobank-ui/src/components/Cashouts/views.tsx  |   5 +-
 .../src/components/Transactions/state.ts           | 108 +++++------
 packages/demobank-ui/src/context/pageState.ts      |   3 +-
 packages/demobank-ui/src/declaration.d.ts          |  22 +--
 packages/demobank-ui/src/hooks/access.ts           | 210 +++++++++++++--------
 packages/demobank-ui/src/hooks/backend.ts          | 125 ++++++++----
 packages/demobank-ui/src/hooks/circuit.ts          | 151 +++++++++------
 packages/demobank-ui/src/pages/AccountPage.tsx     |  81 ++++----
 packages/demobank-ui/src/pages/AdminPage.tsx       | 117 +++++++-----
 packages/demobank-ui/src/pages/BankFrame.tsx       |  81 +++++---
 packages/demobank-ui/src/pages/BusinessAccount.tsx |  90 +++++++++
 packages/demobank-ui/src/pages/HomePage.tsx        |   4 +-
 packages/demobank-ui/src/pages/Routing.tsx         |  23 ++-
 packages/demobank-ui/src/scss/bank.scss            |   5 +-
 packages/demobank-ui/src/utils.ts                  |   6 +-
 18 files changed, 669 insertions(+), 399 deletions(-)

diff --git a/packages/demobank-ui/src/components/Cashouts/index.ts 
b/packages/demobank-ui/src/components/Cashouts/index.ts
index db39ba7e4..1410267be 100644
--- a/packages/demobank-ui/src/components/Cashouts/index.ts
+++ b/packages/demobank-ui/src/components/Cashouts/index.ts
@@ -23,7 +23,7 @@ import { useComponentState } from "./state.js";
 import { LoadingUriView, ReadyView } from "./views.js";
 
 export interface Props {
-  account: string;
+  empty?: boolean;
 }
 
 export type State = State.Loading | State.LoadingUriError | State.Ready;
diff --git a/packages/demobank-ui/src/components/Cashouts/state.ts 
b/packages/demobank-ui/src/components/Cashouts/state.ts
index 7e420940f..178a1e815 100644
--- a/packages/demobank-ui/src/components/Cashouts/state.ts
+++ b/packages/demobank-ui/src/components/Cashouts/state.ts
@@ -18,27 +18,24 @@ import { AbsoluteTime, Amounts } from 
"@gnu-taler/taler-util";
 import { useCashouts } from "../../hooks/circuit.js";
 import { Props, State, Transaction } from "./index.js";
 
-export function useComponentState({
-  account,
-}: Props): State {
-  const result = useCashouts()
+export function useComponentState({ empty }: Props): State {
+  const result = useCashouts();
   if (result.loading) {
     return {
       status: "loading",
-      error: undefined
-    }
+      error: undefined,
+    };
   }
   if (!result.ok) {
     return {
       status: "loading-error",
-      error: result
-    }
+      error: result,
+    };
   }
 
-
   return {
     status: "ready",
     error: undefined,
-    cashout: result.data,
+    cashouts: result.data,
   };
 }
diff --git a/packages/demobank-ui/src/components/Cashouts/stories.tsx 
b/packages/demobank-ui/src/components/Cashouts/stories.tsx
index 77fdde092..05439780c 100644
--- a/packages/demobank-ui/src/components/Cashouts/stories.tsx
+++ b/packages/demobank-ui/src/components/Cashouts/stories.tsx
@@ -26,20 +26,4 @@ export default {
   title: "transaction list",
 };
 
-export const Ready = tests.createExample(ReadyView, {
-  transactions: [
-    {
-      amount: {
-        currency: "USD",
-        fraction: 0,
-        value: 1,
-      },
-      counterpart: "ASD",
-      negative: false,
-      subject: "Some",
-      when: {
-        t_ms: new Date().getTime(),
-      },
-    },
-  ],
-});
+export const Ready = tests.createExample(ReadyView, {});
diff --git a/packages/demobank-ui/src/components/Cashouts/views.tsx 
b/packages/demobank-ui/src/components/Cashouts/views.tsx
index 30803d4d1..16ae8a58f 100644
--- a/packages/demobank-ui/src/components/Cashouts/views.tsx
+++ b/packages/demobank-ui/src/components/Cashouts/views.tsx
@@ -39,7 +39,7 @@ export function ReadyView({ cashouts }: State.Ready): VNode {
           <tr>
             <th>{i18n.str`Created`}</th>
             <th>{i18n.str`Confirmed`}</th>
-            <th>{i18n.str`Counterpart`}</th>
+            <th>{i18n.str`Status`}</th>
             <th>{i18n.str`Subject`}</th>
           </tr>
         </thead>
@@ -53,8 +53,9 @@ export function ReadyView({ cashouts }: State.Ready): VNode {
                     ? format(item.confirmation_time, "dd/MM/yyyy HH:mm:ss")
                     : "-"}
                 </td>
+                <td>{Amounts.stringifyValue(item.amount_debit)}</td>
                 <td>{Amounts.stringifyValue(item.amount_credit)}</td>
-                <td>{item.counterpart}</td>
+                <td>{item.status}</td>
                 <td>{item.subject}</td>
               </tr>
             );
diff --git a/packages/demobank-ui/src/components/Transactions/state.ts 
b/packages/demobank-ui/src/components/Transactions/state.ts
index 9e1bce39b..198ef6c5f 100644
--- a/packages/demobank-ui/src/components/Transactions/state.ts
+++ b/packages/demobank-ui/src/components/Transactions/state.ts
@@ -18,21 +18,19 @@ import { AbsoluteTime, Amounts } from 
"@gnu-taler/taler-util";
 import { useTransactions } from "../../hooks/access.js";
 import { Props, State, Transaction } from "./index.js";
 
-export function useComponentState({
-  account,
-}: Props): State {
-  const result = useTransactions(account)
+export function useComponentState({ account }: Props): State {
+  const result = useTransactions(account);
   if (result.loading) {
     return {
       status: "loading",
-      error: undefined
-    }
+      error: undefined,
+    };
   }
   if (!result.ok) {
     return {
       status: "loading-error",
-      error: result
-    }
+      error: result,
+    };
   }
   // if (error) {
   //   switch (error.status) {
@@ -73,53 +71,57 @@ export function useComponentState({
   //   };
   // }
 
-  const transactions = result.data.transactions.map((item: unknown) => {
-    if (
-      !item ||
-      typeof item !== "object" ||
-      !("direction" in item) ||
-      !("creditorIban" in item) ||
-      !("debtorIban" in item) ||
-      !("date" in item) ||
-      !("subject" in item) ||
-      !("currency" in item) ||
-      !("amount" in item)
-    ) {
-      //not valid
-      return;
-    }
-    const anyItem = item as any;
-    if (
-      !(typeof anyItem.creditorIban === "string") ||
-      !(typeof anyItem.debtorIban === "string") ||
-      !(typeof anyItem.date === "string") ||
-      !(typeof anyItem.subject === "string") ||
-      !(typeof anyItem.currency === "string") ||
-      !(typeof anyItem.amount === "string")
-    ) {
-      return;
-    }
+  const transactions = result.data.transactions
+    .map((item: unknown) => {
+      if (
+        !item ||
+        typeof item !== "object" ||
+        !("direction" in item) ||
+        !("creditorIban" in item) ||
+        !("debtorIban" in item) ||
+        !("date" in item) ||
+        !("subject" in item) ||
+        !("currency" in item) ||
+        !("amount" in item)
+      ) {
+        //not valid
+        return;
+      }
+      const anyItem = item as any;
+      if (
+        !(typeof anyItem.creditorIban === "string") ||
+        !(typeof anyItem.debtorIban === "string") ||
+        !(typeof anyItem.date === "string") ||
+        !(typeof anyItem.subject === "string") ||
+        !(typeof anyItem.currency === "string") ||
+        !(typeof anyItem.amount === "string")
+      ) {
+        return;
+      }
 
-    const negative = anyItem.direction === "DBIT";
-    const counterpart = negative ? anyItem.creditorIban : anyItem.debtorIban;
+      const negative = anyItem.direction === "DBIT";
+      const counterpart = negative ? anyItem.creditorIban : anyItem.debtorIban;
 
-    let date = anyItem.date ? parseInt(anyItem.date, 10) : 0
-    if (isNaN(date) || !isFinite(date)) {
-      date = 0
-    }
-    const when: AbsoluteTime = !date ? AbsoluteTime.never() : {
-      t_ms: date,
-    };
-    const amount = Amounts.parse(`${anyItem.currency}:${anyItem.amount}`);
-    const subject = anyItem.subject;
-    return {
-      negative,
-      counterpart,
-      when,
-      amount,
-      subject,
-    };
-  }).filter((x): x is Transaction => x !== undefined);
+      let date = anyItem.date ? parseInt(anyItem.date, 10) : 0;
+      if (isNaN(date) || !isFinite(date)) {
+        date = 0;
+      }
+      const when: AbsoluteTime = !date
+        ? AbsoluteTime.never()
+        : {
+            t_ms: date,
+          };
+      const amount = Amounts.parse(`${anyItem.currency}:${anyItem.amount}`);
+      const subject = anyItem.subject;
+      return {
+        negative,
+        counterpart,
+        when,
+        amount,
+        subject,
+      };
+    })
+    .filter((x): x is Transaction => x !== undefined);
 
   return {
     status: "ready",
diff --git a/packages/demobank-ui/src/context/pageState.ts 
b/packages/demobank-ui/src/context/pageState.ts
index d5428b9b7..247297c7b 100644
--- a/packages/demobank-ui/src/context/pageState.ts
+++ b/packages/demobank-ui/src/context/pageState.ts
@@ -95,7 +95,7 @@ export type ErrorMessage = {
   description?: string;
   title: TranslatedString;
   debug?: string;
-}
+};
 /**
  * Track page state.
  */
@@ -110,5 +110,4 @@ export interface PageStateType {
    * be moved in a future "withdrawal state" object.
    */
   withdrawalId?: string;
-
 }
diff --git a/packages/demobank-ui/src/declaration.d.ts 
b/packages/demobank-ui/src/declaration.d.ts
index cf3eb5774..c46fcc9ed 100644
--- a/packages/demobank-ui/src/declaration.d.ts
+++ b/packages/demobank-ui/src/declaration.d.ts
@@ -70,7 +70,6 @@ interface WireTransferRequestType {
   amount?: string;
 }
 
-
 type HashCode = string;
 type EddsaPublicKey = string;
 type EddsaSignature = string;
@@ -101,7 +100,6 @@ type UUID = string;
 type Integer = number;
 
 namespace SandboxBackend {
-
   export interface Config {
     // Name of this API, always "circuit".
     name: string;
@@ -126,7 +124,6 @@ namespace SandboxBackend {
     error: SandboxErrorDetail;
   }
   interface SandboxErrorDetail {
-
     // String enum classifying the error.
     type: ErrorType;
 
@@ -147,13 +144,12 @@ namespace SandboxBackend {
      * Sandbox and Nexus, therefore the actual meaning
      * must be carried by the error 'message' field.
      */
-    UtilError = "util-error"
+    UtilError = "util-error",
   }
 
   namespace Access {
-
     interface PublicAccountsResponse {
-      publicAccounts: PublicAccount[]
+      publicAccounts: PublicAccount[];
     }
     interface PublicAccount {
       iban: string;
@@ -213,7 +209,6 @@ namespace SandboxBackend {
     }
 
     interface BankAccountTransactionInfo {
-
       creditorIban: string;
       creditorBic: string; // Optional
       creditorName: string;
@@ -233,7 +228,6 @@ namespace SandboxBackend {
       date: string; // milliseconds since the Unix epoch
     }
     interface CreateBankAccountTransactionCreate {
-
       // Address in the Payto format of the wire transfer receiver.
       // It needs at least the 'message' query string parameter.
       paytoUri: string;
@@ -250,7 +244,6 @@ namespace SandboxBackend {
 
       password: string;
     }
-
   }
 
   namespace Circuit {
@@ -281,7 +274,6 @@ namespace SandboxBackend {
       internal_iban?: string;
     }
     interface CircuitContactData {
-
       // E-Mail address
       email?: string;
 
@@ -289,7 +281,6 @@ namespace SandboxBackend {
       phone?: string;
     }
     interface CircuitAccountReconfiguration {
-
       // Addresses where to send the TAN.
       contact_data: CircuitContactData;
 
@@ -300,7 +291,6 @@ namespace SandboxBackend {
       cashout_address: string;
     }
     interface AccountPasswordChange {
-
       // New password.
       new_password: string;
     }
@@ -314,7 +304,6 @@ namespace SandboxBackend {
 
       // Legal subject owning the account.
       name: string;
-
     }
 
     interface CircuitAccountData {
@@ -336,10 +325,9 @@ namespace SandboxBackend {
     enum TanChannel {
       SMS = "sms",
       EMAIL = "email",
-      FILE = "file"
+      FILE = "file",
     }
     interface CashoutRequest {
-
       // Optional subject to associate to the
       // cashout operation.  This data will appear
       // as the incoming wire transfer subject in
@@ -370,7 +358,6 @@ namespace SandboxBackend {
       uuid: string;
     }
     interface CashoutConfirm {
-
       // the TAN that confirms $cashoutId.
       tan: string;
     }
@@ -398,7 +385,6 @@ namespace SandboxBackend {
       cashouts: string[];
     }
     interface CashoutStatusResponse {
-
       status: CashoutStatus;
       // Amount debited to the circuit bank account.
       amount_debit: Amount;
@@ -415,7 +401,6 @@ namespace SandboxBackend {
       confirmation_time?: number | null; // milliseconds since the Unix epoch
     }
     enum CashoutStatus {
-
       // The payment was initiated after a valid
       // TAN was received by the bank.
       CONFIRMED = "confirmed",
@@ -425,5 +410,4 @@ namespace SandboxBackend {
       PENDING = "pending",
     }
   }
-
 }
diff --git a/packages/demobank-ui/src/hooks/access.ts 
b/packages/demobank-ui/src/hooks/access.ts
index 4d4574dac..9c162acfe 100644
--- a/packages/demobank-ui/src/hooks/access.ts
+++ b/packages/demobank-ui/src/hooks/access.ts
@@ -14,91 +14,113 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import useSWR from "swr";
-import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js";
-import { useEffect, useState } from "preact/hooks";
 import {
-  HttpError,
   HttpResponse,
   HttpResponseOk,
   HttpResponsePaginated,
+  RequestError,
 } from "@gnu-taler/web-util/lib/index.browser";
-import { useAuthenticatedBackend, useMatchMutate, usePublicBackend } from 
"./backend.js";
+import { useEffect, useState } from "preact/hooks";
+import useSWR from "swr";
 import { useBackendContext } from "../context/backend.js";
+import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js";
+import {
+  useAuthenticatedBackend,
+  useMatchMutate,
+  usePublicBackend,
+} from "./backend.js";
 
 export function useAccessAPI(): AccessAPI {
   const mutateAll = useMatchMutate();
   const { request } = useAuthenticatedBackend();
-  const { state } = useBackendContext()
+  const { state } = useBackendContext();
   if (state.status === "loggedOut") {
-    throw Error("access-api can't be used when the user is not logged In")
+    throw Error("access-api can't be used when the user is not logged In");
   }
-  const account = state.username
+  const account = state.username;
 
   const createWithdrawal = async (
     data: SandboxBackend.Access.BankAccountCreateWithdrawalRequest,
-  ): 
Promise<HttpResponseOk<SandboxBackend.Access.BankAccountCreateWithdrawalResponse>>
 => {
-    const res = await 
request<SandboxBackend.Access.BankAccountCreateWithdrawalResponse>(`access-api/accounts/${account}/withdrawals`,
 {
-      method: "POST",
-      data,
-      contentType: "json"
-    });
+  ): Promise<
+    HttpResponseOk<SandboxBackend.Access.BankAccountCreateWithdrawalResponse>
+  > => {
+    const res =
+      await request<SandboxBackend.Access.BankAccountCreateWithdrawalResponse>(
+        `access-api/accounts/${account}/withdrawals`,
+        {
+          method: "POST",
+          data,
+          contentType: "json",
+        },
+      );
     return res;
   };
-  const abortWithdrawal = async (
-    id: string,
-  ): Promise<HttpResponseOk<void>> => {
-    const res = await 
request<void>(`access-api/accounts/${account}/withdrawals/${id}`, {
-      method: "POST",
-      contentType: "json"
-    });
+  const abortWithdrawal = async (id: string): Promise<HttpResponseOk<void>> => 
{
+    const res = await request<void>(
+      `access-api/accounts/${account}/withdrawals/${id}`,
+      {
+        method: "POST",
+        contentType: "json",
+      },
+    );
     await mutateAll(/.*accounts\/.*\/withdrawals\/.*/);
     return res;
   };
   const confirmWithdrawal = async (
     id: string,
   ): Promise<HttpResponseOk<void>> => {
-    const res = await 
request<void>(`access-api/accounts/${account}/withdrawals/${id}`, {
-      method: "POST",
-      contentType: "json"
-    });
+    const res = await request<void>(
+      `access-api/accounts/${account}/withdrawals/${id}`,
+      {
+        method: "POST",
+        contentType: "json",
+      },
+    );
     await mutateAll(/.*accounts\/.*\/withdrawals\/.*/);
     return res;
   };
   const createTransaction = async (
-    data: SandboxBackend.Access.CreateBankAccountTransactionCreate
+    data: SandboxBackend.Access.CreateBankAccountTransactionCreate,
   ): Promise<HttpResponseOk<void>> => {
-    const res = await 
request<void>(`access-api/accounts/${account}/transactions`, {
-      method: "POST",
-      data,
-      contentType: "json"
-    });
+    const res = await request<void>(
+      `access-api/accounts/${account}/transactions`,
+      {
+        method: "POST",
+        data,
+        contentType: "json",
+      },
+    );
     await mutateAll(/.*accounts\/.*\/transactions.*/);
     return res;
   };
-  const deleteAccount = async (
-  ): Promise<HttpResponseOk<void>> => {
+  const deleteAccount = async (): Promise<HttpResponseOk<void>> => {
     const res = await request<void>(`access-api/accounts/${account}`, {
       method: "DELETE",
-      contentType: "json"
+      contentType: "json",
     });
     await mutateAll(/.*accounts\/.*/);
     return res;
   };
 
-  return { abortWithdrawal, confirmWithdrawal, createWithdrawal, 
createTransaction, deleteAccount };
+  return {
+    abortWithdrawal,
+    confirmWithdrawal,
+    createWithdrawal,
+    createTransaction,
+    deleteAccount,
+  };
 }
 
 export function useTestingAPI(): TestingAPI {
   const mutateAll = useMatchMutate();
   const { request: noAuthRequest } = usePublicBackend();
   const register = async (
-    data: SandboxBackend.Access.BankRegistrationRequest
+    data: SandboxBackend.Access.BankRegistrationRequest,
   ): Promise<HttpResponseOk<void>> => {
     const res = await noAuthRequest<void>(`access-api/testing/register`, {
       method: "POST",
       data,
-      contentType: "json"
+      contentType: "json",
     });
     await mutateAll(/.*accounts\/.*/);
     return res;
@@ -107,25 +129,22 @@ export function useTestingAPI(): TestingAPI {
   return { register };
 }
 
-
 export interface TestingAPI {
   register: (
-    data: SandboxBackend.Access.BankRegistrationRequest
+    data: SandboxBackend.Access.BankRegistrationRequest,
   ) => Promise<HttpResponseOk<void>>;
 }
 
 export interface AccessAPI {
   createWithdrawal: (
     data: SandboxBackend.Access.BankAccountCreateWithdrawalRequest,
-  ) => 
Promise<HttpResponseOk<SandboxBackend.Access.BankAccountCreateWithdrawalResponse>>;
-  abortWithdrawal: (
-    wid: string,
-  ) => Promise<HttpResponseOk<void>>;
-  confirmWithdrawal: (
-    wid: string
-  ) => Promise<HttpResponseOk<void>>;
+  ) => Promise<
+    HttpResponseOk<SandboxBackend.Access.BankAccountCreateWithdrawalResponse>
+  >;
+  abortWithdrawal: (wid: string) => Promise<HttpResponseOk<void>>;
+  confirmWithdrawal: (wid: string) => Promise<HttpResponseOk<void>>;
   createTransaction: (
-    data: SandboxBackend.Access.CreateBankAccountTransactionCreate
+    data: SandboxBackend.Access.CreateBankAccountTransactionCreate,
   ) => Promise<HttpResponseOk<void>>;
   deleteAccount: () => Promise<HttpResponseOk<void>>;
 }
@@ -135,13 +154,17 @@ export interface InstanceTemplateFilter {
   position?: string;
 }
 
-
-export function useAccountDetails(account: string): 
HttpResponse<SandboxBackend.Access.BankAccountBalanceResponse, 
SandboxBackend.SandboxError> {
+export function useAccountDetails(
+  account: string,
+): HttpResponse<
+  SandboxBackend.Access.BankAccountBalanceResponse,
+  SandboxBackend.SandboxError
+> {
   const { fetcher } = useAuthenticatedBackend();
 
   const { data, error } = useSWR<
     HttpResponseOk<SandboxBackend.Access.BankAccountBalanceResponse>,
-    HttpError<SandboxBackend.SandboxError>
+    RequestError<SandboxBackend.SandboxError>
   >([`access-api/accounts/${account}`], fetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
@@ -155,17 +178,23 @@ export function useAccountDetails(account: string): 
HttpResponse<SandboxBackend.
   });
 
   if (data) return data;
-  if (error) return error;
+  if (error) return error.info;
   return { loading: true };
 }
 
 // FIXME: should poll
-export function useWithdrawalDetails(account: string, wid: string): 
HttpResponse<SandboxBackend.Access.BankAccountGetWithdrawalResponse, 
SandboxBackend.SandboxError> {
+export function useWithdrawalDetails(
+  account: string,
+  wid: string,
+): HttpResponse<
+  SandboxBackend.Access.BankAccountGetWithdrawalResponse,
+  SandboxBackend.SandboxError
+> {
   const { fetcher } = useAuthenticatedBackend();
 
   const { data, error } = useSWR<
     HttpResponseOk<SandboxBackend.Access.BankAccountGetWithdrawalResponse>,
-    HttpError<SandboxBackend.SandboxError>
+    RequestError<SandboxBackend.SandboxError>
   >([`access-api/accounts/${account}/withdrawals/${wid}`], fetcher, {
     refreshInterval: 1000,
     refreshWhenHidden: false,
@@ -176,21 +205,26 @@ export function useWithdrawalDetails(account: string, 
wid: string): HttpResponse
     errorRetryInterval: 1,
     shouldRetryOnError: false,
     keepPreviousData: true,
-
   });
 
   // if (isValidating) return { loading: true, data: data?.data };
   if (data) return data;
-  if (error) return error;
+  if (error) return error.info;
   return { loading: true };
 }
 
-export function useTransactionDetails(account: string, tid: string): 
HttpResponse<SandboxBackend.Access.BankAccountTransactionInfo, 
SandboxBackend.SandboxError> {
+export function useTransactionDetails(
+  account: string,
+  tid: string,
+): HttpResponse<
+  SandboxBackend.Access.BankAccountTransactionInfo,
+  SandboxBackend.SandboxError
+> {
   const { fetcher } = useAuthenticatedBackend();
 
   const { data, error } = useSWR<
     HttpResponseOk<SandboxBackend.Access.BankAccountTransactionInfo>,
-    HttpError<SandboxBackend.SandboxError>
+    RequestError<SandboxBackend.SandboxError>
   >([`access-api/accounts/${account}/transactions/${tid}`], fetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
@@ -205,17 +239,20 @@ export function useTransactionDetails(account: string, 
tid: string): HttpRespons
 
   // if (isValidating) return { loading: true, data: data?.data };
   if (data) return data;
-  if (error) return error;
+  if (error) return error.info;
   return { loading: true };
 }
 
 interface PaginationFilter {
-  page: number,
+  page: number;
 }
 
 export function usePublicAccounts(
   args?: PaginationFilter,
-): HttpResponsePaginated<SandboxBackend.Access.PublicAccountsResponse, 
SandboxBackend.SandboxError> {
+): HttpResponsePaginated<
+  SandboxBackend.Access.PublicAccountsResponse,
+  SandboxBackend.SandboxError
+> {
   const { paginatedFetcher } = usePublicBackend();
 
   const [page, setPage] = useState(1);
@@ -226,18 +263,21 @@ export function usePublicAccounts(
     isValidating: loadingAfter,
   } = useSWR<
     HttpResponseOk<SandboxBackend.Access.PublicAccountsResponse>,
-    HttpError<SandboxBackend.SandboxError>
+    RequestError<SandboxBackend.SandboxError>
   >([`public-accounts`, args?.page, PAGE_SIZE], paginatedFetcher);
 
   const [lastAfter, setLastAfter] = useState<
-    HttpResponse<SandboxBackend.Access.PublicAccountsResponse, 
SandboxBackend.SandboxError>
+    HttpResponse<
+      SandboxBackend.Access.PublicAccountsResponse,
+      SandboxBackend.SandboxError
+    >
   >({ loading: true });
 
   useEffect(() => {
     if (afterData) setLastAfter(afterData);
   }, [afterData]);
 
-  if (afterError) return afterError;
+  if (afterError) return afterError.info;
 
   // if the query returns less that we ask, then we have reach the end or 
beginning
   const isReachingEnd =
@@ -254,30 +294,33 @@ export function usePublicAccounts(
       }
     },
     loadMorePrev: () => {
-      null
+      null;
     },
   };
 
-  const publicAccounts = !afterData ? [] : (afterData || 
lastAfter).data.publicAccounts;
-  if (loadingAfter)
-    return { loading: true, data: { publicAccounts } };
+  const publicAccounts = !afterData
+    ? []
+    : (afterData || lastAfter).data.publicAccounts;
+  if (loadingAfter) return { loading: true, data: { publicAccounts } };
   if (afterData) {
     return { ok: true, data: { publicAccounts }, ...pagination };
   }
   return { loading: true };
 }
 
-
 /**
  * FIXME: mutate result when balance change (transaction )
- * @param account 
- * @param args 
- * @returns 
+ * @param account
+ * @param args
+ * @returns
  */
 export function useTransactions(
   account: string,
   args?: PaginationFilter,
-): 
HttpResponsePaginated<SandboxBackend.Access.BankAccountTransactionsResponse, 
SandboxBackend.SandboxError> {
+): HttpResponsePaginated<
+  SandboxBackend.Access.BankAccountTransactionsResponse,
+  SandboxBackend.SandboxError
+> {
   const { paginatedFetcher } = useAuthenticatedBackend();
 
   const [page, setPage] = useState(1);
@@ -288,18 +331,24 @@ export function useTransactions(
     isValidating: loadingAfter,
   } = useSWR<
     HttpResponseOk<SandboxBackend.Access.BankAccountTransactionsResponse>,
-    HttpError<SandboxBackend.SandboxError>
-  >([`access-api/accounts/${account}/transactions`, args?.page, PAGE_SIZE], 
paginatedFetcher);
+    RequestError<SandboxBackend.SandboxError>
+  >(
+    [`access-api/accounts/${account}/transactions`, args?.page, PAGE_SIZE],
+    paginatedFetcher,
+  );
 
   const [lastAfter, setLastAfter] = useState<
-    HttpResponse<SandboxBackend.Access.BankAccountTransactionsResponse, 
SandboxBackend.SandboxError>
+    HttpResponse<
+      SandboxBackend.Access.BankAccountTransactionsResponse,
+      SandboxBackend.SandboxError
+    >
   >({ loading: true });
 
   useEffect(() => {
     if (afterData) setLastAfter(afterData);
   }, [afterData]);
 
-  if (afterError) return afterError;
+  if (afterError) return afterError.info;
 
   // if the query returns less that we ask, then we have reach the end or 
beginning
   const isReachingEnd =
@@ -316,13 +365,14 @@ export function useTransactions(
       }
     },
     loadMorePrev: () => {
-      null
+      null;
     },
   };
 
-  const transactions = !afterData ? [] : (afterData || 
lastAfter).data.transactions;
-  if (loadingAfter)
-    return { loading: true, data: { transactions } };
+  const transactions = !afterData
+    ? []
+    : (afterData || lastAfter).data.transactions;
+  if (loadingAfter) return { loading: true, data: { transactions } };
   if (afterData) {
     return { ok: true, data: { transactions }, ...pagination };
   }
diff --git a/packages/demobank-ui/src/hooks/backend.ts 
b/packages/demobank-ui/src/hooks/backend.ts
index f4f5ecfd0..e87bdd5fe 100644
--- a/packages/demobank-ui/src/hooks/backend.ts
+++ b/packages/demobank-ui/src/hooks/backend.ts
@@ -15,7 +15,10 @@
  */
 
 import { canonicalizeBaseUrl } from "@gnu-taler/taler-util";
-import { useLocalStorage } from "@gnu-taler/web-util/lib/index.browser";
+import {
+  RequestError,
+  useLocalStorage,
+} from "@gnu-taler/web-util/lib/index.browser";
 import {
   HttpResponse,
   HttpResponseOk,
@@ -57,7 +60,7 @@ export function getInitialBackendBaseURL(): string {
 
 export const defaultState: BackendState = {
   status: "loggedOut",
-  url: getInitialBackendBaseURL()
+  url: getInitialBackendBaseURL(),
 };
 
 export interface BackendStateHandler {
@@ -91,7 +94,12 @@ export function useBackendState(): BackendStateHandler {
     },
     logIn(info) {
       //admin is defined by the username
-      const nextState: BackendState = { status: "loggedIn", url: state.url, 
...info, isUserAdministrator: info.username === "admin" };
+      const nextState: BackendState = {
+        status: "loggedIn",
+        url: state.url,
+        ...info,
+        isUserAdministrator: info.username === "admin",
+      };
       update(JSON.stringify(nextState));
     },
   };
@@ -103,24 +111,25 @@ interface useBackendType {
     options?: RequestOptions,
   ) => Promise<HttpResponseOk<T>>;
   fetcher: <T>(endpoint: string) => Promise<HttpResponseOk<T>>;
-  multiFetcher: <T>(endpoint: string[]) => Promise<HttpResponseOk<T>[]>;
-  paginatedFetcher: <T>(args: [string, number, number]) => 
Promise<HttpResponseOk<T>>;
-  sandboxAccountsFetcher: <T>(args: [string, number, number, string]) => 
Promise<HttpResponseOk<T>>;
+  multiFetcher: <T>(endpoint: string[][]) => Promise<HttpResponseOk<T>[]>;
+  paginatedFetcher: <T>(
+    args: [string, number, number],
+  ) => Promise<HttpResponseOk<T>>;
+  sandboxAccountsFetcher: <T>(
+    args: [string, number, number, string],
+  ) => Promise<HttpResponseOk<T>>;
 }
-
-
 export function usePublicBackend(): useBackendType {
   const { state } = useBackendContext();
   const { request: requestHandler } = useApiContext();
 
-  const baseUrl = state.url
+  const baseUrl = state.url;
 
   const request = useCallback(
     function requestImpl<T>(
       path: string,
       options: RequestOptions = {},
     ): Promise<HttpResponseOk<T>> {
-
       return requestHandler<T>(baseUrl, path, options);
     },
     [baseUrl],
@@ -133,15 +142,21 @@ export function usePublicBackend(): useBackendType {
     [baseUrl],
   );
   const paginatedFetcher = useCallback(
-    function fetcherImpl<T>([endpoint, page, size]: [string, number, number]): 
Promise<HttpResponseOk<T>> {
-      return requestHandler<T>(baseUrl, endpoint, { params: { page: page || 1, 
size } });
+    function fetcherImpl<T>([endpoint, page, size]: [
+      string,
+      number,
+      number,
+    ]): Promise<HttpResponseOk<T>> {
+      return requestHandler<T>(baseUrl, endpoint, {
+        params: { page: page || 1, size },
+      });
     },
     [baseUrl],
   );
   const multiFetcher = useCallback(
-    function multiFetcherImpl<T>(
-      endpoints: string[],
-    ): Promise<HttpResponseOk<T>[]> {
+    function multiFetcherImpl<T>([endpoints]: string[][]): Promise<
+      HttpResponseOk<T>[]
+    > {
       return Promise.all(
         endpoints.map((endpoint) => requestHandler<T>(baseUrl, endpoint)),
       );
@@ -149,27 +164,39 @@ export function usePublicBackend(): useBackendType {
     [baseUrl],
   );
   const sandboxAccountsFetcher = useCallback(
-    function fetcherImpl<T>([endpoint, page, size, account]: [string, number, 
number, string]): Promise<HttpResponseOk<T>> {
-      return requestHandler<T>(baseUrl, endpoint, { params: { page: page || 1, 
size } });
+    function fetcherImpl<T>([endpoint, page, size, account]: [
+      string,
+      number,
+      number,
+      string,
+    ]): Promise<HttpResponseOk<T>> {
+      return requestHandler<T>(baseUrl, endpoint, {
+        params: { page: page || 1, size },
+      });
     },
     [baseUrl],
   );
-  return { request, fetcher, paginatedFetcher, multiFetcher, 
sandboxAccountsFetcher };
+  return {
+    request,
+    fetcher,
+    paginatedFetcher,
+    multiFetcher,
+    sandboxAccountsFetcher,
+  };
 }
 
 export function useAuthenticatedBackend(): useBackendType {
   const { state } = useBackendContext();
   const { request: requestHandler } = useApiContext();
 
-  const creds = state.status === "loggedIn" ? state : undefined
-  const baseUrl = state.url
+  const creds = state.status === "loggedIn" ? state : undefined;
+  const baseUrl = state.url;
 
   const request = useCallback(
     function requestImpl<T>(
       path: string,
       options: RequestOptions = {},
     ): Promise<HttpResponseOk<T>> {
-
       return requestHandler<T>(baseUrl, path, { basicAuth: creds, ...options 
});
     },
     [baseUrl, creds],
@@ -182,36 +209,66 @@ export function useAuthenticatedBackend(): useBackendType 
{
     [baseUrl, creds],
   );
   const paginatedFetcher = useCallback(
-    function fetcherImpl<T>([endpoint, page = 0, size]: [string, number, 
number]): Promise<HttpResponseOk<T>> {
-      return requestHandler<T>(baseUrl, endpoint, { basicAuth: creds, params: 
{ page, size } });
+    function fetcherImpl<T>([endpoint, page = 0, size]: [
+      string,
+      number,
+      number,
+    ]): Promise<HttpResponseOk<T>> {
+      return requestHandler<T>(baseUrl, endpoint, {
+        basicAuth: creds,
+        params: { page, size },
+      });
     },
     [baseUrl, creds],
   );
   const multiFetcher = useCallback(
-    function multiFetcherImpl<T>(
-      endpoints: string[],
-    ): Promise<HttpResponseOk<T>[]> {
+    function multiFetcherImpl<T>([endpoints]: string[][]): Promise<
+      HttpResponseOk<T>[]
+    > {
+      console.log("list size", endpoints.length, endpoints);
       return Promise.all(
-        endpoints.map((endpoint) => requestHandler<T>(baseUrl, endpoint, { 
basicAuth: creds })),
+        endpoints.map((endpoint) =>
+          requestHandler<T>(baseUrl, endpoint, { basicAuth: creds }),
+        ),
       );
     },
     [baseUrl, creds],
   );
   const sandboxAccountsFetcher = useCallback(
-    function fetcherImpl<T>([endpoint, page, size, account]: [string, number, 
number, string]): Promise<HttpResponseOk<T>> {
-      return requestHandler<T>(baseUrl, endpoint, { basicAuth: creds, params: 
{ page: page || 1, size } });
+    function fetcherImpl<T>([endpoint, page, size, account]: [
+      string,
+      number,
+      number,
+      string,
+    ]): Promise<HttpResponseOk<T>> {
+      return requestHandler<T>(baseUrl, endpoint, {
+        basicAuth: creds,
+        params: { page: page || 1, size },
+      });
     },
     [baseUrl],
   );
-  return { request, fetcher, paginatedFetcher, multiFetcher, 
sandboxAccountsFetcher };
+
+  return {
+    request,
+    fetcher,
+    paginatedFetcher,
+    multiFetcher,
+    sandboxAccountsFetcher,
+  };
 }
 
-export function useBackendConfig(): HttpResponse<SandboxBackend.Config, 
SandboxBackend.SandboxError> {
+export function useBackendConfig(): HttpResponse<
+  SandboxBackend.Config,
+  SandboxBackend.SandboxError
+> {
   const { request } = usePublicBackend();
 
   type Type = SandboxBackend.Config;
 
-  const [result, setResult] = useState<HttpResponse<Type, 
SandboxBackend.SandboxError>>({ loading: true });
+  const [result, setResult] = useState<
+    HttpResponse<Type, SandboxBackend.SandboxError>
+  >({ loading: true });
 
   useEffect(() => {
     request<Type>(`/config`)
@@ -238,10 +295,8 @@ export function useMatchMutate(): (
     const allKeys = Array.from(cache.keys());
     const keys = allKeys.filter((key) => re.test(key));
     const mutations = keys.map((key) => {
-      mutate(key, value, true);
+      return mutate(key, value, true);
     });
     return Promise.all(mutations);
   };
 }
-
-
diff --git a/packages/demobank-ui/src/hooks/circuit.ts 
b/packages/demobank-ui/src/hooks/circuit.ts
index 6e9ada601..91922a6ba 100644
--- a/packages/demobank-ui/src/hooks/circuit.ts
+++ b/packages/demobank-ui/src/hooks/circuit.ts
@@ -15,23 +15,24 @@
  */
 
 import {
-  HttpError,
   HttpResponse,
   HttpResponseOk,
   HttpResponsePaginated,
-  RequestError
+  RequestError,
+  useApiContext,
 } from "@gnu-taler/web-util/lib/index.browser";
 import { useEffect, useMemo, useState } from "preact/hooks";
 import useSWR from "swr";
 import { useBackendContext } from "../context/backend.js";
 import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js";
-import { useAuthenticatedBackend } from "./backend.js";
+import { useAuthenticatedBackend, useMatchMutate } from "./backend.js";
 
 export function useAdminAccountAPI(): AdminAccountAPI {
   const { request } = useAuthenticatedBackend();
-  const { state } = useBackendContext()
+  const mutateAll = useMatchMutate();
+  const { state } = useBackendContext();
   if (state.status === "loggedOut") {
-    throw Error("access-api can't be used when the user is not logged In")
+    throw Error("access-api can't be used when the user is not logged In");
   }
 
   const createAccount = async (
@@ -40,8 +41,9 @@ export function useAdminAccountAPI(): AdminAccountAPI {
     const res = await request<void>(`circuit-api/accounts`, {
       method: "POST",
       data,
-      contentType: "json"
+      contentType: "json",
     });
+    await mutateAll(/.*circuit-api\/accounts.*/);
     return res;
   };
 
@@ -52,8 +54,9 @@ export function useAdminAccountAPI(): AdminAccountAPI {
     const res = await request<void>(`circuit-api/accounts/${account}`, {
       method: "PATCH",
       data,
-      contentType: "json"
+      contentType: "json",
     });
+    await mutateAll(/.*circuit-api\/accounts.*/);
     return res;
   };
   const deleteAccount = async (
@@ -61,8 +64,9 @@ export function useAdminAccountAPI(): AdminAccountAPI {
   ): Promise<HttpResponseOk<void>> => {
     const res = await request<void>(`circuit-api/accounts/${account}`, {
       method: "DELETE",
-      contentType: "json"
+      contentType: "json",
     });
+    await mutateAll(/.*circuit-api\/accounts.*/);
     return res;
   };
   const changePassword = async (
@@ -72,7 +76,7 @@ export function useAdminAccountAPI(): AdminAccountAPI {
     const res = await request<void>(`circuit-api/accounts/${account}/auth`, {
       method: "PATCH",
       data,
-      contentType: "json"
+      contentType: "json",
     });
     return res;
   };
@@ -82,9 +86,10 @@ export function useAdminAccountAPI(): AdminAccountAPI {
 
 export function useCircuitAccountAPI(): CircuitAccountAPI {
   const { request } = useAuthenticatedBackend();
-  const { state } = useBackendContext()
+  const mutateAll = useMatchMutate();
+  const { state } = useBackendContext();
   if (state.status === "loggedOut") {
-    throw Error("access-api can't be used when the user is not logged In")
+    throw Error("access-api can't be used when the user is not logged In");
   }
   const account = state.username;
 
@@ -94,8 +99,9 @@ export function useCircuitAccountAPI(): CircuitAccountAPI {
     const res = await request<void>(`circuit-api/accounts/${account}`, {
       method: "PATCH",
       data,
-      contentType: "json"
+      contentType: "json",
     });
+    await mutateAll(/.*circuit-api\/accounts.*/);
     return res;
   };
   const changePassword = async (
@@ -104,7 +110,7 @@ export function useCircuitAccountAPI(): CircuitAccountAPI {
     const res = await request<void>(`circuit-api/accounts/${account}/auth`, {
       method: "PATCH",
       data,
-      contentType: "json"
+      contentType: "json",
     });
     return res;
   };
@@ -120,57 +126,72 @@ export interface AdminAccountAPI {
 
   updateAccount: (
     account: string,
-    data: SandboxBackend.Circuit.CircuitAccountReconfiguration
+    data: SandboxBackend.Circuit.CircuitAccountReconfiguration,
   ) => Promise<HttpResponseOk<void>>;
   changePassword: (
     account: string,
-    data: SandboxBackend.Circuit.AccountPasswordChange
+    data: SandboxBackend.Circuit.AccountPasswordChange,
   ) => Promise<HttpResponseOk<void>>;
 }
 
 export interface CircuitAccountAPI {
   updateAccount: (
-    data: SandboxBackend.Circuit.CircuitAccountReconfiguration
+    data: SandboxBackend.Circuit.CircuitAccountReconfiguration,
   ) => Promise<HttpResponseOk<void>>;
   changePassword: (
-    data: SandboxBackend.Circuit.AccountPasswordChange
+    data: SandboxBackend.Circuit.AccountPasswordChange,
   ) => Promise<HttpResponseOk<void>>;
 }
 
-
 export interface InstanceTemplateFilter {
   //FIXME: add filter to the template list
   position?: string;
 }
 
-
-export function useMyAccountDetails(): 
HttpResponse<SandboxBackend.Circuit.CircuitAccountData, 
SandboxBackend.SandboxError> {
-  const { fetcher } = useAuthenticatedBackend();
-  const { state } = useBackendContext()
-  if (state.status === "loggedOut") {
-    throw Error("can't access my-account-details when logged out")
+async function getBusinessStatus(
+  request: ReturnType<typeof useApiContext>["request"],
+  url: string,
+  basicAuth: { username: string; password: string },
+): Promise<boolean> {
+  try {
+    const result = await request<
+      HttpResponseOk<SandboxBackend.Circuit.CircuitAccountData>
+    >(url, `circuit-api/accounts/${basicAuth.username}`, { basicAuth });
+    return result.ok;
+  } catch (error) {
+    return false;
   }
-  const { data, error } = useSWR<
-    HttpResponseOk<SandboxBackend.Circuit.CircuitAccountData>,
-    HttpError<SandboxBackend.SandboxError>
-  >([`accounts/${state.username}`], fetcher, {
-    refreshInterval: 0,
-    refreshWhenHidden: false,
-    revalidateOnFocus: false,
-    revalidateOnReconnect: false,
-    refreshWhenOffline: false,
-    errorRetryCount: 0,
-    errorRetryInterval: 1,
-    shouldRetryOnError: false,
-    keepPreviousData: true,
+}
+
+export function useBusinessAccountFlag(): boolean | undefined {
+  const [isBusiness, setIsBusiness] = useState<boolean | undefined>();
+  const { state } = useBackendContext();
+  const { request } = useApiContext();
+  const creds =
+    state.status === "loggedOut"
+      ? undefined
+      : { username: state.username, password: state.password };
+
+  useEffect(() => {
+    if (!creds) return;
+    getBusinessStatus(request, state.url, creds)
+      .then((result) => {
+        setIsBusiness(result);
+      })
+      .catch((error) => {
+        setIsBusiness(false);
+      });
   });
 
-  if (data) return data;
-  if (error) return error;
-  return { loading: true };
+  return isBusiness;
 }
 
-export function useAccountDetails(account: string): 
HttpResponse<SandboxBackend.Circuit.CircuitAccountData, 
SandboxBackend.SandboxError> {
+export function useBusinessAccountDetails(
+  account: string,
+): HttpResponse<
+  SandboxBackend.Circuit.CircuitAccountData,
+  SandboxBackend.SandboxError
+> {
   const { fetcher } = useAuthenticatedBackend();
 
   const { data, error } = useSWR<
@@ -188,20 +209,22 @@ export function useAccountDetails(account: string): 
HttpResponse<SandboxBackend.
     keepPreviousData: true,
   });
 
-  // if (isValidating) return { loading: true, data: data?.data };
   if (data) return data;
   if (error) return error.info;
   return { loading: true };
 }
 
 interface PaginationFilter {
-  account?: string,
-  page?: number,
+  account?: string;
+  page?: number;
 }
 
-export function useAccounts(
+export function useBusinessAccounts(
   args?: PaginationFilter,
-): HttpResponsePaginated<SandboxBackend.Circuit.CircuitAccounts, 
SandboxBackend.SandboxError> {
+): HttpResponsePaginated<
+  SandboxBackend.Circuit.CircuitAccounts,
+  SandboxBackend.SandboxError
+> {
   const { sandboxAccountsFetcher } = useAuthenticatedBackend();
   const [page, setPage] = useState(0);
 
@@ -212,17 +235,21 @@ export function useAccounts(
   } = useSWR<
     HttpResponseOk<SandboxBackend.Circuit.CircuitAccounts>,
     RequestError<SandboxBackend.SandboxError>
-  >([`circuit-api/accounts`, args?.page, PAGE_SIZE, args?.account], 
sandboxAccountsFetcher, {
-    refreshInterval: 0,
-    refreshWhenHidden: false,
-    revalidateOnFocus: false,
-    revalidateOnReconnect: false,
-    refreshWhenOffline: false,
-    errorRetryCount: 0,
-    errorRetryInterval: 1,
-    shouldRetryOnError: false,
-    keepPreviousData: true,
-  });
+  >(
+    [`circuit-api/accounts`, args?.page, PAGE_SIZE, args?.account],
+    sandboxAccountsFetcher,
+    {
+      refreshInterval: 0,
+      refreshWhenHidden: false,
+      revalidateOnFocus: false,
+      revalidateOnReconnect: false,
+      refreshWhenOffline: false,
+      errorRetryCount: 0,
+      errorRetryInterval: 1,
+      shouldRetryOnError: false,
+      keepPreviousData: true,
+    },
+  );
 
   // const [lastAfter, setLastAfter] = useState<
   //   HttpResponse<SandboxBackend.Circuit.CircuitAccounts, 
SandboxBackend.SandboxError>
@@ -247,18 +274,18 @@ export function useAccounts(
       }
     },
     loadMorePrev: () => {
-      null
+      null;
     },
   };
 
   const result = useMemo(() => {
-    const customers = !afterData ? [] : (afterData)?.data?.customers ?? [];
-    return { ok: true as const, data: { customers }, ...pagination }
-  }, [afterData?.data])
+    const customers = !afterData ? [] : afterData?.data?.customers ?? [];
+    return { ok: true as const, data: { customers }, ...pagination };
+  }, [afterData?.data]);
 
   if (afterError) return afterError.info;
   if (afterData) {
-    return result
+    return result;
   }
 
   // if (loadingAfter)
diff --git a/packages/demobank-ui/src/pages/AccountPage.tsx 
b/packages/demobank-ui/src/pages/AccountPage.tsx
index 769e85804..370605871 100644
--- a/packages/demobank-ui/src/pages/AccountPage.tsx
+++ b/packages/demobank-ui/src/pages/AccountPage.tsx
@@ -104,49 +104,48 @@ export function AccountPage({ account, onLoadNotOk }: 
Props): VNode {
       )}
 
       <section style={{ marginTop: "2em" }}>
-        <Moves account={account} />
+        <div class="active">
+          <h3>{i18n.str`Latest transactions`}</h3>
+          <Transactions account={account} />
+        </div>
       </section>
     </Fragment>
   );
 }
 
-function Moves({ account }: { account: string }): VNode {
-  const [tab, setTab] = useState<"transactions" | "cashouts">("transactions");
-  const { i18n } = useTranslationContext();
-  return (
-    <article>
-      <div class="payments">
-        <div class="tab">
-          <button
-            class={tab === "transactions" ? "tablinks active" : "tablinks"}
-            onClick={(): void => {
-              setTab("transactions");
-            }}
-          >
-            {i18n.str`Transactions`}
-          </button>
-          <button
-            class={tab === "cashouts" ? "tablinks active" : "tablinks"}
-            onClick={(): void => {
-              setTab("cashouts");
-            }}
-          >
-            {i18n.str`Cashouts`}
-          </button>
-        </div>
-        {tab === "transactions" && (
-          <div class="active">
-            <h3>{i18n.str`Latest transactions`}</h3>
-            <Transactions account={account} />
-          </div>
-        )}
-        {tab === "cashouts" && (
-          <div class="active">
-            <h3>{i18n.str`Latest cashouts`}</h3>
-            <Cashouts account={account} />
-          </div>
-        )}
-      </div>
-    </article>
-  );
-}
+// function Moves({ account }: { account: string }): VNode {
+//   const [tab, setTab] = useState<"transactions" | 
"cashouts">("transactions");
+//   const { i18n } = useTranslationContext();
+//   return (
+//     <article>
+//       <div class="payments">
+//         <div class="tab">
+//           <button
+//             class={tab === "transactions" ? "tablinks active" : "tablinks"}
+//             onClick={(): void => {
+//               setTab("transactions");
+//             }}
+//           >
+//             {i18n.str`Transactions`}
+//           </button>
+//           <button
+//             class={tab === "cashouts" ? "tablinks active" : "tablinks"}
+//             onClick={(): void => {
+//               setTab("cashouts");
+//             }}
+//           >
+//             {i18n.str`Cashouts`}
+//           </button>
+//         </div>
+//         {tab === "transactions" && (
+//         )}
+//         {tab === "cashouts" && (
+//           <div class="active">
+//             <h3>{i18n.str`Latest cashouts`}</h3>
+//             <Cashouts account={account} />
+//           </div>
+//         )}
+//       </div>
+//     </article>
+//   );
+// }
diff --git a/packages/demobank-ui/src/pages/AdminPage.tsx 
b/packages/demobank-ui/src/pages/AdminPage.tsx
index 9efd37f12..f8efddd80 100644
--- a/packages/demobank-ui/src/pages/AdminPage.tsx
+++ b/packages/demobank-ui/src/pages/AdminPage.tsx
@@ -24,8 +24,8 @@ import { Fragment, h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { ErrorMessage, usePageContext } from "../context/pageState.js";
 import {
-  useAccountDetails,
-  useAccounts,
+  useBusinessAccountDetails,
+  useBusinessAccounts,
   useAdminAccountAPI,
 } from "../hooks/circuit.js";
 import {
@@ -71,7 +71,7 @@ export function AdminPage({ onLoadNotOk }: Props): VNode {
     }));
   }
 
-  const result = useAccounts({ account });
+  const result = useBusinessAccounts({ account });
   const { i18n } = useTranslationContext();
 
   if (result.loading) return <div />;
@@ -86,6 +86,10 @@ export function AdminPage({ onLoadNotOk }: Props): VNode {
       <ShowAccountDetails
         account={showDetails}
         onLoadNotOk={onLoadNotOk}
+        onChangePassword={() => {
+          setUpdatePassword(showDetails);
+          setShowDetails(undefined);
+        }}
         onUpdateSuccess={() => {
           showInfoMessage(i18n.str`Account updated`);
           setShowDetails(undefined);
@@ -230,7 +234,7 @@ function initializeFromTemplate(
   return initial as any;
 }
 
-function UpdateAccountPassword({
+export function UpdateAccountPassword({
   account,
   onClear,
   onUpdateSuccess,
@@ -242,7 +246,7 @@ function UpdateAccountPassword({
   account: string;
 }): VNode {
   const { i18n } = useTranslationContext();
-  const result = useAccountDetails(account);
+  const result = useBusinessAccountDetails(account);
   const { changePassword } = useAdminAccountAPI();
   const [password, setPassword] = useState<string | undefined>();
   const [repeat, setRepeat] = useState<string | undefined>();
@@ -268,7 +272,7 @@ function UpdateAccountPassword({
     <div>
       <div>
         <h1 class="nav welcome-text">
-          <i18n.Translate>Admin panel</i18n.Translate>
+          <i18n.Translate>Update password for {account}</i18n.Translate>
         </h1>
       </div>
       {error && (
@@ -276,10 +280,6 @@ function UpdateAccountPassword({
       )}
 
       <form class="pure-form">
-        <fieldset>
-          <label for="username">{i18n.str`Username`}</label>
-          <input name="username" type="text" readOnly value={account} />
-        </fieldset>
         <fieldset>
           <label>{i18n.str`Password`}</label>
           <input
@@ -366,7 +366,7 @@ function CreateNewAccount({
     <div>
       <div>
         <h1 class="nav welcome-text">
-          <i18n.Translate>Admin panel</i18n.Translate>
+          <i18n.Translate>New account</i18n.Translate>
         </h1>
       </div>
       {error && (
@@ -428,19 +428,21 @@ function CreateNewAccount({
   );
 }
 
-function ShowAccountDetails({
+export function ShowAccountDetails({
   account,
   onClear,
   onUpdateSuccess,
   onLoadNotOk,
+  onChangePassword,
 }: {
   onLoadNotOk: <T, E>(error: HttpResponsePaginated<T, E>) => VNode;
-  onClear: () => void;
+  onClear?: () => void;
+  onChangePassword: () => void;
   onUpdateSuccess: () => void;
   account: string;
 }): VNode {
   const { i18n } = useTranslationContext();
-  const result = useAccountDetails(account);
+  const result = useBusinessAccountDetails(account);
   const { updateAccount } = useAdminAccountAPI();
   const [update, setUpdate] = useState(false);
   const [submitAccount, setSubmitAccount] = useState<
@@ -459,7 +461,7 @@ function ShowAccountDetails({
     <div>
       <div>
         <h1 class="nav welcome-text">
-          <i18n.Translate>Admin panel</i18n.Translate>
+          <i18n.Translate>Business account details</i18n.Translate>
         </h1>
       </div>
       {error && (
@@ -474,42 +476,59 @@ function ShowAccountDetails({
       <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();
-              }}
-            />
+            {onClear ? (
+              <input
+                class="pure-button"
+                type="submit"
+                value={i18n.str`Close`}
+                onClick={async (e) => {
+                  e.preventDefault();
+                  onClear();
+                }}
+              />
+            ) : undefined}
           </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) {
-                    handleError(error, saveError, i18n);
+          <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) {
+                      handleError(error, saveError, i18n);
+                    }
                   }
-                }
-              }}
-            />
+                }}
+              />
+            </div>
           </div>
         </div>
       </p>
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx 
b/packages/demobank-ui/src/pages/BankFrame.tsx
index ed36daa21..0fb75b87b 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -15,6 +15,7 @@
  */
 
 import { Logger } from "@gnu-taler/taler-util";
+import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
 import { ComponentChildren, Fragment, h, VNode } from "preact";
 import talerLogo from "../assets/logo-white.svg";
 import { LangSelectorLikePy as LangSelector } from 
"../components/LangSelector.js";
@@ -24,41 +25,46 @@ import {
   PageStateType,
   usePageContext,
 } from "../context/pageState.js";
-import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
+import { useBusinessAccountDetails } from "../hooks/circuit.js";
 import { bankUiSettings } from "../settings.js";
 
 const logger = new Logger("BankFrame");
 
+function MaybeBusinessButton({
+  account,
+  onClick,
+}: {
+  account: string;
+  onClick: () => void;
+}): VNode {
+  const { i18n } = useTranslationContext();
+  const result = useBusinessAccountDetails(account);
+  if (!result.ok) return <Fragment />;
+  return (
+    <div class="some-space">
+      <a
+        href="#"
+        class="pure-button pure-button-primary"
+        onClick={(e) => {
+          e.preventDefault();
+          onClick();
+        }}
+      >{i18n.str`Business Profile`}</a>
+    </div>
+  );
+}
+
 export function BankFrame({
   children,
+  goToBusinessAccount,
 }: {
   children: ComponentChildren;
+  goToBusinessAccount?: () => void;
 }): VNode {
   const { i18n } = useTranslationContext();
   const backend = useBackendContext();
   const { pageState, pageStateSetter } = usePageContext();
   logger.trace("state", pageState);
-  const logOut = (
-    <div class="logout">
-      <a
-        href="#"
-        class="pure-button logout-button"
-        onClick={() => {
-          pageStateSetter((prevState: PageStateType) => {
-            const { talerWithdrawUri, withdrawalId, ...rest } = prevState;
-            backend.logOut();
-            return {
-              ...rest,
-              withdrawalInProgress: false,
-              error: undefined,
-              info: undefined,
-              isRawPayto: false,
-            };
-          });
-        }}
-      >{i18n.str`Logout`}</a>
-    </div>
-  );
 
   const demo_sites = [];
   for (const i in bankUiSettings.demoSites)
@@ -120,7 +126,36 @@ export function BankFrame({
           />
         )}
         <StatusBanner />
-        {backend.state.status === "loggedIn" ? logOut : null}
+        {backend.state.status === "loggedIn" ? (
+          <div class="top-right">
+            {goToBusinessAccount ? (
+              <MaybeBusinessButton
+                account={backend.state.username}
+                onClick={goToBusinessAccount}
+              />
+            ) : undefined}
+            <div class="some-space">
+              <a
+                href="#"
+                class="pure-button logout-button"
+                onClick={() => {
+                  pageStateSetter((prevState: PageStateType) => {
+                    const { talerWithdrawUri, withdrawalId, ...rest } =
+                      prevState;
+                    backend.logOut();
+                    return {
+                      ...rest,
+                      withdrawalInProgress: false,
+                      error: undefined,
+                      info: undefined,
+                      isRawPayto: false,
+                    };
+                  });
+                }}
+              >{i18n.str`Logout`}</a>
+            </div>
+          </div>
+        ) : null}
         {children}
       </section>
       <section id="footer" class="footer">
diff --git a/packages/demobank-ui/src/pages/BusinessAccount.tsx 
b/packages/demobank-ui/src/pages/BusinessAccount.tsx
new file mode 100644
index 000000000..d845c2fa0
--- /dev/null
+++ b/packages/demobank-ui/src/pages/BusinessAccount.tsx
@@ -0,0 +1,90 @@
+/*
+ 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 { TranslatedString } from "@gnu-taler/taler-util";
+import {
+  HttpResponsePaginated,
+  useTranslationContext,
+} from "@gnu-taler/web-util/lib/index.browser";
+import { h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { Cashouts } from "../components/Cashouts/index.js";
+import { useBackendContext } from "../context/backend.js";
+import { usePageContext } from "../context/pageState.js";
+import { ShowAccountDetails, UpdateAccountPassword } from "./AdminPage.js";
+import { LoginForm } from "./LoginForm.js";
+
+interface Props {
+  onClose: () => void;
+  onRegister: () => void;
+  onLoadNotOk: <T, E>(error: HttpResponsePaginated<T, E>) => VNode;
+}
+export function BusinessAccount({
+  onClose,
+  onLoadNotOk,
+  onRegister,
+}: Props): VNode {
+  const { i18n } = useTranslationContext();
+  const { pageStateSetter } = usePageContext();
+  const backend = useBackendContext();
+  const [updatePassword, setUpdatePassword] = useState(false);
+  function showInfoMessage(info: TranslatedString): void {
+    pageStateSetter((prev) => ({
+      ...prev,
+      info,
+    }));
+  }
+
+  if (backend.state.status === "loggedOut") {
+    return <LoginForm onRegister={onRegister} />;
+  }
+
+  if (updatePassword) {
+    return (
+      <UpdateAccountPassword
+        account={backend.state.username}
+        onLoadNotOk={onLoadNotOk}
+        onUpdateSuccess={() => {
+          showInfoMessage(i18n.str`Password changed`);
+          setUpdatePassword(false);
+        }}
+        onClear={() => {
+          setUpdatePassword(false);
+        }}
+      />
+    );
+  }
+  return (
+    <div>
+      <ShowAccountDetails
+        account={backend.state.username}
+        onLoadNotOk={onLoadNotOk}
+        onUpdateSuccess={() => {
+          showInfoMessage(i18n.str`Account updated`);
+        }}
+        onChangePassword={() => {
+          setUpdatePassword(true);
+        }}
+        onClear={onClose}
+      />
+      <section style={{ marginTop: "2em" }}>
+        <div class="active">
+          <h3>{i18n.str`Latest cashouts`}</h3>
+          <Cashouts />
+        </div>
+      </section>
+    </div>
+  );
+}
diff --git a/packages/demobank-ui/src/pages/HomePage.tsx 
b/packages/demobank-ui/src/pages/HomePage.tsx
index e60732d42..76eb8d515 100644
--- a/packages/demobank-ui/src/pages/HomePage.tsx
+++ b/packages/demobank-ui/src/pages/HomePage.tsx
@@ -50,6 +50,7 @@ export function HomePage({ onRegister }: { onRegister: () => 
void }): VNode {
   }
 
   function saveErrorAndLogout(error: PageStateType["error"]): void {
+    console.log("rrot", error);
     saveError(error);
     backend.logOut();
   }
@@ -123,6 +124,7 @@ function handleNotOkResult(
   return function handleNotOkResult2<T, E>(
     result: HttpResponsePaginated<T, E>,
   ): VNode {
+    console.log("qweqwe", JSON.stringify(result, undefined, 2));
     if (result.clientError && result.isUnauthorized) {
       onErrorHandler({
         title: i18n.str`Wrong credentials for "${account}"`,
@@ -139,7 +141,7 @@ function handleNotOkResult(
     if (!result.ok) {
       onErrorHandler({
         title: i18n.str`The backend reported a problem: HTTP status 
#${result.status}`,
-        description: `Diagnostic from ${result.info?.url.href} is 
"${result.message}"`,
+        description: `Diagnostic from ${result.info?.url} is 
"${result.message}"`,
         debug: JSON.stringify(result.error),
       });
       return <LoginForm onRegister={onRegister} />;
diff --git a/packages/demobank-ui/src/pages/Routing.tsx 
b/packages/demobank-ui/src/pages/Routing.tsx
index a88af9b0b..cff561aac 100644
--- a/packages/demobank-ui/src/pages/Routing.tsx
+++ b/packages/demobank-ui/src/pages/Routing.tsx
@@ -28,6 +28,7 @@ import { HomePage } from "./HomePage.js";
 import { BankFrame } from "./BankFrame.js";
 import { PublicHistoriesPage } from "./PublicHistoriesPage.js";
 import { RegistrationPage } from "./RegistrationPage.js";
+import { BusinessAccount } from "./BusinessAccount.js";
 
 function handleNotOkResult(
   safe: string,
@@ -96,7 +97,11 @@ export function Routing(): VNode {
       <Route
         path="/account"
         component={() => (
-          <BankFrame>
+          <BankFrame
+            goToBusinessAccount={() => {
+              route("/business");
+            }}
+          >
             <HomePage
               onRegister={() => {
                 route("/register");
@@ -105,6 +110,22 @@ export function Routing(): VNode {
           </BankFrame>
         )}
       />
+      <Route
+        path="/business"
+        component={() => (
+          <BankFrame>
+            <BusinessAccount
+              onClose={() => {
+                route("/account");
+              }}
+              onRegister={() => {
+                route("/register");
+              }}
+              onLoadNotOk={handleNotOkResult("/account", saveError, i18n)}
+            />
+          </BankFrame>
+        )}
+      />
       <Route default component={Redirect} to="/account" />
     </Router>
   );
diff --git a/packages/demobank-ui/src/scss/bank.scss 
b/packages/demobank-ui/src/scss/bank.scss
index c55dfe966..2bd5f317a 100644
--- a/packages/demobank-ui/src/scss/bank.scss
+++ b/packages/demobank-ui/src/scss/bank.scss
@@ -51,8 +51,11 @@ input[type="number"]::-webkit-inner-spin-button {
   overflow: hidden;
 }
 
-.logout {
+.top-right {
   float: right;
+}
+.some-space {
+  display: inline-block;
   border: 20px;
   margin-right: 15px;
   margin-top: 15px;
diff --git a/packages/demobank-ui/src/utils.ts 
b/packages/demobank-ui/src/utils.ts
index 0dc24e468..642b3c68d 100644
--- a/packages/demobank-ui/src/utils.ts
+++ b/packages/demobank-ui/src/utils.ts
@@ -54,8 +54,10 @@ export type PartialButDefined<T> = {
 };
 
 export type WithIntermediate<Type extends object> = {
-  [prop in keyof Type]: Type[prop] extends object ? 
WithIntermediate<Type[prop]> : (Type[prop] | undefined);
-}
+  [prop in keyof Type]: Type[prop] extends object
+    ? WithIntermediate<Type[prop]>
+    : Type[prop] | undefined;
+};
 
 // export function partialWithObjects<T extends object>(obj: T | undefined, () 
=> complete): WithIntermediate<T> {
 //   const root = obj === undefined ? {} : obj;

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