gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant-backoffice] branch master updated: async button and othe


From: gnunet
Subject: [taler-merchant-backoffice] branch master updated: async button and other things
Date: Tue, 18 May 2021 22:11:05 +0200

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

sebasjm pushed a commit to branch master
in repository merchant-backoffice.

The following commit(s) were added to refs/heads/master by this push:
     new 7408661  async button and other things
7408661 is described below

commit 740866142fed3a3af0a7db535eb85bc49098a4e3
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Tue May 18 15:13:21 2021 -0300

    async button and other things
---
 .../src/components/exception/AsyncButton.tsx       | 25 +++++++
 .../frontend/src/components/form/FormProvider.tsx  |  6 +-
 packages/frontend/src/components/form/Input.tsx    |  1 -
 .../frontend/src/components/form/InputSelector.tsx |  1 -
 packages/frontend/src/components/form/useField.tsx | 17 ++---
 packages/frontend/src/components/modal/index.tsx   | 23 +++++++
 .../src/components/product/ProductForm.tsx         | 46 ++++++-------
 packages/frontend/src/hooks/async.ts               | 76 ++++++++++++++++++++++
 packages/frontend/src/hooks/backend.ts             | 14 +++-
 packages/frontend/src/hooks/index.ts               | 49 ++++++++++----
 packages/frontend/src/hooks/transfer.ts            |  2 -
 packages/frontend/src/i18n/index.tsx               |  1 -
 .../frontend/src/paths/admin/create/CreatePage.tsx | 40 +++++++-----
 packages/frontend/src/paths/admin/create/index.tsx |  2 +-
 .../paths/instance/orders/create/CreatePage.tsx    |  6 +-
 .../orders/create/NonInventoryProductForm.tsx      |  5 +-
 .../paths/instance/orders/details/DetailPage.tsx   | 21 +++---
 .../src/paths/instance/orders/list/index.tsx       |  4 +-
 .../paths/instance/products/create/CreatePage.tsx  |  8 ++-
 .../src/paths/instance/products/create/index.tsx   |  2 +-
 .../paths/instance/products/update/UpdatePage.tsx  | 17 ++---
 .../src/paths/instance/products/update/index.tsx   |  4 +-
 .../paths/instance/reserves/create/CreatePage.tsx  | 26 ++++----
 .../src/paths/instance/reserves/create/index.tsx   |  2 +-
 .../paths/instance/transfers/create/CreatePage.tsx | 36 +++++-----
 .../src/paths/instance/transfers/create/index.tsx  |  2 +-
 .../src/paths/instance/transfers/list/Table.tsx    |  6 +-
 .../src/paths/instance/transfers/list/index.tsx    | 23 ++++---
 .../src/paths/instance/update/UpdatePage.tsx       | 56 ++++++++--------
 packages/frontend/src/utils/constants.ts           |  5 +-
 30 files changed, 353 insertions(+), 173 deletions(-)

