gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] 12/20: more ui


From: gnunet
Subject: [taler-wallet-core] 12/20: more ui
Date: Mon, 25 Sep 2023 19:51:16 +0200

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

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

commit a59df74fb2b4374fd58f68fd4abaffe623cd54d6
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Fri Sep 22 15:29:19 2023 -0300

    more ui
---
 packages/demobank-ui/src/components/Routing.tsx    |  27 +-
 packages/demobank-ui/src/hooks/settings.ts         |   3 +
 .../demobank-ui/src/pages/AccountPage/index.ts     |   2 +
 .../demobank-ui/src/pages/AccountPage/state.ts     |   3 +-
 .../demobank-ui/src/pages/AccountPage/views.tsx    |   4 +-
 packages/demobank-ui/src/pages/BankFrame.tsx       |  15 +
 packages/demobank-ui/src/pages/HomePage.tsx        |  17 +-
 .../demobank-ui/src/pages/OperationState/index.ts  |  10 +-
 .../demobank-ui/src/pages/OperationState/state.ts  | 109 ++++++-
 .../demobank-ui/src/pages/OperationState/views.tsx | 363 ++++++++++++++++++++-
 packages/demobank-ui/src/pages/PaymentOptions.tsx  |  17 +-
 packages/demobank-ui/src/pages/QrCodeSection.tsx   | 104 ------
 .../demobank-ui/src/pages/WalletWithdrawForm.tsx   | 273 ++++++++++------
 .../demobank-ui/src/pages/WithdrawalQRCode.tsx     | 163 +--------
 packages/demobank-ui/src/pages/business/Home.tsx   |   1 -
 15 files changed, 693 insertions(+), 418 deletions(-)

diff --git a/packages/demobank-ui/src/components/Routing.tsx 
b/packages/demobank-ui/src/components/Routing.tsx
index e1fd93737..90d2d4c48 100644
--- a/packages/demobank-ui/src/components/Routing.tsx
+++ b/packages/demobank-ui/src/components/Routing.tsx
@@ -45,6 +45,20 @@ export function Routing(): VNode {
             />
           )}
         />
