gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated (93420e064 -> 7582855e2)


From: gnunet
Subject: [taler-wallet-core] branch master updated (93420e064 -> 7582855e2)
Date: Thu, 19 Oct 2023 20:11:24 +0200

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

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

    from 93420e064 justify balance
     new 9e925a2f5 add not documented api
     new 7582855e2 some ui fixes

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../src/components/Transactions/views.tsx          |  16 +-
 packages/demobank-ui/src/hooks/access.ts           |  22 ++-
 packages/demobank-ui/src/hooks/circuit.ts          |  15 +-
 packages/demobank-ui/src/i18n/strings.ts           |   2 +-
 packages/demobank-ui/src/pages/LoginForm.tsx       |  69 ++++-----
 .../demobank-ui/src/pages/OperationState/state.ts  |  58 ++------
 packages/demobank-ui/src/pages/PaymentOptions.tsx  |  52 +++----
 .../src/pages/PaytoWireTransferForm.tsx            |  54 +++----
 packages/demobank-ui/src/pages/QrCodeSection.tsx   |  28 +---
 .../demobank-ui/src/pages/RegistrationPage.tsx     | 163 ++++++++-------------
 .../demobank-ui/src/pages/ShowAccountDetails.tsx   |  77 +++++-----
 .../src/pages/UpdateAccountPassword.tsx            |  36 ++---
 .../demobank-ui/src/pages/WalletWithdrawForm.tsx   |  18 +--
 .../src/pages/WithdrawalConfirmationQuestion.tsx   |  47 ++----
 .../demobank-ui/src/pages/WithdrawalQRCode.tsx     |  16 +-
 .../src/pages/admin/CreateNewAccount.tsx           |  17 +--
 .../demobank-ui/src/pages/admin/RemoveAccount.tsx  |  15 +-
 packages/demobank-ui/src/pages/business/Home.tsx   |  93 +++---------
 packages/demobank-ui/src/utils.ts                  |  27 +++-
 packages/taler-util/src/http-client/bank-core.ts   |   2 +
 20 files changed, 308 insertions(+), 519 deletions(-)

diff --git a/packages/demobank-ui/src/components/Transactions/views.tsx 
b/packages/demobank-ui/src/components/Transactions/views.tsx
index 404e25619..5cdb47a0c 100644
--- a/packages/demobank-ui/src/components/Transactions/views.tsx
+++ b/packages/demobank-ui/src/components/Transactions/views.tsx
@@ -17,7 +17,7 @@
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { format } from "date-fns";
 import { Fragment, h, VNode } from "preact";
-import { RenderAmount } from "../../pages/PaytoWireTransferForm.js";
+import { doAutoFocus, RenderAmount } from 
"../../pages/PaytoWireTransferForm.js";
 import { State } from "./index.js";
 
 export function LoadingUriView({ error }: State.LoadingUriError): VNode {
@@ -57,7 +57,7 @@ export function ReadyView({ transactions, onNext, onPrev }: 
State.Ready): VNode
               <th scope="col" class="pl-2 py-3.5 text-left text-sm 
font-semibold text-gray-900 ">{i18n.str`Date`}</th>
               <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 
text-left text-sm font-semibold text-gray-900 ">{i18n.str`Amount`}</th>
               <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 
text-left text-sm font-semibold text-gray-900 ">{i18n.str`Counterpart`}</th>
-              <th scope="col" class="pl-2 py-3.5 text-left text-sm 
font-semibold text-gray-900 ">{i18n.str`Subject`}</th>
+              <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 
text-left text-sm font-semibold text-gray-900 ">{i18n.str`Subject`}</th>
             </tr>
           </thead>
           <tbody>
@@ -70,10 +70,7 @@ export function ReadyView({ transactions, onNext, onPrev }: 
State.Ready): VNode
                 </tr>
                 {txs.map(item => {
                   const time = item.when.t_ms === "never" ? "" : 
format(item.when.t_ms, "HH:mm:ss")
-                  const amount = <Fragment>
-                    { }
-                  </Fragment>
-                  return (<tr key={idx}>
+                  return (<tr key={idx} class="border-b border-gray-200 
last:border-none">
                     <td class="relative py-2 pl-2 pr-2 text-sm ">
                       <div class="font-medium text-gray-900">{time}</div>
                       <dl class="font-normal sm:hidden">
@@ -91,6 +88,11 @@ export function ReadyView({ transactions, onNext, onPrev }: 
State.Ready): VNode
                         <dd class="mt-1 truncate text-gray-500 sm:hidden">
                           {item.negative ? i18n.str`to` : i18n.str`from`} 
{item.counterpart}
                         </dd>
+                        <dd class="mt-1 text-gray-500 sm:hidden" >
+                          <pre class="break-words w-56 whitespace-break-spaces 
p-2 rounded-md mx-auto my-2 bg-gray-100">
+                          {item.subject}
+                          </pre>
+                        </dd>
                       </dl>
                     </td>
                     <td data-negative={item.negative ? "true" : "false"}
@@ -101,7 +103,7 @@ export function ReadyView({ transactions, onNext, onPrev }: 
State.Ready): VNode
                       )}
                     </td>
                     <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500">{item.counterpart}</td>
-                    <td class="px-3 py-3.5 text-sm text-gray-500 break-all 
min-w-md">{item.subject}</td>
+                    <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500 break-all min-w-md">{item.subject}</td>
                   </tr>)
                 })}
               </Fragment>
diff --git a/packages/demobank-ui/src/hooks/access.ts 
b/packages/demobank-ui/src/hooks/access.ts
index 2533d32fe..7023b8803 100644
--- a/packages/demobank-ui/src/hooks/access.ts
+++ b/packages/demobank-ui/src/hooks/access.ts
@@ -38,7 +38,8 @@ export function useAccountDetails(account: string) {
     return await api.getAccount({ username, token })
   }
   const token = credentials.status !== "loggedIn" ? undefined : 
