gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] 02/02: show cashout details


From: gnunet
Subject: [taler-wallet-core] 02/02: show cashout details
Date: Fri, 24 Nov 2023 18:43:26 +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 6f7512be74005f169f98654f11360fd4fff9162f
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Fri Nov 24 14:43:19 2023 -0300

    show cashout details
---
 packages/demobank-ui/src/Routing.tsx               |   2 +-
 .../demobank-ui/src/components/Cashouts/views.tsx  |  57 ++-
 packages/demobank-ui/src/hooks/circuit.ts          |  41 +-
 .../demobank-ui/src/pages/AccountPage/views.tsx    |  17 +
 packages/demobank-ui/src/pages/BankFrame.tsx       |   1 -
 packages/demobank-ui/src/pages/PaymentOptions.tsx  |   2 +-
 .../src/pages/PaytoWireTransferForm.tsx            |   2 +-
 .../demobank-ui/src/pages/ProfileNavigation.tsx    |   4 +-
 .../src/pages/account/ShowAccountDetails.tsx       |   3 +-
 .../src/pages/account/UpdateAccountPassword.tsx    |   2 +-
 .../demobank-ui/src/pages/admin/AccountForm.tsx    |  93 +++--
 .../src/pages/business/CreateCashout.tsx           |  52 ++-
 .../src/pages/business/ShowCashoutDetails.tsx      | 431 ++++++++++++---------
 13 files changed, 422 insertions(+), 285 deletions(-)

diff --git a/packages/demobank-ui/src/Routing.tsx 
b/packages/demobank-ui/src/Routing.tsx
index 733d55a0f..8ed66d4cf 100644
--- a/packages/demobank-ui/src/Routing.tsx
+++ b/packages/demobank-ui/src/Routing.tsx
@@ -258,7 +258,7 @@ export function Routing(): VNode {
             <ShowCashoutDetails
               id={cid}
               onCancel={() => {
-                route("/account");
+                route("/my-cashouts");
               }}
             />
           )}