+        <Route
+          path="/operation/:wopid"
+          component={({ wopid }: { wopid: string }) => (
+            <WithdrawalOperationPage
+              operationId={wopid}
+              onContinue={() => {
+                route("/account");
+              }}
+              // onLoadNotOk={() => {
+              //   route("/account");
+              // }}
+            />
+          )}
+        />
         <Route
           path="/register"
           component={() => (
@@ -64,10 +78,6 @@ export function Routing(): VNode {
   return (
     <BankFrame account={backend.state.username}>
       <Router history={history}>
-        <Route
-          path="/test"
-          component={Test}
-        />
         <Route
           path="/operation/:wopid"
           component={({ wopid }: { wopid: string }) => (
@@ -76,9 +86,6 @@ export function Routing(): VNode {
               onContinue={() => {
                 route("/account");
               }}
-              // onLoadNotOk={() => {
-              //   route("/account");
-              // }}
             />
           )}
         />
@@ -108,9 +115,9 @@ export function Routing(): VNode {
             } else {
               return <HomePage
                 account={username}
-                // onPendingOperationFound={(wopid) => {
-                //   route(`/operation/${wopid}`);
-                // }}
+                goToConfirmOperation={(wopid) => {
+                  route(`/operation/${wopid}`);
+                }}
                 goToBusinessAccount={() => {
                   route("/business");
                 }}
diff --git a/packages/demobank-ui/src/hooks/settings.ts 
b/packages/demobank-ui/src/hooks/settings.ts
index c2fd93a0c..5f004c6d4 100644
--- a/packages/demobank-ui/src/hooks/settings.ts
+++ b/packages/demobank-ui/src/hooks/settings.ts
@@ -30,6 +30,7 @@ interface Settings {
   currentWithdrawalOperationId: string | undefined;
   showWithdrawalSuccess: boolean;
   showDemoDescription: boolean;
+  showInstallWallet: boolean;
   maxWithdrawalAmount: number;
   fastWithdrawal: boolean;
 }
@@ -39,6 +40,7 @@ export const codecForSettings = (): Codec<Settings> =>
     .property("currentWithdrawalOperationId", codecOptional(codecForString()))
     .property("showWithdrawalSuccess", (codecForBoolean()))
     .property("showDemoDescription", (codecForBoolean()))
+    .property("showInstallWallet", (codecForBoolean()))
     .property("fastWithdrawal", (codecForBoolean()))
     .property("maxWithdrawalAmount", codecForNumber())
     .build("Settings");
@@ -47,6 +49,7 @@ const defaultSettings: Settings = {
   currentWithdrawalOperationId: undefined,
   showWithdrawalSuccess: true,
   showDemoDescription: true,
+  showInstallWallet: true,
   maxWithdrawalAmount: 25,
   fastWithdrawal: false,
 };
diff --git a/packages/demobank-ui/src/pages/AccountPage/index.ts 
b/packages/demobank-ui/src/pages/AccountPage/index.ts
index 128a6d30f..81eeb4a03 100644
--- a/packages/demobank-ui/src/pages/AccountPage/index.ts
+++ b/packages/demobank-ui/src/pages/AccountPage/index.ts
@@ -29,6 +29,7 @@ export interface Props {
     error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
   ) => VNode;
   goToBusinessAccount: () => void;
+  goToConfirmOperation: (id:string) => void;
 }
 
 export type State = State.Loading | State.LoadingError | State.Ready | 
State.InvalidIban | State.UserNotFound;
@@ -54,6 +55,7 @@ export namespace State {
     account: string, 
     limit: AmountJson,
     goToBusinessAccount: () => void;
+    goToConfirmOperation: (id:string) => void;
   }
 
   export interface InvalidIban {
diff --git a/packages/demobank-ui/src/pages/AccountPage/state.ts 
b/packages/demobank-ui/src/pages/AccountPage/state.ts
index a57e19901..1a1475c0d 100644
--- a/packages/demobank-ui/src/pages/AccountPage/state.ts
+++ b/packages/demobank-ui/src/pages/AccountPage/state.ts
@@ -20,7 +20,7 @@ import { useBackendContext } from "../../context/backend.js";
 import { useAccountDetails } from "../../hooks/access.js";
 import { Props, State } from "./index.js";
 
-export function useComponentState({ account, goToBusinessAccount }: Props): 
State {
+export function useComponentState({ account, goToBusinessAccount, 
goToConfirmOperation }: Props): State {
   const result = useAccountDetails(account);
   const backend = useBackendContext();
   const { i18n } = useTranslationContext();
@@ -75,6 +75,7 @@ export function useComponentState({ account, 
goToBusinessAccount }: Props): Stat
   return {
     status: "ready",
     goToBusinessAccount,
+    goToConfirmOperation,
     error: undefined,
     account,
     limit,
diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx 
b/packages/demobank-ui/src/pages/AccountPage/views.tsx
index 0187989af..23a815bd8 100644
--- a/packages/demobank-ui/src/pages/AccountPage/views.tsx
+++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx
@@ -123,7 +123,7 @@ function ShowDemoInfo():VNode {
 </div>
 }
 
-export function ReadyView({ account, limit, goToBusinessAccount }: 
State.Ready): VNode<{}> {
+export function ReadyView({ account, limit, goToBusinessAccount, 
goToConfirmOperation }: State.Ready): VNode<{}> {
   const { i18n } = useTranslationContext();
 
   return <Fragment>
@@ -131,7 +131,7 @@ export function ReadyView({ account, limit, 
goToBusinessAccount }: State.Ready):
 
     <ShowDemoInfo />
     
-    <PaymentOptions limit={limit} />
+    <PaymentOptions limit={limit} goToConfirmOperation={goToConfirmOperation} 
/>
     <Transactions account={account} />
   </Fragment>;
 }
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx 
b/packages/demobank-ui/src/pages/BankFrame.tsx
index d1c94135b..5bfaa63ec 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -206,6 +206,21 @@ export function BankFrame({
                                     </button>
                                   </div>
                                 </li>
+                                <li class="mt-2">
+                                  <div class="flex items-center 
justify-between">
+                                    <span class="flex flex-grow flex-col">
+                                      <span class="text-sm text-black 
font-medium leading-6 " id="availability-label">
+                                        <i18n.Translate>Show install wallet 
first</i18n.Translate>
+                                      </span>
+                                    </span>
+                                    <button type="button" 
data-enabled={settings.showInstallWallet} class="bg-indigo-600 
data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 
cursor-pointer rounded-full border-2 border-transparent transition-colors 
duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 
focus:ring-offset-2" role="switch" aria-checked="false" 
aria-labelledby="availability-label" aria-describedby="availability-description"
+                                      onClick={() => {
+                                        updateSettings("showInstallWallet", 
!settings.showInstallWallet);
+                                      }}>
+                                      <span aria-hidden="true" 
data-enabled={settings.showInstallWallet} class="translate-x-5 
data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 
transform rounded-full bg-white shadow ring-0 transition duration-200 
ease-in-out"></span>
+                                    </button>
+                                  </div>
+                                </li>
                                 <li class="mt-2">
                                   <div class="flex items-center 
justify-between">
                                     <span class="flex flex-grow flex-col">
diff --git a/packages/demobank-ui/src/pages/HomePage.tsx 
b/packages/demobank-ui/src/pages/HomePage.tsx
index 2acfc9b57..8d5e1f3b9 100644
--- a/packages/demobank-ui/src/pages/HomePage.tsx
+++ b/packages/demobank-ui/src/pages/HomePage.tsx
@@ -36,6 +36,7 @@ import { useSettings } from "../hooks/settings.js";
 import { AccountPage } from "./AccountPage/index.js";
 import { LoginForm } from "./LoginForm.js";
 import { WithdrawalQRCode } from "./WithdrawalQRCode.js";
+import { route } from "preact-router";
 
 const logger = new Logger("AccountPage");
 
@@ -52,25 +53,20 @@ const logger = new Logger("AccountPage");
 export function HomePage({
   onRegister,
   account,
-  // onPendingOperationFound,
+  goToConfirmOperation,
   goToBusinessAccount,
 }: {
   account: string,
-  // onPendingOperationFound: (id: string) => void;
   onRegister: () => void;
   goToBusinessAccount: () => void;
+  goToConfirmOperation: (id:string) => void;
 }): VNode {
-  const [settings] = useSettings();
   const { i18n } = useTranslationContext();
 
-  // if (settings.currentWithdrawalOperationId) {
-  //   onPendingOperationFound(settings.currentWithdrawalOperationId);
-  //   return <Loading />;
-  // }
-
   return (
     <AccountPage
       account={account}
+      goToConfirmOperation={goToConfirmOperation}
       goToBusinessAccount={goToBusinessAccount}
       onLoadNotOk={handleNotOkResult(i18n, onRegister)}
     />
@@ -102,12 +98,13 @@ export function WithdrawalOperationPage({
     );
     return <Loading />;
   }
-
+  
   return (
     <WithdrawalQRCode
       withdrawUri={parsedUri}
       onClose={() => {
         updateSettings("currentWithdrawalOperationId", undefined)
+        onContinue()
       }}
     />
   );
@@ -178,7 +175,7 @@ export function handleNotOkResult(
           assertUnreachable(result);
         }
       }
-
+      route("/")
       return <div>error</div>;
     }
     return <div />;
diff --git a/packages/demobank-ui/src/pages/OperationState/index.ts 
b/packages/demobank-ui/src/pages/OperationState/index.ts
index 254fcba5f..32302f272 100644
--- a/packages/demobank-ui/src/pages/OperationState/index.ts
+++ b/packages/demobank-ui/src/pages/OperationState/index.ts
@@ -26,6 +26,7 @@ import { ErrorLoading } from 
"../../components/ErrorLoading.js";
 export interface Props {
   currency: string;
   onClose: () => void;
+  goToConfirmOperation: (id: string) => void;
 }
 
 export type State = State.Loading |
@@ -57,26 +58,33 @@ export namespace State {
     error: undefined;
     uri: WithdrawUriResult,
     onClose: () => void;
+    onAbort: () => void;
   }
 
   export interface InvalidPayto {
     status: "invalid-payto",
     error: undefined;
     payto: string | null;
+    onClose: () => void;
   }
   export interface InvalidWithdrawal {
     status: "invalid-withdrawal",
     error: undefined;
+    onClose: () => void;
     uri: string,
   }
   export interface InvalidReserve {
     status: "invalid-reserve",
     error: undefined;
+    onClose: () => void;
     reserve: string | null;
   }
   export interface NeedConfirmation {
     status: "need-confirmation",
+    onAbort: () => void;
+    onConfirm: () => void;
     error: undefined;
+    busy: boolean,
   }
   export interface Aborted {
     status: "aborted",
@@ -111,7 +119,7 @@ const viewMapping: utils.StateViewMap<State> = {
   ready: ReadyView,
 };
 
-export const AccountPage = utils.compose(
+export const OperationState = utils.compose(
   (p: Props) => useComponentState(p),
   viewMapping,
 );
diff --git a/packages/demobank-ui/src/pages/OperationState/state.ts 
b/packages/demobank-ui/src/pages/OperationState/state.ts
index 6fb7bb28f..ae03ed529 100644
--- a/packages/demobank-ui/src/pages/OperationState/state.ts
+++ b/packages/demobank-ui/src/pages/OperationState/state.ts
@@ -15,21 +15,24 @@
  */
 
 import { Amounts, HttpStatusCode, TranslatedString, parsePaytoUri, 
parseWithdrawUri, stringifyWithdrawUri } from "@gnu-taler/taler-util";
-import { ErrorType, RequestError, notify, notifyError, useTranslationContext, 
utils } from "@gnu-taler/web-util/browser";
+import { ErrorType, RequestError, notify, notifyError, notifyInfo, 
useTranslationContext, utils } from "@gnu-taler/web-util/browser";
 import { useBackendContext } from "../../context/backend.js";
-import { useAccessAPI, useAccountDetails, useWithdrawalDetails } from 
"../../hooks/access.js";
+import { useAccessAPI, useAccessAnonAPI, useAccountDetails, 
useWithdrawalDetails } from "../../hooks/access.js";
 import { Props, State } from "./index.js";
 import { useSettings } from "../../hooks/settings.js";
-import { buildRequestErrorMessage } from "../../utils.js";
-import { useEffect } from "preact/hooks";
+import { buildRequestErrorMessage, undefinedIfEmpty } from "../../utils.js";
+import { useEffect, useMemo, useState } from "preact/hooks";
 import { getInitialBackendBaseURL } from "../../hooks/backend.js";
 
-export function useComponentState({ currency, onClose }: Props): 
utils.RecursiveState<State> {
+export function useComponentState({ currency, onClose,goToConfirmOperation }: 
Props): utils.RecursiveState<State> {
   const { i18n } = useTranslationContext();
   const [settings, updateSettings] = useSettings()
   const { createWithdrawal } = useAccessAPI();
+  const { abortWithdrawal, confirmWithdrawal } = useAccessAnonAPI();
+  const [busy, setBusy] = useState<Record<string, undefined>>()
 
   const amount = settings.maxWithdrawalAmount
+
   async function doSilentStart() {
     //FIXME: if amount is not enough use balance
     const parsedAmount = Amounts.parseOrThrow(`${currency}:${amount}`)
@@ -67,12 +70,14 @@ export function useComponentState({ currency, onClose }: 
Props): utils.Recursive
     }
   }
 
+  const withdrawalOperationId = settings.currentWithdrawalOperationId
   useEffect(() => {
-    doSilentStart()
+    if (withdrawalOperationId === undefined) {
+      doSilentStart()
+    }
   }, [settings.fastWithdrawal, amount])
 
   const baseUrl = getInitialBackendBaseURL()
-  const withdrawalOperationId = settings.currentWithdrawalOperationId
 
   if (!withdrawalOperationId) {
     return {
@@ -81,6 +86,63 @@ export function useComponentState({ currency, onClose }: 
Props): utils.Recursive
     }
   }
 
+  const wid = withdrawalOperationId
+
+  async function doAbort() {
+    try {
+      setBusy({})
+      await abortWithdrawal(wid);
+      onClose();
+    } catch (error) {
+      if (error instanceof RequestError) {
+        notify(
+          buildRequestErrorMessage(i18n, error.cause, {
+            onClientError: (status) =>
+              status === HttpStatusCode.Conflict
+                ? i18n.str`The reserve operation has been confirmed previously 
and can't be aborted`
+                : undefined,
+          }),
+        );
+      } 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({})
+      await confirmWithdrawal(wid);
+      notifyInfo(i18n.str`Wire transfer completed!`)
+    } catch (error) {
+      if (error instanceof RequestError) {
+        notify(
+          buildRequestErrorMessage(i18n, error.cause, {
+            onClientError: (status) =>
+              status === HttpStatusCode.Conflict
+                ? i18n.str`The withdrawal has been aborted previously and 
can't be confirmed`
+                : status === HttpStatusCode.UnprocessableEntity
+                  ? i18n.str`The withdraw operation cannot be confirmed 
because no exchange and reserve public key selection happened before`
+                  : undefined,
+          }),
+        );
+      } else {
+        notifyError(
+          i18n.str`Operation failed, please report`,
+          (error instanceof Error
+            ? error.message
+            : JSON.stringify(error)) as TranslatedString
+        )
+      }
+    }
+    setBusy(undefined)
+  }  
   const bankIntegrationApiBaseUrl = `${baseUrl}/integration-api`
   const uri = stringifyWithdrawUri({
     bankIntegrationApiBaseUrl,
@@ -92,11 +154,13 @@ export function useComponentState({ currency, onClose }: 
Props): utils.Recursive
       status: "invalid-withdrawal",
       error: undefined,
       uri,
+      onClose,
     }
   }
 
   return (): utils.RecursiveState<State> => {
     const result = useWithdrawalDetails(withdrawalOperationId);
+
     if (!result.ok) {
       if (result.loading) {
         return {
@@ -119,10 +183,17 @@ export function useComponentState({ currency, onClose }: 
Props): utils.Recursive
     }
 
     if (data.confirmation_done) {
+      if (!settings.showWithdrawalSuccess) {
+        updateSettings("currentWithdrawalOperationId", undefined)
+        onClose()
+      }
       return {
         status: "confirmed",
         error: undefined,
-        onClose,
+        onClose: async () => {
+          updateSettings("currentWithdrawalOperationId", undefined)
+          onClose()
+        },
       }
     }
 
@@ -131,7 +202,12 @@ export function useComponentState({ currency, onClose }: 
Props): utils.Recursive
         status: "ready",
         error: undefined,
         uri: parsedUri,
-        onClose
+        onClose: async () => {
+          await doAbort()
+          updateSettings("currentWithdrawalOperationId", undefined)
+          onClose()
+        },
+        onAbort: doAbort,
       }
     }
 
@@ -139,7 +215,8 @@ export function useComponentState({ currency, onClose }: 
Props): utils.Recursive
       return {
         status: "invalid-reserve",
         error: undefined,
-        reserve: data.selected_reserve_pub
+        reserve: data.selected_reserve_pub,
+        onClose,
       }
     }
 
@@ -149,13 +226,23 @@ export function useComponentState({ currency, onClose }: 
Props): utils.Recursive
       return {
         status: "invalid-payto",
         error: undefined,
-        payto: data.selected_exchange_account
+        payto: data.selected_exchange_account,
+        onClose,
       }
     }
 
+
+    // goToConfirmOperation(withdrawalOperationId)
     return {
       status: "need-confirmation",
       error: undefined,
+      onAbort: async () => {
+        await doAbort()
+        updateSettings("currentWithdrawalOperationId", undefined)
+        onClose()
+      },
+      busy: !!busy,
+      onConfirm: doConfirm
     }
   }
 
diff --git a/packages/demobank-ui/src/pages/OperationState/views.tsx 
b/packages/demobank-ui/src/pages/OperationState/views.tsx
index db25eaf61..17f1d8457 100644
--- a/packages/demobank-ui/src/pages/OperationState/views.tsx
+++ b/packages/demobank-ui/src/pages/OperationState/views.tsx
@@ -14,7 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { Amounts, stringifyPaytoUri } from "@gnu-taler/taler-util";
+import { Amounts, stringifyPaytoUri, stringifyWithdrawUri } from 
"@gnu-taler/taler-util";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { Fragment, h, VNode } from "preact";
 import { Transactions } from "../../components/Transactions/index.js";
@@ -24,42 +24,375 @@ import { CopyButton } from 
"../../components/CopyButton.js";
 import { bankUiSettings } from "../../settings.js";
 import { useBusinessAccountDetails } from "../../hooks/circuit.js";
 import { useSettings } from "../../hooks/settings.js";
+import { useEffect, useMemo, useState } from "preact/hooks";
+import { undefinedIfEmpty } from "../../utils.js";
+import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
+import { QR } from "../../components/QR.js";
 
-export function InvalidPaytoView({ error }: State.InvalidPayto) {
+export function InvalidPaytoView({ payto, onClose }: State.InvalidPayto) {
   return (
-    <div>Payto from server is not valid &quot;{error.data.paytoUri}&quot;</div>
+    <div>Payto from server is not valid &quot;{payto}&quot;</div>
   );
 }
-export function InvalidWithdrawalView({ error }: State.InvalidWithdrawal) {
+export function InvalidWithdrawalView({ uri, onClose }: 
State.InvalidWithdrawal) {
   return (
-    <div>Payto from server is not valid &quot;{error.data.paytoUri}&quot;</div>
+    <div>Withdrawal uri from server is not valid &quot;{uri}&quot;</div>
   );
 }
-export function InvalidReserveView({ error }: State.InvalidReserve) {
+export function InvalidReserveView({ reserve, onClose }: State.InvalidReserve) 
{
   return (
-    <div>Payto from server is not valid &quot;{error.data.paytoUri}&quot;</div>
+    <div>Reserve from server is not valid &quot;{reserve}&quot;</div>
   );
 }
 
-export function NeedConfirmationView({ error }: State.NeedConfirmation) {
+export function NeedConfirmationView({ error, onAbort, onConfirm, busy }: 
State.NeedConfirmation) {
+  const { i18n } = useTranslationContext()
+
+  const captchaNumbers = useMemo(() => {
+    return {
+      a: Math.floor(Math.random() * 10),
+      b: Math.floor(Math.random() * 10),
+    };
+  }, []);
+  const [captchaAnswer, setCaptchaAnswer] = useState<string | undefined>();
+  const answer = parseInt(captchaAnswer ?? "", 10);
+  const errors = undefinedIfEmpty({
+    answer: !captchaAnswer
+      ? i18n.str`Answer the question before continue`
+      : Number.isNaN(answer)
+        ? i18n.str`The answer should be a number`
+        : answer !== captchaNumbers.a + captchaNumbers.b
+          ? i18n.str`The answer "${answer}" to "${captchaNumbers.a} + 
${captchaNumbers.b}" is wrong.`
+          : undefined,
+  }) ?? (busy ? {} as Record<string, undefined> : undefined);
+
   return (
-    <div>Payto from server is not valid &quot;{error.data.paytoUri}&quot;</div>
+    <div class="bg-white shadow sm:rounded-lg">
+      <div class="px-4 py-5 sm:p-6">
+        <h3 class="text-base font-semibold text-gray-900">
+          <i18n.Translate>Confirm the withdrawal operation</i18n.Translate>
+        </h3>
+        <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 sm:grid-cols-4 
sm:gap-x-3">
+
+            <label class={"relative sm:col-span-2 flex cursor-pointer 
rounded-lg border bg-white p-4 shadow-sm focus:outline-noneborder-indigo-600 
ring-2 ring-indigo-600"}>
+              <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" />
+              <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>challenge response test</i18n.Translate>
+                  </span>
+                </span>
+              </span>
+              <svg class="h-5 w-5 text-indigo-600" 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>
+            </label>
+
+
+            <label class="relative flex cursor-pointer rounded-lg border 
bg-gray-100  p-4 shadow-sm focus:outline-none border-gray-300">
+              <input type="radio" name="project-type" value="Existing 
Customers" class="sr-only" aria-labelledby="project-type-1-label" 
aria-describedby="project-type-1-description-0 project-type-1-description-1" />
+              <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>using SMS</i18n.Translate>
+                  </span>
+                  <span id="project-type-1-description-0" class="mt-1 flex 
items-center text-sm text-gray-500">
+                    <i18n.Translate>not available</i18n.Translate>
+                  </span>
+                </span>
+              </span>
+              <svg class="h-5 w-5 text-indigo-600 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>
+            </label>
+
+            <label class="relative flex cursor-pointer rounded-lg border 
bg-gray-100 p-4 shadow-sm focus:outline-none border-gray-300">
+              <input type="radio" name="project-type" value="Existing 
Customers" class="sr-only" aria-labelledby="project-type-1-label" 
aria-describedby="project-type-1-description-0 project-type-1-description-1" />
+              <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>one time password</i18n.Translate>
+                  </span>
+                  <span id="project-type-1-description-0" class="mt-1 flex 
items-center text-sm text-gray-500">
+                    <i18n.Translate>not available</i18n.Translate>
+                  </span>
+                </span>
+              </span>
+              <svg class="h-5 w-5 text-indigo-600 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>
+            </label>
+          </div>
+        </div>
+        <div class="mt-3 text-sm leading-6">
+
+          <form
+            class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl 
md:col-span-2"
+            autoCapitalize="none"
+            autoCorrect="off"
+            onSubmit={e => {
+              e.preventDefault()
+            }}
+          >
+            <div class="px-4 py-6 sm:p-8">
+              <label for="withdraw-amount">{i18n.str`What is`}&nbsp;
+                <em>
+                  {captchaNumbers.a}&nbsp;+&nbsp;{captchaNumbers.b}
+                </em>
+                ?
+              </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={captchaAnswer ?? ""}
+                    required
+
+                    name="answer"
+                    id="answer"
+                    autocomplete="off"
+                    onChange={(e): void => {
+                      setCaptchaAnswer(e.currentTarget.value)
+                    }}
+                  />
+                </div>
+                <ShowInputErrorLabel message={errors?.answer} 
isDirty={captchaAnswer !== 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="text-sm font-semibold leading-6 
text-gray-900"
+                onClick={onAbort}
+              >
+                <i18n.Translate>Cancel</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) => {
+                  e.preventDefault()
+                  onConfirm()
+                }}
+              >
+                <i18n.Translate>Transfer</i18n.Translate>
+              </button>
+            </div>
+
+          </form>
+        </div>
+        <div class="px-4 mt-4 ">
+          {/* <div class="w-full">
+          <div class="px-4 sm:px-0 text-sm">
+            <p><i18n.Translate>Wire transfer details</i18n.Translate></p>
+          </div>
+          <div class="mt-6 border-t border-gray-100">
+            <dl class="divide-y divide-gray-100">
+              {((): VNode => {
+                switch (details.account.targetType) {
+                  case "iban": {
+                    const p = details.account as PaytoUriIBAN
+                    const name = p.params["receiver-name"]
+                    return <Fragment>
+                      <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
+                        <dt class="text-sm font-medium leading-6 
text-gray-900">Exchange account</dt>
+                        <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">{p.iban}</dd>
+                      </div>
+                      {name &&
+                        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
+                          <dt class="text-sm font-medium leading-6 
text-gray-900">Exchange name</dt>
+                          <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">{p.params["receiver-name"]}</dd>
+                        </div>
+                      }
+                    </Fragment>
+                  }
+                  case "x-taler-bank": {
+                    const p = details.account as PaytoUriTalerBank
+                    const name = p.params["receiver-name"]
+                    return <Fragment>
+                      <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
+                        <dt class="text-sm font-medium leading-6 
text-gray-900">Exchange account</dt>
+                        <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">{p.account}</dd>
+                      </div>
+                      {name &&
+                        <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 
sm:px-0">
+                          <dt class="text-sm font-medium leading-6 
text-gray-900">Exchange name</dt>
+                          <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">{p.params["receiver-name"]}</dd>
+                        </div>
+                      }
+                    </Fragment>
+                  }
+                  default:
+                    return <div class="px-4 py-2 sm:grid sm:grid-cols-3 
sm:gap-4 sm:px-0">
+                      <dt class="text-sm font-medium leading-6 
text-gray-900">Exchange account</dt>
+                      <dd class="mt-1 text-sm leading-6 text-gray-700 
sm:col-span-2 sm:mt-0">{details.account.targetPath}</dd>
+                    </div>
+
+                }
+              })()}
+              <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
+                <dt class="text-sm font-medium leading-6 
text-gray-900">Withdrawal identification</dt>
+                <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 
sm:mt-0 break-words">{details.reserve}</dd>
+              </div>
+              <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
+                <dt class="text-sm font-medium leading-6 
text-gray-900">Amount</dt>
+                <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 
sm:mt-0">To be added</dd>
+                // {/* Amounts.stringifyValue(details.amount) 
+              </div>
+            </dl>
+          </div>
+        </div> */}
+
+        </div>
+      </div>
+    </div>
+
   );
 }
-export function AbortedView({ error }: State.Aborted) {
+export function AbortedView({ error, onClose }: State.Aborted) {
   return (
-    <div>Payto from server is not valid &quot;{error.data.paytoUri}&quot;</div>
+    <div>aborted</div>
   );
 }
-export function ConfirmedView({ error }: State.Confirmed) {
+export function ConfirmedView({ error, onClose }: State.Confirmed) {
+  const { i18n } = useTranslationContext();
+  const [settings, updateSettings] = useSettings()
   return (
-    <div>Payto from server is not valid &quot;{error.data.paytoUri}&quot;</div>
+    <Fragment>
+
+      <div class="relative ml-auto mr-auto transform overflow-hidden 
rounded-lg bg-white p-4 text-left shadow-xl transition-all ">
+
+        <div class="mx-auto flex h-12 w-12 items-center justify-center 
rounded-full bg-green-100">
+          <svg class="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" 
stroke-width="1.5" stroke="currentColor" aria-hidden="true">
+            <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 
12.75l6 6 9-13.5" />
+          </svg>
+        </div>
+        <div class="mt-3 text-center sm:mt-5">
+          <h3 class="text-base font-semibold leading-6 text-gray-900" 
id="modal-title">
+            <i18n.Translate>Withdrawal OK</i18n.Translate>
+          </h3>
+          <div class="mt-2">
+            <p class="text-sm text-gray-500">
+              <i18n.Translate>
+                The wire transfer to the Taler exchange bank's account is 
completed, now the
+                exchange will send the requested amount into your GNU Taler 
wallet.
+              </i18n.Translate>
+            </p>
+          </div>
+        </div>
+      </div>
+      <div class="mt-4">
+        <div class="flex items-center justify-between">
+          <span class="flex flex-grow flex-col">
+            <span class="text-sm text-black font-medium leading-6 " 
id="availability-label">
+              <i18n.Translate>Do not show this again</i18n.Translate>
+            </span>
+          </span>
+          <button type="button" data-enabled={!settings.showWithdrawalSuccess} 
class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 
w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent 
transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 
focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" 
aria-labelledby="availability-label" aria-describedby="availability-description"
+            onClick={() => {
+              updateSettings("showWithdrawalSuccess", 
!settings.showWithdrawalSuccess);
+            }}>
+            <span aria-hidden="true" 
data-enabled={!settings.showWithdrawalSuccess} class="translate-x-5 
data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 
transform rounded-full bg-white shadow ring-0 transition duration-200 
ease-in-out"></span>
+          </button>
+        </div>
+      </div>
+      <div class="mt-5 sm:mt-6">
+        <button type="button"
+          class="inline-flex w-full justify-center rounded-md bg-indigo-600 
px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
+          onClick={async (e) => {
+            e.preventDefault();
+            onClose()
+          }}>
+          <i18n.Translate>Close</i18n.Translate>
+        </button>
+      </div>
+    </Fragment>
+
   );
 }
 
-export function ReadyView({ account, limit, goToBusinessAccount }: 
State.Ready): VNode<{}> {
+export function ReadyView({ uri, onClose }: State.Ready): VNode<{}> {
   const { i18n } = useTranslationContext();
 
-  return <div />
+  useEffect(() => {
+    //Taler Wallet WebExtension is listening to headers response and tab 
updates.
+    //In the SPA there is no header response with the Taler URI so
+    //this hack manually triggers the tab update after the QR is in the DOM.
+    // WebExtension will be using
+    // 
https://developer.chrome.com/docs/extensions/reference/tabs/#event-onUpdated
+    document.title = `${document.title} ${uri.withdrawalOperationId}`;
+  }, []);
+  const talerWithdrawUri = stringifyWithdrawUri(uri);
+  const [show, setShow] = useState(false)
+  return <Fragment>
+
+    <div class="bg-white shadow sm:rounded-lg mt-4">
+      <div class="p-4">
+        <h3 class="text-base font-semibold leading-6 text-gray-900">
+          <i18n.Translate>On this device</i18n.Translate>
+        </h3>
+        <div class="mt-2 sm:flex sm:items-start sm:justify-between">
+          <div class="max-w-xl text-sm text-gray-500">
+            <p>
+              <i18n.Translate>If you are using a desktop browser you can open 
the popup now or click the link if you have the "Inject Taler support" option 
enabled.</i18n.Translate>
+            </p>
+          </div>
+          <div class="mt-5 sm:ml-6 sm:mt-0 sm:flex sm:flex-shrink-0 
sm:items-center">
+            <a href={talerWithdrawUri}
+              class="inline-flex items-center  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"
+            >
+              <i18n.Translate>Start</i18n.Translate>
+            </a>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="bg-white shadow sm:rounded-lg mt-2">
+      <div class="p-4">
+        <h3 class="text-base font-semibold leading-6 text-gray-900">
+          <i18n.Translate>On a mobile phone</i18n.Translate>
+        </h3>
+        <div class="mt-2 sm:flex sm:items-start sm:justify-between">
+          <div class="max-w-xl text-sm text-gray-500">
+            <p>
+              <i18n.Translate>Scan the QR code with your mobile 
device.</i18n.Translate>
+            </p>
+          </div>
+          <div class="mt-5 sm:ml-6 sm:mt-0 sm:flex sm:flex-shrink-0 
sm:items-center">
+            <button type="button"
+              class="inline-flex items-center 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-500"
+              onClick={() => {
+                setShow(!show)
+              }}
+            >
+              {!show ?
+                <i18n.Translate>Show QR</i18n.Translate>
+                :
+                <i18n.Translate>Hide QR</i18n.Translate>
+              }
+            </button>
+          </div>
+        </div>
+        {show &&
+          <div class="mt-2 max-w-md ml-auto mr-auto">
+            <QR text={talerWithdrawUri} />
+          </div>
+        }
+      </div>
+    </div>
+
+    <div class="flex justify-end mt-4">
+      <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={() => {
+          onClose()
+        }}
+      >
+        Cancel
+      </button>
+    </div>
+  </Fragment>
 
 }
diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx 
b/packages/demobank-ui/src/pages/PaymentOptions.tsx
index 573f8c769..2830f5c1e 100644
--- a/packages/demobank-ui/src/pages/PaymentOptions.tsx
+++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx
@@ -26,12 +26,11 @@ import { useSettings } from "../hooks/settings.js";
  * Let the user choose a payment option,
  * then specify the details trigger the action.
  */
-export function PaymentOptions({ limit }: { limit: AmountJson }): VNode {
+export function PaymentOptions({ limit, goToConfirmOperation }: { limit: 
AmountJson, goToConfirmOperation: (id: string) => void }): VNode {
   const { i18n } = useTranslationContext();
-  const [settings, updateSettings] = useSettings();
+  const [settings] = useSettings();
 
   const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | 
undefined>();
-  // const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | 
undefined>(undefined);
 
   return (
     <div class="mt-2">
@@ -56,6 +55,14 @@ export function PaymentOptions({ limit }: { limit: 
AmountJson }): VNode {
                 <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>
+                {!!settings.currentWithdrawalOperationId &&
+                  <span class="inline-flex items-center gap-x-1.5 rounded-full 
bg-green-100 px-1.5 py-0.5 text-xs font-medium text-green-700">
+                    <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 in progress</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">
@@ -88,9 +95,7 @@ export function PaymentOptions({ limit }: { limit: AmountJson 
}): VNode {
           <WalletWithdrawForm
             focus
             limit={limit}
-            onSuccess={(id) => {
-              updateSettings("currentWithdrawalOperationId", id);
-            }}
+            goToConfirmOperation={goToConfirmOperation}
             onCancel={() => {
               setTab(undefined)
             }}
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx 
b/packages/demobank-ui/src/pages/QrCodeSection.tsx
index 416c714e2..0a5a386ae 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx
@@ -137,107 +137,3 @@ export function QrCodeSection({
 }
 
 
-export function QrCodeSectionSimpler({
-  withdrawUri,
-  onAborted,
-}: {
-  withdrawUri: WithdrawUriResult;
-  onAborted: () => void;
-}): VNode {
-  const { i18n } = useTranslationContext();
-  useEffect(() => {
-    //Taler Wallet WebExtension is listening to headers response and tab 
updates.
-    //In the SPA there is no header response with the Taler URI so
-    //this hack manually triggers the tab update after the QR is in the DOM.
-    // WebExtension will be using
-    // 
https://developer.chrome.com/docs/extensions/reference/tabs/#event-onUpdated
-    document.title = `${document.title} ${withdrawUri.withdrawalOperationId}`;
-  }, []);
-  const talerWithdrawUri = stringifyWithdrawUri(withdrawUri);
-
-  const { abortWithdrawal } = useAccessAnonAPI();
-
-  async function doAbort() {
-    try {
-      await abortWithdrawal(withdrawUri.withdrawalOperationId);
-      onAborted();
-    } catch (error) {
-      if (error instanceof RequestError) {
-        notify(
-          buildRequestErrorMessage(i18n, error.cause, {
-            onClientError: (status) =>
-              status === HttpStatusCode.Conflict
-                ? i18n.str`The reserve operation has been confirmed previously 
and can't be aborted`
-                : undefined,
-          }),
-        );
-      } else {
-        notifyError(
-          i18n.str`Operation failed, please report`,
-          (error instanceof Error
-            ? error.message
-            : JSON.stringify(error)) as TranslatedString
-        )
-      }
-    }
-  }
-
-  return (
-    <Fragment>
-      <div class="bg-white shadow-xl sm:rounded-lg">
-        <div class="p2 ">
-          <h3 class="text-base font-semibold leading-6 text-gray-900">
-            <i18n.Translate>If you have a Taler wallet installed in this 
device</i18n.Translate>
-          </h3>
-          <div class="mt-4">
-            <a href={talerWithdrawUri}
-              // class="text-sm font-semibold leading-6 text-gray-900 btn "
-              class="inline-flex items-center  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"
-            >
-              <i18n.Translate>Click here to start</i18n.Translate>
-            </a>
-          </div>
-          <div class="mt-4 max-w-xl text-sm text-gray-500">
-            <p><i18n.Translate>
-              You will see the details of the operation in your wallet 
including the fees (if applies).
-              If you still one you can install it from <a class="font-semibold 
text-gray-500 hover:text-gray-400" 
href="https://taler.net/en/wallet.html";>here</a>.
-            </i18n.Translate></p>
-          </div>
-          <div class="flex items-center justify-between gap-x-6 border-t 
border-gray-900/10 pt-2 mt-2 ">
-            <div />
-            <button type="button"
-              class="disabled:opacity-50 disabled:cursor-default 
cursor-pointer 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-600"
-              onClick={doAbort}
-            >
-              Cancel withdrawal
-            </button>
-          </div>
-        </div>
-      </div>
-
-      <div class="bg-white shadow-xl sm:rounded-lg mt-8">
-        <div class="px-4 py-5 sm:p-6">
-          <h3 class="text-base font-semibold leading-6 text-gray-900">
-            <i18n.Translate>Or if you have the wallet in another 
device</i18n.Translate>
-          </h3>
-          <div class="mt-4 max-w-xl text-sm text-gray-500">
-            <i18n.Translate>Scan the QR below to start the 
withdrawal</i18n.Translate>
-          </div>
-          <div class="mt-2 max-w-md ml-auto mr-auto">
-            <QR text={talerWithdrawUri} />
-          </div>
-        </div>
-        <div class="flex items-center justify-center gap-x-6 border-t 
border-gray-900/10 px-4 py-4 sm:px-8">
-          <button type="button"
-            class="disabled:opacity-50 disabled:cursor-default cursor-pointer 
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-600"
-            onClick={doAbort}
-          >
-            Cancel withdrawal
-          </button>
-        </div>
-      </div>
-
-    </Fragment>
-  );
-}
-
diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx 
b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
index 08f706919..8dbdd9da6 100644
--- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
+++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
@@ -36,33 +36,52 @@ import { useAccessAPI } from "../hooks/access.js";
 import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
 import { Amount } from "./PaytoWireTransferForm.js";
 import { useSettings } from "../hooks/settings.js";
-import { WithdrawalOperationState } from "./WithdrawalQRCode.js";
-import { Loading } from "../components/Loading.js";
+import { OperationState } from "./OperationState/index.js";
 
 const logger = new Logger("WalletWithdrawForm");
 const RefAmount = forwardRef(Amount);
 
-export function WalletWithdrawForm({
-  focus,
-  limit,
-  onSuccess,
-  onCancel,
-}: {
+
+function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
   limit: AmountJson;
   focus?: boolean;
-  onSuccess: (operationId: string) => void;
+  goToConfirmOperation: (operationId: string) => void;
   onCancel: () => void;
 }): VNode {
   const { i18n } = useTranslationContext();
-  const { createWithdrawal } = useAccessAPI();
   const [settings, updateSettings] = useSettings()
 
+  const { createWithdrawal } = useAccessAPI();
   const [amountStr, setAmountStr] = useState<string | 
undefined>(`${settings.maxWithdrawalAmount}`);
   const ref = useRef<HTMLInputElement>(null);
   useEffect(() => {
     if (focus) ref.current?.focus();
   }, [focus]);
 
+  if (!!settings.currentWithdrawalOperationId) {
+    return <div class="rounded-md bg-yellow-50 ring-yellow-2 p-4">
+      <div class="flex">
+        <div class="flex-shrink-0">
+          <svg class="h-5 w-5 text-yellow-300" viewBox="0 0 20 20" 
fill="currentColor" aria-hidden="true">
+            <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 
0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 
01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 
0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
+          </svg>
+        </div>
+        <div class="ml-3">
+          <h3 class="text-sm font-bold text-yellow-800">
+            <i18n.Translate>There is an operation already</i18n.Translate>
+          </h3>
+          <div class="mt-2 text-sm text-yellow-700">
+            <p>
+              <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>
+            </p>
+        </div>
+      </div>
+    </div>
+  </div >
+  }
+
   const trimmedAmountStr = amountStr?.trim();
 
   const parsedAmount = trimmedAmountStr
@@ -92,7 +111,8 @@ export function WalletWithdrawForm({
           i18n.str`Server responded with an invalid  withdraw URI`,
           i18n.str`Withdraw URI: ${result.data.taler_withdraw_uri}`);
       } else {
-        onSuccess(uri.withdrawalOperationId);
+        updateSettings("currentWithdrawalOperationId", 
uri.withdrawalOperationId)
+        goToConfirmOperation(uri.withdrawalOperationId);
       }
     } catch (error) {
       if (error instanceof RequestError) {
@@ -115,113 +135,168 @@ export function WalletWithdrawForm({
     }
   }
 
+  return <form
+    class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl 
md:col-span-2 mt-4"
+    autoCapitalize="none"
+    autoCorrect="off"
+    onSubmit={e => {
+      e.preventDefault()
+    }}
+  >
+    <div class="px-4 py-6 sm:p-8">
+      <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
+        <div class="sm:col-span-5">
+          <label for="withdraw-amount">{i18n.str`Amount`}</label>
+          <RefAmount
+            currency={limit.currency}
+            value={amountStr}
+            name="withdraw-amount"
+            onChange={(v) => {
+              setAmountStr(v);
+            }}
+            error={errors?.amount}
+            ref={ref}
+          />
+        </div>
+        <div class="sm:col-span-5">
+          <span class="isolate inline-flex rounded-md shadow-sm">
+            <button type="button"
+              class="relative               inline-flex px-6 py-4 text-sm 
items-center rounded-l-md bg-white text-gray-900 ring-1 ring-inset 
ring-gray-300 hover:bg-gray-50 focus:z-10"
+              onClick={(e) => {
+                e.preventDefault();
+                setAmountStr("50.00")
+              }}
+            >
+              50.00
+            </button>
+            <button type="button"
+              class="relative -ml-px -mr-px inline-flex px-6 py-4 text-sm 
items-center              bg-white text-gray-900 ring-1 ring-inset 
ring-gray-300 hover:bg-gray-50 focus:z-10"
+              onClick={(e) => {
+                e.preventDefault();
+                setAmountStr("25.00")
+              }}
+            >
+
+              25.00
+            </button>
+            <button type="button"
+              class="relative -ml-px -mr-px inline-flex px-6 py-4 text-sm 
items-center              bg-white text-gray-900 ring-1 ring-inset 
ring-gray-300 hover:bg-gray-50 focus:z-10"
+              onClick={(e) => {
+                e.preventDefault();
+                setAmountStr("10.00")
+              }}
+            >
+              10.00
+            </button>
+            <button type="button"
+              class="relative               inline-flex px-6 py-4 text-sm 
items-center rounded-r-md bg-white  text-gray-900 ring-1 ring-inset 
ring-gray-300 hover:bg-gray-50 focus:z-10"
+              onClick={(e) => {
+                e.preventDefault();
+                setAmountStr("5.00")
+              }}
+            >
+              5.00
+            </button>
+          </span>
+        </div>
+
+      </div>
+    </div>
+    <div class="flex items-center justify-between gap-x-6 border-t 
border-gray-900/10 px-4 py-4 sm:px-8">
+      <button type="button" class="text-sm font-semibold leading-6 
text-gray-900"
+        onClick={onCancel}
+      >
+        <i18n.Translate>Cancel</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={isRawPayto ? !!errorsPayto : !!errorsWire}
+        onClick={(e) => {
+          e.preventDefault()
+          doStart()
+        }}
+      >
+        <i18n.Translate>Continue</i18n.Translate>
+      </button>
+    </div>
+
+  </form>
+}
+
+
+export function WalletWithdrawForm({
+  focus,
+  limit,
+  onCancel,
+  goToConfirmOperation,
+}: {
+  limit: AmountJson;
+  focus?: boolean;
+  goToConfirmOperation: (operationId: string) => void;
+  onCancel: () => void;
+}): VNode {
+  const { i18n } = useTranslationContext();
+  const [settings, updateSettings] = useSettings()
+
   return (<div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 
bg-gray-100 my-4 px-4 pb-4 rounded-lg">
     <div class="px-4 sm:px-0">
       <h2 class="text-base font-semibold leading-7 
text-gray-900"><i18n.Translate>Prepare your wallet</i18n.Translate></h2>
       <p class="mt-1 text-sm text-gray-500">
-        <i18n.Translate>Upon starting you will receive the money in your 
digital wallet, if you don't have one please <a target="_blank" rel="noreferrer 
noopener" class="font-semibold text-gray-500 hover:text-gray-400" 
href="https://taler.net/en/wallet.html";>install one from 
here</a></i18n.Translate>.
-      </p>
-      <p class="mt-1 text-sm text-gray-500">
-        <i18n.Translate>After using your wallet you will be redirected here to 
confirm or cancel the operation.</i18n.Translate>
+        <i18n.Translate>After using your wallet you will confirm or cancel the 
operation.</i18n.Translate>
       </p>
     </div>
 
-    {!settings.fastWithdrawal ?
-      <form
-        class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl 
md:col-span-2"
-        autoCapitalize="none"
-        autoCorrect="off"
-        onSubmit={e => {
-          e.preventDefault()
-        }}
-      >
-        <div class="px-4 py-6 sm:p-8">
-          <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 
sm:grid-cols-6">
-            <div class="sm:col-span-5">
-              <label for="withdraw-amount">{i18n.str`Amount`}</label>
-              <RefAmount
-                currency={limit.currency}
-                value={amountStr}
-                name="withdraw-amount"
-                onChange={(v) => {
-                  setAmountStr(v);
-                }}
-                error={errors?.amount}
-                ref={ref}
-              />
-            </div>
-            <div class="sm:col-span-5">
-              <span class="isolate inline-flex rounded-md shadow-sm">
-                <button type="button"
-                  class="relative               inline-flex px-6 py-4 text-sm 
items-center rounded-l-md bg-white text-gray-900 ring-1 ring-inset 
ring-gray-300 hover:bg-gray-50 focus:z-10"
+    <div class="col-span-2">
+      {settings.showInstallWallet && <div class="rounded-md bg-blue-50 
ring-blue-2 ring-2 p-4">
+        <div class="flex">
+          <div class="flex-shrink-0">
+            <svg class="h-5 w-5 text-blue-300" viewBox="0 0 20 20" 
fill="currentColor" aria-hidden="true">
+              <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 
0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 
01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 
0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
+            </svg>
+          </div>
+          <div class="ml-3">
+            <h3 class="text-sm font-bold text-blue-800">
+              <i18n.Translate>You need a GNU Taler Wallet</i18n.Translate>
+            </h3>
+            <div class="mt-2 text-sm text-blue-700">
+              <p>
+                <i18n.Translate>
+                  If you dont have one yet you can follow the instruction <a 
target="_blank" rel="noreferrer noopener" class="font-semibold text-blue-700 
hover:text-blue-600" href="https://taler.net/en/wallet.html";>here</a>
+                </i18n.Translate>
+              </p>
+              <p class="mt-3 text-sm flex justify-end">
+                <button type="button" class="inline-flex font-semibold 
items-center rounded bg-white px-2 py-1 text-xs text-gray-900 shadow-sm ring-1 
ring-inset ring-gray-300 hover:bg-gray-50"
                   onClick={(e) => {
                     e.preventDefault();
-                    setAmountStr("50.00")
+                    updateSettings("showInstallWallet", false);
                   }}
                 >
-                  50.00
+                  I know
+                  <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" 
aria-hidden="true">
+                    <path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 
3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 
10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
+                  </svg>
                 </button>
-                <button type="button"
-                  class="relative -ml-px -mr-px inline-flex px-6 py-4 text-sm 
items-center              bg-white text-gray-900 ring-1 ring-inset 
ring-gray-300 hover:bg-gray-50 focus:z-10"
-                  onClick={(e) => {
-                    e.preventDefault();
-                    setAmountStr("25.00")
-                  }}
-                >
+              </p>
 
-                  25.00
-                </button>
-                <button type="button"
-                  class="relative -ml-px -mr-px inline-flex px-6 py-4 text-sm 
items-center              bg-white text-gray-900 ring-1 ring-inset 
ring-gray-300 hover:bg-gray-50 focus:z-10"
-                  onClick={(e) => {
-                    e.preventDefault();
-                    setAmountStr("10.00")
-                  }}
-                >
-                  10.00
-                </button>
-                <button type="button"
-                  class="relative               inline-flex px-6 py-4 text-sm 
items-center rounded-r-md bg-white  text-gray-900 ring-1 ring-inset 
ring-gray-300 hover:bg-gray-50 focus:z-10"
-                  onClick={(e) => {
-                    e.preventDefault();
-                    setAmountStr("5.00")
-                  }}
-                >
-                  5.00
-                </button>
-              </span>
             </div>
-
           </div>
         </div>
-        <div class="flex items-center justify-between gap-x-6 border-t 
border-gray-900/10 px-4 py-4 sm:px-8">
-          <button type="button" class="text-sm font-semibold leading-6 
text-gray-900"
-            onClick={onCancel}
-          >
-            <i18n.Translate>Cancel</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={isRawPayto ? !!errorsPayto : !!errorsWire}
-            onClick={(e) => {
-              e.preventDefault()
-              doStart()
-            }}
-          >
-            <i18n.Translate>Continue</i18n.Translate>
-          </button>
-        </div>
+      </div>}
 
-      </form>
-      : settings.currentWithdrawalOperationId === undefined ?
-        <Loading /> :
-        <WithdrawalOperationState
-          currentOperation={settings.currentWithdrawalOperationId}
+      {!settings.fastWithdrawal ?
+        <OldWithdrawalForm
+          focus={focus}
+          limit={limit}
+          onCancel={onCancel}
+          goToConfirmOperation={goToConfirmOperation}
+        />
+        :
+        <OperationState
           currency={limit.currency}
-          onClose={() => {
-            onCancel()
-          }}
+          onClose={onCancel}
+          goToConfirmOperation={goToConfirmOperation}
         />
-    }
+      }
+    </div>
   </div>
   );
 }
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx 
b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 9976babdb..25c571e28 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -18,23 +18,17 @@ import {
   Amounts,
   HttpStatusCode,
   Logger,
-  TranslatedString,
   WithdrawUriResult,
-  parsePaytoUri,
-  parseWithdrawUri,
-  stringifyWithdrawUri,
+  parsePaytoUri
 } from "@gnu-taler/taler-util";
-import { ErrorType, RequestError, notify, notifyError, notifyInfo, 
useTranslationContext } from "@gnu-taler/web-util/browser";
+import { ErrorType, notifyInfo, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { Loading } from "../components/Loading.js";
-import { useAccessAPI, useWithdrawalDetails } from "../hooks/access.js";
+import { useWithdrawalDetails } from "../hooks/access.js";
 import { useSettings } from "../hooks/settings.js";
 import { handleNotOkResult } from "./HomePage.js";
-import { QrCodeSection, QrCodeSectionSimpler } from "./QrCodeSection.js";
+import { QrCodeSection } from "./QrCodeSection.js";
 import { WithdrawalConfirmationQuestion } from 
"./WithdrawalConfirmationQuestion.js";
-import { useEffect, useState } from "preact/hooks";
-import { buildRequestErrorMessage } from "../utils.js";
-import { getInitialBackendBaseURL } from "../hooks/backend.js";
 
 const logger = new Logger("WithdrawalQRCode");
 
@@ -54,18 +48,11 @@ export function WithdrawalQRCode({
   const [settings, updateSettings] = useSettings();
   const { i18n } = useTranslationContext();
   const result = useWithdrawalDetails(withdrawUri.withdrawalOperationId);
+
   if (!result.ok) {
     if (result.loading) {
       return <Loading />;
     }
-    if (
-      result.type === ErrorType.CLIENT &&
-      result.status === HttpStatusCode.NotFound
-    ) {
-      onClose()
-      return <div>operation not found</div>;
-    }
-    // onLoadNotOk();
     return handleNotOkResult(i18n)(result);
   }
   const { data } = result;
@@ -127,22 +114,6 @@ export function WithdrawalQRCode({
           </div>
         </div>
       </div>
-      <div class="mt-4">
-        <div class="flex items-center justify-between">
-          <span class="flex flex-grow flex-col">
-            <span class="text-sm text-black font-medium leading-6 " 
id="availability-label">
-              <i18n.Translate>Do not show this again</i18n.Translate>
-            </span>
-          </span>
-          <button type="button" data-enabled={!settings.showWithdrawalSuccess} 
class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 
w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent 
transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 
focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" 
aria-labelledby="availability-label" aria-describedby="availability-description"
-
-            onClick={() => {
-              updateSettings("showWithdrawalSuccess", 
!settings.showWithdrawalSuccess);
-            }}>
-            <span aria-hidden="true" 
data-enabled={!settings.showWithdrawalSuccess} class="translate-x-5 
data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 
transform rounded-full bg-white shadow ring-0 transition duration-200 
ease-in-out"></span>
-          </button>
-        </div>
-      </div>
       <div class="mt-5 sm:mt-6">
         <button type="button"
           class="inline-flex w-full justify-center 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"
@@ -182,7 +153,6 @@ export function WithdrawalQRCode({
       the exchange is selcted but no account
     </div>
   }
-
   return (
     <WithdrawalConfirmationQuestion
       withdrawUri={withdrawUri}
@@ -198,126 +168,3 @@ export function WithdrawalQRCode({
     />
   );
 }
-
-
-export function WithdrawalOperationState({
-  currency,
-  currentOperation,
-  onClose,
-}: {currency:string, currentOperation: string, onClose: () => void}): VNode {
-  const { i18n } = useTranslationContext();
-  const [settings, updateSettings] = useSettings()
-  const { createWithdrawal } = useAccessAPI();
-
-  const amount = settings.maxWithdrawalAmount
-  async function doSilentStart() {
-    //FIXME: if amount is not enough use balance
-    const parsedAmount = Amounts.parseOrThrow(`${currency}:${amount}`)
-
-    try {
-      const result = await createWithdrawal({
-        amount: Amounts.stringify(parsedAmount),
-      });
-      const uri = parseWithdrawUri(result.data.taler_withdraw_uri);
-      if (!uri) {
-        return notifyError(
-          i18n.str`Server responded with an invalid withdraw URI`,
-          i18n.str`Withdraw URI: ${result.data.taler_withdraw_uri}`);
-      } else {
-        updateSettings("currentWithdrawalOperationId", 
uri.withdrawalOperationId)
-      }
-    } catch (error) {
-      if (error instanceof RequestError) {
-        notify(
-          buildRequestErrorMessage(i18n, error.cause, {
-            onClientError: (status) =>
-              status === HttpStatusCode.Forbidden
-                ? i18n.str`The operation was rejected due to insufficient 
funds`
-                : undefined,
-          }),
-        );
-      } else {
-        notifyError(
-          i18n.str`Operation failed, please report`,
-          (error instanceof Error
-            ? error.message
-            : JSON.stringify(error)) as TranslatedString
-        )
-      }
-    }
-  }
-
-  useEffect(() => {
-    doSilentStart()
-  }, [settings.fastWithdrawal, amount])
-
-  const result = useWithdrawalDetails(currentOperation);
-  if (!result.ok) {
-    if (result.loading) {
-      return <Loading />;
-    }
-    if (
-      result.type === ErrorType.CLIENT &&
-      result.status === HttpStatusCode.NotFound
-    ) {
-      onClose()
-      return <div>operation not found</div>;
-    }
-    // onLoadNotOk();
-    return handleNotOkResult(i18n)(result);
-  }
-  const { data } = result;
-
-  const baseUrl = getInitialBackendBaseURL()
-  const uri = stringifyWithdrawUri({
-    bankIntegrationApiBaseUrl: `${baseUrl}/integration-api`,
-    withdrawalOperationId: currentOperation,
-  });
-  const parsedUri = parseWithdrawUri(uri);
-
-  if (data.aborted) {
-    return <div>
-      the operation was aborted, you can create another one
-    </div>
-  }
-
-  if (data.confirmation_done) {
-    return <div>
-      the wire transfer is made, you coin should arrive shortly
-    </div>
-  }
-  if (!parsedUri) {
-    return <div>
-      the operation is not valid, create another one
-    </div>
-  }
-  if (!data.selection_done) {
-    return (
-      <QrCodeSectionSimpler
-        withdrawUri={parsedUri}
-        onAborted={() => {
-          notifyInfo(i18n.str`Operation canceled`);
-          onClose()
-        }}
-      />
-    );
-  }
-
-  if (!data.selected_reserve_pub) {
-    return <div>
-      the exchange is selcted but no reserve pub
-    </div>
-  }
-
-  const account = !data.selected_exchange_account ? undefined : 
parsePaytoUri(data.selected_exchange_account)
-
-  if (!account) {
-    return <div>
-      the exchange is selected but no account
-    </div>
-  }
-
-  return <div>
-    the operation is wating for the question to be answered
-  </div>;
-}
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/business/Home.tsx 
b/packages/demobank-ui/src/pages/business/Home.tsx
index 318a4cfda..f5f77a3ea 100644
--- a/packages/demobank-ui/src/pages/business/Home.tsx
+++ b/packages/demobank-ui/src/pages/business/Home.tsx
@@ -360,7 +360,6 @@ function CreateCashout({
                 type="checkbox"
                 name="asd"
                 onChange={(e): void => {
-                  console.log("asdasd", form.isDebit);
                   form.isDebit = !form.isDebit;
                   updateForm(structuredClone(form));
                 }}

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