gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] 04/05: account form


From: gnunet
Subject: [taler-wallet-core] 04/05: account form
Date: Wed, 06 Dec 2023 18:10:54 +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 35ee1ddd797be94557ab23f500c69fabee468387
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Wed Dec 6 14:05:37 2023 -0300

    account form
---
 packages/demobank-ui/src/components/app.tsx        |   2 +-
 packages/demobank-ui/src/pages/BankFrame.tsx       |   5 +-
 .../demobank-ui/src/pages/ProfileNavigation.tsx    |   7 +-
 .../demobank-ui/src/pages/PublicHistoriesPage.tsx  |  12 +-
 .../src/pages/account/ShowAccountDetails.tsx       |   4 +-
 .../src/pages/account/UpdateAccountPassword.tsx    |   2 +-
 .../demobank-ui/src/pages/admin/AccountForm.tsx    | 654 +++++++++++----------
 .../src/pages/admin/CreateNewAccount.tsx           |   1 -
 .../src/pages/business/CreateCashout.tsx           | 105 ++--
 packages/demobank-ui/src/stories.test.ts           |   3 +
 10 files changed, 402 insertions(+), 393 deletions(-)

diff --git a/packages/demobank-ui/src/components/app.tsx 
b/packages/demobank-ui/src/components/app.tsx
index 27898caeb..4921b6bff 100644
--- a/packages/demobank-ui/src/components/app.tsx
+++ b/packages/demobank-ui/src/components/app.tsx
@@ -79,7 +79,7 @@ export default App;
 function getInitialBackendBaseURL(backendFromSettings: string | undefined): 