diff --git a/packages/demobank-ui/src/components/Cashouts/views.tsx 
b/packages/demobank-ui/src/components/Cashouts/views.tsx
index 651a7a034..59bb4a16b 100644
--- a/packages/demobank-ui/src/components/Cashouts/views.tsx
+++ b/packages/demobank-ui/src/components/Cashouts/views.tsx
@@ -93,7 +93,7 @@ export function ReadyView({ cashouts, onSelected }: 
State.Ready): VNode {
             {Object.entries(txByDate).map(([date, txs], idx) => {
               return <Fragment key={idx}>
                 <tr class="border-t border-gray-200">
-                  <th colSpan={4} scope="colgroup" class="bg-gray-50 py-2 pl-4 
pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-3">
+                  <th colSpan={6} scope="colgroup" class="bg-gray-50 py-2 pl-4 
pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-3">
                     {date}
                   </th>
                 </tr>
@@ -102,9 +102,12 @@ export function ReadyView({ cashouts, onSelected }: 
State.Ready): VNode {
                   const confirmationTime = item.confirmation_time
                     ? item.confirmation_time.t_s === "never" ? i18n.str`never` 
: format(item.confirmation_time.t_s, "dd/MM/yyyy HH:mm:ss")
                     : "-"
-                  return (<tr key={idx} class="border-b border-gray-200 
last:border-none">
+                  return (<tr key={idx} class="border-b border-gray-200 
hover:bg-gray-200 last:border-none">
 
-                    <td class="relative py-2 pl-2 pr-2 text-sm ">
+                    <td onClick={(e) => {
+                        e.preventDefault();
+                        onSelected(item.id);
+                      }} class="relative py-2 pl-2 pr-2 text-sm ">
                       <div class="font-medium 
text-gray-900">{creationTime}</div>
                       {/* <dl class="font-normal sm:hidden">
                         <dt class="sr-only 
sm:hidden"><i18n.Translate>Amount</i18n.Translate></dt>
@@ -128,18 +131,28 @@ export function ReadyView({ cashouts, onSelected }: 
State.Ready): VNode {
                         </dd>
                       </dl> */}
                     </td>
-                    <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500">{confirmationTime}</td>
-                    <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-red-600"><RenderAmount value={Amounts.parseOrThrow(item.amount_debit)}  
spec={resp.body.regional_currency_specification} /></td>
-                    <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-green-600"><RenderAmount value={Amounts.parseOrThrow(item.amount_credit)} 
spec={resp.body.fiat_currency_specification} /></td>
+                    <td onClick={(e) => {
+                        e.preventDefault();
+                        onSelected(item.id);
+                      }}class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500 cursor-pointer">{confirmationTime}</td>
+                    <td onClick={(e) => {
+                        e.preventDefault();
+                        onSelected(item.id);
+                      }}class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-red-600 cursor-pointer"><RenderAmount 
value={Amounts.parseOrThrow(item.amount_debit)} 
spec={resp.body.regional_currency_specification} /></td>
+                    <td onClick={(e) => {
+                        e.preventDefault();
+                        onSelected(item.id);
+                      }}class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-green-600 cursor-pointer"><RenderAmount 
value={Amounts.parseOrThrow(item.amount_credit)} 
spec={resp.body.fiat_currency_specification} /></td>
 
-                    <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500">{item.status}</td>
-                    <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500 break-all min-w-md">
-                      <a href="#" onClick={(e) => {
+                    <td  onClick={(e) => {
                         e.preventDefault();
                         onSelected(item.id);
-                      }}>
-                        {item.subject}
-                      </a>
+                      }}class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500 cursor-pointer">{item.status}</td>
+                    <td onClick={(e) => {
+                        e.preventDefault();
+                        onSelected(item.id);
+                      }} class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500 break-all min-w-md">
+                      {item.subject}
                     </td>
                   </tr>)
                 })}
@@ -150,27 +163,9 @@ export function ReadyView({ cashouts, onSelected }: 
State.Ready): VNode {
 
         </table>
 
-        {/* <nav class="flex items-center justify-between border-t 
border-gray-200 bg-white px-4 py-3 sm:px-6 rounded-lg" aria-label="Pagination">
-          <div class="flex flex-1 justify-between sm:justify-end">
-            <button
-              class="relative disabled:bg-gray-100 disabled:text-gray-500 
inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold 
text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 
focus-visible:outline-offset-0"
-              disabled={!onPrev}
-              onClick={onPrev}
-            >
-              <i18n.Translate>First page</i18n.Translate>
-            </button>
-            <button
-              class="relative disabled:bg-gray-100 disabled:text-gray-500 ml-3 
inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold 
text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 
focus-visible:outline-offset-0"
-              disabled={!onNext}
-              onClick={onNext}
-            >
-              <i18n.Translate>Next</i18n.Translate>
-            </button>
-          </div>
-        </nav> */}
+
       </div>
     </div>
   );
-  // }
 
 }
diff --git a/packages/demobank-ui/src/hooks/circuit.ts 
b/packages/demobank-ui/src/hooks/circuit.ts
index b483a5420..bb9f5801e 100644
--- a/packages/demobank-ui/src/hooks/circuit.ts
+++ b/packages/demobank-ui/src/hooks/circuit.ts
@@ -18,7 +18,7 @@ import { useState } from "preact/hooks";
 import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js";
 import { useBackendState } from "./backend.js";
 
-import { AccessToken, AmountJson, AmountString, Amounts, OperationOk, 
TalerBankConversionResultByMethod, TalerCoreBankErrorsByMethod, 
TalerCoreBankResultByMethod, TalerCorebankApi, TalerError, TalerHttpError } 
from "@gnu-taler/taler-util";
+import { AccessToken, AmountJson, AmountString, Amounts, OperationOk, 
TalerBankConversionResultByMethod, TalerCoreBankErrorsByMethod, 
TalerCoreBankResultByMethod, TalerCorebankApi, TalerError, TalerHttpError, 
opFixedSuccess } from "@gnu-taler/taler-util";
 import _useSWR, { SWRHook } from "swr";
 import { useBankCoreApiContext } from "../context/config.js";
 import { assertUnreachable } from "../pages/WithdrawalOperationPage.js";
@@ -174,6 +174,43 @@ type CashoutWithId = 
TalerCorebankApi.CashoutStatusResponse & { id: number }
 function notUndefined(c: CashoutWithId | undefined): c is CashoutWithId {
   return c !== undefined
 }
+export function useOnePendingCashouts(account: string) {
+  const { state: credentials } = useBackendState();
+  const { api, config } = useBankCoreApiContext();
+  const token = credentials.status !== "loggedIn" ? undefined : 
credentials.token
+
+  async function fetcher([username, token]: [string, AccessToken]) {
+    const list = await api.getAccountCashouts({ username, token })
+    if (list.type !== "ok") {
+      return list;
+    }
+    const pendingCashout = list.body.cashouts.find(c => c.status === "pending")
+    if (!pendingCashout) return opFixedSuccess(undefined)
+    const cashoutInfo = await api.getCashoutById({ username, token }, 
pendingCashout?.cashout_id)
+    if (cashoutInfo.type !== "ok") {
+      return cashoutInfo;
+    }
+    return opFixedSuccess({ ...cashoutInfo.body, id: pendingCashout.cashout_id 
})
+  }
+
+  const { data, error } = useSWR<OperationOk<CashoutWithId | undefined> | 
TalerCoreBankErrorsByMethod<"getAccountCashouts"> | 
TalerCoreBankErrorsByMethod<"getCashoutById">, TalerHttpError>(
+    !config.allow_conversion ? undefined : [account, token, 
"getAccountCashouts"], fetcher, {
+    refreshInterval: 0,
+    refreshWhenHidden: false,
+    revalidateOnFocus: false,
+    revalidateOnReconnect: false,
+    refreshWhenOffline: false,
+    errorRetryCount: 0,
+    errorRetryInterval: 1,
+    shouldRetryOnError: false,
+    keepPreviousData: true,
+  });
+
+  if (data) return data;
+  if (error) return error;
+  return undefined;
+}
+
 export function useCashouts(account: string) {
   const { state: credentials } = useBackendState();
   const { api, config } = useBankCoreApiContext();
@@ -182,13 +219,11 @@ export function useCashouts(account: string) {
   async function fetcher([username, token]: [string, AccessToken]) {
     const list = await api.getAccountCashouts({ username, token })
     if (list.type !== "ok") {
-      console.error(list)
       return list;
     }
     const all: Array<CashoutWithId | undefined> = await 
Promise.all(list.body.cashouts.map(c => {
       return api.getCashoutById({ username, token }, c.cashout_id).then(r => {
         if (r.type === "fail") {
-          console.error("failed", r)
           return undefined
         }
         return { ...r.body, id: c.cashout_id }
diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx 
b/packages/demobank-ui/src/pages/AccountPage/views.tsx
index cfee684fa..d760543c6 100644
--- a/packages/demobank-ui/src/pages/AccountPage/views.tsx
+++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx
@@ -21,6 +21,8 @@ import { Transactions } from 
"../../components/Transactions/index.js";
 import { usePreferences } from "../../hooks/preferences.js";
 import { PaymentOptions } from "../PaymentOptions.js";
 import { State } from "./index.js";
+import { useCashouts, useOnePendingCashouts } from "../../hooks/circuit.js";
+import { TalerError } from "@gnu-taler/taler-util";
 
 export function InvalidIbanView({ error }: State.InvalidIban) {
   return (
@@ -57,8 +59,23 @@ export function ReadyView({ account, limit, 
goToConfirmOperation }: State.Ready)
 
   return <Fragment>
     <ShowDemoInfo />
+    <PendingCashouts account={account}/>
     <PaymentOptions limit={limit} goToConfirmOperation={goToConfirmOperation} 
/>
     <Transactions account={account} />
   </Fragment>;
 }
 
+
+function PendingCashouts({account}: {account: string}):VNode {
+  const { i18n } = useTranslationContext();
+  const result = useOnePendingCashouts(account)
+  if (!result || result instanceof TalerError || result.type !== "ok" || 
!result.body) {
+    return <Fragment />
+  }
+
+  return <Attention title={i18n.str`You have pending cashout operation to 
complete`} >
+    <i18n.Translate>
+      Cashout with subject "{result.body.subject}", look for the code and 
complete the operation <a target="_blank" rel="noreferrer noopener" 
class="font-semibold text-blue-700 hover:text-blue-600" 
href={`#/cashout/${result.body.id}`}>here</a>.
+    </i18n.Translate>
+  </Attention>
+}
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx 
b/packages/demobank-ui/src/pages/BankFrame.tsx
index 34c39e9d3..24012cd2b 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -46,7 +46,6 @@ export function BankFrame({
   useEffect(() => {
     if (error) {
       const desc = (error instanceof Error ? error.stack : String(error)) as 
TranslatedString
-      console.log(error)
       if (error instanceof Error) {
         notifyException(i18n.str`Internal error, please report.`, error)
       } else {
diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx 
b/packages/demobank-ui/src/pages/PaymentOptions.tsx
index 76d20867e..bbe33eb57 100644
--- a/packages/demobank-ui/src/pages/PaymentOptions.tsx
+++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx
@@ -33,7 +33,7 @@ export function PaymentOptions({ limit, goToConfirmOperation 
}: { limit: AmountJ
   const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | 
undefined>();
 
   return (
-    <div class="mt-2">
+    <div class="mt-4">
 
       <fieldset>
         <legend class="px-4 text-base font-semibold leading-6 text-gray-900">
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx 
b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index e035c7fed..33bf18abc 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -447,7 +447,7 @@ export function InputAmount(
         <input
           type="number"
           data-left={left}
-          class="text-right rounded-md rounded-l-none 
data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900  
placeholder:text-gray-400  sm:text-sm sm:leading-6"
+          class="disabled:bg-gray-200 text-right rounded-md rounded-l-none 
data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900  
placeholder:text-gray-400  sm:text-sm sm:leading-6"
           placeholder="0.00" aria-describedby="price-currency"
           ref={ref}
           name={name}
diff --git a/packages/demobank-ui/src/pages/ProfileNavigation.tsx 
b/packages/demobank-ui/src/pages/ProfileNavigation.tsx
index 1a4b4b865..61a55fe16 100644
--- a/packages/demobank-ui/src/pages/ProfileNavigation.tsx
+++ b/packages/demobank-ui/src/pages/ProfileNavigation.tsx
@@ -3,7 +3,7 @@ import { Fragment, VNode, h } from "preact";
 import { useBankCoreApiContext } from "../context/config.js";
 import { assertUnreachable } from "./WithdrawalOperationPage.js";
 
-export function ProfileNavigation({ current }: { current: "details" | 
"credentials" | "cashouts" }): VNode {
+export function ProfileNavigation({ current, noCashout }: { noCashout?: 
boolean, current: "details" | "credentials" | "cashouts" }): VNode {
   const { i18n } = useTranslationContext()
   const { config } = useBankCoreApiContext()
   return <div>
@@ -44,7 +44,7 @@ export function ProfileNavigation({ current }: { current: 
"details" | "credentia
           <span><i18n.Translate>Credentials</i18n.Translate></span>
           <span aria-hidden="true" data-selected={current == "credentials"} 
class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 
bottom-0 h-0.5"></span>
         </a>
-        {config.allow_conversion ?
+        {config.allow_conversion && !noCashout ?
           <a href="#/my-cashouts" data-selected={current == "cashouts"} 
class="rounded-r-lg text-gray-500 hover:text-gray-700 
data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 
overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium 
hover:bg-gray-50 focus:z-10">
             <span>Cashouts</span>
             <span aria-hidden="true" data-selected={current == "cashouts"} 
class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 
bottom-0 h-0.5"></span>
diff --git a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx 
b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
index 3ef3f568c..4332284e8 100644
--- a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
+++ b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
@@ -99,7 +99,7 @@ export function ShowAccountDetails({
     <Fragment>
       <LocalNotificationBanner notification={notification} />
       {accountIsTheCurrentUser ?
-        <ProfileNavigation current="details" />
+        <ProfileNavigation current="details" noCashout={credentials.status === 
"loggedIn" ? credentials.isUserAdministrator : undefined} />
         :
         <h1 class="text-base font-semibold leading-6 text-gray-900">
           <i18n.Translate>Account "{account}"</i18n.Translate>
@@ -128,6 +128,7 @@ export function ShowAccountDetails({
 
         <AccountForm
           focus={update}
+          noCashout={credentials.status === "loggedIn" ? 
credentials.isUserAdministrator : undefined}
           username={account}
           template={result.body}
           purpose={update ? "update" : "show"}
diff --git a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx 
b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
index d7f5155c9..3c00ad1b8 100644
--- a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
+++ b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
@@ -81,7 +81,7 @@ export function UpdateAccountPassword({
     <Fragment>
       <LocalNotificationBanner notification={notification} />
       {accountIsTheCurrentUser ?
-        <ProfileNavigation current="credentials" /> :
+        <ProfileNavigation current="credentials" noCashout={credentials.status 
=== "loggedIn" ? credentials.isUserAdministrator : undefined} /> :
         <h1 class="text-base font-semibold leading-6 text-gray-900">
           <i18n.Translate>Account "{accountName}"</i18n.Translate>
         </h1>
diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx 
b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
index b38d40012..526deeeab 100644
--- a/packages/demobank-ui/src/pages/admin/AccountForm.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
@@ -27,11 +27,13 @@ export function AccountForm({
   purpose,
   onChange,
   focus,
+  noCashout,
   children,
 }: {
   focus?: boolean,
   children: ComponentChildren,
   username?: string,
+  noCashout?: boolean,
   template: TalerCorebankApi.AccountData | undefined;
   onChange: (a: AccountFormData | undefined) => void;
   purpose: "create" | "update" | "show";
@@ -44,14 +46,14 @@ export function AccountForm({
   const { i18n } = useTranslationContext();
 
   function updateForm(newForm: typeof initial): void {
-
+    console.log(newForm)
     const parsed = !newForm.cashout_payto_uri
       ? undefined
       : buildPayto("iban", newForm.cashout_payto_uri, undefined);;
 
     const errors = undefinedIfEmpty<RecursivePartial<typeof initial>>({
       cashout_payto_uri: (!newForm.cashout_payto_uri
-        ? i18n.str`required`
+        ? undefined
         : !parsed
           ? i18n.str`does not follow the pattern`
           : !parsed.isKnown || parsed.targetType !== "iban"
@@ -81,10 +83,10 @@ export function AccountForm({
     if (errors) {
       onChange(undefined)
     } else {
-      const cashout = buildPayto("iban", newForm.cashout_payto_uri!, undefined)
+      const cashout = !newForm.cashout_payto_uri? undefined 
:buildPayto("iban", newForm.cashout_payto_uri, undefined)
       const account: AccountFormData = {
         ...newForm as any,
-        cashout_payto_uri: stringifyPaytoUri(cashout)
+        cashout_payto_uri: !cashout ? undefined : stringifyPaytoUri(cashout)
       }
       onChange(account);
     }
@@ -194,6 +196,9 @@ export function AccountForm({
                 onChange={(e) => {
                   if (form.contact_data) {
                     form.contact_data.email = e.currentTarget.value;
+                    if (!form.contact_data.email) {
+                      form.contact_data.email = undefined
+                    }
                     updateForm(structuredClone(form));
                   }
                 }}
@@ -220,12 +225,15 @@ export function AccountForm({
                 class="block w-full disabled:bg-gray-100 rounded-md border-0 
py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 
focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                 name="phone"
                 id="phone"
-                disabled={purpose !== "create"}
+                disabled={purpose === "show"}
                 value={form.contact_data?.phone ?? ""}
                 data-error={!!errors?.contact_data?.phone && 
form.contact_data?.phone !== undefined}
                 onChange={(e) => {
                   if (form.contact_data) {
                     form.contact_data.phone = e.currentTarget.value;
+                    if (!form.contact_data.email) {
+                      form.contact_data.email = undefined
+                    }
                     updateForm(structuredClone(form));
                   }
                 }}
@@ -240,44 +248,45 @@ export function AccountForm({
           </div>
 
 
-          <div class="sm:col-span-5">
-            <label
-              class="block text-sm font-medium leading-6 text-gray-900"
-              for="cashout"
-            >
-              {i18n.str`Cashout IBAN`}
-              {purpose !== "show" && <b style={{ color: "red" }}> *</b>}
-            </label>
-            <div class="mt-2">
-              <input
-                type="text"
-                ref={focus && purpose === "update" ? doAutoFocus : undefined}
-                data-error={!!errors?.cashout_payto_uri && 
form.cashout_payto_uri !== undefined}
-                class="block w-full disabled:bg-gray-100 rounded-md border-0 
py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 
focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
-                name="cashout"
-                id="cashout"
-                disabled={purpose === "show"}
-                value={form.cashout_payto_uri ?? ""}
-                onChange={(e) => {
-                  form.cashout_payto_uri = e.currentTarget.value as 
PaytoString;
-                  updateForm(structuredClone(form));
-                }}
-                autocomplete="off"
-              />
-              <ShowInputErrorLabel
-                message={errors?.cashout_payto_uri}
-                isDirty={form.cashout_payto_uri !== undefined}
-              />
+          {!noCashout &&
+            <div class="sm:col-span-5">
+              <label
+                class="block text-sm font-medium leading-6 text-gray-900"
+                for="cashout"
+              >
+                {i18n.str`Cashout IBAN`}
+                {purpose !== "show" && <b style={{ color: "red" }}> *</b>}
+              </label>
+              <div class="mt-2">
+                <input
+                  type="text"
+                  ref={focus && purpose === "update" ? doAutoFocus : undefined}
+                  data-error={!!errors?.cashout_payto_uri && 
form.cashout_payto_uri !== undefined}
+                  class="block w-full disabled:bg-gray-100 rounded-md border-0 
py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 
focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+                  name="cashout"
+                  id="cashout"
+                  disabled={purpose === "show"}
+                  value={form.cashout_payto_uri ?? ""}
+                  onChange={(e) => {
+                    form.cashout_payto_uri = e.currentTarget.value as 
PaytoString;
+                    if (!form.cashout_payto_uri) {
+                      form.cashout_payto_uri= undefined
+                    }
+                    updateForm(structuredClone(form));
+                  }}
+                  autocomplete="off"
+                />
+                <ShowInputErrorLabel
+                  message={errors?.cashout_payto_uri}
+                  isDirty={form.cashout_payto_uri !== undefined}
+                />
+              </div>
+              <p class="mt-2 text-sm text-gray-500" >
+                <i18n.Translate>account number where the money is going to be 
sent when doing cashouts</i18n.Translate>
+              </p>
             </div>
-            <p class="mt-2 text-sm text-gray-500" >
-              <i18n.Translate>account number where the money is going to be 
sent when doing cashouts</i18n.Translate>
-            </p>
-          </div>
-          <div class="sm:col-span-5">
-            <pre>
-              {JSON.stringify(errors, undefined, 2)}
-            </pre>
-          </div>
+          }
+
         </div>
       </div>
       {children}
diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx 
b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
index 2f77f3960..3d3f30250 100644
--- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx
+++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
@@ -18,7 +18,8 @@ import {
   TalerError,
   TranslatedString,
   encodeCrock,
-  getRandomBytes
+  getRandomBytes,
+  parsePaytoUri
 } from "@gnu-taler/taler-util";
 import {
   Attention,
@@ -83,7 +84,7 @@ export function CreateCashout({
   const creds = credentials.status !== "loggedIn" ? undefined : credentials
 
   const { api, config } = useBankCoreApiContext()
-  const [form, setForm] = useState<Partial<FormType>>({ isDebit: true, amount: 
"2" });
+  const [form, setForm] = useState<Partial<FormType>>({ isDebit: true, });
   const [notification, notify, handleError] = useLocalNotification()
   const info = useConversionInfo();
 
@@ -171,7 +172,7 @@ export function CreateCashout({
         : Amounts.cmp(limit, calc.debit) === -1
           ? i18n.str`balance is not enough`
           : Amounts.cmp(calc.credit, sellFee) === -1
-            ? i18n.str`the total amount to transfer does not cover the fees`
+            ? i18n.str`need to be higher due to fees`
             : Amounts.isZero(calc.credit)
               ? i18n.str`the total transfer at destination will be zero`
               : undefined,
@@ -242,7 +243,9 @@ export function CreateCashout({
       }
     })
   }
-
+  const cashoutAccount = !resultAccount.body.cashout_payto_uri ? undefined :
+    parsePaytoUri(resultAccount.body.cashout_payto_uri);
+  const cashoutAccountName = !cashoutAccount ? undefined : 
cashoutAccount.targetPath
   return (
     <div>
       <LocalNotificationBanner notification={notification} />
@@ -275,6 +278,24 @@ export function CreateCashout({
                 <RenderAmount value={sellFee} 
spec={fiat_currency_specification} />
               </dd>
             </div>
+            {cashoutAccountName ?
+              <div class="flex items-center justify-between border-t-2 afu 
pt-4">
+                <dt class="flex items-center text-sm text-gray-600">
+                  <span><i18n.Translate>To account</i18n.Translate></span>
+                </dt>
+                <dd class="text-sm text-gray-900">
+                  {cashoutAccountName }
+                </dd>
+              </div> :
+              <div class="flex items-center justify-between border-t-2 afu 
pt-4">
+                <Attention type="warning" title={i18n.str`No cashout account`}>
+                  <i18n.Translate>
+                    Before doing a cashout you need to complete your profile
+                  </i18n.Translate>
+                </Attention>
+              </div>
+            }
+
           </dl>
 
         </section>
@@ -301,9 +322,10 @@ export function CreateCashout({
                   <input
                     ref={focus ? doAutoFocus : undefined}
                     type="text"
-                    class="block w-full rounded-md border-0 py-1.5 
text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 
focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+                    class="block w-full rounded-md disabled:bg-gray-200 
border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 
focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                     name="subject"
                     id="subject"
+                    disabled={!resultAccount.body.cashout_payto_uri}
                     data-error={!!errors?.subject && form.subject !== 
undefined}
                     value={form.subject ?? ""}
                     onChange={(e) => {
@@ -346,7 +368,7 @@ export function CreateCashout({
                     left
                     currency={limit.currency}
                     value={trimmedAmountStr}
-                    onChange={(value) => {
+                    onChange={!resultAccount.body.cashout_payto_uri ? 
undefined : (value) => {
                       form.amount = value;
                       updateForm(structuredClone(form));
                     }}
@@ -411,7 +433,7 @@ export function CreateCashout({
 
               {/* channel */}
               <div class="sm:col-span-5">
-              <label
+                <label
                   class="block text-sm font-medium leading-6 text-gray-900"
                   for="channel"
                 >
@@ -421,16 +443,19 @@ export function CreateCashout({
                 <div class="mt-2 max-w-xl text-sm text-gray-500">
                   <div class="px-4 mt-4 grid grid-cols-1 gap-y-6">
 
-                    <label onClick={()=>{
+                    <label onClick={() => {
+                      if (!resultAccount.body.contact_data?.email) return;
                       form.channel = TanChannel.EMAIL
                       updateForm(structuredClone(form))
-                    }} data-selected={form.channel === TanChannel.EMAIL} 
class="relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm 
focus:outline-none border-gray-300 data-[selected=true]:ring-2 
data-[selected=true]:ring-indigo-600">
+                    }} data-disabled={!resultAccount.body.contact_data?.email} 
data-selected={form.channel === TanChannel.EMAIL}
+                      class="relative flex 
data-[disabled=false]:cursor-pointer rounded-lg border bg-white 
data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none 
border-gray-300 data-[selected=true]:ring-2 
data-[selected=true]:ring-indigo-600">
                       <input type="radio" name="channel" value="Newsletter" 
class="sr-only" />
                       <span class="flex flex-1">
                         <span class="flex flex-col">
                           <span id="project-type-0-label" class="block text-sm 
font-medium text-gray-900 ">
                             <i18n.Translate>Email</i18n.Translate>
                           </span>
+                          {!resultAccount.body.contact_data?.email && 
i18n.str`add a email in your profile to enable this option`}
                         </span>
                       </span>
                       <svg data-selected={form.channel === TanChannel.EMAIL} 
class="h-5 w-5 text-indigo-600 data-[selected=false]:hidden" viewBox="0 0 20 
20" fill="currentColor" aria-hidden="true">
@@ -438,16 +463,19 @@ export function CreateCashout({
                       </svg>
                     </label>
 
-                    <label onClick={()=>{
+                    <label onClick={() => {
+                      if (!resultAccount.body.contact_data?.phone) return;
                       form.channel = TanChannel.SMS
                       updateForm(structuredClone(form))
-                    }} data-selected={form.channel === TanChannel.SMS} 
class="relative flex cursor-pointer rounded-lg border bg-white  p-4 shadow-sm 
focus:outline-none border-gray-300 data-[selected=true]:ring-2 
data-[selected=true]:ring-indigo-600">
-                      <input type="radio" name="channel" value="Existing 
Customers" class="sr-only"  />
+                    }} data-disabled={!resultAccount.body.contact_data?.phone} 
data-selected={form.channel === TanChannel.SMS}
+                      class="relative flex 
data-[disabled=false]:cursor-pointer rounded-lg border 
data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none 
border-gray-300 data-[selected=true]:ring-2 
data-[selected=true]:ring-indigo-600">
+                      <input type="radio" name="channel" value="Existing 
Customers" class="sr-only" />
                       <span class="flex flex-1">
                         <span class="flex flex-col">
                           <span id="project-type-1-label" class="block text-sm 
font-medium text-gray-900">
                             <i18n.Translate>SMS</i18n.Translate>
                           </span>
+                          {!resultAccount.body.contact_data?.phone && 
i18n.str`add a phone number in your profile to enable this option`}
                         </span>
                       </span>
                       <svg data-selected={form.channel === TanChannel.SMS} 
class="h-5 w-5 text-indigo-600 data-[selected=false]:hidden" viewBox="0 0 20 
20" fill="currentColor" aria-hidden="true">
diff --git a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx 
b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
index 52ff713e2..6fd9eb18c 100644
--- a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
+++ b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
@@ -14,6 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 import {
+  Amounts,
   TalerError,
   TranslatedString
 } from "@gnu-taler/taler-util";
@@ -32,7 +33,7 @@ import { ShowInputErrorLabel } from 
"@gnu-taler/web-util/browser";
 import { useBankCoreApiContext } from "../../context/config.js";
 import { useBackendState } from "../../hooks/backend.js";
 import {
-  useCashoutDetails
+  useCashoutDetails, useConversionInfo
 } from "../../hooks/circuit.js";
 import {
   undefinedIfEmpty,
@@ -40,6 +41,7 @@ import {
 } from "../../utils.js";
 import { assertUnreachable } from "../WithdrawalOperationPage.js";
 import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
+import { RenderAmount } from "../PaytoWireTransferForm.js";
 
 interface Props {
   id: string;
@@ -58,7 +60,12 @@ export function ShowCashoutDetails({
   const result = useCashoutDetails(Number.isNaN(cid) ? undefined : cid);
   const [code, setCode] = useState<string | undefined>(undefined);
   const [notification, notify, handleError] = useLocalNotification()
+  const info = useConversionInfo();
 
+  if (Number.isNaN(cid)) {
+    //TODO: better error message
+    return <div>cashout id should be a number</div>
+  }
   if (!result) {
     return <Loading />
   }
@@ -74,206 +81,252 @@ export function ShowCashoutDetails({
       default: assertUnreachable(result)
     }
   }
-  if (Number.isNaN(cid)) {
-    //TODO: better error message
-    return <div>cashout id should be a number</div>
+  if (!info) {
+    return <Loading />
+  }
+
+  if (info instanceof TalerError) {
+    return <ErrorLoading error={info} />
   }
+
   const errors = undefinedIfEmpty({
     code: !code ? i18n.str`required` : undefined,
   });
   const isPending = String(result.body.status).toUpperCase() === "PENDING";
+  const { fiat_currency_specification, regional_currency_specification } = 
info.body
+  async function doAbortCashout() {
+    if (!creds) return;
+    await handleError(async () => {
+      const resp = await api.abortCashoutById(creds, cid);
+      if (resp.type === "ok") {
+        onCancel();
+      } else {
+        switch (resp.case) {
+          case "not-found": return notify({
+            type: "error",
+            title: i18n.str`Cashout not found. It may be also mean that it was 
already aborted.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          case "already-confirmed": return notify({
+            type: "error",
+            title: i18n.str`Cashout was already confimed.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          case "cashout-not-supported": return notify({
+            type: "error",
+            title: i18n.str`Cashout operation is not supported.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          default: {
+            assertUnreachable(resp)
+          }
+        }
+      }
+    })
+  }
+  async function doConfirmCashout() {
+    if (!creds || !code) return;
+    await handleError(async () => {
+      const resp = await api.confirmCashoutById(creds, cid, {
+        tan: code,
+      });
+      if (resp.type === "ok") {
+        mutate(() => true)//clean cashout state
+      } else {
+        switch (resp.case) {
+          case "not-found": return notify({
+            type: "error",
+            title: i18n.str`Cashout not found. It may be also mean that it was 
already aborted.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          case "no-enough-balance": return notify({
+            type: "error",
+            title: i18n.str`The account does not have sufficient funds`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          });
+          case "incorrect-exchange-rate": return notify({
+            type: "error",
+            title: i18n.str`The exchange rate was incorrectly applied`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          });
+          case "already-aborted": return notify({
+            type: "error",
+            title: i18n.str`The cashout operation is already aborted.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          });
+          case "no-cashout-payto": return notify({
+            type: "error",
+            title: i18n.str`Missing destination account.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          case "too-many-attempts": return notify({
+            type: "error",
+            title: i18n.str`Too many failed attempts.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          case "cashout-not-supported": return notify({
+            type: "error",
+            title: i18n.str`Cashout operation is not supported.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          case "invalid-code": return notify({
+            type: "error",
+            title: i18n.str`The code for this cashout is invalid.`,
+            description: resp.detail.hint as TranslatedString,
+            debug: resp.detail,
+          })
+          default: assertUnreachable(resp)
+        }
+      }
+    })
+  }
+
   return (
     <div>
       <LocalNotificationBanner notification={notification} />
-      <h1>Cashout details {id}</h1>
-      <form class="pure-form">
-        <fieldset>
-          <label>
-            <i18n.Translate>Subject</i18n.Translate>
-          </label>
-          <input readOnly value={result.body.subject} />
-        </fieldset>
-        <fieldset>
-          <label>
-            <i18n.Translate>Created</i18n.Translate>
-          </label>
-          <input readOnly value={result.body.creation_time.t_s === "never" ? 
i18n.str`never` : format(result.body.creation_time.t_s, "dd/MM/yyyy HH:mm:ss")} 
/>
-        </fieldset>
-        <fieldset>
-          <label>
-            <i18n.Translate>Confirmed</i18n.Translate>
-          </label>
-          <input readOnly value={result.body.confirmation_time === undefined ? 
"-" :
-            (result.body.confirmation_time.t_s === "never" ?
-              i18n.str`never` :
-              format(result.body.confirmation_time.t_s, "dd/MM/yyyy HH:mm:ss"))
-          } />
-        </fieldset>
-        <fieldset>
-          <label>
-            <i18n.Translate>Debited</i18n.Translate>
-          </label>
-          <input readOnly value={result.body.amount_debit} />
-        </fieldset>
-        <fieldset>
-          <label>
-            <i18n.Translate>Credit</i18n.Translate>
-          </label>
-          <input readOnly value={result.body.amount_credit} />
-        </fieldset>
-        <fieldset>
-          <label>
-            <i18n.Translate>Status</i18n.Translate>
-          </label>
-          <input readOnly value={result.body.status} />
-        </fieldset>
-        {/* <fieldset>
-          <label>
-            <i18n.Translate>Destination</i18n.Translate>
-          </label>
-          <input readOnly value={result.body.credit_payto_uri} />
-        </fieldset> */}
-        {isPending ? (
-          <fieldset>
-            <label>
-              <i18n.Translate>Code</i18n.Translate>
-            </label>
-            <input
-              value={code ?? ""}
-              onChange={(e) => {
-                setCode(e.currentTarget.value);
+      <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">
+
+        <section class="rounded-sm px-4">
+          <h2 id="summary-heading" class="font-medium 
text-lg"><i18n.Translate>Cashout detail</i18n.Translate></h2>
+          <dl class="mt-8 space-y-4">
+            <div class="justify-between items-center flex">
+              <dt class="text-sm 
text-gray-600"><i18n.Translate>Subject</i18n.Translate></dt>
+              <dd class="text-sm ">{result.body.subject}</dd>
+            </div>
+
+
+            <div class="flex items-center justify-between border-t-2 afu pt-4">
+              <dt class="flex items-center text-sm text-gray-600">
+                <span><i18n.Translate>Status</i18n.Translate></span>
+              </dt>
+              <dd data-status={result.body.status} class="text-sm uppercase 
data-[status=pending]:text-yellow-600 data-[status=aborted]:text-red-600 
data-[status=confirmed]:text-green-600" >
+                {result.body.status}
+              </dd>
+            </div>
+          </dl>
+        </section>
+        <div class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl 
md:col-span-2">
+          <div class="px-4 py-6 sm:p-8">
+            <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 
sm:grid-cols-6">
+              <div class="sm:col-span-5">
+                <dl class="space-y-4">
+
+                  {result.body.creation_time.t_s !== "never" ?
+                    <div class="justify-between items-center flex ">
+                      <dt class=" 
text-gray-600"><i18n.Translate>Created</i18n.Translate></dt>
+                      <dd class="text-sm ">
+                        {format(result.body.creation_time.t_s * 1000, 
"dd/MM/yyyy HH:mm:ss")}
+                      </dd>
+                    </div>
+                    : undefined}
+
+                  <div class="flex justify-between items-center border-t-2 afu 
pt-4">
+                    <dt 
class="text-gray-600"><i18n.Translate>Debited</i18n.Translate></dt>
+                    <dd class=" font-medium">
+                      <RenderAmount 
value={Amounts.parseOrThrow(result.body.amount_debit)} negative withColor 
spec={regional_currency_specification} />
+                    </dd>
+                  </div>
+
+                  <div class="flex items-center justify-between border-t-2 afu 
pt-4">
+                    <dt class="flex items-center text-gray-600">
+                      <span><i18n.Translate>Credited</i18n.Translate></span>
+
+                    </dt>
+                    <dd class="text-sm ">
+                      <RenderAmount 
value={Amounts.parseOrThrow(result.body.amount_credit)} withColor 
spec={fiat_currency_specification} />
+                    </dd>
+                  </div>
+
+                  {result.body.confirmation_time && 
result.body.confirmation_time.t_s !== "never" ?
+                    <div class="flex justify-between items-center border-t-2 
afu pt-4">
+                      <dt class="  
font-medium"><i18n.Translate>Confirmed</i18n.Translate></dt>
+                      <dd class="  font-medium">
+                        {format(result.body.confirmation_time.t_s * 1000, 
"dd/MM/yyyy HH:mm:ss")}
+                      </dd>
+                    </div>
+                    : undefined}
+                </dl>
+              </div>
+            </div>
+          </div>
+
+        </div>
+
+        {!isPending ? undefined :
+          <Fragment>
+
+            <div />
+            <form
+              class="bg-white shadow-sm ring-1 ring-gray-900/5"
+              autoCapitalize="none"
+              autoCorrect="off"
+              onSubmit={e => {
+                e.preventDefault()
               }}
-            />
-            <ShowInputErrorLabel
-              message={errors?.code}
-              isDirty={code !== undefined}
-            />
-          </fieldset>
-        ) : undefined}
-      </form>
+            >
+              <div class="px-4 py-6 sm:p-8">
+                <label for="withdraw-amount">
+                  Enter the confirmation code
+                </label>
+                <div class="mt-2">
+                  <div class="relative rounded-md shadow-sm">
+                    <input
+                      type="text"
+                      // class="block w-full rounded-md border-0 py-1.5 pl-16 
text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 
focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+                      aria-describedby="answer"
+                      autoFocus
+                      class="block w-full rounded-md border-0 py-1.5 
text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 
sm:text-sm sm:leading-6"
+                      value={code ?? ""}
+                      required
+
+                      name="answer"
+                      id="answer"
+                      autocomplete="off"
+                      onChange={(e): void => {
+                        setCode(e.currentTarget.value)
+                      }}
+                    />
+                  </div>
+                  <ShowInputErrorLabel message={errors?.code} isDirty={code 
!== undefined} />
+                </div>
+              </div>
+              <div class="flex items-center justify-between gap-x-6 border-t 
border-gray-900/10 px-4 py-4 sm:px-8">
+                <button type="button"
+                  class="inline-flex items-center rounded-md bg-red-600 px-3 
py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-red-500"
+                  onClick={doAbortCashout}
+                >
+                  <i18n.Translate>Abort</i18n.Translate></button>
+                <button type="submit"
+                  class="disabled:opacity-50 disabled:cursor-default 
cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold 
text-white shadow-sm hover:bg-indigo-500 focus-visible:outline 
focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
+                  disabled={!!errors}
+                  onClick={(e) => {
+                    doConfirmCashout()
+                  }}
+                >
+                  <i18n.Translate>Confirm</i18n.Translate>
+                </button>
+              </div>
+
+            </form>
+          </Fragment>}
+      </div>
+
       <br />
       <div style={{ display: "flex", justifyContent: "space-between" }}>
-        <button
-          class="pure-button pure-button-secondary btn-cancel"
-          onClick={(e) => {
-            e.preventDefault();
-            onCancel();
-          }}
+        <button type="button" class="text-sm font-semibold leading-6 
text-gray-900"
+          onClick={onCancel}
         >
-          {i18n.str`Back`}
-        </button>
-        {isPending ? (
-          <div>
-            <button
-              type="submit"
-              class="pure-button pure-button-primary button-error"
-              onClick={async (e) => {
-                e.preventDefault();
-                if (!creds) return;
-                await handleError(async () => {
-                  const resp = await api.abortCashoutById(creds, cid);
-                  if (resp.type === "ok") {
-                    onCancel();
-                  } else {
-                    switch (resp.case) {
-                      case "not-found": return notify({
-                        type: "error",
-                        title: i18n.str`Cashout not found. It may be also mean 
that it was already aborted.`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      })
-                      case "already-confirmed": return notify({
-                        type: "error",
-                        title: i18n.str`Cashout was already confimed.`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      })
-                      case "cashout-not-supported": return notify({
-                        type: "error",
-                        title: i18n.str`Cashout operation is not supported.`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      })
-                      default: {
-                        assertUnreachable(resp)
-                      }
-                    }
-                  }
-                })
-              }}
-            >
-              {i18n.str`Abort`}
-            </button>
-            &nbsp;
-            <button
-              type="submit"
-              disabled={!code}
-              class="pure-button pure-button-primary "
-              onClick={async (e) => {
-                e.preventDefault();
-                if (!creds || !code) return;
-                await handleError(async () => {
-                  const resp = await api.confirmCashoutById(creds, cid, {
-                    tan: code,
-                  });
-                  if (resp.type === "ok") {
-                    mutate(() => true)//clean cashout state
-                  } else {
-                    switch (resp.case) {
-                      case "not-found": return notify({
-                        type: "error",
-                        title: i18n.str`Cashout not found. It may be also mean 
that it was already aborted.`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      })
-                      case "no-enough-balance": return notify({
-                        type: "error",
-                        title: i18n.str`The account does not have sufficient 
funds`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      });
-                      case "incorrect-exchange-rate": return notify({
-                        type: "error",
-                        title: i18n.str`The exchange rate was incorrectly 
applied`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      });
-                      case "already-aborted": return notify({
-                        type: "error",
-                        title: i18n.str`The cashout operation is already 
aborted.`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      });
-                      case "no-cashout-payto": return notify({
-                        type: "error",
-                        title: i18n.str`Missing destination account.`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      })
-                      case "too-many-attempts": return notify({
-                        type: "error",
-                        title: i18n.str`Too many failed attempts.`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      })
-                      case "cashout-not-supported": return notify({
-                        type: "error",
-                        title: i18n.str`Cashout operation is not supported.`,
-                        description: resp.detail.hint as TranslatedString,
-                        debug: resp.detail,
-                      })
-                      default: assertUnreachable(resp)
-                    }
-                  }
-                })
-              }}
-            >
-              {i18n.str`Confirm`}
-            </button>
-          </div>
-        ) : (
-          <div />
-        )}
+          <i18n.Translate>Cancel</i18n.Translate></button>
       </div>
     </div>
   );

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