credentials.token
-  const { data, error } = useSWR<TalerCoreBankResultByMethod<"getAccount">, 
TalerHttpError>([account, token], fetcher, {
+  const { data, error } = useSWR<TalerCoreBankResultByMethod<"getAccount">, 
TalerHttpError>(
+    [account, token, "getAccount"], fetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
@@ -65,7 +66,7 @@ export function useWithdrawalDetails(wid: string) {
   }
 
   const { data, error } = 
useSWR<TalerCoreBankResultByMethod<"getWithdrawalById">, TalerHttpError>(
-    [wid], fetcher, {
+    [wid, "getWithdrawalById"], fetcher, {
     refreshInterval: 1000,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
@@ -92,7 +93,7 @@ export function useTransactionDetails(account: string, tid: 
number) {
   }
 
   const { data, error } = 
useSWR<TalerCoreBankResultByMethod<"getTransactionById">, TalerHttpError>(
-    [account, token, tid], fetcher, {
+    [account, token, tid, "getTransactionById"], fetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
@@ -121,7 +122,18 @@ export function usePublicAccounts(initial?: number) {
     })
   }
 
-  const { data, error } = 
useSWR<TalerCoreBankResultByMethod<"getPublicAccounts">, 
TalerHttpError>([offset], fetcher);
+  const { data, error } = 
useSWR<TalerCoreBankResultByMethod<"getPublicAccounts">, TalerHttpError>(
+    [offset, "getPublicAccounts"], fetcher, {
+      refreshInterval: 0,
+      refreshWhenHidden: false,
+      revalidateOnFocus: false,
+      revalidateOnReconnect: false,
+      refreshWhenOffline: false,
+      errorRetryCount: 0,
+      errorRetryInterval: 1,
+      shouldRetryOnError: false,
+      keepPreviousData: true,  
+    });
 
   const isLastPage =
     data && data.body.public_accounts.length < PAGE_SIZE;
@@ -174,7 +186,7 @@ export function useTransactions(account: string, initial?: 
number) {
   }
 
   const { data, error } = 
useSWR<TalerCoreBankResultByMethod<"getTransactions">, TalerHttpError>(
-    [account, token, offset], fetcher, {
+    [account, token, offset, "getTransactions"], fetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
     refreshWhenOffline: false,
diff --git a/packages/demobank-ui/src/hooks/circuit.ts 
b/packages/demobank-ui/src/hooks/circuit.ts
index 208663f8b..0f7af5fe5 100644
--- a/packages/demobank-ui/src/hooks/circuit.ts
+++ b/packages/demobank-ui/src/hooks/circuit.ts
@@ -106,9 +106,8 @@ export function useRatiosAndFeeConfig() {
     return api.getConversionRates()
   }
 
-  const { data, error } = useSWR<
-    TalerCoreBankResultByMethod<"getConversionRates">, TalerHttpError
-  >([], fetcher, {
+  const { data, error } = 
useSWR<TalerCoreBankResultByMethod<"getConversionRates">, TalerHttpError>(
+    [, "getConversionRates"], fetcher, {
     refreshInterval: 60 * 1000,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
@@ -138,7 +137,7 @@ export function useBusinessAccounts() {
 
   const [offset, setOffset] = useState<string | undefined>();
 
-  function fetcher(token: AccessToken, offset?: string) {
+  function fetcher([token, offset]: [AccessToken, string]) {
     return api.getAccounts(token, {
       limit: MAX_RESULT_SIZE,
       offset,
@@ -147,7 +146,7 @@ export function useBusinessAccounts() {
   }
 
   const { data, error } = useSWR<TalerCoreBankResultByMethod<"getAccounts">, 
TalerHttpError>(
-    [token, offset], fetcher, {
+    [token, offset, "getAccounts"], fetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
@@ -192,8 +191,8 @@ function notUndefined(c: CashoutWithId | undefined): c is 
CashoutWithId {
 }
 export function useCashouts(account: string) {
   const { state: credentials } = useBackendState();
-  const token = credentials.status !== "loggedIn" ? undefined : 
credentials.token
   const { api } = useBankCoreApiContext();
+  const token = credentials.status !== "loggedIn" ? undefined : 
credentials.token
 
   async function fetcher([username, token]: [string, AccessToken]) {
     const list = await api.getAccountCashouts({ username, token })
@@ -212,7 +211,7 @@ export function useCashouts(account: string) {
   }
 
   const { data, error } = useSWR<OperationOk<{ cashouts: CashoutWithId[] }>, 
TalerHttpError>(
-    [account, token], fetcher, {
+    [account, token, "getAccountCashouts"], fetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
@@ -239,7 +238,7 @@ export function useCashoutDetails(cashoutId: string) {
   }
 
   const { data, error } = 
useSWR<TalerCoreBankResultByMethod<"getCashoutById">, TalerHttpError>(
-    [creds?.username, creds?.token, cashoutId], fetcher, {
+    [creds?.username, creds?.token, cashoutId, "getCashoutById"], fetcher, {
     refreshInterval: 0,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
diff --git a/packages/demobank-ui/src/i18n/strings.ts 
b/packages/demobank-ui/src/i18n/strings.ts
index 1b19f7df7..86d1fff5b 100644
--- a/packages/demobank-ui/src/i18n/strings.ts
+++ b/packages/demobank-ui/src/i18n/strings.ts
@@ -167,7 +167,7 @@ strings["en"] = {
       "Transfer creation gave response error": [""],
       "Wire transfer created!": [""],
       "Amount to withdraw:": ["Amount to withdraw"],
-      Withdraw: ["Confirm withdrawal"],
+      Withdraw: ["Withdraw"],
       "No credentials given.": [""],
       "Could not create withdrawal operation": [""],
       "Withdrawal creation gave response error": [""],
diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx 
b/packages/demobank-ui/src/pages/LoginForm.tsx
index a8167cca5..981b0f880 100644
--- a/packages/demobank-ui/src/pages/LoginForm.tsx
+++ b/packages/demobank-ui/src/pages/LoginForm.tsx
@@ -21,7 +21,7 @@ import { useEffect, useRef, useState } from "preact/hooks";
 import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
 import { useBackendContext } from "../context/backend.js";
 import { bankUiSettings } from "../settings.js";
-import { undefinedIfEmpty } from "../utils.js";
+import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
 import { doAutoFocus } from "./PaytoWireTransferForm.js";
 import { useBankCoreApiContext } from "../context/config.js";
 import { assertUnreachable } from "./HomePage.js";
@@ -78,43 +78,36 @@ export function LoginForm({ reason, onRegister }: { 
reason?: "not-found" | "forb
   async function doLogin() {
     if (!username || !password) return;
     setBusy({})
-    const data: TalerAuthentication.TokenRequest = {
-      // scope: "readwrite" as "write", //FIX: different than merchant
-      scope: "readwrite",
-      duration: {
-        d_us: "forever" //FIX: should return shortest
-        // d_us: 60 * 60 * 24 * 7 * 1000 * 1000
-      },
-      refreshable: true,
-    }
-    const resp = await 
api.getAuthenticationAPI(username).createAccessToken(password, {
-      // scope: "readwrite" as "write", //FIX: different than merchant
-      scope: "readwrite",
-      duration: {
-        d_us: "forever" //FIX: should return shortest
-        // d_us: 60 * 60 * 24 * 7 * 1000 * 1000
-      },
-      refreshable: true,
-    })
-    if (resp.type === "ok") {
-      backend.logIn({ username, token: resp.body.access_token });
-    } else {
-      switch (resp.case) {
-        case "wrong-credentials": return notify({
-          type: "error",
-          title: i18n.str`Wrong credentials for "${username}"`,
-          description: resp.detail.hint as TranslatedString,
-          debug: resp.detail,
-        })
-        case "not-found": return notify({
-          type: "error",
-          title: i18n.str`Account not found`,
-          description: resp.detail.hint as TranslatedString,
-          debug: resp.detail,
-        })
-        default: assertUnreachable(resp)
+    await withRuntimeErrorHandling(i18n, async () => {
+      const resp = await 
api.getAuthenticationAPI(username).createAccessToken(password, {
+        // scope: "readwrite" as "write", //FIX: different than merchant
+        scope: "readwrite",
+        duration: {
+          d_us: "forever" //FIX: should return shortest
+          // d_us: 60 * 60 * 24 * 7 * 1000 * 1000
+        },
+        refreshable: true,
+      })
+      if (resp.type === "ok") {
+        backend.logIn({ username, token: resp.body.access_token });
+      } else {
+        switch (resp.case) {
+          case "wrong-credentials": return notify({
+            type: "error",
+            title: i18n.str`Wrong credentials for "${username}"`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          case "not-found": return notify({
+            type: "error",
+            title: i18n.str`Account not found`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          default: assertUnreachable(resp)
+        }
       }
-    }
+    })
     setPassword(undefined);
     setBusy(undefined)
   }
@@ -198,7 +191,7 @@ export function LoginForm({ reason, onRegister }: { 
reason?: "not-found" | "forb
             <button type="submit"
               class="rounded-md bg-indigo-600 disabled:bg-gray-300 px-3 py-1.5 
text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
               disabled={!!errors}
-              onClick={(e) => {
+              onClick={async (e) => {
                 e.preventDefault()
                 doLogin()
               }}
diff --git a/packages/demobank-ui/src/pages/OperationState/state.ts 
b/packages/demobank-ui/src/pages/OperationState/state.ts
index 148571ec9..c9c1fa238 100644
--- a/packages/demobank-ui/src/pages/OperationState/state.ts
+++ b/packages/demobank-ui/src/pages/OperationState/state.ts
@@ -21,7 +21,7 @@ import { useBankCoreApiContext } from 
"../../context/config.js";
 import { useWithdrawalDetails } from "../../hooks/access.js";
 import { useBackendState } from "../../hooks/backend.js";
 import { useSettings } from "../../hooks/settings.js";
-import { buildRequestErrorMessage } from "../../utils.js";
+import { buildRequestErrorMessage, withRuntimeErrorHandling } from 
"../../utils.js";
 import { Props, State } from "./index.js";
 import { assertUnreachable } from "../HomePage.js";
 import { mutate } from "swr";
@@ -41,9 +41,8 @@ export function useComponentState({ currency, onClose }: 
Props): utils.Recursive
   async function doSilentStart() {
     //FIXME: if amount is not enough use balance
     const parsedAmount = Amounts.parseOrThrow(`${currency}:${amount}`)
-
-    try {
-      if (!creds) return;
+    if (!creds) return;
+    await withRuntimeErrorHandling(i18n, async () => {
       const resp = await api.createWithdrawal(creds, {
         amount: Amounts.stringify(parsedAmount),
       });
@@ -67,18 +66,7 @@ export function useComponentState({ currency, onClose }: 
Props): utils.Recursive
       } else {
         updateSettings("currentWithdrawalOperationId", 
uri.withdrawalOperationId)
       }
-    } catch (error) {
-      if (error instanceof TalerError) {
-        notify(buildRequestErrorMessage(i18n, error))
-      } else {
-        notifyError(
-          i18n.str`Operation failed, please report`,
-          (error instanceof Error
-            ? error.message
-            : JSON.stringify(error)) as TranslatedString
-        )
-      }
-    }
+    })
   }
 
   const withdrawalOperationId = settings.currentWithdrawalOperationId
@@ -98,10 +86,9 @@ export function useComponentState({ currency, onClose }: 
Props): utils.Recursive
   const wid = withdrawalOperationId
 
   async function doAbort() {
-    try {
-      setBusy({})
+    setBusy({})
+    await withRuntimeErrorHandling(i18n, async () => {
       const resp = await api.abortWithdrawalById(wid);
-      setBusy(undefined)
       if (resp.type === "ok") {
         onClose();
       } else {
@@ -115,30 +102,19 @@ export function useComponentState({ currency, onClose }: 
Props): utils.Recursive
           default: assertUnreachable(resp.case)
         }
       }
-    } catch (error) {
-      if (error instanceof TalerError) {
-        notify(buildRequestErrorMessage(i18n, error))
-      } else {
-        notifyError(
-          i18n.str`Operation failed, please report`,
-          (error instanceof Error
-            ? error.message
-            : JSON.stringify(error)) as TranslatedString
-        )
-      }
-    }
+    })
+    setBusy(undefined)
   }
 
   async function doConfirm() {
-    try {
-      setBusy({})
+    setBusy({})
+    await withRuntimeErrorHandling(i18n, async () => {
       const resp = await api.confirmWithdrawalById(wid);
       if (resp.type === "ok") {
         mutate(() => true)//clean withdrawal state
         if (!settings.showWithdrawalSuccess) {
           notifyInfo(i18n.str`Wire transfer completed!`)
         }
-        setBusy(undefined)
       } else {
         switch (resp.case) {
           case "previously-aborted": return notify({
@@ -156,18 +132,8 @@ export function useComponentState({ currency, onClose }: 
Props): utils.Recursive
           default: assertUnreachable(resp)
         }
       }
-    } catch (error) {
-      if (error instanceof TalerError) {
-        notify(buildRequestErrorMessage(i18n, error))
-      } else {
-        notifyError(
-          i18n.str`Operation failed, please report`,
-          (error instanceof Error
-            ? error.message
-            : JSON.stringify(error)) as TranslatedString
-        )
-      }
-    }
+    })
+    setBusy(undefined)
   }
 
   const uri = stringifyWithdrawUri({
diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx 
b/packages/demobank-ui/src/pages/PaymentOptions.tsx
index f60ba3270..c36d58691 100644
--- a/packages/demobank-ui/src/pages/PaymentOptions.tsx
+++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx
@@ -18,7 +18,7 @@ import { AmountJson } from "@gnu-taler/taler-util";
 import { notifyInfo, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
-import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js";
+import { PaytoWireTransferForm, doAutoFocus } from 
"./PaytoWireTransferForm.js";
 import { WalletWithdrawForm } from "./WalletWithdrawForm.js";
 import { useSettings } from "../hooks/settings.js";
 
@@ -30,7 +30,7 @@ export function PaymentOptions({ limit, goToConfirmOperation 
}: { limit: AmountJ
   const { i18n } = useTranslationContext();
   const [settings] = useSettings();
 
-  const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | 
undefined>();
+  const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | 
undefined>("wire-transfer");
 
   return (
     <div class="mt-2">
@@ -46,28 +46,28 @@ export function PaymentOptions({ limit, 
goToConfirmOperation }: { limit: AmountJ
             <input type="radio" name="project-type" value="Newsletter" 
class="sr-only" aria-labelledby="project-type-0-label" 
aria-describedby="project-type-0-description-0 project-type-0-description-1" 
onClick={() => {
               setTab("charge-wallet")
             }} />
-            <span class="flex flex-1">
+            <div class="flex flex-col">
+              <span class="flex">
               <div class="text-4xl mr-4 my-auto">&#x1F4B5;</div>
-              <span class="flex flex-col">
-                <span id="project-type-0-label" class="block text-sm 
font-medium text-gray-900">
-                  <i18n.Translate>a <b>Taler</b> wallet</i18n.Translate>
-                </span>
-                <span id="project-type-0-description-0" class="mt-1 flex 
items-center text-sm text-gray-500">
-                  <i18n.Translate>Withdraw digital money into your mobile 
wallet or browser extension</i18n.Translate>
+                <span class="grow self-center text-lg text-gray-900 
align-middle text-center">
+                <i18n.Translate>a <b>Taler</b> wallet</i18n.Translate>
                 </span>
+                <svg class="self-center flex-none h-5 w-5 text-indigo-600" 
style={{ visibility: tab === "charge-wallet" ? "visible" : "hidden" }} 
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+                  <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 
16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 
1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
+                </svg>
+              </span>
+              <div class="mt-1 flex items-center text-sm text-gray-500">
+                <i18n.Translate>Withdraw digital money into your mobile wallet 
or browser extension</i18n.Translate>
+              </div>
                 {!!settings.currentWithdrawalOperationId &&
-                  <span class="inline-flex items-center gap-x-1.5 w-fit 
rounded-full bg-green-100 px-2 py-1 text-xs font-medium text-green-700">
+                  <span class="flex items-center gap-x-1.5 w-fit rounded-md 
bg-green-100 px-2 py-1 text-xs font-medium text-green-700 whitespace-pre">
                     <svg class="h-1.5 w-1.5 fill-green-500" viewBox="0 0 6 6" 
aria-hidden="true">
                       <circle cx="3" cy="3" r="3" />
                     </svg>
                     <i18n.Translate>operation ready</i18n.Translate>
                   </span>
                 }
-              </span>
-            </span>
-            <svg class="h-5 w-5 text-indigo-600" style={{ visibility: tab === 
"charge-wallet" ? "visible" : "hidden" }} viewBox="0 0 20 20" 
fill="currentColor" aria-hidden="true">
-              <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 
16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 
1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
-            </svg>
+            </div>
           </label>
 
 
@@ -75,20 +75,20 @@ export function PaymentOptions({ limit, 
goToConfirmOperation }: { limit: AmountJ
             <input type="radio" name="project-type" value="Existing Customers" 
class="sr-only" aria-labelledby="project-type-1-label" 
aria-describedby="project-type-1-description-0 project-type-1-description-1" 
onClick={() => {
               setTab("wire-transfer")
             }} />
-            <span class="flex flex-1">
-              <div class="text-4xl mr-4 my-auto">&#x2194;</div>
-              <span class="flex flex-col">
-                <span id="project-type-1-label" class="block text-sm 
font-medium text-gray-900">
+            <div class="flex flex-col">
+              <span class="flex">
+                <div class="text-4xl mr-4 my-auto">&#x2194;</div>
+                <span class="grow self-center text-lg font-medium 
text-gray-900 align-middle text-center">
                   <i18n.Translate>another bank account</i18n.Translate>
                 </span>
-                <span id="project-type-1-description-0" class="mt-1 flex 
items-center text-sm text-gray-500">
-                  <i18n.Translate>Make a wire transfer to an account which you 
know the bank account number</i18n.Translate>
-                </span>
+                <svg class="self-center flex-none h-5 w-5 text-indigo-600" 
style={{ visibility: tab === "wire-transfer" ? "visible" : "hidden" }} 
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+                  <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 
16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 
1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
+                </svg>
               </span>
-            </span>
-            <svg class="h-5 w-5 text-indigo-600" style={{ visibility: tab === 
"wire-transfer" ? "visible" : "hidden" }} viewBox="0 0 20 20" 
fill="currentColor" aria-hidden="true">
-              <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 
16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 
1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
-            </svg>
+              <div class="mt-1 flex items-center text-sm text-gray-500">
+                <i18n.Translate>Make a wire transfer to an account which you 
know the bank account number</i18n.Translate>
+              </div>
+            </div>
           </label>
         </div>
         {tab === "charge-wallet" && (
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx 
b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index 7861bb0b3..e713324c5 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -39,6 +39,7 @@ import {
   buildRequestErrorMessage,
   undefinedIfEmpty,
   validateIBAN,
+  withRuntimeErrorHandling,
 } from "../utils.js";
 import { useBankCoreApiContext } from "../context/config.js";
 import { useBackendState } from "../hooks/backend.js";
@@ -60,8 +61,7 @@ export function PaytoWireTransferForm({
   onCancel: (() => void) | undefined;
   limit: AmountJson;
 }): VNode {
-  const [isRawPayto, setIsRawPayto] = useState(false);
-  // FIXME: remove this
+  const [isRawPayto, setIsRawPayto] = useState(true);
   const { state: credentials } = useBackendState()
   const { api } = useBankCoreApiContext();
   const [iban, setIban] = useState<string | undefined>();
@@ -73,10 +73,6 @@ export function PaytoWireTransferForm({
   );
   const { i18n } = useTranslationContext();
   const ibanRegex = "^[A-Z][A-Z][0-9]+$";
-  const ref = useRef<HTMLInputElement>(null);
-  useEffect(() => {
-    if (focus) ref.current?.focus();
-  }, [focus, isRawPayto]);
 
   const trimmedAmountStr = amount?.trim();
   const parsedAmount = Amounts.parse(`${limit.currency}:${trimmedAmountStr}`);
@@ -100,8 +96,6 @@ export function PaytoWireTransferForm({
             : undefined,
   });
 
-  // const { createTransaction } = useAccessAPI();
-
   const parsed = !rawPaytoInput ? undefined : parsePaytoUri(rawPaytoInput);
 
   const errorsPayto = undefinedIfEmpty({
@@ -125,12 +119,13 @@ export function PaytoWireTransferForm({
   async function doSend() {
     let payto_uri: string | undefined;
     let sendingAmount: AmountString | undefined;
+    if (credentials.status !== "loggedIn") return;
     if (rawPaytoInput) {
       const p = parsePaytoUri(rawPaytoInput)
       if (!p) return;
       sendingAmount = p.params.amount
       delete p.params.amount
-      //it should have message
+      //if this payto is valid then it already have message
       payto_uri = stringifyPaytoUri(p)
     } else {
       if (!iban || !subject) return;
@@ -139,11 +134,11 @@ export function PaytoWireTransferForm({
       payto_uri = stringifyPaytoUri(ibanPayto);
       sendingAmount = `${limit.currency}:${trimmedAmountStr}`
     }
+    const puri = payto_uri;
 
-    try {
-      if (credentials.status !== "loggedIn") return;
+    await withRuntimeErrorHandling(i18n, async () => {
       const res = await api.createTransaction(credentials, {
-        payto_uri,
+        payto_uri: puri,
         amount: sendingAmount,
       });
       mutate(() => true)
@@ -168,32 +163,20 @@ export function PaytoWireTransferForm({
       setAmount(undefined);
       setIban(undefined);
       setSubject(undefined);
-      rawPaytoInputSetter(undefined)
-    } catch (error) {
-      if (error instanceof TalerError) {
-        notify(buildRequestErrorMessage(i18n, error))
-      } else {
-        notifyError(
-          i18n.str`Operation failed, please report`,
-          (error instanceof Error
-            ? error.message
-            : JSON.stringify(error)) as TranslatedString
-        )
-      }
-    }
-
+      rawPaytoInputSetter(undefined)      
+    })
   }
 
   return (<div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 
bg-gray-100 my-4 px-4 pb-4 rounded-lg">
     {/**
      * FIXME: Scan a qr code
      */}
-    <div class="px-4 sm:px-0">
+    <div class="">
       <h2 class="text-base font-semibold leading-7 text-gray-900">
         {title}
       </h2>
       <div>
-        <div class="px-4 mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-1 
sm:gap-x-4">
+        <div class="px-2 mt-2 grid grid-cols-1 gap-y-4 sm:gap-x-4">
           <label class={"relative flex cursor-pointer rounded-lg border 
bg-white p-4 shadow-sm focus:outline-none" + (!isRawPayto ? "border-indigo-600 
ring-2 ring-indigo-600" : "border-gray-300")}>
             <input type="radio" name="project-type" value="Newsletter" 
class="sr-only" aria-labelledby="project-type-0-label" 
aria-describedby="project-type-0-description-0 project-type-0-description-1" 
onChange={() => {
               if (parsed && parsed.isKnown && parsed.targetType === "iban") {
@@ -207,8 +190,6 @@ export function PaytoWireTransferForm({
                   setSubject(subject)
                 }
               }
-              
//payto://iban/DE9714548806481?amount=LOCAL%3A2&message=011Y8V8KDCPFDEKPDZTHS7KZ14GHX7BVWKRDDPZ1N75TJ90166T0
-              
//payto://iban/DE9714548806481?receiver-name=Exchanger&amount=LOCAL%3A2&message=011Y8V8KDCPFDEKPDZTHS7KZ14GHX7BVWKRDDPZ1N75TJ90166T0
               setIsRawPayto(false)
             }} />
             <span class="flex flex-1">
@@ -236,7 +217,7 @@ export function PaytoWireTransferForm({
             }} />
             <span class="flex flex-1">
               <span class="flex flex-col">
-                <span class="block text-sm  font-medium text-gray-900">
+                <span class="block text-sm font-medium text-gray-900">
                   <i18n.Translate>Import payto:// URI</i18n.Translate>
                 </span>
               </span>
@@ -247,17 +228,16 @@ export function PaytoWireTransferForm({
     </div>
 
     <form
-      class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl 
md:col-span-2 w-fit mx-auto"
+      class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-md 
sm:rounded-xl md:col-span-2 w-fit mx-auto"
       autoCapitalize="none"
       autoCorrect="off"
       onSubmit={e => {
         e.preventDefault()
       }}
     >
-      <div class="px-4 py-6 sm:p-8">
+      <div class="p-4 sm:p-8">
         {!isRawPayto ?
           <div class="grid max-w-xs grid-cols-1 gap-x-6 gap-y-8 
sm:grid-cols-6">
-
             <div class="sm:col-span-5">
               <label for="iban" class="block text-sm font-medium leading-6 
text-gray-900">{i18n.str`Recipient`}</label>
               <div class="mt-2">
@@ -338,8 +318,8 @@ export function PaytoWireTransferForm({
                   name="address"
                   id="address"
                   type="textarea"
-                  rows={3}
-                  class="block overflow-hidden w-64 rounded-md border-0 py-1.5 
text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 
sm:text-sm sm:leading-6"
+                  rows={5}
+                  class="block overflow-hidden w-44 sm:w-96 rounded-md 
border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 
sm:text-sm sm:leading-6"
                   value={rawPaytoInput ?? ""}
                   required
                   
placeholder={i18n.str`payto://iban/[receiver-iban]?message=[subject]&amount=[${limit.currency}:X.Y]`}
@@ -393,7 +373,7 @@ export function doAutoFocus(element: HTMLElement | null) {
       element.scrollIntoView({
         behavior: "smooth",
         block: "center",
-        inline: "center"
+        inline: "center",
       })
     }, 100)
   }
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx 
b/packages/demobank-ui/src/pages/QrCodeSection.tsx
index a37de383d..64f9ec5ab 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx
@@ -30,7 +30,7 @@ import {
 import { Fragment, h, VNode } from "preact";
 import { useEffect } from "preact/hooks";
 import { QR } from "../components/QR.js";
-import { buildRequestErrorMessage } from "../utils.js";
+import { buildRequestErrorMessage, withRuntimeErrorHandling } from 
"../utils.js";
 import { useBankCoreApiContext } from "../context/config.js";
 import { assertUnreachable } from "./HomePage.js";
 
@@ -55,36 +55,22 @@ export function QrCodeSection({
   const { api } = useBankCoreApiContext()
 
   async function doAbort() {
-    try {
+    await withRuntimeErrorHandling(i18n, async () => {
       const result = await 
api.abortWithdrawalById(withdrawUri.withdrawalOperationId);
       if (result.type === "ok") {
         onAborted();
       } else {
         switch (result.case) {
-          case "previously-confirmed": {
-            notify({
-              type: "info",
-              title: i18n.str`The reserve operation has been confirmed 
previously and can't be aborted`
-            })
-            break;
-          }
+          case "previously-confirmed": return notify({
+            type: "info",
+            title: i18n.str`The reserve operation has been confirmed 
previously and can't be aborted`
+          })
           default: {
             assertUnreachable(result.case)
           }
         }
       }
-    } catch (error) {
-      if (error instanceof TalerError) {
-        notify(buildRequestErrorMessage(i18n, error))
-      } else {
-        notifyError(
-          i18n.str`Operation failed, please report`,
-          (error instanceof Error
-            ? error.message
-            : JSON.stringify(error)) as TranslatedString
-        )
-      }
-    }
+    })
   }
 
   return (
diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx 
b/packages/demobank-ui/src/pages/RegistrationPage.tsx
index fda2d904d..ce38a9fb8 100644
--- a/packages/demobank-ui/src/pages/RegistrationPage.tsx
+++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx
@@ -24,7 +24,7 @@ import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { useBackendContext } from "../context/backend.js";
 import { bankUiSettings } from "../settings.js";
-import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
+import { buildRequestErrorMessage, undefinedIfEmpty, withRuntimeErrorHandling 
} from "../utils.js";
 import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
 import { getRandomPassword, getRandomUsername } from "./rnd.js";
 import { useBankCoreApiContext } from "../context/config.js";
@@ -95,118 +95,80 @@ function RegistrationForm({ onComplete, onCancel }: { 
onComplete: () => void, on
   });
 
   async function doRegistrationAndLogin(name: string | undefined, username: 
string, password: string) {
-    const creationResponse = await api.createAccount("" as AccessToken, { 
name: name ?? "", username, password });
-    if (creationResponse.type === "fail") {
-      switch (creationResponse.case) {
-        case "invalid-input": return notify({
-          type: "error",
-          title: i18n.str`Some of the input fields are invalid.`,
-          description: creationResponse.detail.hint as TranslatedString,
-          debug: creationResponse.detail,
-        })
-        case "unable-to-create": return notify({
-          type: "error",
-          title: i18n.str`Unable to create that account.`,
-          description: creationResponse.detail.hint as TranslatedString,
-          debug: creationResponse.detail,
-        })
-        case "unauthorized": return notify({
-          type: "error",
-          title: i18n.str`No enough permission to create that account.`,
-          description: creationResponse.detail.hint as TranslatedString,
-          debug: creationResponse.detail,
-        })
-        case "already-exist": return notify({
-          type: "error",
-          title: i18n.str`That username is already taken`,
-          description: creationResponse.detail.hint as TranslatedString,
-          debug: creationResponse.detail,
-        })
-        default: assertUnreachable(creationResponse)
+    await withRuntimeErrorHandling(i18n, async () => {
+      const creationResponse = await api.createAccount("" as AccessToken, { 
name: name ?? "", username, password });
+      if (creationResponse.type === "fail") {
+        switch (creationResponse.case) {
+          case "invalid-input": return notify({
+            type: "error",
+            title: i18n.str`Some of the input fields are invalid.`,
+            description: creationResponse.detail.hint as TranslatedString,
+            debug: creationResponse.detail,
+          })
+          case "unable-to-create": return notify({
+            type: "error",
+            title: i18n.str`Unable to create that account.`,
+            description: creationResponse.detail.hint as TranslatedString,
+            debug: creationResponse.detail,
+          })
+          case "unauthorized": return notify({
+            type: "error",
+            title: i18n.str`No enough permission to create that account.`,
+            description: creationResponse.detail.hint as TranslatedString,
+            debug: creationResponse.detail,
+          })
+          case "already-exist": return notify({
+            type: "error",
+            title: i18n.str`That username is already taken`,
+            description: creationResponse.detail.hint as TranslatedString,
+            debug: creationResponse.detail,
+          })
+          default: assertUnreachable(creationResponse)
+        }
       }
-    }
-    const resp = await 
api.getAuthenticationAPI(username).createAccessToken(password, {
-      // scope: "readwrite" as "write", //FIX: different than merchant
-      scope: "readwrite",
-      duration: {
-        d_us: "forever" //FIX: should return shortest
-        // d_us: 60 * 60 * 24 * 7 * 1000 * 1000
-      },
-      refreshable: true,
-    })
+      const resp = await 
api.getAuthenticationAPI(username).createAccessToken(password, {
+        scope: "readwrite",
+        duration: { d_us: "forever" },
+        refreshable: true,
+      })
 
-    if (resp.type === "ok") {
-      backend.logIn({ username, token: resp.body.access_token });
-    } else {
-      switch (resp.case) {
-        case "wrong-credentials": return notify({
-          type: "error",
-          title: i18n.str`Wrong credentials for "${username}"`,
-          description: resp.detail.hint as TranslatedString,
-          debug: resp.detail,
-        })
-        case "not-found": return notify({
-          type: "error",
-          title: i18n.str`Account not found`,
-          description: resp.detail.hint as TranslatedString,
-          debug: resp.detail,
-        })
-        default: assertUnreachable(resp)
+      if (resp.type === "ok") {
+        backend.logIn({ username, token: resp.body.access_token });
+      } else {
+        switch (resp.case) {
+          case "wrong-credentials": return notify({
+            type: "error",
+            title: i18n.str`Wrong credentials for "${username}"`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          case "not-found": return notify({
+            type: "error",
+            title: i18n.str`Account not found`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          default: assertUnreachable(resp)
+        }
       }
-    }
+    })
   }
 
   async function doRegistrationStep() {
     if (!username || !password) return;
-    try {
-      await doRegistrationAndLogin(name, username, password)
-      setUsername(undefined);
-      onComplete();
-    } catch (error) {
-      if (error instanceof TalerError) {
-        notify(buildRequestErrorMessage(i18n, error))
-      } else {
-        notifyError(
-          i18n.str`Operation failed, please report`,
-          (error instanceof Error
-            ? error.message
-            : JSON.stringify(error)) as TranslatedString
-        )
-      }
-    }
+    await doRegistrationAndLogin(name, username, password)
+    setUsername(undefined);
     setPassword(undefined);
     setRepeatPassword(undefined);
+    onComplete();
   }
 
-  async function delay(ms: number): Promise<void> {
-    return new Promise((resolve) => {
-      setTimeout(() => {
-        resolve(undefined);
-      }, ms)
-    })
-  }
   async function doRandomRegistration(tries: number = 3) {
     const user = getRandomUsername();
     const pass = getRandomPassword();
-    try {
-      setUsername(undefined);
-      setPassword(undefined);
-      setRepeatPassword(undefined);
-      const username = `_${user.first}-${user.second}_`
-      await doRegistrationAndLogin(name, username, pass)
-      onComplete();
-    } catch (error) {
-      if (error instanceof TalerError) {
-        notify(buildRequestErrorMessage(i18n, error))
-      } else {
-        notifyError(
-          i18n.str`Operation failed, please report`,
-          (error instanceof Error
-            ? error.message
-            : JSON.stringify(error)) as TranslatedString
-        )
-      }
-    }
+    const username = `_${user.first}-${user.second}_`
+    await doRegistrationAndLogin(name, username, pass)
+    onComplete();
   }
 
   return (
@@ -403,8 +365,9 @@ function RegistrationForm({ onComplete, onCancel }: { 
onComplete: () => void, on
               <button type="submit"
                 class=" rounded-md bg-indigo-600 disabled:bg-gray-300 px-3 
py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
                 disabled={!!errors}
-                onClick={(e) => {
+                onClick={async (e) => {
                   e.preventDefault()
+
                   doRegistrationStep()
                 }}
               >
diff --git a/packages/demobank-ui/src/pages/ShowAccountDetails.tsx 
b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
index 3534f9733..c65b90503 100644
--- a/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
+++ b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
@@ -7,7 +7,7 @@ import { Loading } from "../components/Loading.js";
 import { useBankCoreApiContext } from "../context/config.js";
 import { useAccountDetails } from "../hooks/access.js";
 import { useBackendState } from "../hooks/backend.js";
-import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
+import { buildRequestErrorMessage, undefinedIfEmpty, withRuntimeErrorHandling 
} from "../utils.js";
 import { assertUnreachable } from "./HomePage.js";
 import { LoginForm } from "./LoginForm.js";
 import { AccountForm } from "./admin/AccountForm.js";
@@ -49,50 +49,41 @@ export function ShowAccountDetails({
   async function doUpdate() {
     if (!update) {
       setUpdate(true);
-    } else {
-      if (!submitAccount || !creds) return;
-      try {
-        const resp = await api.updateAccount(creds, {
-          cashout_address: submitAccount.cashout_payto_uri,
-          challenge_contact_data: undefinedIfEmpty({
-            email: submitAccount.contact_data?.email,
-            phone: submitAccount.contact_data?.phone,
-          }),
-          is_exchange: false,
-          name: submitAccount.name,
-        });
-        if (resp.type === "ok") {
-          onUpdateSuccess();
-        } else {
-          switch (resp.case) {
-            case "unauthorized": return notify({
-              type: "error",
-              title: i18n.str`The rights to change the account are not 
sufficient`,
-              description: resp.detail.hint as TranslatedString,
-              debug: resp.detail,
-            })
-            case "not-found": return notify({
-              type: "error",
-              title: i18n.str`The username was not found`,
-              description: resp.detail.hint as TranslatedString,
-              debug: resp.detail,
-            })
-            default: assertUnreachable(resp)
-          }
-        }
-      } catch (error) {
-        if (error instanceof TalerError) {
-          notify(buildRequestErrorMessage(i18n, error))
-        } else {
-          notifyError(
-            i18n.str`Operation failed, please report`,
-            (error instanceof Error
-              ? error.message
-              : JSON.stringify(error)) as TranslatedString
-          )
+      return;
+    }
+    if (!submitAccount || !creds) return;
+    await withRuntimeErrorHandling(i18n, async () => {
+      const resp = await api.updateAccount(creds, {
+        cashout_address: submitAccount.cashout_payto_uri,
+        challenge_contact_data: undefinedIfEmpty({
+          email: submitAccount.contact_data?.email,
+          phone: submitAccount.contact_data?.phone,
+        }),
+        is_exchange: false,
+        name: submitAccount.name,
+      });
+      
+      if (resp.type === "ok") {
+        onUpdateSuccess();
+      } else {
+        switch (resp.case) {
+          case "unauthorized": return notify({
+            type: "error",
+            title: i18n.str`The rights to change the account are not 
sufficient`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          case "not-found": return notify({
+            type: "error",
+            title: i18n.str`The username was not found`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          default: assertUnreachable(resp)
         }
       }
-    }
+    })
+
   }
 
   return (
diff --git a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx 
b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
index ac6e9fa9b..d82dac4b1 100644
--- a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
+++ b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
@@ -3,7 +3,7 @@ import { HttpResponsePaginated, RequestError, notify, 
notifyError, useTranslatio
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
 import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
-import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
+import { buildRequestErrorMessage, undefinedIfEmpty, withRuntimeErrorHandling 
} from "../utils.js";
 import { doAutoFocus } from "./PaytoWireTransferForm.js";
 import { useBankCoreApiContext } from "../context/config.js";
 import { assertUnreachable } from "./HomePage.js";
@@ -39,7 +39,7 @@ export function UpdateAccountPassword({
 
   async function doChangePassword() {
     if (!!errors || !password || !creds) return;
-    try {
+    await withRuntimeErrorHandling(i18n, async () => {
       const resp = await api.updatePassword(creds, {
         new_password: password,
       });
@@ -47,32 +47,18 @@ export function UpdateAccountPassword({
         onUpdateSuccess();
       } else {
         switch (resp.case) {
-          case "unauthorized": {
-            notify({
-              type: "error",
-              title: i18n.str`Not authorized to change the password, maybe the 
session is invalid.`
-            })
-            break;
-          }
-          case "not-found": {
-            notify({
-              type: "error",
-              title: i18n.str`Account not found`
-            })
-            break;
-          }
+          case "unauthorized": return notify({
+            type: "error",
+            title: i18n.str`Not authorized to change the password, maybe the 
session is invalid.`
+          })
+          case "not-found": return notify({
+            type: "error",
+            title: i18n.str`Account not found`
+          })
           default: assertUnreachable(resp)
         }
       }
-    } catch (error) {
-      if (error instanceof TalerError) {
-        notify(buildRequestErrorMessage(i18n, error))
-      } else {
-        notifyError(i18n.str`Operation failed, please report`, (error 
instanceof Error
-          ? error.message
-          : JSON.stringify(error)) as TranslatedString)
-      }
-    }
+    })
   }
 
   return (
diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx 
b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
index 2d80bad1f..28d5d7749 100644
--- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
+++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
@@ -36,7 +36,7 @@ import { Attention } from "../components/Attention.js";
 import { useBankCoreApiContext } from "../context/config.js";
 import { useBackendState } from "../hooks/backend.js";
 import { useSettings } from "../hooks/settings.js";
-import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
+import { buildRequestErrorMessage, undefinedIfEmpty, withRuntimeErrorHandling 
} from "../utils.js";
 import { assertUnreachable } from "./HomePage.js";
 import { OperationState } from "./OperationState/index.js";
 import { InputAmount, doAutoFocus } from "./PaytoWireTransferForm.js";
@@ -62,6 +62,7 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, 
onCancel, focus }: {
 
   if (!!settings.currentWithdrawalOperationId) {
     return <Attention type="warning" title={i18n.str`There is an operation 
already`}>
+      <span ref={focus ? doAutoFocus : undefined}/>
       <i18n.Translate>
         To complete or cancel the operation click <a class="font-semibold 
text-yellow-700 hover:text-yellow-600" 
href={`#/operation/${settings.currentWithdrawalOperationId}`}>here</a>
       </i18n.Translate>
@@ -87,7 +88,7 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, 
onCancel, focus }: {
 
   async function doStart() {
     if (!parsedAmount || !creds) return;
-    try {
+    await withRuntimeErrorHandling(i18n, async () => {
       const result = await api.createWithdrawal(creds, {
         amount: Amounts.stringify(parsedAmount),
       });
@@ -110,18 +111,7 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, 
onCancel, focus }: {
           default: assertUnreachable(result.case)
         }
       }
-    } catch (error) {
-      if (error instanceof TalerError) {
-        notify(buildRequestErrorMessage(i18n, error))
-      } else {
-        notifyError(
-          i18n.str`Operation failed, please report`,
-          (error instanceof Error
-            ? error.message
-            : JSON.stringify(error)) as TranslatedString
-        )
-      }
-    }
+    })
   }
 
   return <form
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx 
b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index 602ec9bd8..87637f7ef 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -36,7 +36,7 @@ import {
 import { Fragment, VNode, h } from "preact";
 import { useMemo, useState } from "preact/hooks";
 import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
-import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
+import { buildRequestErrorMessage, undefinedIfEmpty, withRuntimeErrorHandling 
} from "../utils.js";
 import { useSettings } from "../hooks/settings.js";
 import { RenderAmount } from "./PaytoWireTransferForm.js";
 import { useBankCoreApiContext } from "../context/config.js";
@@ -88,8 +88,8 @@ export function WithdrawalConfirmationQuestion({
   }) ?? busy;
 
   async function doTransfer() {
-    try {
-      setBusy({})
+    setBusy({})
+    await withRuntimeErrorHandling(i18n, async () => {
       const resp = await 
api.confirmWithdrawalById(withdrawUri.withdrawalOperationId);
       if (resp.type === "ok") {
         mutate(() => true)// clean any info that we have
@@ -113,53 +113,28 @@ export function WithdrawalConfirmationQuestion({
           default: assertUnreachable(resp)
         }
       }
-    } catch (error) {
-      if (error instanceof TalerError) {
-        notify(buildRequestErrorMessage(i18n, error))
-      } else {
-        notifyError(
-          i18n.str`Operation failed, please report`,
-          (error instanceof Error
-            ? error.message
-            : JSON.stringify(error)) as TranslatedString
-        )
-      }
-    }
+    })
     setBusy(undefined)
   }
 
   async function doCancel() {
-    try {
-      setBusy({})
+    setBusy({})
+    await withRuntimeErrorHandling(i18n, async () => {
       const resp = await 
api.abortWithdrawalById(withdrawUri.withdrawalOperationId);
       if (resp.type === "ok") {
         onAborted();
       } else {
         switch (resp.case) {
-          case "previously-confirmed": {
-            notify({
-              type: "error",
-              title: i18n.str`The reserve operation has been confirmed 
previously and can't be aborted`
-            });
-            break;
-          }
+          case "previously-confirmed": return notify({
+            type: "error",
+            title: i18n.str`The reserve operation has been confirmed 
previously and can't be aborted`
+          });
           default: {
             assertUnreachable(resp.case)
           }
         }
       }
-    } catch (error) {
-      if (error instanceof TalerError) {
-        notify(buildRequestErrorMessage(i18n, error))
-      } else {
-        notifyError(
-          i18n.str`Operation failed, please report`,
-          (error instanceof Error
-            ? error.message
-            : JSON.stringify(error)) as TranslatedString
-        )
-      }
-    }
+    })
     setBusy(undefined)
   }
 
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx 
b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 15910201e..7266e4de4 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -29,6 +29,7 @@ import { useWithdrawalDetails } from "../hooks/access.js";
 import { assertUnreachable } from "./HomePage.js";
 import { QrCodeSection } from "./QrCodeSection.js";
 import { WithdrawalConfirmationQuestion } from 
"./WithdrawalConfirmationQuestion.js";
+import { Attention } from "../components/Attention.js";
 
 const logger = new Logger("WithdrawalQRCode");
 
@@ -139,18 +140,21 @@ export function WithdrawalQRCode({
   }
 
   if (!data.selected_reserve_pub) {
-    return <div>
-      the exchange is selcted but no reserve pub
-    </div>
+    return <Attention type="danger"
+      title={i18n.str`The operation is incomplete or some step in the 
withdrawal failed`} >
+      <i18n.Translate>The exchange is selected but no reserve public key 
found.</i18n.Translate>
+    </Attention>
   }
 
   const account = !data.selected_exchange_account ? undefined : 
parsePaytoUri(data.selected_exchange_account)
 
   if (!account) {
-    return <div>
-      the exchange is selcted but no account
-    </div>
+    return <Attention type="danger"
+      title={i18n.str`The operation is incomplete or some step in the 
withdrawal failed`} >
+      <i18n.Translate>The exchange is selected but the exchange payto URI is 
missing or invalid.</i18n.Translate>
+    </Attention>
   }
+
   return (
     <WithdrawalConfirmationQuestion
       withdrawUri={withdrawUri}
diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx 
b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
index f6176e772..e10c3ad41 100644
--- a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
@@ -2,7 +2,7 @@ import { HttpStatusCode, TalerCorebankApi, TalerError, 
TranslatedString } from "
 import { RequestError, notify, notifyError, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
-import { buildRequestErrorMessage } from "../../utils.js";
+import { buildRequestErrorMessage, withRuntimeErrorHandling } from 
"../../utils.js";
 import { getRandomPassword } from "../rnd.js";
 import { AccountForm } from "./AccountForm.js";
 import { useBackendState } from "../../hooks/backend.js";
@@ -29,7 +29,7 @@ export function CreateNewAccount({
 
   async function doCreate() {
     if (!submitAccount || !token) return;
-    try {
+    await withRuntimeErrorHandling(i18n, async () => {
       const account: TalerCorebankApi.RegisterAccountRequest = {
         cashout_payto_uri: submitAccount.cashout_payto_uri,
         challenge_contact_data: submitAccount.contact_data,
@@ -72,18 +72,7 @@ export function CreateNewAccount({
           default: assertUnreachable(resp)
         }
       }
-    } catch (error) {
-      if (error instanceof TalerError) {
-        notify(buildRequestErrorMessage(i18n, error))
-      } else {
-        notifyError(
-          i18n.str`Operation failed, please report`,
-          (error instanceof Error
-            ? error.message
-            : JSON.stringify(error)) as TranslatedString
-        )
-      }
-    }
+    })
   }
 
   return (
diff --git a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx 
b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
index ce8a53ca1..9a212ebd0 100644
--- a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
@@ -7,7 +7,7 @@ import { ErrorLoading } from "../../components/ErrorLoading.js";
 import { Loading } from "../../components/Loading.js";
 import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
 import { useAccountDetails } from "../../hooks/access.js";
-import { buildRequestErrorMessage, undefinedIfEmpty } from "../../utils.js";
+import { buildRequestErrorMessage, undefinedIfEmpty, withRuntimeErrorHandling 
} from "../../utils.js";
 import { assertUnreachable } from "../HomePage.js";
 import { LoginForm } from "../LoginForm.js";
 import { doAutoFocus } from "../PaytoWireTransferForm.js";
@@ -60,7 +60,7 @@ export function RemoveAccount({
 
   async function doRemove() {
     if (!token) return;
-    try {
+    await withRuntimeErrorHandling(i18n, async () => {
       const resp = await api.deleteAccount({ username: account, token });
       if (resp.type === "ok") {
         onUpdateSuccess();
@@ -95,16 +95,7 @@ export function RemoveAccount({
           }
         }
       }
-    } catch (error) {
-      if (error instanceof TalerError) {
-        notify(buildRequestErrorMessage(i18n, error))
-      } else {
-        notifyError(i18n.str`Operation failed, please report`,
-          (error instanceof Error
-            ? error.message
-            : JSON.stringify(error)) as TranslatedString);
-      }
-    }
+    })
   }
 
   const errors = undefinedIfEmpty({
diff --git a/packages/demobank-ui/src/pages/business/Home.tsx 
b/packages/demobank-ui/src/pages/business/Home.tsx
index 03d7895e3..d7beda01d 100644
--- a/packages/demobank-ui/src/pages/business/Home.tsx
+++ b/packages/demobank-ui/src/pages/business/Home.tsx
@@ -45,6 +45,7 @@ import {
   TanChannel,
   buildRequestErrorMessage,
   undefinedIfEmpty,
+  withRuntimeErrorHandling,
 } from "../../utils.js";
 import { LoginForm } from "../LoginForm.js";
 import { InputAmount } from "../PaytoWireTransferForm.js";
@@ -241,41 +242,15 @@ function CreateCashout({
   );
 
   useEffect(() => {
-    if (form.isDebit) {
-      calculateFromDebit(amount, sellFee, sellRate)
-        .then((r) => {
-          setCalc(r);
-        })
-        .catch((error) => {
-          if (error instanceof TalerError) {
-            notify(buildRequestErrorMessage(i18n, error))
-          } else {
-            notifyError(
-              i18n.str`Operation failed, please report`,
-              (error instanceof Error
-                ? error.message
-                : JSON.stringify(error)) as TranslatedString
-            )
-          }
-        });
-    } else {
-      calculateFromCredit(amount, sellFee, sellRate)
-        .then((r) => {
-          setCalc(r);
-        })
-        .catch((error) => {
-          if (error instanceof TalerError) {
-            notify(buildRequestErrorMessage(i18n, error))
-          } else {
-            notifyError(
-              i18n.str`Operation failed, please report`,
-              (error instanceof Error
-                ? error.message
-                : JSON.stringify(error)) as TranslatedString
-            )
-          }
-        });
+    async function doAsync() {
+      await withRuntimeErrorHandling(i18n, async () => {
+        const resp = await (form.isDebit ?
+          calculateFromDebit(amount, sellFee, sellRate) :
+          calculateFromCredit(amount, sellFee, sellRate));
+        setCalc(resp)
+      })
     }
+    doAsync()
   }, [form.amount, form.isDebit]);
 
   const balanceAfter = Amounts.sub(account.balance, calc.debit).amount;
@@ -484,7 +459,7 @@ function CreateCashout({
               e.preventDefault();
 
               if (errors || !creds) return;
-              try {
+              await withRuntimeErrorHandling(i18n, async () => {
                 const resp = await api.createCashout(creds, {
                   amount_credit: Amounts.stringify(calc.credit),
                   amount_debit: Amounts.stringify(calc.debit),
@@ -529,18 +504,7 @@ function CreateCashout({
                     default: assertUnreachable(resp)
                   }
                 }
-              } catch (error) {
-                if (error instanceof TalerError) {
-                  notify(buildRequestErrorMessage(i18n, error))
-                } else {
-                  notifyError(
-                    i18n.str`Operation failed, please report`,
-                    (error instanceof Error
-                      ? error.message
-                      : JSON.stringify(error)) as TranslatedString
-                  )
-                }
-              }
+              })
             }}
           >
             {i18n.str`Create`}
@@ -669,7 +633,7 @@ export function ShowCashoutDetails({
               onClick={async (e) => {
                 e.preventDefault();
                 if (!creds) return;
-                try {
+                await withRuntimeErrorHandling(i18n, async () => {
                   const resp = await api.abortCashoutById(creds, id);
                   if (resp.type === "ok") {
                     onCancel();
@@ -692,18 +656,7 @@ export function ShowCashoutDetails({
                       }
                     }
                   }
-                } catch (error) {
-                  if (error instanceof TalerError) {
-                    notify(buildRequestErrorMessage(i18n, error))
-                  } else {
-                    notifyError(
-                      i18n.str`Operation failed, please report`,
-                      (error instanceof Error
-                        ? error.message
-                        : JSON.stringify(error)) as TranslatedString
-                    )
-                  }
-                }
+                })
               }}
             >
               {i18n.str`Abort`}
@@ -715,9 +668,8 @@ export function ShowCashoutDetails({
               class="pure-button pure-button-primary "
               onClick={async (e) => {
                 e.preventDefault();
-                if (!creds) return;
-                try {
-                  if (!code) return;
+                if (!creds || !code) return;
+                await withRuntimeErrorHandling(i18n, async () => {
                   const resp = await api.confirmCashoutById(creds, id, {
                     tan: code,
                   });
@@ -745,19 +697,8 @@ export function ShowCashoutDetails({
                       })
                       default: assertUnreachable(resp)
                     }
-                  }
-                } catch (error) {
-                  if (error instanceof TalerError) {
-                    notify(buildRequestErrorMessage(i18n, error))
-                  } else {
-                    notifyError(
-                      i18n.str`Operation failed, please report`,
-                      (error instanceof Error
-                        ? error.message
-                        : JSON.stringify(error)) as TranslatedString
-                    )
-                  }
-                }
+                  }                  
+                })
               }}
             >
               {i18n.str`Confirm`}
diff --git a/packages/demobank-ui/src/utils.ts 
b/packages/demobank-ui/src/utils.ts
index 310e80cd6..437618150 100644
--- a/packages/demobank-ui/src/utils.ts
+++ b/packages/demobank-ui/src/utils.ts
@@ -19,6 +19,8 @@ import {
   ErrorNotification,
   ErrorType,
   HttpError,
+  notify,
+  notifyError,
   useTranslationContext,
 } from "@gnu-taler/web-util/browser";
 
@@ -92,10 +94,27 @@ export enum CashoutStatus {
 export const PAGE_SIZE = 20;
 export const MAX_RESULT_SIZE = PAGE_SIZE * 2 - 1;
 
-export function buildRequestErrorMessage(
-  i18n: ReturnType<typeof useTranslationContext>["i18n"],
-  cause: TalerError<{}>,
-): ErrorNotification {
+type Translator = ReturnType<typeof useTranslationContext>["i18n"]
+
+export async function withRuntimeErrorHandling<T>(i18n: Translator, cb: () => 
Promise<T>): Promise<void> {
+  try {
+    await cb()
+  } catch (error: unknown) {
+    if (error instanceof TalerError) {
+      notify(buildRequestErrorMessage(i18n, error))
+    } else {
+      notifyError(
+        i18n.str`Operation failed, please report`,
+        (error instanceof Error
+          ? error.message
+          : JSON.stringify(error)) as TranslatedString
+      )
+    }      
+  }
+}
+
+
+export function buildRequestErrorMessage( i18n: Translator, cause: 
TalerError): ErrorNotification {
   let result: ErrorNotification;
   switch (cause.errorDetail.code) {
     case TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT: {
diff --git a/packages/taler-util/src/http-client/bank-core.ts 
b/packages/taler-util/src/http-client/bank-core.ts
index 593daa2c3..033b78f67 100644
--- a/packages/taler-util/src/http-client/bank-core.ts
+++ b/packages/taler-util/src/http-client/bank-core.ts
@@ -514,6 +514,8 @@ export class TalerCoreBankHttpClient {
     });
     switch (resp.status) {
       case HttpStatusCode.Ok: return opSuccess(resp, codecForCashouts())
+      // FIXME: not in docs (maybe for admin and bank)
+      case HttpStatusCode.NotFound: return opFixedSuccess({ cashouts: [] });
       case HttpStatusCode.NoContent: return opFixedSuccess({ cashouts: [] });
       default: return opUnknownFailure(resp, await resp.text())
     }

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