string {
   const overrideUrl =
     typeof localStorage !== "undefined"
-      ? localStorage.getItem("bank-base-url")
+      ? localStorage.getItem("corebank-api-base-url")
       : undefined;
   let result: string;
 
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx 
b/packages/demobank-ui/src/pages/BankFrame.tsx
index 0ac9ed8f1..737a00b57 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -119,10 +119,7 @@ export function BankFrame({
     </main>
 
     <Footer
-      testingUrl={
-        (typeof localStorage !== "undefined") && 
localStorage.getItem("bank-base-url") ?
-          localStorage.getItem("bank-base-url") ?? undefined :
-          undefined}
+      testingUrlKey="corebank-api-base-url"
       GIT_HASH={GIT_HASH}
       VERSION={VERSION}
     />
diff --git a/packages/demobank-ui/src/pages/ProfileNavigation.tsx 
b/packages/demobank-ui/src/pages/ProfileNavigation.tsx
index 61a55fe16..5b0f09360 100644
--- a/packages/demobank-ui/src/pages/ProfileNavigation.tsx
+++ b/packages/demobank-ui/src/pages/ProfileNavigation.tsx
@@ -2,10 +2,13 @@ import { useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useBankCoreApiContext } from "../context/config.js";
 import { assertUnreachable } from "./WithdrawalOperationPage.js";
+import { useBackendState } from "../hooks/backend.js";
 
-export function ProfileNavigation({ current, noCashout }: { noCashout?: 
boolean, current: "details" | "credentials" | "cashouts" }): VNode {
+export function ProfileNavigation({ current }: { current: "details" | 
"credentials" | "cashouts" }): VNode {
   const { i18n } = useTranslationContext()
   const { config } = useBankCoreApiContext()
+  const { state: credentials } = useBackendState();
+  const nonAdminUser = credentials.status !== "loggedIn" ? false : 
!credentials.isUserAdministrator
   return <div>
     <div class="sm:hidden">
       <label for="tabs" class="sr-only"><i18n.Translate>Select a 
section</i18n.Translate></label>
@@ -44,7 +47,7 @@ export function ProfileNavigation({ current, noCashout }: { 
noCashout?: boolean,
           <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 && !noCashout ?
+        {config.allow_conversion && nonAdminUser ?
           <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/PublicHistoriesPage.tsx 
b/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx
index eb6f6fd27..08503fb9b 100644
--- a/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx
+++ b/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx
@@ -35,7 +35,7 @@ export function PublicHistoriesPage({ }: Props): VNode {
   //TODO: implemented filter by account name
   const result = usePublicAccounts(undefined);
   const firstAccount = result && !(result instanceof TalerError) && 
result.data.public_accounts.length > 0
-    ? result.data.public_accounts[0].account_name
+    ? result.data.public_accounts[0].username
     : undefined;
 
   const [showAccount, setShowAccount] = useState(firstAccount);
@@ -54,8 +54,8 @@ export function PublicHistoriesPage({ }: Props): VNode {
 
   // Ask story of all the public accounts.
   for (const account of data.public_accounts) {
-    logger.trace("Asking transactions for", account.account_name);
-    const isSelected = account.account_name == showAccount;
+    logger.trace("Asking transactions for", account.username);
+    const isSelected = account.username == showAccount;
     accountsBar.push(
       <li
         class={
@@ -67,13 +67,13 @@ export function PublicHistoriesPage({ }: Props): VNode {
         <a
           href="#"
           class="pure-menu-link"
-          onClick={() => setShowAccount(account.account_name)}
+          onClick={() => setShowAccount(account.username)}
         >
-          {account.account_name}
+          {account.username}
         </a>
       </li>,
     );
-    txs[account.account_name] = <Transactions account={account.account_name} 
/>;
+    txs[account.username] = <Transactions account={account.username} />;
   }
 
   return (
diff --git a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx 
b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
index 92419b7ed..994a8286e 100644
--- a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
+++ b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
@@ -104,7 +104,7 @@ export function ShowAccountDetails({
     <Fragment>
       <LocalNotificationBanner notification={notification} />
       {accountIsTheCurrentUser ?
-        <ProfileNavigation current="details" noCashout={credentials.status === 
"loggedIn" ? credentials.isUserAdministrator : undefined} />
+        <ProfileNavigation current="details" />
         :
         <h1 class="text-base font-semibold leading-6 text-gray-900">
           <i18n.Translate>Account "{account}"</i18n.Translate>
@@ -133,9 +133,7 @@ export function ShowAccountDetails({
 
         <AccountForm
           focus={update}
-          noCashout={credentials.status === "loggedIn" ? 
credentials.isUserAdministrator : undefined}
           username={account}
-          admin={credentials.status === "loggedIn" ? 
credentials.isUserAdministrator : undefined}
           template={result.body}
           purpose={update ? "update" : "show"}
           onChange={(a) => setSubmitAccount(a)}
diff --git a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx 
b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
index eef2a0692..ece1f63e7 100644
--- a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
+++ b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
@@ -83,7 +83,7 @@ export function UpdateAccountPassword({
     <Fragment>
       <LocalNotificationBanner notification={notification} />
       {accountIsTheCurrentUser ?
-        <ProfileNavigation current="credentials" noCashout={credentials.status 
=== "loggedIn" ? credentials.isUserAdministrator : undefined} /> :
+        <ProfileNavigation current="credentials" /> :
         <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 61702f7d4..c64431918 100644
--- a/packages/demobank-ui/src/pages/admin/AccountForm.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
@@ -1,4 +1,4 @@
-import { AmountString, Amounts, PaytoString, TalerCorebankApi, buildPayto, 
parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
+import { AmountString, Amounts, PaytoString, TalerCorebankApi, 
TranslatedString, buildPayto, parsePaytoUri, stringifyPaytoUri } from 
"@gnu-taler/taler-util";
 import { CopyButton, ShowInputErrorLabel, useTranslationContext } from 
"@gnu-taler/web-util/browser";
 import { ComponentChildren, Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
@@ -7,16 +7,23 @@ import { ErrorMessageMappingFor, PartialButDefined, 
WithIntermediate, undefinedI
 import { InputAmount, doAutoFocus } from "../PaytoWireTransferForm.js";
 import { assertUnreachable } from "../WithdrawalOperationPage.js";
 import { getRandomPassword } from "../rnd.js";
+import { useBackendState } from "../../hooks/backend.js";
 
 const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/;
 const EMAIL_REGEX =
   
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
 const REGEX_JUST_NUMBERS_REGEX = /^\+[0-9 ]*$/;
 
-export type AccountFormData = TalerCorebankApi.AccountData & { 
-  username: string,
-  isExchange: boolean,
-  isPublic: boolean,
+export type AccountFormData = {
+  debit_threshold?: string,
+  isExchange?: boolean,
+  isPublic?: boolean,
+  name?: string,
+  username?: string,
+  payto_uri?: string,
+  cashout_payto_uri?: string,
+  email?: string,
+  phone?: string,
 }
 
 type ChangeByPurposeType = {
@@ -39,69 +46,102 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
   purpose,
   onChange,
   focus,
-  admin,
-  noCashout,
   children,
 }: {
   focus?: boolean,
   children: ComponentChildren,
   username?: string,
-  noCashout?: boolean,
-  admin?: boolean,
   template: TalerCorebankApi.AccountData | undefined;
   onChange: ChangeByPurposeType[PurposeType];
   purpose: PurposeType;
 }): VNode {
   const { config } = useBankCoreApiContext()
+  const { i18n } = useTranslationContext();
+  const { state: credentials } = useBackendState();
+  const [form, setForm] = useState<AccountFormData>({});
 
-  const initial = initializeFromTemplate(username, template, 
config.default_debit_threshold);
-  const [form, setForm] = useState(initial);
   const [errors, setErrors] = useState<
-    ErrorMessageMappingFor<typeof initial> | undefined
+    ErrorMessageMappingFor<typeof defaultValue> | undefined
   >(undefined);
-  const { i18n } = useTranslationContext();
 
-  function updateForm(newForm: typeof initial): void {
-    const parsed = !newForm.cashout_payto_uri
+
+  const defaultValue: AccountFormData = {
+    debit_threshold: Amounts.stringifyValue(template?.debit_threshold 
??config.default_debit_threshold),
+    isExchange: template?.is_taler_exchange,
+    isPublic: template?.is_public,
+    name: template?.name ?? "",
+    cashout_payto_uri: stringifyIbanPayto(template?.cashout_payto_uri) ?? "" 
as PaytoString,
+    payto_uri: stringifyIbanPayto(template?.payto_uri) ?? "" as PaytoString,
+    email: template?.contact_data?.email ?? "",
+    phone: template?.contact_data?.phone ?? "",
+    username: username ?? "",
+  }
+
+  const showingCurrentUserInfo = credentials.status !== "loggedIn" ? false : 
username === credentials.username
+  const userIsAdmin = credentials.status !== "loggedIn" ? false : 
credentials.isUserAdministrator
+
+  const editableUsername = (purpose === "create")
+  const editableName = (purpose === "create" || purpose === "update" && 
(config.allow_edit_name || userIsAdmin))
+  const editableCashout = showingCurrentUserInfo && (purpose === "create" || 
purpose === "update" && (config.allow_edit_cashout_payto_uri || userIsAdmin))
+  const editableThreshold = userIsAdmin && (purpose === "create" || purpose 
=== "update")
+  const editableAccount = purpose === "create" && userIsAdmin
+
+  function updateForm(newForm: typeof defaultValue): void {
+    const cashoutParsed = !newForm.cashout_payto_uri
       ? undefined
       : buildPayto("iban", newForm.cashout_payto_uri, undefined);;
 
+    const internalParsed = !newForm.payto_uri
+      ? undefined
+      : buildPayto("iban", newForm.payto_uri, undefined);;
+
     const trimmedAmountStr = newForm.debit_threshold?.trim();
     const parsedAmount = 
Amounts.parse(`${config.currency}:${trimmedAmountStr}`);
 
-    const errors = undefinedIfEmpty<ErrorMessageMappingFor<typeof initial>>({
+    const errors = undefinedIfEmpty<ErrorMessageMappingFor<typeof 
defaultValue>>({
       cashout_payto_uri: (!newForm.cashout_payto_uri
-        ? undefined
-        : !parsed
-          ? i18n.str`does not follow the pattern`
-          : !parsed.isKnown || parsed.targetType !== "iban"
-            ? i18n.str`only "IBAN" target are supported`
-            : !IBAN_REGEX.test(parsed.iban)
-              ? i18n.str`IBAN should have just uppercased letters and numbers`
-              : validateIBAN(parsed.iban, i18n)),
-      contact_data: undefinedIfEmpty({
-        email: !newForm.contact_data?.email
-          ? undefined
-          : !EMAIL_REGEX.test(newForm.contact_data.email)
-            ? i18n.str`it should be an email`
-            : undefined,
-        phone: !newForm.contact_data?.phone
-          ? undefined
-          : !newForm.contact_data.phone.startsWith("+")
-            ? i18n.str`should start with +`
-            : !REGEX_JUST_NUMBERS_REGEX.test(newForm.contact_data.phone)
-              ? i18n.str`phone number can't have other than numbers`
-              : undefined,
-      }),
-      debit_threshold: !trimmedAmountStr
-        ? (purpose === "create" ? i18n.str`required` : undefined)
-        : !parsedAmount
-          ? i18n.str`not valid`
-            : undefined,
-      name: !newForm.name ? i18n.str`required` : undefined,
-      username: !newForm.username ? i18n.str`required` : undefined,
+        ? undefined :
+        !editableCashout ? undefined :
+          !cashoutParsed
+            ? i18n.str`does not follow the pattern` :
+            !cashoutParsed.isKnown || cashoutParsed.targetType !== "iban"
+              ? i18n.str`only "IBAN" target are supported` :
+              !IBAN_REGEX.test(cashoutParsed.iban)
+                ? i18n.str`IBAN should have just uppercased letters and 
numbers` :
+                validateIBAN(cashoutParsed.iban, i18n)),
+      payto_uri: (!newForm.payto_uri
+        ? undefined :
+        !editableAccount ? undefined :
+          !internalParsed
+            ? i18n.str`does not follow the pattern` :
+            !internalParsed.isKnown || internalParsed.targetType !== "iban"
+              ? i18n.str`only "IBAN" target are supported` :
+              !IBAN_REGEX.test(internalParsed.iban)
+                ? i18n.str`IBAN should have just uppercased letters and 
numbers` :
+                validateIBAN(internalParsed.iban, i18n)),
+      email: !newForm.email
+        ? undefined :
+        !EMAIL_REGEX.test(newForm.email)
+          ? i18n.str`it should be an email` :
+          undefined,
+      phone: !newForm.phone
+        ? undefined :
+        !newForm.phone.startsWith("+") // FIXME: better phone number check
+          ? i18n.str`should start with +` :
+          !REGEX_JUST_NUMBERS_REGEX.test(newForm.phone)
+            ? i18n.str`phone number can't have other than numbers`
+            :
+            undefined,
+      debit_threshold: !editableThreshold ? undefined :
+        !trimmedAmountStr ? undefined :
+          !parsedAmount ? i18n.str`not valid` :
+            undefined,
+      name: !editableName ? undefined : //disabled
+        !newForm.name ? i18n.str`required` : undefined,
+      username: !editableUsername ? undefined : !newForm.username ? 
i18n.str`required` : undefined,
     });
     setErrors(errors);
+
     setForm(newForm);
     if (!onChange) return;
 
@@ -114,23 +154,25 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
       const internal = !newForm.payto_uri ? undefined : buildPayto("iban", 
newForm.payto_uri, undefined);
       const internalURI = !internal ? undefined : stringifyPaytoUri(internal)
 
+      const threshold = !parsedAmount ? undefined : 
Amounts.stringify(parsedAmount)
+
       switch (purpose) {
         case "create": {
           //typescript doesn't correctly narrow a generic type
           const callback = onChange as ChangeByPurposeType["create"]
           const result: TalerCorebankApi.RegisterAccountRequest = {
-            cashout_payto_uri: cashoutURI,
             name: newForm.name!,
             password: getRandomPassword(),
             username: newForm.username!,
-            challenge_contact_data: undefinedIfEmpty({
-              email: newForm.contact_data?.email,
-              phone: newForm.contact_data?.phone,
+            contact_data: undefinedIfEmpty({
+              email: newForm.email,
+              phone: newForm.phone,
             }),
-            debit_threshold: `${config.currency}:${newForm.debit_threshold}` 
as AmountString,
-            internal_payto_uri: internalURI,
-            is_public: newForm.isPublic,
-            is_taler_exchange: newForm.isExchange,
+            debit_threshold: threshold ?? config.default_debit_threshold,
+            cashout_payto_uri: cashoutURI,
+            payto_uri: internalURI,
+            is_public: !!newForm.isPublic,
+            is_taler_exchange: !!newForm.isExchange,
           }
           callback(result)
           return;
@@ -138,17 +180,16 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
         case "update": {
           //typescript doesn't correctly narrow a generic type
           const callback = onChange as ChangeByPurposeType["update"]
-          
+
           const result: TalerCorebankApi.AccountReconfiguration = {
             cashout_payto_uri: cashoutURI,
-            challenge_contact_data: undefinedIfEmpty({
-              email: newForm.contact_data?.email,
-              phone: newForm.contact_data?.phone,
+            contact_data: undefinedIfEmpty({
+              email: newForm.email ?? template?.contact_data?.email,
+              phone: newForm.phone ?? template?.contact_data?.phone,
             }),
-            debit_threshold: newForm.debit_threshold as AmountString,
-            is_taler_exchange: newForm.isExchange,
-            name: newForm.name
-            // is_public: newForm.isPublic
+            debit_threshold: threshold,
+            is_public: !!newForm.isPublic,
+            name: newForm.name,
           }
           callback(result)
           return;
@@ -162,7 +203,6 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
       }
     }
   }
-
   return (
     <form
       class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl 
md:col-span-2"
@@ -181,7 +221,7 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
               for="username"
             >
               {i18n.str`Username`}
-              {purpose === "create" && <b style={{ color: "red" }}> *</b>}
+              {editableUsername && <b style={{ color: "red" }}> *</b>}
             </label>
             <div class="mt-2">
               <input
@@ -191,8 +231,8 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
                 name="username"
                 id="username"
                 data-error={!!errors?.username && form.username !== undefined}
-                disabled={purpose !== "create"}
-                value={form.username ?? ""}
+                disabled={!editableUsername}
+                value={form.username ?? defaultValue.username}
                 onChange={(e) => {
                   form.username = e.currentTarget.value;
                   updateForm(structuredClone(form));
@@ -216,7 +256,7 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
               for="name"
             >
               {i18n.str`Name`}
-              {purpose === "create" && <b style={{ color: "red" }}> *</b>}
+              {editableName && <b style={{ color: "red" }}> *</b>}
             </label>
             <div class="mt-2">
               <input
@@ -225,8 +265,8 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
                 name="name"
                 data-error={!!errors?.name && form.name !== undefined}
                 id="name"
-                disabled={purpose !== "create"}
-                value={form.name ?? ""}
+                disabled={!editableName}
+                value={form.name ?? defaultValue.name}
                 onChange={(e) => {
                   form.name = e.currentTarget.value;
                   updateForm(structuredClone(form));
@@ -245,7 +285,20 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
           </div>
 
 
-          {purpose !== "create" && (<RenderPaytoDisabledField 
paytoURI={form.payto_uri as PaytoString} />)}
+          <PaytoField
+            type="iban"
+            name="internal-account"
+            label={i18n.str`Internal IBAN`}
+            help={purpose === "create" ?
+              i18n.str`if empty a random account number will be assigned` :
+              i18n.str`account identification for bank transfer`}
+            value={(form.payto_uri ?? defaultValue.payto_uri) as PaytoString}
+            disabled={!editableAccount}
+            error={errors?.payto_uri}
+            onChange={(e) => {
+              form.payto_uri = e as PaytoString
+              updateForm(structuredClone(form))
+            }} />
 
           <div class="sm:col-span-5">
             <label
@@ -260,23 +313,18 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
                 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="email"
                 id="email"
-                data-error={!!errors?.contact_data?.email && 
form.contact_data?.email !== undefined}
+                data-error={!!errors?.email && form.email !== undefined}
                 disabled={purpose === "show"}
-                value={form.contact_data?.email ?? ""}
+                value={form.email ?? defaultValue.email}
                 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));
-                  }
+                  form.email = e.currentTarget.value;
+                  updateForm(structuredClone(form));
                 }}
                 autocomplete="off"
               />
               <ShowInputErrorLabel
-                message={errors?.contact_data?.email}
-                isDirty={form.contact_data?.email !== undefined}
+                message={errors?.email}
+                isDirty={form.email !== undefined}
               />
             </div>
           </div>
@@ -295,85 +343,56 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
                 name="phone"
                 id="phone"
                 disabled={purpose === "show"}
-                value={form.contact_data?.phone ?? ""}
-                data-error={!!errors?.contact_data?.phone && 
form.contact_data?.phone !== undefined}
+                value={form.phone ?? defaultValue.phone}
+                data-error={!!errors?.phone && form.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));
-                  }
+                  form.phone = e.currentTarget.value;
+                  updateForm(structuredClone(form));
                 }}
-                // placeholder=""
                 autocomplete="off"
               />
               <ShowInputErrorLabel
-                message={errors?.contact_data?.phone}
-                isDirty={form.contact_data?.phone !== undefined}
+                message={errors?.phone}
+                isDirty={form.phone !== undefined}
               />
             </div>
           </div>
 
-          {!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`}
-              </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>
+          {showingCurrentUserInfo &&
+            <PaytoField
+              type="iban"
+              name="cashout-account"
+              label={i18n.str`Cashout IBAN`}
+              help={i18n.str`account number where the money is going to be 
sent when doing cashouts`}
+              value={(form.cashout_payto_uri ?? 
defaultValue.cashout_payto_uri) as PaytoString}
+              disabled={!editableCashout}
+              error={errors?.cashout_payto_uri}
+              onChange={(e) => {
+                form.cashout_payto_uri = e as PaytoString
+                updateForm(structuredClone(form))
+              }} />
           }
 
-          {admin ? <Fragment>
-            <div class="sm:col-span-5">
-              <label for="debit" class="block text-sm font-medium leading-6 
text-gray-900">{i18n.str`Max debt`}</label>
-              <InputAmount
-                name="debit"
-                left
-                currency={config.currency}
-                value={form.debit_threshold ?? ""}
-                onChange={(e) => {
-                  form.debit_threshold = e as AmountString
-                  updateForm(structuredClone(form))
-                }}
-              />
-              <ShowInputErrorLabel
-                message={errors?.debit_threshold ? 
String(errors?.debit_threshold) : undefined}
-                isDirty={form.debit_threshold !== undefined}
-              />
-              <p class="mt-2 text-sm text-gray-500" >how much is user able to 
transfer </p>
-            </div>
+          <div class="sm:col-span-5">
+            <label for="debit" class="block text-sm font-medium leading-6 
text-gray-900">{i18n.str`Max debt`}</label>
+            <InputAmount
+              name="debit"
+              left
+              currency={config.currency}
+              value={form.debit_threshold ?? defaultValue.debit_threshold}
+              onChange={!editableThreshold ? undefined : (e) => {
+                form.debit_threshold = e as AmountString
+                updateForm(structuredClone(form))
+              }}
+            />
+            <ShowInputErrorLabel
+              message={errors?.debit_threshold ? 
String(errors?.debit_threshold) : undefined}
+              isDirty={form.debit_threshold !== undefined}
+            />
+            <p class="mt-2 text-sm text-gray-500" >how much is user able to 
transfer </p>
+          </div>
 
+          {purpose !== "create" || !userIsAdmin ? undefined :
             <div class="sm:col-span-5">
               <div class="flex items-center justify-between">
                 <span class="flex flex-grow flex-col">
@@ -381,210 +400,197 @@ export function AccountForm<PurposeType extends keyof 
ChangeByPurposeType>({
                     <i18n.Translate>Is an exchange</i18n.Translate>
                   </span>
                 </span>
-                <button type="button" data-enabled={!!form.isExchange} 
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"
+                <button type="button" data-enabled={form.isExchange ?? 
defaultValue.isExchange ? "true" : "false"} 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-de [...]
 
                   onClick={() => {
                     form.isExchange = !form.isExchange
                     updateForm(structuredClone(form))
                   }}>
-                  <span aria-hidden="true" data-enabled={!!form.isExchange} 
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>
+                  <span aria-hidden="true" data-enabled={form.isExchange ?? 
defaultValue.isExchange ? "true" : "false"} 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>
-          </Fragment> :
-            undefined
-          }
+            </div>}
 
-          {purpose === "create" ?
-            <div class="sm:col-span-5">
-              <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>Is public</i18n.Translate>
-                  </span>
+          <div class="sm:col-span-5">
+            <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>Is public</i18n.Translate>
                 </span>
-                <button type="button" data-enabled={!!form.isPublic} 
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"
+              </span>
+              <button type="button" data-enabled={form.isPublic ?? 
defaultValue.isPublic  ? "true" : "false"} 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={() => {
-                    form.isPublic = !form.isPublic
-                    updateForm(structuredClone(form))
-                  }}>
-                  <span aria-hidden="true" data-enabled={!!form.isPublic} 
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>
-              <p class="mt-2 text-sm text-gray-500" >
-                <i18n.Translate>public accounts have their balance publicly 
accesible</i18n.Translate>
-              </p>
+                onClick={() => {
+                  form.isPublic = !form.isPublic
+                  updateForm(structuredClone(form))
+                }}>
+                <span aria-hidden="true" data-enabled={form.isPublic ?? 
defaultValue.isPublic  ? "true" : "false"} 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>
-            : undefined
-          }
+            <p class="mt-2 text-sm text-gray-500" >
+              <i18n.Translate>public accounts have their balance publicly 
accesible</i18n.Translate>
+            </p>
+          </div>
 
         </div>
       </div>
+      <pre>
+        {JSON.stringify(errors, undefined, 2)}
+      </pre>
       {children}
     </form>
   );
 }
-function initializeFromTemplate(
-  username: string | undefined,
-  account: TalerCorebankApi.AccountData | undefined,
-  default_debit_threshold: AmountString,
-): WithIntermediate<AccountFormData> {
-  const emptyAccount = {
-    cashout_payto_uri: undefined,
-    contact_data: undefined,
-    payto_uri: undefined,
-    balance: undefined,
-    debit_threshold: Amounts.stringifyValue(default_debit_threshold) as 
AmountString,
-    name: undefined,
-  };
-  const emptyContact = {
-    email: undefined,
-    phone: undefined,
-  };
-
-  const initial: PartialButDefined<TalerCorebankApi.AccountData> =
-    structuredClone(account) ?? emptyAccount;
-  if (typeof initial.contact_data === "undefined") {
-    initial.contact_data = emptyContact;
-  }
-  if (initial.cashout_payto_uri) {
-    const ac = parsePaytoUri(initial.cashout_payto_uri)
-    if (ac?.isKnown && ac.targetType === "iban") {
-      // we are using the cashout field for the iban number
-      initial.cashout_payto_uri = ac.targetPath as any
-    }
-  }
-  const result: WithIntermediate<AccountFormData> = initial as any // FIXME: 
check types
-  result.username = username
 
-  return initial as any;
+function stringifyIbanPayto(s: PaytoString | undefined): string | undefined {
+  if (s === undefined) return undefined
+  const p = parsePaytoUri(s)
+  if (p === undefined) return undefined
+  if (!p.isKnown) return undefined
+  if (p.targetType !== "iban") return undefined
+  return p.iban
 }
 
+{/* <div class="sm:col-span-5">
+            <label
+              class="block text-sm font-medium leading-6 text-gray-900"
+              for="cashout"
+            >
+              {}
+            </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 ?? 
defaultValue.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></i18n.Translate>
+            </p>
+          </div> */}
 
-function RenderPaytoDisabledField({ paytoURI }: { paytoURI: PaytoString | 
undefined }): VNode {
-  const { i18n } = useTranslationContext()
-  const payto = parsePaytoUri(paytoURI ?? "");
-  if (payto?.isKnown) {
-    if (payto.targetType === "iban") {
-      const value = payto.iban;
-      return <div class="sm:col-span-5">
-        <label
-          class="block text-sm font-medium leading-6 text-gray-900"
-          for="internal-iban"
-        >
-          {i18n.str`Internal IBAN`}
-        </label>
-        <div class="mt-2">
-          <div class="flex justify-between">
-            <input
-              type="text"
-              class="mr-4 w-full block-inline  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="internal-iban"
-              id="internal-iban"
-              disabled={true}
-              value={value ?? ""}
-            />
-            <CopyButton
-              class="p-2 rounded-full  text-black shadow-sm  
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
-              getContent={() => value ?? ""}
-            />
-          </div>
+function PaytoField({ name, label, help, type, value, disabled, onChange, 
error }: { error: TranslatedString | undefined, name: string, label: 
TranslatedString, help: TranslatedString, onChange: (s: string) => void, type: 
"iban" | "x-taler-bank" | "bitcoin", disabled?: boolean, value: string | 
undefined }): VNode {
+  if (type === "iban") {
+    return <div class="sm:col-span-5">
+      <label
+        class="block text-sm font-medium leading-6 text-gray-900"
+        for={name}
+      >
+        {label}
+      </label>
+      <div class="mt-2">
+        <div class="flex justify-between">
+          <input
+            type="text"
+            class="mr-4 w-full block-inline  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={name}
+            id={name}
+            disabled={disabled}
+            value={value ?? ""}
+            onChange={(e) => {
+              onChange(e.currentTarget.value)
+            }}
+          />
+          <CopyButton
+            class="p-2 rounded-full  text-black shadow-sm  
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
+            getContent={() => value ?? ""}
+          />
         </div>
-        <p class="mt-2 text-sm text-gray-500" >
-          <i18n.Translate>international bank account number</i18n.Translate>
-        </p>
-      </div>
-    }
-    if (payto.targetType === "x-taler-bank") {
-      const value = payto.account;
-      return <div class="sm:col-span-5">
-        <label
-          class="block text-sm font-medium leading-6 text-gray-900"
-          for="account-id"
-        >
-          {i18n.str`Account ID`}
-        </label>
-        <div class="mt-2">
-          <div class="flex justify-between">
-            <input
-              type="text"
-              class="mr-4 w-full block-inline  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="account-id"
-              id="account-id"
-              disabled={true}
-              value={value ?? ""}
-            />
-            <CopyButton
-              class="p-2 rounded-full  text-black shadow-sm  
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
-              getContent={() => value ?? ""}
-            />
-          </div>
-        </div>
-        <p class="mt-2 text-sm text-gray-500" >
-          <i18n.Translate>internal account id</i18n.Translate>
-        </p>
+        <ShowInputErrorLabel
+          message={error}
+          isDirty={value !== undefined}
+        />
       </div>
-    }
-    if (payto.targetType === "bitcoin") {
-      const value = payto.targetPath;
-      return <div class="sm:col-span-5">
-        <label
-          class="block text-sm font-medium leading-6 text-gray-900"
-          for="account-id"
-        >
-          {i18n.str`Bitcoin address`}
-        </label>
-        <div class="mt-2">
-          <div class="flex justify-between">
-            <input
-              type="text"
-              class="mr-4 w-full block-inline  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="account-id"
-              id="account-id"
-              disabled={true}
-              value={value ?? ""}
-            />
-            <CopyButton
-              class="p-2 rounded-full  text-black shadow-sm  
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
-              getContent={() => value ?? ""}
-            />
-          </div>
+      <p class="mt-2 text-sm text-gray-500" >
+        {help}
+      </p>
+    </div>
+  }
+  if (type === "x-taler-bank") {
+    return <div class="sm:col-span-5">
+      <label
+        class="block text-sm font-medium leading-6 text-gray-900"
+        for={name}
+      >
+        {label}
+      </label>
+      <div class="mt-2">
+        <div class="flex justify-between">
+          <input
+            type="text"
+            class="mr-4 w-full block-inline  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={name}
+            id={name}
+            disabled={disabled}
+            value={value ?? ""}
+          />
+          <CopyButton
+            class="p-2 rounded-full  text-black shadow-sm  
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
+            getContent={() => value ?? ""}
+          />
         </div>
-        <p class="mt-2 text-sm text-gray-500" >
-          <i18n.Translate>bitcoin address</i18n.Translate>
-        </p>
+        <ShowInputErrorLabel
+          message={error}
+          isDirty={value !== undefined}
+        />
       </div>
-    }
-    assertUnreachable(payto)
+      <p class="mt-2 text-sm text-gray-500" >
+        {/* <i18n.Translate>internal account id</i18n.Translate> */}
+        {help}
+      </p>
+    </div>
   }
-
-  const value = paytoURI ?? ""
-  return <div class="sm:col-span-5">
-    <label
-      class="block text-sm font-medium leading-6 text-gray-900"
-      for="internal-payto"
-    >
-      {i18n.str`Internal account`}
-    </label>
-    <div class="mt-2">
-      <div class="flex justify-between">
-        <input
-          type="text"
-          class="mr-4 w-full block-inline  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="internal-payto"
-          id="internal-payto"
-          disabled={true}
-          value={value ?? ""}
-        />
-        <CopyButton
-          class="p-2 rounded-full  text-black shadow-sm  focus-visible:outline 
focus-visible:outline-2 focus-visible:outline-offset-2 "
-          getContent={() => value ?? ""}
-        />
+  if (type === "bitcoin") {
+    return <div class="sm:col-span-5">
+      <label
+        class="block text-sm font-medium leading-6 text-gray-900"
+        for={name}
+      >
+        {label}
+      </label>
+      <div class="mt-2">
+        <div class="flex justify-between">
+          <input
+            type="text"
+            class="mr-4 w-full block-inline  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={name}
+            id={name}
+            disabled={disabled}
+            value={value ?? ""}
+          />
+          <CopyButton
+            class="p-2 rounded-full  text-black shadow-sm  
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
+            getContent={() => value ?? ""}
+          />
+          <ShowInputErrorLabel
+            message={error}
+            isDirty={value !== undefined}
+          />
+        </div>
       </div>
+      <p class="mt-2 text-sm text-gray-500" >
+        {/* <i18n.Translate>bitcoin address</i18n.Translate> */}
+        {help}
+      </p>
     </div>
-    <p class="mt-2 text-sm text-gray-500" >
-      <i18n.Translate>generic payto URI</i18n.Translate>
-    </p>
-  </div>
+  }
+  assertUnreachable(type)
 }
diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx 
b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
index 6ff723a31..3d196973e 100644
--- a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
@@ -113,7 +113,6 @@ export function CreateNewAccount({
       </div>
       <AccountForm
         template={undefined}
-        admin
         purpose="create"
         onChange={(a) => {
           setSubmitAccount(a);
diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx 
b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
index ce1a6cf49..4b3077984 100644
--- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx
+++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
@@ -433,61 +433,64 @@ export function CreateCashout({
               )}
 
               {/* channel */}
-              <div class="sm:col-span-5">
-                <label
-                  class="block text-sm font-medium leading-6 text-gray-900"
-                  for="channel"
-                >
-                  {i18n.str`Confirmation the operation using`}
-                </label>
-
-                <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={() => {
-                      if (!resultAccount.body.contact_data?.email) return;
-                      form.channel = TanChannel.EMAIL
-                      updateForm(structuredClone(form))
-                    }} 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>
+              {config.supported_tan_channels.length === 0 ? undefined :
+                <div class="sm:col-span-5">
+                  <label
+                    class="block text-sm font-medium leading-6 text-gray-900"
+                    for="channel"
+                  >
+                    {i18n.str`Confirmation the operation using`}
+                  </label>
+                  <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">
+                      {config.supported_tan_channels.indexOf(TanChannel.EMAIL) 
=== -1 ? undefined :
+                        <label onClick={() => {
+                          if (!resultAccount.body.contact_data?.email) return;
+                          form.channel = TanChannel.EMAIL
+                          updateForm(structuredClone(form))
+                        }} 
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>
-                          {!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">
-                        <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 onClick={() => {
-                      if (!resultAccount.body.contact_data?.phone) return;
-                      form.channel = TanChannel.SMS
-                      updateForm(structuredClone(form))
-                    }} 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>
+                          <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">
+                            <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>
+                      }
+
+                      {config.supported_tan_channels.indexOf(TanChannel.SMS) 
=== -1 ? undefined :
+                        <label onClick={() => {
+                          if (!resultAccount.body.contact_data?.phone) return;
+                          form.channel = TanChannel.SMS
+                          updateForm(structuredClone(form))
+                        }} 
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>
-                          {!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">
-                        <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>
-
+                          <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">
+                            <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>
 
-              </div>
+                </div>
+              }
             </div>
           </div>
 
diff --git a/packages/demobank-ui/src/stories.test.ts 
b/packages/demobank-ui/src/stories.test.ts
index a060a6b48..ebd9e6d8a 100644
--- a/packages/demobank-ui/src/stories.test.ts
+++ b/packages/demobank-ui/src/stories.test.ts
@@ -64,8 +64,11 @@ function DefaultTestingContext({
   const cfg: TalerCorebankApi.Config = {
     name: "libeufin-bank",
     allow_deletions: true,
+    supported_tan_channels: [],
     allow_registrations: true,
     allow_conversion: true,
+    allow_edit_cashout_payto_uri: false,
+    allow_edit_name: false,
     currency: "ASR",
     currency_specification: {
       name: "ARS",

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