[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.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [taler-merchant-backoffice] branch master updated: async button and other things,
gnunet <=