diff --git a/packages/frontend/src/components/exception/AsyncButton.tsx 
b/packages/frontend/src/components/exception/AsyncButton.tsx
new file mode 100644
index 0000000..b7472fd
--- /dev/null
+++ b/packages/frontend/src/components/exception/AsyncButton.tsx
@@ -0,0 +1,25 @@
+import { ComponentChildren, h } from "preact";
+import { LoadingModal } from "../modal";
+import { useAsync } from "../../hooks/async";
+import { Translate } from "../../i18n";
+
+type Props = {
+  children: ComponentChildren, 
+  disabled: boolean;
+  onClick?: () => Promise<void>;
+};
+
+export function AsyncButton({ onClick, disabled, children }: Props) {
+  const { isSlow, isLoading, request, cancel } = useAsync(onClick);
+
+  if (isSlow) {
+    return <LoadingModal onCancel={cancel} />;
+  }
+  if (isLoading) {
+    return <button class="button"><Translate>Loading...</Translate></button>;
+  }
+
+  return <button class="button is-success" onClick={request} 
disabled={disabled}>
+    {children}
+  </button>;
+}
diff --git a/packages/frontend/src/components/form/FormProvider.tsx 
b/packages/frontend/src/components/form/FormProvider.tsx
index 992ac88..d1fb77b 100644
--- a/packages/frontend/src/components/form/FormProvider.tsx
+++ b/packages/frontend/src/components/form/FormProvider.tsx
@@ -35,8 +35,8 @@ function noop() {
 }
 
 export function FormProvider<T>({ object = {}, errors = {}, name = '', 
valueHandler, children }: Props<T>): VNode {
-  const initial = useMemo(() => object, []);
-  const value = useMemo<FormType<T>>(() => ({ errors, object, initial, 
valueHandler: valueHandler ? valueHandler : noop, name, toStr: {}, fromStr: {} 
}), [errors, object, valueHandler]);
+  const initialObject = useMemo(() => object, []);
+  const value = useMemo<FormType<T>>(() => ({ errors, object, initialObject, 
valueHandler: valueHandler ? valueHandler : noop, name, toStr: {}, fromStr: {} 
}), [errors, object, valueHandler]);
 
   return <FormContext.Provider value={value}>
     <form class="field" onSubmit={(e) => {
@@ -50,7 +50,7 @@ export function FormProvider<T>({ object = {}, errors = {}, 
name = '', valueHand
 
 export interface FormType<T> {
   object: Partial<T>;
-  initial: Partial<T>;
+  initialObject: Partial<T>;
   errors: FormErrors<T>;
   toStr: FormtoStr<T>;
   name: string;
diff --git a/packages/frontend/src/components/form/Input.tsx 
b/packages/frontend/src/components/form/Input.tsx
index 5b22667..f257d59 100644
--- a/packages/frontend/src/components/form/Input.tsx
+++ b/packages/frontend/src/components/form/Input.tsx
@@ -40,7 +40,6 @@ const TextInput = ({ inputType, error, ...rest }: any) => 
inputType === 'multili
 
 export function Input<T>({ name, readonly, placeholder, tooltip, label, 
expand, help, children, inputType, inputExtra, side, fromStr = 
defaultFromString, toStr = defaultToString }: Props<keyof T>): VNode {
   const { error, value, onChange } = useField<T>(name);
-
   return <div class="field is-horizontal">
     <div class="field-label is-normal">
       <label class="label">
diff --git a/packages/frontend/src/components/form/InputSelector.tsx 
b/packages/frontend/src/components/form/InputSelector.tsx
index eab6551..d787495 100644
--- a/packages/frontend/src/components/form/InputSelector.tsx
+++ b/packages/frontend/src/components/form/InputSelector.tsx
@@ -35,7 +35,6 @@ const defaultFromString = (v: string): any => v as any
 export function InputSelector<T>({ name, readonly, expand, placeholder, 
tooltip, label, help, values, fromStr = defaultFromString, toStr = 
defaultToString }: Props<keyof T>): VNode {
   const { error, value, onChange } = useField<T>(name);
 
-
   return <div class="field is-horizontal">
     <div class="field-label is-normal">
       <label class="label">
diff --git a/packages/frontend/src/components/form/useField.tsx 
b/packages/frontend/src/components/form/useField.tsx
index 6b64e23..969291c 100644
--- a/packages/frontend/src/components/form/useField.tsx
+++ b/packages/frontend/src/components/form/useField.tsx
@@ -25,7 +25,6 @@ import { useFormContext } from "./FormProvider";
 interface Use<V> {
   error?: string;
   value: any;
-  formName: string;
   initial: any;
   onChange: (v: V) => void;
   toStr: (f: V | undefined) => string;
@@ -33,7 +32,7 @@ interface Use<V> {
 }
 
 export function useField<T>(name: keyof T): Use<T[typeof name]> {
-  const { errors, object, initial, name: formName, toStr, fromStr, 
valueHandler } = useFormContext<T>()
+  const { errors, object, initialObject, toStr, fromStr, valueHandler } = 
useFormContext<T>()
   type P = typeof name
   type V = T[P]
 
@@ -45,19 +44,21 @@ export function useField<T>(name: keyof T): Use<T[typeof 
name]> {
   
   const defaultToString = ((f?: V):string => String(!f ? '': f))
   const defaultFromString = ((v: string):V => v as any)
-    
+  const value = readField(object, String(name))
+  const initial = readField(initialObject, String(name))
+  const isDirty = value !== initial
+
   return {
-    error: errors[name] as any,
-    value: readField(object, String(name)),
-    formName,
-    initial: initial[name],
+    error: isDirty ? readField(errors, String(name)) : undefined,
+    value,
+    initial,
     onChange: updateField(name) as any,
     toStr: toStr[name] ? toStr[name]! : defaultToString,
     fromStr: fromStr[name] ? fromStr[name]! : defaultFromString,
   }
 }
 /**
- * read the field of an object an support accesing it using '.'
+ * read the field of an object an support accessing it using '.'
  * 
  * @param object 
  * @param name 
diff --git a/packages/frontend/src/components/modal/index.tsx 
b/packages/frontend/src/components/modal/index.tsx
index f4445f0..8229dde 100644
--- a/packages/frontend/src/components/modal/index.tsx
+++ b/packages/frontend/src/components/modal/index.tsx
@@ -23,6 +23,7 @@
 import { ComponentChildren, h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { Translate, useTranslator } from "../../i18n";
+import { DEFAULT_REQUEST_TIMEOUT } from "../../utils/constants";
 import { FormProvider } from "../form/FormProvider";
 import { Input } from "../form/Input";
 
@@ -150,3 +151,25 @@ export function UpdateTokenModal({ element, onCancel, 
onClear, onConfirm, oldTok
 }
 
 
+export function LoadingModal({ onCancel }: { onCancel: () => void}): VNode {
+  const i18n = useTranslator()
+  return <div class={"modal is-active"}>
+    <div class="modal-background " onClick={onCancel} />
+    <div class="modal-card">
+      <header class="modal-card-head">
+        <p class="modal-card-title"><Translate>Operation is taking to much 
time</Translate></p>
+      </header>
+      <section class="modal-card-body">
+        <p><Translate>You can wait a little longer or abort the request to the 
backend. If the problem persist 
+        contact the administrator.</Translate></p>
+        <p>{i18n`The operation will be automatically canceled after 
${DEFAULT_REQUEST_TIMEOUT} seconds`}</p>
+      </section>
+      <footer class="modal-card-foot">
+        <div class="buttons is-right" style={{ width: '100%' }}>
+          <button class="button " onClick={onCancel} 
><Translate>Abort</Translate></button>
+        </div>
+      </footer>
+    </div>
+    <button class="modal-close is-large " aria-label="close" 
onClick={onCancel} />
+  </div>
+}
diff --git a/packages/frontend/src/components/product/ProductForm.tsx 
b/packages/frontend/src/components/product/ProductForm.tsx
index b04ff47..5ff77c2 100644
--- a/packages/frontend/src/components/product/ProductForm.tsx
+++ b/packages/frontend/src/components/product/ProductForm.tsx
@@ -39,7 +39,7 @@ import { InputWithAddon } from "../form/InputWithAddon";
 type Entity = MerchantBackend.Products.ProductDetail & { product_id: string }
 
 interface Props {
-  onSubscribe: (c: () => Entity | undefined) => void;
+  onSubscribe: (c?: () => Entity | undefined) => void;
   initial?: Partial<Entity>;
   alreadyExist?: boolean;
 }
@@ -59,33 +59,35 @@ export function ProductForm({ onSubscribe, initial, 
alreadyExist, }: Props) {
       nextRestock: initial.next_restock,
     }
   })
-  const [errors, setErrors] = useState<FormErrors<Entity>>({})
+  let errors : FormErrors<Entity>= {}
+
+  try {
+    (alreadyExist ? updateSchema : createSchema).validateSync(value, { 
abortEarly: false })
+  } catch (err) {
+    const yupErrors = err.inner as yup.ValidationError[]
+    errors = yupErrors.reduce((prev, cur) => !cur.path ? prev : ({ ...prev, 
[cur.path]: cur.message }), {})
+  }
+  const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== 
undefined)
 
   const submit = useCallback((): Entity | undefined => {
-    try {
-      (alreadyExist ? updateSchema : createSchema).validateSync(value, { 
abortEarly: false })
-      const stock: Stock = (value as any).stock;
-      delete (value as any).stock;
-
-      if (!stock) {
-        value.total_stock = -1
-      } else {
-        value.total_stock = stock.current;
-        value.total_lost = stock.lost;
-        value.next_restock = stock.nextRestock instanceof Date ? { t_ms: 
stock.nextRestock.getTime() } : stock.nextRestock;
-        value.address = stock.address;
-      }
-      return value as MerchantBackend.Products.ProductDetail & { product_id: 
string }
-    } catch (err) {
-      const errors = err.inner as yup.ValidationError[]
-      const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev : ({ 
...prev, [cur.path]: cur.message }), {})
-      setErrors(pathMessages)
+    const stock: Stock = (value as any).stock;
+
+    if (!stock) {
+      value.total_stock = -1
+    } else {
+      value.total_stock = stock.current;
+      value.total_lost = stock.lost;
+      value.next_restock = stock.nextRestock instanceof Date ? { t_ms: 
stock.nextRestock.getTime() } : stock.nextRestock;
+      value.address = stock.address;
     }
+    delete (value as any).stock;
+
+    return value as MerchantBackend.Products.ProductDetail & { product_id: 
string }
   }, [value])
 
   useEffect(() => {
-    onSubscribe(submit)
-  }, [submit])
+    onSubscribe(hasErrors ? undefined : submit)
+  }, [submit, hasErrors])
 
   const backend = useBackendContext();
   const i18n = useTranslator()
diff --git a/packages/frontend/src/hooks/async.ts 
b/packages/frontend/src/hooks/async.ts
new file mode 100644
index 0000000..4e2ca10
--- /dev/null
+++ b/packages/frontend/src/hooks/async.ts
@@ -0,0 +1,76 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
+import { useState } from "preact/hooks";
+import { cancelPendingRequest } from "./backend";
+
+export interface Options {
+  slowTolerance: number,
+}
+
+export interface AsyncOperationApi<T> {
+  request: (...a: any) => void,
+  cancel: () => void,
+  data: T | undefined,
+  isSlow: boolean,
+  isLoading: boolean,
+  error: string | undefined
+}
+
+export function useAsync<T>(fn?: (...args: any) => Promise<T>, { 
slowTolerance: tooLong }: Options = { slowTolerance: 1000 }): 
AsyncOperationApi<T> {
+  const [data, setData] = useState<T | undefined>(undefined);
+  const [isLoading, setLoading] = useState<boolean>(false);
+  const [error, setError] = useState<any>(undefined);
+  const [isSlow, setSlow] = useState(false)
+
+  const request = async (...args: any) => {
+    if (!fn) return;
+    setLoading(true);
+
+    const handler = setTimeout(() => {
+      setSlow(true)
+    }, tooLong)
+
+    try {
+      const result = await fn(...args);
+      setData(result);
+    } catch (error) {
+      setError(error);
+    }
+    setLoading(false);
+    setSlow(false)
+    clearTimeout(handler)
+  };
+
+  function cancel() {
+    cancelPendingRequest()
+    setLoading(false);
+    setSlow(false)
+  }
+
+  return {
+    request,
+    cancel,
+    data,
+    isSlow,
+    isLoading,
+    error
+  };
+};
diff --git a/packages/frontend/src/hooks/backend.ts 
b/packages/frontend/src/hooks/backend.ts
index 92b33e9..0c86d89 100644
--- a/packages/frontend/src/hooks/backend.ts
+++ b/packages/frontend/src/hooks/backend.ts
@@ -24,6 +24,7 @@ import axios, { AxiosError, AxiosResponse } from 'axios'
 import { MerchantBackend } from '../declaration';
 import { useBackendContext } from '../context/backend';
 import { useEffect, useState } from 'preact/hooks';
+import { DEFAULT_REQUEST_TIMEOUT } from '../utils/constants';
 
 export function mutateAll(re: RegExp, value?: unknown): Array<Promise<any>> {
   return cache.keys().filter(key => {
@@ -183,18 +184,27 @@ function buildRequestFailed(ex: 
AxiosError<MerchantBackend.ErrorDetail>, url: st
 }
 
 
+const CancelToken = axios.CancelToken;
+let source = CancelToken.source();
+
+export function cancelPendingRequest() {
+  source.cancel('canceled by the user')
+  source = CancelToken.source()
+}
+
 export async function request<T>(url: string, options: RequestOptions = {}): 
Promise<HttpResponseOk<T>> {
   const headers = options.token ? { Authorization: `Bearer ${options.token}` } 
: undefined
 
   try {
-    // console.log(options.method || 'get', url, options.data, options.params)
     const res = await axios({
       url,
       responseType: 'json',
       headers,
+      cancelToken: source.token,
       method: options.method || 'get',
       data: options.data,
-      params: options.params
+      params: options.params,
+      timeout: DEFAULT_REQUEST_TIMEOUT * 1000,
     })
     return buildRequestOk<T>(res, url, !!options.token)
   } catch (e) {
diff --git a/packages/frontend/src/hooks/index.ts 
b/packages/frontend/src/hooks/index.ts
index 4a01d2b..d170233 100644
--- a/packages/frontend/src/hooks/index.ts
+++ b/packages/frontend/src/hooks/index.ts
@@ -59,9 +59,9 @@ export function useBackendInstanceToken(id: string): [string 
| undefined, StateU
   return [token, setToken]
 }
 
-export function useLang(initial?:string): [string, StateUpdater<string>] {
+export function useLang(initial?: string): [string, StateUpdater<string>] {
   const browserLang = typeof window !== "undefined" ? navigator.language || 
(navigator as any).userLanguage : undefined;
-  const defaultLang = (browserLang || initial || 'en').substring(0,2)
+  const defaultLang = (browserLang || initial || 'en').substring(0, 2)
   return useNotNullLocalStorage('lang-preference', defaultLang)
 }
 
@@ -107,19 +107,42 @@ export function useNotNullLocalStorage(key: string, 
initialValue: string): [stri
   return [storedValue, setValue];
 }
 
-// eslint-disable-next-line @typescript-eslint/no-empty-function
-const noop = () => {}
-
-export function useListener<T>(onCall: (r: T) => void): [() => void, 
(listener: () => T) => void] {
-  const [state, setState] = useState({ run: noop })
-
-  const subscriber = (listener: () => T) => {
-    setState({
-      run: () => onCall(listener())
-    })
+/**
+ * returns subscriber and activator
+ * subscriber will receive a method (listener) that will be call when the 
activator runs.
+ * the result of calling the listener will be sent to @action
+ * 
+ * @param action from <T> to <R>
+ * @returns activator and subscriber, undefined activator means that there is 
not subscriber
+ */
+export function useListener<T, R = any>(action: (r: T) => Promise<R>): 
[undefined | (() => Promise<R>), (listener?: () => T) => void] {
+  const [state, setState] = useState<{ toBeRan?: () => Promise<R> }>({})
+
+  /**
+   * subscriber will receive a method that will be call when the activator runs
+   * 
+   * @param listener function to be run when the activator runs
+   */
+  const subscriber = (listener?: () => T) => {
+    if (listener) {
+      setState({
+        toBeRan: () => {
+          const whatWeGetFromTheListener = listener()
+          return action(whatWeGetFromTheListener)
+        }
+      })
+    }
   }
 
-  const activator = () => state.run()
+  /**
+   * activator will call runner if there is someone subscribed
+   */
+  const activator = state.toBeRan ? async () => {
+    if (state.toBeRan) {
+      return state.toBeRan()
+    }
+    return Promise.reject()
+  } : undefined
 
   return [activator, subscriber]
 }
diff --git a/packages/frontend/src/hooks/transfer.ts 
b/packages/frontend/src/hooks/transfer.ts
index def92c4..02e041a 100644
--- a/packages/frontend/src/hooks/transfer.ts
+++ b/packages/frontend/src/hooks/transfer.ts
@@ -125,12 +125,10 @@ export function useInstanceTransfers(args?: 
InstanceTransferFilter, updatePositi
         setPageAfter(pageAfter + 1)
       } else {
         const from = 
""+afterData.data.transfers[afterData.data.transfers.length - 
1].transfer_serial_id
-        console.log('load more', from)
         if (from && updatePosition) updatePosition(from)
       }
     },
     loadMorePrev: () => {
-      console.log('load more prev')
       if (!beforeData) return
       if (beforeData.data.transfers.length < MAX_RESULT_SIZE) {
         setPageBefore(pageBefore + 1)
diff --git a/packages/frontend/src/i18n/index.tsx 
b/packages/frontend/src/i18n/index.tsx
index 4192c16..fb8250b 100644
--- a/packages/frontend/src/i18n/index.tsx
+++ b/packages/frontend/src/i18n/index.tsx
@@ -70,7 +70,6 @@ function stringifyChildren(children: ComponentChildren): 
string {
     return `%${n++}$s`;
   });
   const s = ss.join("").replace(/ +/g, " ").trim();
-  // console.log("translation lookup", JSON.stringify(s));
   return s;
 }
 
diff --git a/packages/frontend/src/paths/admin/create/CreatePage.tsx 
b/packages/frontend/src/paths/admin/create/CreatePage.tsx
index 9592a90..61d7690 100644
--- a/packages/frontend/src/paths/admin/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/admin/create/CreatePage.tsx
@@ -22,6 +22,7 @@
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import * as yup from 'yup';
+import { AsyncButton } from "../../../components/exception/AsyncButton";
 import { FormErrors, FormProvider } from 
"../../../components/form/FormProvider";
 import { Input } from "../../../components/form/Input";
 import { InputCurrency } from "../../../components/form/InputCurrency";
@@ -39,7 +40,7 @@ import { InstanceCreateSchema as schema } from 
'../../../schemas';
 type Entity = MerchantBackend.Instances.InstanceConfigurationMessage & { 
auth_token?: string }
 
 interface Props {
-  onCreate: (d: Entity) => void;
+  onCreate: (d: Entity) => Promise<void>;
   onBack?: () => void;
   forceId?: string;
 }
@@ -55,22 +56,25 @@ function with_defaults(id?: string): Partial<Entity> {
 
 export function CreatePage({ onCreate, onBack, forceId }: Props): VNode {
   const [value, valueHandler] = useState(with_defaults(forceId))
-  const [errors, setErrors] = useState<FormErrors<Entity>>({})
-
-  const submit = (): void => {
-    try {
-      // use conversion instead of this
-      const newToken = value.auth_token;
-      value.auth_token = undefined;
-      value.auth = newToken === null || newToken === undefined ? { method: 
"external" } : { method: "token", token: `secret-token:${newToken}` };
-      // remove above use conversion
-      schema.validateSync(value, { abortEarly: false })
-      onCreate(schema.cast(value) as Entity);
-    } catch (err) {
-      const errors = err.inner as yup.ValidationError[]
-      const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev : ({ 
...prev, [cur.path]: cur.message }), {})
-      setErrors(pathMessages)
-    }
+  // const [errors, setErrors] = useState<FormErrors<Entity>>({})
+
+  let errors: FormErrors<Entity> = {}
+  try {
+    schema.validateSync(value, { abortEarly: false })
+  } catch (err) {
+    const yupErrors = err.inner as yup.ValidationError[]
+    errors = yupErrors.reduce((prev, cur) => !cur.path ? prev : ({ ...prev, 
[cur.path]: cur.message }), {})
+  }
+  const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== 
undefined)
+
+  const submit = (): Promise<void> => {
+    // use conversion instead of this
+    const newToken = value.auth_token;
+    value.auth_token = undefined;
+    value.auth = newToken === null || newToken === undefined ? { method: 
"external" } : { method: "token", token: `secret-token:${newToken}` };
+    // remove above use conversion
+    // schema.validateSync(value, { abortEarly: false })
+    return onCreate(schema.cast(value) as Entity);
   }
   const backend = useBackendContext()
   const i18n = useTranslator()
@@ -112,7 +116,7 @@ export function CreatePage({ onCreate, onBack, forceId }: 
Props): VNode {
 
           <div class="buttons is-right mt-5">
             {onBack && <button class="button" onClick={onBack} 
><Translate>Cancel</Translate></button>}
-            <button class="button is-success" onClick={submit} 
><Translate>Confirm</Translate></button>
+            <AsyncButton onClick={submit} disabled={hasErrors} 
><Translate>Confirm</Translate></AsyncButton>
           </div>
 
         </div>
diff --git a/packages/frontend/src/paths/admin/create/index.tsx 
b/packages/frontend/src/paths/admin/create/index.tsx
index 1480a27..8789b45 100644
--- a/packages/frontend/src/paths/admin/create/index.tsx
+++ b/packages/frontend/src/paths/admin/create/index.tsx
@@ -51,7 +51,7 @@ export default function Create({ onBack, onConfirm, forceId 
}: Props): VNode {
       onBack={onBack}
       forceId={forceId}
       onCreate={(d: MerchantBackend.Instances.InstanceConfigurationMessage) => 
{
-        createInstance(d).then(() => {
+        return createInstance(d).then(() => {
           setCreatedOk(d)
         }).catch((error) => {
           setNotif({
diff --git a/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx 
b/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx
index 8953744..f760fd4 100644
--- a/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx
@@ -145,8 +145,8 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
     })
   }
 
-  const addNewProduct = (product: MerchantBackend.Product) => {
-    valueHandler(v => {
+  const addNewProduct = async (product: MerchantBackend.Product) => {
+    return valueHandler(v => {
       const products = [...v.products, product]
       return ({ ...v, products })
     })
@@ -256,7 +256,7 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
           } tooltip={i18n`add products to the order`}>
             <NonInventoryProductFrom value={editingProduct} onAddProduct={(p) 
=> {
               setEditingProduct(undefined)
-              addNewProduct(p)
+              return addNewProduct(p)
             }} />
 
             {productList.length > 0 &&
diff --git 
a/packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx
 
b/packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx
index 1226f43..4429fb7 100644
--- 
a/packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx
+++ 
b/packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx
@@ -33,7 +33,7 @@ import { useTranslator } from "../../../../i18n";
 type Entity = MerchantBackend.Product
 
 interface Props {
-  onAddProduct: (p: Entity) => void;
+  onAddProduct: (p: Entity) => Promise<void>;
   value?: Entity;
 }
 export function NonInventoryProductFrom({ value, onAddProduct }: Props): VNode 
{
@@ -48,7 +48,7 @@ export function NonInventoryProductFrom({ value, onAddProduct 
}: Props): VNode {
   const [submitForm, addFormSubmitter] = 
useListener<Partial<MerchantBackend.Product> | undefined>((result) => {
     if (result) {
       setShowCreateProduct(false)
-      onAddProduct({
+      return onAddProduct({
         quantity: result.quantity || 0,
         taxes: result.taxes || [],
         description: result.description || '',
@@ -57,6 +57,7 @@ export function NonInventoryProductFrom({ value, onAddProduct 
}: Props): VNode {
         unit: result.unit || ''
       })
     }
+    return Promise.reject()
   })
 
   return <Fragment>
diff --git a/packages/frontend/src/paths/instance/orders/details/DetailPage.tsx 
b/packages/frontend/src/paths/instance/orders/details/DetailPage.tsx
index 4e920a7..d8dbe0f 100644
--- a/packages/frontend/src/paths/instance/orders/details/DetailPage.tsx
+++ b/packages/frontend/src/paths/instance/orders/details/DetailPage.tsx
@@ -408,7 +408,7 @@ function UnpaidPage({ id, order }: { id: string; order: 
MerchantBackend.Orders.C
   </div>
 }
 
-export function DetailPage({ id, selected, onRefund }: Props): VNode {
+export function DetailPage({ id, selected, onRefund, onBack }: Props): VNode {
   const [showRefund, setShowRefund] = useState<string | undefined>(undefined)
 
   const DetailByStatus = function () {
@@ -421,15 +421,6 @@ export function DetailPage({ id, selected, onRefund }: 
Props): VNode {
   }
 
   return <Fragment>
-    <NotificationCard notification={{
-      message: 'DEMO WARNING',
-      type: 'WARN',
-      description: <ul>
-        <li>wired event is faked</li>
-        <li>fee value is fake, is not being calculated</li>
-      </ul>
-    }} />
-
     {DetailByStatus()}
     {showRefund && <RefundModal
       id={id}
@@ -439,6 +430,16 @@ export function DetailPage({ id, selected, onRefund }: 
Props): VNode {
         setShowRefund(undefined)
       }}
     />}
+    <div class="columns">
+      <div class="column" />
+      <div class="column is-two-thirds">
+        <div class="buttons is-right mt-5">
+          <button class="button" 
onClick={onBack}><Translate>Back</Translate></button>
+        </div>
+      </div>
+      <div class="column" />
+    </div>
+
   </Fragment>
 }
 
diff --git a/packages/frontend/src/paths/instance/orders/list/index.tsx 
b/packages/frontend/src/paths/instance/orders/list/index.tsx
index aeba05b..60d9428 100644
--- a/packages/frontend/src/paths/instance/orders/list/index.tsx
+++ b/packages/frontend/src/paths/instance/orders/list/index.tsx
@@ -41,7 +41,7 @@ interface Props {
 
 
 export default function ({ onUnauthorized, onLoadError, onCreate, onSelect, 
onNotFound }: Props): VNode {
-  const [filter, setFilter] = useState<InstanceOrderFilter>({ paid: 'yes' })
+  const [filter, setFilter] = useState<InstanceOrderFilter>({ })
   const [pickDate, setPickDate] = useState(false)
 
   const setNewDate = (date: Date) => setFilter(prev => ({ ...prev, date }))
@@ -104,10 +104,10 @@ export default function ({ onUnauthorized, onLoadError, 
onCreate, onSelect, onNo
       <div class="column">
         <div class="tabs">
           <ul>
+            <li class={isAllActive}><a onClick={() => 
setFilter({})}><Translate>All</Translate></a></li>
             <li class={isPaidActive}><a onClick={() => setFilter({ paid: 'yes' 
})}><Translate>Paid</Translate></a></li>
             <li class={isRefundedActive}><a onClick={() => setFilter({ 
refunded: 'yes' })}><Translate>Refunded</Translate></a></li>
             <li class={isNotWiredActive}><a onClick={() => setFilter({ wired: 
'no' })}><Translate>Not wired</Translate></a></li>
-            <li class={isAllActive}><a onClick={() => 
setFilter({})}><Translate>All</Translate></a></li>
           </ul>
         </div>
       </div>
diff --git 
a/packages/frontend/src/paths/instance/products/create/CreatePage.tsx 
b/packages/frontend/src/paths/instance/products/create/CreatePage.tsx
index de33564..e6e6f1e 100644
--- a/packages/frontend/src/paths/instance/products/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/instance/products/create/CreatePage.tsx
@@ -20,6 +20,7 @@
 */
 
 import { h, VNode } from "preact";
+import { AsyncButton } from "../../../../components/exception/AsyncButton";
 import { ProductForm } from "../../../../components/product/ProductForm";
 import { MerchantBackend } from "../../../../declaration";
 import { useListener } from "../../../../hooks";
@@ -28,7 +29,7 @@ import { Translate } from "../../../../i18n";
 type Entity = MerchantBackend.Products.ProductAddDetail & { product_id: string}
 
 interface Props {
-  onCreate: (d: Entity) => void;
+  onCreate: (d: Entity) => Promise<void>;
   onBack?: () => void;
 }
 
@@ -36,7 +37,8 @@ interface Props {
 export function CreatePage({ onCreate, onBack }: Props): VNode {
 
   const [submitForm, addFormSubmitter] = useListener<Entity | 
undefined>((result) => {
-    if (result) onCreate(result)
+    if (result) return onCreate(result)
+    return Promise.reject()
   })
 
   return <div>
@@ -48,7 +50,7 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
 
           <div class="buttons is-right mt-5">
             {onBack && <button class="button" onClick={onBack} 
><Translate>Cancel</Translate></button>}
-            <button class="button is-success" onClick={submitForm} 
><Translate>Confirm</Translate></button>
+            <AsyncButton onClick={submitForm} 
disabled={!submitForm}><Translate>Confirm</Translate></AsyncButton>
           </div>
 
         </div>
diff --git a/packages/frontend/src/paths/instance/products/create/index.tsx 
b/packages/frontend/src/paths/instance/products/create/index.tsx
index 6e81a04..4645458 100644
--- a/packages/frontend/src/paths/instance/products/create/index.tsx
+++ b/packages/frontend/src/paths/instance/products/create/index.tsx
@@ -43,7 +43,7 @@ export default function CreateProduct({ onConfirm, onBack }: 
Props): VNode {
     <CreatePage
       onBack={onBack}
       onCreate={(request: MerchantBackend.Products.ProductAddDetail) => {
-        createProduct(request).then(() => onConfirm()).catch((error) => {
+        return createProduct(request).then(() => onConfirm()).catch((error) => 
{
           setNotif({
             message: i18n`could not create product`,
             type: "ERROR",
diff --git 
a/packages/frontend/src/paths/instance/products/update/UpdatePage.tsx 
b/packages/frontend/src/paths/instance/products/update/UpdatePage.tsx
index 4a6248d..e0a2b16 100644
--- a/packages/frontend/src/paths/instance/products/update/UpdatePage.tsx
+++ b/packages/frontend/src/paths/instance/products/update/UpdatePage.tsx
@@ -20,22 +20,24 @@
 */
 
 import { h, VNode } from "preact";
+import { AsyncButton } from "../../../../components/exception/AsyncButton";
 import { ProductForm } from "../../../../components/product/ProductForm";
 import { MerchantBackend, WithId } from "../../../../declaration";
 import { useListener } from "../../../../hooks";
 import { Translate } from "../../../../i18n";
 
-type Entity = MerchantBackend.Products.ProductDetail & WithId
+type Entity = MerchantBackend.Products.ProductDetail & { product_id: string}
 
 interface Props {
-  onUpdate: (d: Entity) => void;
+  onUpdate: (d: Entity) => Promise<void>;
   onBack?: () => void;
   product: Entity;
 }
 
 export function UpdatePage({ product, onUpdate, onBack }: Props): VNode {
   const [submitForm, addFormSubmitter] = useListener<Entity | 
undefined>((result) => {
-    if (result) onUpdate(result)
+    if (result) return onUpdate(result)
+    return Promise.resolve()
   })
 
   return <div>
@@ -43,16 +45,11 @@ export function UpdatePage({ product, onUpdate, onBack }: 
Props): VNode {
       <div class="columns">
         <div class="column" />
         <div class="column is-two-thirds">
-          <ProductForm initial={product} onSubscribe={(a) => {
-            addFormSubmitter(() => {
-              const p = a()
-              return p as any
-            })
-          }} alreadyExist />
+          <ProductForm initial={product} onSubscribe={addFormSubmitter} 
alreadyExist />
 
           <div class="buttons is-right mt-5">
             {onBack && <button class="button" onClick={onBack} 
><Translate>Cancel</Translate></button>}
-            <button class="button is-success" onClick={submitForm} 
><Translate>Confirm</Translate></button>
+            <AsyncButton onClick={submitForm} 
disabled={!submitForm}><Translate>Confirm</Translate></AsyncButton>
           </div>
 
         </div>
diff --git a/packages/frontend/src/paths/instance/products/update/index.tsx 
b/packages/frontend/src/paths/instance/products/update/index.tsx
index 338924f..a6a61c8 100644
--- a/packages/frontend/src/paths/instance/products/update/index.tsx
+++ b/packages/frontend/src/paths/instance/products/update/index.tsx
@@ -54,10 +54,10 @@ export default function UpdateProduct({ pid, onConfirm, 
onBack, onUnauthorized,
   return <Fragment>
     <NotificationCard notification={notif} />
     <UpdatePage
-      product={{ ...result.data, id: pid }}
+      product={{ ...result.data, product_id: pid }}
       onBack={onBack}
       onUpdate={(data) => {
-        updateProduct(pid, data)
+        return updateProduct(pid, data)
           .then(onConfirm)
           .catch((error) => {
             setNotif({
diff --git 
a/packages/frontend/src/paths/instance/reserves/create/CreatePage.tsx 
b/packages/frontend/src/paths/instance/reserves/create/CreatePage.tsx
index 6c32a15..6c079e6 100644
--- a/packages/frontend/src/paths/instance/reserves/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/instance/reserves/create/CreatePage.tsx
@@ -24,37 +24,33 @@ import { useState } from "preact/hooks";
 import { FormErrors, FormProvider } from 
"../../../../components/form/FormProvider";
 import { Input } from "../../../../components/form/Input";
 import { InputCurrency } from "../../../../components/form/InputCurrency";
-import { useConfigContext } from "../../../../context/config";
 import { MerchantBackend } from "../../../../declaration";
 import { Translate, useTranslator } from "../../../../i18n";
+import { AsyncButton } from "../../../../components/exception/AsyncButton";
 
 type Entity = MerchantBackend.Tips.ReserveCreateRequest
 
 interface Props {
-  onCreate: (d: Entity) => void;
+  onCreate: (d: Entity) => Promise<void>;
   onBack?: () => void;
 }
 
 
 export function CreatePage({ onCreate, onBack }: Props): VNode {
-  const { currency } = useConfigContext()
-
-  const [reserve, setReserve] = useState<Partial<Entity>>({
-    initial_balance: `${currency}:2`,
-    exchange_url: 'http://exchange.taler:8081/',
-    wire_method: 'x-taler-bank',
-  })
+  const [reserve, setReserve] = useState<Partial<Entity>>({})
   const i18n = useTranslator()
 
   const errors: FormErrors<Entity> = {
-
+    initial_balance: !reserve.initial_balance ? 'cannot be empty' : 
!(parseInt(reserve.initial_balance.split(':')[1], 10) > 0) ? i18n`it should be 
greater than 0` : undefined,
+    exchange_url: !reserve.exchange_url ? i18n`cannot be empty` : undefined,
+    wire_method: !reserve.wire_method ? i18n`cannot be empty` : undefined,
   }
 
   const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== 
undefined)
 
   const submitForm = () => {
-    if (hasErrors) return
-    onCreate(reserve as Entity)
+    if (hasErrors) return Promise.reject()
+    return onCreate(reserve as Entity)
   }
 
   return <div>
@@ -62,7 +58,7 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
       <div class="columns">
         <div class="column" />
         <div class="column is-two-thirds">
-          <FormProvider<Entity> object={reserve} valueHandler={setReserve}>
+          <FormProvider<Entity> object={reserve} errors={errors} 
valueHandler={setReserve}>
             <InputCurrency<Entity> name="initial_balance" label={i18n`Initial 
balance`} />
             <Input<Entity> name="exchange_url" label={i18n`Exchange`} />
             <Input<Entity> name="wire_method" label={i18n`Wire method`} />
@@ -70,11 +66,11 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
 
           <div class="buttons is-right mt-5">
             {onBack && <button class="button" onClick={onBack} 
><Translate>Cancel</Translate></button>}
-            <button class="button is-success" onClick={submitForm} 
><Translate>Confirm</Translate></button>
+            <AsyncButton onClick={submitForm} disabled={hasErrors} 
><Translate>Confirm</Translate></AsyncButton>
           </div>
         </div>
         <div class="column" />
       </div>
     </section>
   </div>
-}
\ No newline at end of file
+}
diff --git a/packages/frontend/src/paths/instance/reserves/create/index.tsx 
b/packages/frontend/src/paths/instance/reserves/create/index.tsx
index d7de65b..ad990e9 100644
--- a/packages/frontend/src/paths/instance/reserves/create/index.tsx
+++ b/packages/frontend/src/paths/instance/reserves/create/index.tsx
@@ -49,7 +49,7 @@ export default function CreateReserve({ onBack, onConfirm }: 
Props): VNode {
     <CreatePage
       onBack={onBack}
       onCreate={(request: MerchantBackend.Tips.ReserveCreateRequest) => {
-        createReserve(request).then((r) => setCreatedOk(r.data)).catch((error) 
=> {
+        return createReserve(request).then((r) => 
setCreatedOk(r.data)).catch((error) => {
           setNotif({
             message: i18n`could not create reserve`,
             type: "ERROR",
diff --git 
a/packages/frontend/src/paths/instance/transfers/create/CreatePage.tsx 
b/packages/frontend/src/paths/instance/transfers/create/CreatePage.tsx
index cffe0c1..8b7cd95 100644
--- a/packages/frontend/src/paths/instance/transfers/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/instance/transfers/create/CreatePage.tsx
@@ -21,19 +21,23 @@
 
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
+import { AsyncButton } from "../../../../components/exception/AsyncButton";
 import { FormErrors, FormProvider } from 
"../../../../components/form/FormProvider";
 import { Input } from "../../../../components/form/Input";
 import { InputCurrency } from "../../../../components/form/InputCurrency";
+import { InputPayto } from "../../../../components/form/InputPayto";
+import { InputSelector } from "../../../../components/form/InputSelector";
 import { InputWithAddon } from "../../../../components/form/InputWithAddon";
 import { useConfigContext } from "../../../../context/config";
 import { MerchantBackend } from "../../../../declaration";
+import { useInstanceDetails } from "../../../../hooks/instance";
 import { Translate, useTranslator } from "../../../../i18n";
 import { CROCKFORD_BASE32_REGEX, URL_REGEX } from 
"../../../../utils/constants";
 
 type Entity = MerchantBackend.Transfers.TransferInformation
 
 interface Props {
-  onCreate: (d: Entity) => void;
+  onCreate: (d: Entity) => Promise<void>;
   onBack?: () => void;
 }
 
@@ -41,13 +45,16 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
   const i18n = useTranslator()
   const { currency } = useConfigContext()
 
+  const instance = useInstanceDetails()
+  const accounts = !instance.ok ? [] : instance.data.accounts.map(a => 
a.payto_uri)
+
   const [state, setState] = useState<Partial<Entity>>({
-    wtid: 'DCMGEM7F0DPW930M06C2AVNC6CFXT6HBQ2YVQH7EC8ZQ0W8SS9TG',
-    payto_uri: 'payto://x-taler-bank/bank.taler:5882/blogger',
+    wtid: '',
+    // payto_uri: ,
     exchange_url: 'http://exchange.taler:8081/',
-    credit_amount: `${currency}:22.80`,
+    credit_amount: ``,
   });
-  
+
   const errors: FormErrors<Entity> = {
     wtid: !state.wtid ? i18n`cannot be empty` :
       (!CROCKFORD_BASE32_REGEX.test(state.wtid) ? i18n`check the id, doest 
look valid` :
@@ -62,8 +69,8 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
   const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== 
undefined)
 
   const submitForm = () => {
-    if (hasErrors) return
-    onCreate(state as any)
+    if (hasErrors) return Promise.reject()
+    return onCreate(state as any)
   }
 
   return <div>
@@ -74,23 +81,22 @@ export function CreatePage({ onCreate, onBack }: Props): 
VNode {
 
           <FormProvider object={state} valueHandler={setState} errors={errors}>
             <Input<Entity> name="wtid" label={i18n`Transfer ID`} help="" 
tooltip={i18n`unique identifier of the wire transfer, usually 52 random 
characters long`} />
-            <InputWithAddon<Entity> name="payto_uri"
-              label={i18n`Account Address`}
-              addonBefore="payto://"
-              toStr={(v?: string) => v ? v.substring("payto://".length) : ''}
-              fromStr={(v: string) => !v ? '' : `payto://${v}`}
-              tooltip={i18n`account address where the transfer has being 
received`}
-              help="x-taler-bank/bank.taler:5882/blogger" />
+            <InputSelector name="payto_uri" label={i18n`Address`}
+              values={accounts}
+              placeholder={i18n`Select one account`}
+              tooltip={i18n`filter by account address`}
+            />
             <Input<Entity> name="exchange_url"
               label={i18n`Exchange URL`}
               tooltip={i18n`exchange that made the transfer`}
               help="http://exchange.taler:8081/"; />
             <InputCurrency<Entity> name="credit_amount" label={i18n`Amount`} 
tooltip={i18n`how much money transferred into the account`} />
+
           </FormProvider>
 
           <div class="buttons is-right mt-5">
             {onBack && <button class="button" onClick={onBack} 
><Translate>Cancel</Translate></button>}
-            <button class="button is-success" disabled={hasErrors} 
onClick={submitForm} ><Translate>Confirm</Translate></button>
+            <AsyncButton disabled={hasErrors} onClick={submitForm} 
><Translate>Confirm</Translate></AsyncButton>
           </div>
 
         </div>
diff --git a/packages/frontend/src/paths/instance/transfers/create/index.tsx 
b/packages/frontend/src/paths/instance/transfers/create/index.tsx
index e2aec58..a9e56a5 100644
--- a/packages/frontend/src/paths/instance/transfers/create/index.tsx
+++ b/packages/frontend/src/paths/instance/transfers/create/index.tsx
@@ -44,7 +44,7 @@ export default function CreateTransfer({onConfirm, 
onBack}:Props): VNode {
     <CreatePage
       onBack={onBack}
       onCreate={(request: MerchantBackend.Transfers.TransferInformation) => {
-        informTransfer(request).then(() => onConfirm()).catch((error) => {
+        return informTransfer(request).then(() => onConfirm()).catch((error) 
=> {
           setNotif({
             message: i18n`could not inform transfer`,
             type: "ERROR",
diff --git a/packages/frontend/src/paths/instance/transfers/list/Table.tsx 
b/packages/frontend/src/paths/instance/transfers/list/Table.tsx
index b001b84..c91b28a 100644
--- a/packages/frontend/src/paths/instance/transfers/list/Table.tsx
+++ b/packages/frontend/src/paths/instance/transfers/list/Table.tsx
@@ -111,7 +111,7 @@ function toggleSelected<T>(id: T): (prev: T[]) => T[] {
   return (prev: T[]): T[] => prev.indexOf(id) == -1 ? [...prev, id] : 
prev.filter(e => e != id)
 }
 
-function Table({ instances, onLoadMoreAfter, onLoadMoreBefore, hasMoreAfter, 
hasMoreBefore }: TableProps): VNode {
+function Table({ instances, onLoadMoreAfter, onDelete, onLoadMoreBefore, 
hasMoreAfter, hasMoreBefore }: TableProps): VNode {
   const i18n = useTranslator()
   return (
     <div class="table-container">
@@ -126,6 +126,7 @@ function Table({ instances, onLoadMoreAfter, 
onLoadMoreBefore, hasMoreAfter, has
             <th><Translate>Confirmed</Translate></th>
             <th><Translate>Verified</Translate></th>
             <th><Translate>Executed at</Translate></th>
+            <th></th>
           </tr>
         </thead>
         <tbody>
@@ -138,6 +139,9 @@ function Table({ instances, onLoadMoreAfter, 
onLoadMoreBefore, hasMoreAfter, has
               <td>{i.confirmed ? i18n`yes` : i18n`no`}</td>
               <td>{i.verified ? i18n`yes` : i18n`no`}</td>
               <td>{i.execution_time ? (i.execution_time.t_ms == 'never' ? 
i18n`never` : format(i.execution_time.t_ms, 'yyyy/MM/dd HH:mm:ss')) : 
i18n`unknown`}</td>
+              <td>
+                {i.verified === undefined ? <button class="button is-danger 
is-small" onClick={() => onDelete(i) }>Delete</button> : undefined }
+              </td>
             </tr>
           })}
         </tbody>
diff --git a/packages/frontend/src/paths/instance/transfers/list/index.tsx 
b/packages/frontend/src/paths/instance/transfers/list/index.tsx
index 5de1403..d141011 100644
--- a/packages/frontend/src/paths/instance/transfers/list/index.tsx
+++ b/packages/frontend/src/paths/instance/transfers/list/index.tsx
@@ -47,6 +47,7 @@ interface Form {
 
 export default function ListTransfer({ onUnauthorized, onLoadError, onCreate, 
onNotFound }: Props): VNode {
   const [form, setForm] = useState<Form>({ payto_uri: '' })
+  const setFilter = (s?: 'yes' | 'no') => setForm({ ...form, verified: s })
 
   const i18n = useTranslator()
   const [position, setPosition] = useState<string | undefined>(undefined)
@@ -54,25 +55,31 @@ export default function ListTransfer({ onUnauthorized, 
onLoadError, onCreate, on
   const instance = useInstanceDetails()
   const accounts = !instance.ok ? [] : instance.data.accounts.map(a => 
a.payto_uri)
 
+  const isVerifiedTransfers = form.verified === 'yes' ? "is-active" : ''
+  const isNonVerifiedTransfers = form.verified === 'no' ? "is-active" : ''
+  const isAllTransfers = form.verified === undefined ? 'is-active' : ''
+
   return <section class="section is-main-section">
     <div class="columns">
       <div class="column" />
       <div class="column is-6">
         <FormProvider object={form} valueHandler={setForm as any}>
-          <InputBoolean name="verified" label={i18n`Verified`} threeState
-            tooltip={i18n`checked will query for verified transfer, unchecked 
will query for unverified transfer and (-) sign will query for both`}
-            fromBoolean={(b?: boolean) => b === undefined ? undefined : (b ? 
'yes' : 'no')}
-            toBoolean={(b?: string) => b === undefined ? undefined : (b === 
'yes')}
-          />
-          <InputSelector name="payto_uri" label={i18n`Address`} 
-            values={accounts} 
-            placeholder={i18n`Select one account`} 
+          <InputSelector name="payto_uri" label={i18n`Address`}
+            values={accounts}
+            placeholder={i18n`Select one account`}
             tooltip={i18n`filter by account address`}
           />
         </FormProvider>
       </div>
       <div class="column" />
     </div>
+    <div class="tabs">
+      <ul>
+        <li class={isAllTransfers}><a onClick={() => 
setFilter(undefined)}><Translate>All</Translate></a></li>
+        <li class={isVerifiedTransfers}><a onClick={() => 
setFilter('yes')}><Translate>Verified</Translate></a></li>
+        <li class={isNonVerifiedTransfers}><a onClick={() => 
setFilter('no')}><Translate>Non Verified</Translate></a></li>
+      </ul>
+    </div>
     <View
       accounts={accounts}
       form={form} onCreate={onCreate} onLoadError={onLoadError} 
onNotFound={onNotFound} onUnauthorized={onUnauthorized}
diff --git a/packages/frontend/src/paths/instance/update/UpdatePage.tsx 
b/packages/frontend/src/paths/instance/update/UpdatePage.tsx
index 5fa8e47..d1a1d7c 100644
--- a/packages/frontend/src/paths/instance/update/UpdatePage.tsx
+++ b/packages/frontend/src/paths/instance/update/UpdatePage.tsx
@@ -22,6 +22,7 @@
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import * as yup from 'yup';
+import { AsyncButton } from "../../../components/exception/AsyncButton";
 import { FormProvider, FormErrors } from 
"../../../components/form/FormProvider";
 import { Input } from "../../../components/form/Input";
 import { InputCurrency } from "../../../components/form/InputCurrency";
@@ -67,31 +68,34 @@ export function UpdatePage({ onUpdate, selected, onBack }: 
Props): VNode {
   const { token } = useInstanceContext()
   const currentTokenValue = getTokenValuePart(token)
   const [value, valueHandler] = useState<Partial<Entity>>(convert(selected, 
currentTokenValue))
-  const [errors, setErrors] = useState<FormErrors<Entity>>({})
-
-  const submit = (): void => {
-    try {
-      // use conversion instead of this
-      const newToken = value.auth_token;
-      value.auth_token = undefined;
-
-      //if new token was not set or has been set to the actual current token
-      //it is not needed to send a change
-      //otherwise, checked where we are setting a new token or removing it
-      const auth: MerchantBackend.Instances.InstanceAuthConfigurationMessage | 
undefined =
-        newToken === undefined || newToken === currentTokenValue ? undefined : 
(newToken === null ?
-          { method: "external" } :
-          { method: "token", token: `secret-token:${newToken}` });
-
-      // remove above use conversion
-      schema.validateSync(value, { abortEarly: false })
-      onUpdate(schema.cast(value), auth);
-      onBack()
-    } catch (err) {
-      const errors = err.inner as yup.ValidationError[]
-      const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev : ({ 
...prev, [cur.path]: cur.message }), {})
-      setErrors(pathMessages)
-    }
+
+  let errors: FormErrors<Entity> = {}
+  try {
+    schema.validateSync(value, { abortEarly: false })
+  } catch (err) {
+    const yupErrors = err.inner as yup.ValidationError[]
+    errors = yupErrors.reduce((prev, cur) => !cur.path ? prev : ({ ...prev, 
[cur.path]: cur.message }), {})
+  }
+  const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== 
undefined)
+
+  const submit = async (): Promise<void> => {
+    // use conversion instead of this
+    const newToken = value.auth_token;
+    value.auth_token = undefined;
+
+    //if new token was not set or has been set to the actual current token
+    //it is not needed to send a change
+    //otherwise, checked where we are setting a new token or removing it
+    const auth: MerchantBackend.Instances.InstanceAuthConfigurationMessage | 
undefined =
+      newToken === undefined || newToken === currentTokenValue ? undefined : 
(newToken === null ?
+        { method: "external" } :
+        { method: "token", token: `secret-token:${newToken}` });
+
+    // remove above use conversion
+    schema.validateSync(value, { abortEarly: false })
+    await onUpdate(schema.cast(value), auth);
+    await onBack()
+    return Promise.resolve()
   }
 
   const i18n = useTranslator()
@@ -131,7 +135,7 @@ export function UpdatePage({ onUpdate, selected, onBack }: 
Props): VNode {
 
           <div class="buttons is-right mt-4">
             <button class="button" onClick={onBack} 
><Translate>Cancel</Translate></button>
-            <button class="button is-success" onClick={submit} 
><Translate>Confirm</Translate></button>
+            <AsyncButton onClick={submit} disabled={hasErrors} 
><Translate>Confirm</Translate></AsyncButton>
           </div>
         </div>
         <div class="column" />
diff --git a/packages/frontend/src/utils/constants.ts 
b/packages/frontend/src/utils/constants.ts
index d7c26c4..a8ab9ca 100644
--- a/packages/frontend/src/utils/constants.ts
+++ b/packages/frontend/src/utils/constants.ts
@@ -36,4 +36,7 @@ export const URL_REGEX = 
/^((https?:)(\/\/\/?)([\w]*(?::[\w]*)?@)?([\d\w\.-]+)(?
 export const PAGE_SIZE = 20
 // how bigger can be the result set
 // after this threshold, load more with move the cursor
-export const MAX_RESULT_SIZE = PAGE_SIZE * 2 - 1;
\ No newline at end of file
+export const MAX_RESULT_SIZE = PAGE_SIZE * 2 - 1;
+
+// how much we will wait for all request, in seconds
+export const DEFAULT_REQUEST_TIMEOUT = 10;
\ No newline at end of file

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