gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated (a7ec09cd8 -> c036dc201)


From: gnunet
Subject: [taler-wallet-core] branch master updated (a7ec09cd8 -> c036dc201)
Date: Wed, 06 Dec 2023 18:10:50 +0100

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

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

    from a7ec09cd8 wallet-core: return exchange base URL in 
PreparePeerPushCreditResponse
     new cb9c115be default threshold
     new f576bf0eb sync api with libeufin
     new 71acfc274 testing key
     new 35ee1ddd7 account form
     new c036dc201 update footer for exchange

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


Summary of changes:
 packages/aml-backoffice-ui/src/Dashboard.tsx       |   2 +-
 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       |   3 +-
 .../src/pages/account/UpdateAccountPassword.tsx    |   2 +-
 .../demobank-ui/src/pages/admin/AccountForm.tsx    | 674 +++++++++++----------
 .../src/pages/admin/CreateNewAccount.tsx           |   1 -
 .../src/pages/business/CreateCashout.tsx           | 105 ++--
 packages/demobank-ui/src/stories.test.ts           |   3 +
 packages/taler-util/src/http-client/types.ts       |  67 +-
 packages/web-util/src/components/Footer.tsx        |  10 +-
 13 files changed, 471 insertions(+), 422 deletions(-)

diff --git a/packages/aml-backoffice-ui/src/Dashboard.tsx 
b/packages/aml-backoffice-ui/src/Dashboard.tsx
index a38391efb..f791906b9 100644
--- a/packages/aml-backoffice-ui/src/Dashboard.tsx
+++ b/packages/aml-backoffice-ui/src/Dashboard.tsx
@@ -165,7 +165,7 @@ export function ExchangeAmlFrame({
     </div>
 
     <Footer
-      testingUrl={localStorage.getItem("exchange-base-url") ?? undefined}
+      testingUrlKey="exchange-base-url"
       GIT_HASH={GIT_HASH}
       VERSION={VERSION}
     />
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 d435673a2..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,7 +133,6 @@ export function ShowAccountDetails({
 
         <AccountForm
           focus={update}
-          noCashout={credentials.status === "loggedIn" ? 
credentials.isUserAdministrator : undefined}
           username={account}
           template={result.body}
           purpose={update ? "update" : "show"}
diff --git a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx 
b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx
index 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 e76204a81..c64431918 100644
--- a/packages/demobank-ui/src/pages/admin/AccountForm.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
@@ -1,24 +1,29 @@
-import { AmountString, Amounts, ChallengeContactData, PaytoString, 
TalerCorebankApi, TranslatedString, 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";
-import { ErrorMessageMappingFor, PartialButDefined, RecursivePartial, 
WithIntermediate, undefinedIfEmpty, validateIBAN } from "../../utils.js";
+import { useBankCoreApiContext } from "../../context/config.js";
+import { ErrorMessageMappingFor, PartialButDefined, WithIntermediate, 
undefinedIfEmpty, validateIBAN } from "../../utils.js";
 import { InputAmount, doAutoFocus } from "../PaytoWireTransferForm.js";
 import { assertUnreachable } from "../WithdrawalOperationPage.js";
-import { useBackendContext } from "../../context/backend.js";
-import { useBankCoreApiContext } from "../../context/config.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,
-  debitAmount: 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 = {
@@ -27,10 +32,11 @@ type ChangeByPurposeType = {
   "show": undefined
 }
 /**
- * Create valid account object to update or create
- * Take template as initial values for the form
- * Purpose indicate if all field al read only (show), part of them (update)
- * or none (create)
+ * FIXME:
+ * is_public is missing on PATCH
+ * account email/password should require 2FA
+ * 
+ * 
  * @param param0
  * @returns
  */
@@ -40,71 +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 initial = initializeFromTemplate(username, template);
-  const [form, setForm] = useState(initial);
+  const { config } = useBankCoreApiContext()
+  const { i18n } = useTranslationContext();
+  const { state: credentials } = useBackendState();
+  const [form, setForm] = useState<AccountFormData>({});
+
   const [errors, setErrors] = useState<
-    ErrorMessageMappingFor<typeof initial> | undefined
+    ErrorMessageMappingFor<typeof defaultValue> | undefined
   >(undefined);
-  const { i18n } = useTranslationContext();
 
-  const { config } = useBankCoreApiContext()
 
-  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 trimmedAmountStr = newForm.debitAmount?.trim();
+    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`
-          : Amounts.isZero(parsedAmount)
-            ? i18n.str`should be greater than 0`
-            : 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;
 
@@ -117,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: 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;
@@ -141,16 +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;
@@ -164,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"
@@ -183,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
@@ -193,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));
@@ -218,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
@@ -227,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));
@@ -247,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
@@ -262,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>
@@ -297,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.debitAmount ?? ""}
-                onChange={(e) => {
-                  form.debitAmount = e
-                  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">
@@ -383,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>
   );
 }
-// JNTMECG7RM3AAQB6SRAZNWDSM8
-function initializeFromTemplate(
-  username: string | undefined,
-  account: TalerCorebankApi.AccountData | undefined,
-): WithIntermediate<AccountFormData> {
-  const emptyAccount = {
-    cashout_payto_uri: undefined,
-    contact_data: undefined,
-    payto_uri: undefined,
-    balance: undefined,
-    debit_threshold: undefined,
-    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",
diff --git a/packages/taler-util/src/http-client/types.ts 
b/packages/taler-util/src/http-client/types.ts
index 436a06874..efff7bb0a 100644
--- a/packages/taler-util/src/http-client/types.ts
+++ b/packages/taler-util/src/http-client/types.ts
@@ -274,9 +274,15 @@ export const codecForCoreBankConfig = (): 
Codec<TalerCorebankApi.Config> =>
     .property("allow_conversion", codecForBoolean())
     .property("allow_deletions", codecForBoolean())
     .property("allow_registrations", codecForBoolean())
+    .property("allow_edit_cashout_payto_uri", codecForBoolean())
+    .property("allow_edit_name", codecForBoolean())
     .property("default_debit_threshold", codecForAmountString())
     .property("currency_specification", codecForCurrencySpecificiation())
     .property("currency", codecForString())
+    .property("supported_tan_channels", codecForList(codecForEither(
+      codecForConstString(TanChannel.SMS),
+      codecForConstString(TanChannel.EMAIL),
+    )))
     .build("TalerCorebankApi.Config");
 
 export const codecForMerchantConfig =
@@ -312,9 +318,10 @@ const codecForBalance = (): 
Codec<TalerCorebankApi.Balance> =>
 
 const codecForPublicAccount = (): Codec<TalerCorebankApi.PublicAccount> =>
   buildCodecForObject<TalerCorebankApi.PublicAccount>()
-    .property("account_name", codecForString())
+    .property("username", codecForString())
     .property("balance", codecForBalance())
     .property("payto_uri", codecForPaytoString())
+    .property("is_taler_exchange", codecForBoolean())
     .build("TalerCorebankApi.PublicAccount");
 
 export const codecForPublicAccountsResponse =
@@ -330,6 +337,8 @@ export const codecForAccountMinimalData =
       .property("debit_threshold", codecForAmountString())
       .property("name", codecForString())
       .property("username", codecForString())
+      .property("is_public", codecForBoolean())
+      .property("is_taler_exchange", codecForBoolean())
       .build("TalerCorebankApi.AccountMinimalData");
 
 export const codecForListBankAccountsResponse =
@@ -346,6 +355,8 @@ export const codecForAccountData = (): 
Codec<TalerCorebankApi.AccountData> =>
     .property("debit_threshold", codecForAmountString())
     .property("contact_data", codecOptional(codecForChallengeContactData()))
     .property("cashout_payto_uri", codecOptional(codecForPaytoString()))
+    .property("is_public", codecForBoolean())
+    .property("is_taler_exchange", codecForBoolean())
     .build("TalerCorebankApi.AccountData");
 
 export const codecForChallengeContactData =
@@ -1308,18 +1319,29 @@ export namespace TalerCorebankApi {
     // If 'false' only the admin can
     allow_registrations: boolean;
 
-    // Default debt limit for newly created accounts
-    default_debit_threshold: AmountString;
-
     // If 'true' account can delete themselves
     // If 'false' only the admin can delete accounts
     allow_deletions: boolean;
 
+    // If 'true' anyone can edit their name
+    // If 'false' only admin can
+    allow_edit_name: boolean;
+
+    // If 'true' anyone can edit their cashout account
+    // If 'false' only the admin
+    allow_edit_cashout_payto_uri: boolean;
+
+    // Default debt limit for newly created accounts
+    default_debit_threshold: AmountString;
+
     // Currency used by this bank.
     currency: string;
 
     // How the bank SPA should render this currency.
     currency_specification: CurrencySpecification;
+
+    // TAN channels supported by the server
+    supported_tan_channels: TanChannel[];
   }
 
   export interface BankAccountCreateWithdrawalRequest {
@@ -1424,7 +1446,7 @@ export namespace TalerCorebankApi {
     // If missing, cashouts will fail.
     // In the future, might be used for other transactions
     // as well.
-    challenge_contact_data?: ChallengeContactData;
+    contact_data?: ChallengeContactData;
 
     // 'payto' address of a fiat bank account.
     // Payments will be sent to this bank account
@@ -1434,7 +1456,7 @@ export namespace TalerCorebankApi {
 
     // Internal payto URI of this bank account.
     // Used mostly for testing.
-    internal_payto_uri?: PaytoString;
+    payto_uri?: PaytoString;
 
     // If present, set the max debit allowed for this user
     // Only admin can change this property.
@@ -1455,7 +1477,7 @@ export namespace TalerCorebankApi {
     // If missing, cashouts will fail.
     // In the future, might be used for other transactions
     // as well.
-    challenge_contact_data?: ChallengeContactData;
+    contact_data?: ChallengeContactData;
 
     // 'payto' address pointing a bank account
     // external to the libeufin-bank.
@@ -1464,14 +1486,13 @@ export namespace TalerCorebankApi {
     // back to fiat currency outside libeufin-bank.
     cashout_payto_uri?: PaytoString;
 
+    // Make this account visible to anyone?
+    is_public?: boolean;
+
     // Legal name associated with $username.
     // When missing, the old name is kept.
     name?: string;
 
-    // If present, change the is_exchange configuration.
-    // See RegisterAccountRequest
-    is_taler_exchange?: boolean;
-
     // If present, change the max debit allowed for this user
     // Only admin can change this property.
     debit_threshold?: AmountString;
@@ -1489,13 +1510,17 @@ export namespace TalerCorebankApi {
     public_accounts: PublicAccount[];
   }
   export interface PublicAccount {
-    payto_uri: PaytoString;
+    // Username of the account
+    username: string;
 
+    // Internal payto URI of this bank account.
+    payto_uri: string;
+
+    // Current balance of the account
     balance: Balance;
 
-    // The account name (=username) of the
-    // libeufin-bank account.
-    account_name: string;
+    // Is this a taler exchange account?
+    is_taler_exchange: boolean;
   }
 
   export interface ListBankAccountsResponse {
@@ -1517,6 +1542,12 @@ export namespace TalerCorebankApi {
 
     // Number indicating the max debit allowed for the requesting user.
     debit_threshold: AmountString;
+
+    // Is this account visible to anyone?
+    is_public: boolean;
+
+    // Is this a taler exchange account?
+    is_taler_exchange: boolean;
   }
 
   export interface AccountData {
@@ -1541,6 +1572,12 @@ export namespace TalerCorebankApi {
     // that never cashouts.  Registering these accounts can
     // be done via the access API.
     cashout_payto_uri?: PaytoString;
+
+    // Is this account visible to anyone?
+    is_public: boolean;
+
+    // Is this a taler exchange account?
+    is_taler_exchange: boolean;
   }
 
   export interface CashoutRequest {
diff --git a/packages/web-util/src/components/Footer.tsx 
b/packages/web-util/src/components/Footer.tsx
index 8fdf6b8f0..58dd2a60a 100644
--- a/packages/web-util/src/components/Footer.tsx
+++ b/packages/web-util/src/components/Footer.tsx
@@ -1,8 +1,12 @@
 import { useTranslationContext } from "../index.browser.js";
 import { h } from "preact";
 
-export function Footer({ testingUrl, VERSION, GIT_HASH }: { VERSION?: string, 
GIT_HASH?: string, testingUrl?: string }) {
+export function Footer({ testingUrlKey, VERSION, GIT_HASH }: { VERSION?: 
string, GIT_HASH?: string, testingUrlKey?: string }) {
   const { i18n } = useTranslationContext()
+
+  const testingUrl = (testingUrlKey && typeof localStorage !== "undefined") && 
localStorage.getItem(testingUrlKey) ?
+    localStorage.getItem(testingUrlKey) ?? undefined :
+    undefined
   const versionText = VERSION
     ? GIT_HASH
       ? <a href={`https://git.taler.net/wallet-core.git/tree/?id=${GIT_HASH}`} 
target="_blank" rel="noreferrer noopener">
@@ -23,7 +27,7 @@ export function Footer({ testingUrl, VERSION, GIT_HASH }: { 
VERSION?: string, GI
       <p class="text-xs leading-5 text-gray-400">
         Copyright &copy; 2014&mdash;2023 Taler Systems SA. {versionText}{" "}
       </p>
-      {testingUrl &&
+      {testingUrlKey && testingUrl &&
 
         <p class="text-xs leading-5 text-gray-300">
           Testing with {testingUrl}{" "}
@@ -31,7 +35,7 @@ export function Footer({ testingUrl, VERSION, GIT_HASH }: { 
VERSION?: string, GI
             href=""
             onClick={(e) => {
               e.preventDefault();
-              localStorage.removeItem("bank-base-url");
+              localStorage.removeItem(testingUrlKey);
               window.location.reload();
             }}
           >

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