[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-merchant-backoffice] branch master updated: refactor YUP, just us
From: |
gnunet |
Subject: |
[taler-merchant-backoffice] branch master updated: refactor YUP, just using it to build errors object |
Date: |
Thu, 25 Feb 2021 15:54:57 +0100 |
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 b4c42f7 refactor YUP, just using it to build errors object
b4c42f7 is described below
commit b4c42f71b69d017bb7275565a915479cbae6b342
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Thu Feb 25 11:54:44 2021 -0300
refactor YUP, just using it to build errors object
---
CHANGELOG.md | 16 ++-
packages/frontend/src/ApplicationReadyRoutes.tsx | 20 ++++
packages/frontend/src/InstanceRoutes.tsx | 21 ++++
packages/frontend/src/components/form/Field.tsx | 132 +++++++++++++++++++++
.../{yup/YupInput.tsx => form/Input.tsx} | 22 ++--
.../{yup/YupInputArray.tsx => form/InputArray.tsx} | 30 ++---
.../form/InputCurrency.tsx} | 26 ++--
.../frontend/src/components/form/InputDuration.tsx | 48 ++++++++
.../YupObjectInput.tsx => form/InputGroup.tsx} | 20 +---
.../YupInputSecured.tsx => form/InputSecured.tsx} | 21 ++--
.../InputWithAddon.tsx} | 27 +++--
packages/frontend/src/components/modal/index.tsx | 80 ++++++++++++-
packages/frontend/src/components/yup/YupField.tsx | 79 ------------
packages/frontend/src/context/backend.ts | 9 +-
packages/frontend/src/hooks/backend.ts | 24 +++-
packages/frontend/src/index.tsx | 2 +-
packages/frontend/src/messages/en.po | 14 ++-
.../src/routes/instances/create/CreatePage.tsx | 61 ++++++++--
.../src/routes/instances/details/DetailPage.tsx | 20 ++--
.../src/routes/instances/details/index.tsx | 2 +-
.../src/routes/instances/list/CardTable.tsx | 100 ----------------
.../src/routes/instances/list/EmptyTable.tsx | 32 -----
.../frontend/src/routes/instances/list/Table.tsx | 90 +++++++++++++-
.../frontend/src/routes/instances/list/View.tsx | 2 +-
.../frontend/src/routes/instances/list/index.tsx | 3 +-
.../src/routes/instances/update/UpdatePage.tsx | 71 ++++++++---
.../frontend/src/routes/instances/update/index.tsx | 35 ++++--
27 files changed, 648 insertions(+), 359 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4797763..4ea2679 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,10 +11,8 @@ and this project adheres to [Semantic
Versioning](https://semver.org/spec/v2.0.0
- allow point separator for amounts
- prevent letters to be input in numbers
- red color when input is invalid (onchange)
- - auth token config as popup with 3 actions (clear (sure?), cancel, set token)
- - remove checkbox from auth token, use button (manage auth)
- prepend payto:// to account
- - validate on change everything
+ - validate everything using onChange
- all button to the right
- feature: input as date format
- bug: there is missing a mutate call when updating to remove the instance
from cache
@@ -30,9 +28,15 @@ and this project adheres to [Semantic
Versioning](https://semver.org/spec/v2.0.0
- prune scss styles to reduce size
## [Unreleased]
- - check the url from the backend when login
- - edit button should be a pen
- - implement correct weblate feature
+ - REFACTOR: remove react-i18n and implement messageformat
+ - REFACTOR: routes definitions to allow nested routes and tokens
+ - REFACTOR: remove yup from input form defitions
+ - added PORT environment variable for `make dev` and `make serve`
+ - added `make dist` and `make install`
+ - remove checkbox from auth token, use button (manage auth)
+ - auth token config as popup with 3 actions (clear (sure?), cancel, set token)
+ - remove last '/' on the backend url
+ - change button on table row from "edit" to "view"
- what happend if cannot access the config
- reorder the fields from the address/juriction section (take example)
- save every auth token of different instances
diff --git a/packages/frontend/src/ApplicationReadyRoutes.tsx
b/packages/frontend/src/ApplicationReadyRoutes.tsx
index 21fa1b6..a245e27 100644
--- a/packages/frontend/src/ApplicationReadyRoutes.tsx
+++ b/packages/frontend/src/ApplicationReadyRoutes.tsx
@@ -1,3 +1,23 @@
+/*
+ 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 { h, VNode } from 'preact';
import { useContext } from "preact/hooks";
import { Route, Router, route } from 'preact-router';
diff --git a/packages/frontend/src/InstanceRoutes.tsx
b/packages/frontend/src/InstanceRoutes.tsx
index a9ae009..e0f4452 100644
--- a/packages/frontend/src/InstanceRoutes.tsx
+++ b/packages/frontend/src/InstanceRoutes.tsx
@@ -1,3 +1,24 @@
+/*
+ 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 { h, VNode } from 'preact';
import { useCallback, useContext, useEffect } from "preact/hooks";
import { Route, Router, route } from 'preact-router';
diff --git a/packages/frontend/src/components/form/Field.tsx
b/packages/frontend/src/components/form/Field.tsx
new file mode 100644
index 0000000..08b6361
--- /dev/null
+++ b/packages/frontend/src/components/form/Field.tsx
@@ -0,0 +1,132 @@
+/*
+ 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 { h, VNode, createContext } from "preact";
+import { StateUpdater, useContext, useMemo, useState } from "preact/hooks";
+import { BackendContext, ConfigContext } from '../../context/backend';
+import { Input } from "./Input";
+import { InputArray } from "./InputArray";
+import { InputWithAddon } from "./InputWithAddon";
+import { InputSecured } from "./InputSecured";
+
+interface Props<T> {
+ name: keyof T;
+ info: any;
+ readonly?: boolean;
+}
+
+export interface FormType<T> {
+ object: Partial<T>;
+ errors: FormErrors<T>;
+ toStr: FormtoStr<T>;
+ fromStr: FormfromStr<T>;
+ valueHandler: StateUpdater<Partial<T>>;
+}
+const FormContext = createContext<FormType<any>>(null!)
+
+export type ValidationError = {
+ type?: string;
+ message: string;
+ params?: any;
+}
+
+export type FormErrors<T> = {
+ [P in keyof T]?: ValidationError
+}
+
+export type FormtoStr<T> = {
+ [P in keyof T]?: ((f?: T[P]) => string)
+}
+
+export type FormfromStr<T> = {
+ [P in keyof T]?: ((f: string) => T[P])
+}
+
+export type FormUpdater<T> = {
+ [P in keyof T]?: (f: keyof T) => (v: T[P]) => void
+}
+
+interface ProviderProps<T> {
+ object?: Partial<T>;
+ errors?: FormErrors<T>;
+ // toStr?: FormtoStr<T>;
+ // fromStr?: FormfromStr<T>;
+ valueHandler: StateUpdater<Partial<T>>;
+ children: VNode[] | VNode
+}
+
+export function FormProvider<T>({ object = {}, errors = {}, valueHandler,
children }: ProviderProps<T>) {
+ const value = useMemo<FormType<T>>(() => ({errors, object, valueHandler,
toStr: {}, fromStr: {}}), [errors, object, valueHandler])
+ return <FormContext.Provider value={value}>
+ {children}
+ </FormContext.Provider>
+}
+
+export function useField<T>(name: keyof T) {
+ const { errors, object, toStr, fromStr, valueHandler } =
useContext<FormType<T>>(FormContext)
+ type P = typeof name
+ type V = T[P]
+
+ const updateField = (f: P) => (v: V): void => {
+ return valueHandler((prev) => {
+ return ({ ...prev, [f]: v })
+ })
+ }
+
+ const defaultToString = ((f?: V):string => String(!f ? '': f))
+ const defaultFromString = ((v: string):V => v as any)
+
+ return {
+ error: errors[name],
+ value: object[name],
+ onChange: updateField(name),
+ toStr: toStr[name] ? toStr[name]! : defaultToString,
+ fromStr: fromStr[name] ? fromStr[name]! : defaultFromString,
+ }
+}
+
+// export function Field<T>({ name, info, readonly }: Props<T>): VNode {
+// const {errors, object, valueHandler, updateField} = useForm<T>()
+
+// const backend = useContext(BackendContext)
+// const config = useContext(ConfigContext)
+
+// switch (info.meta?.type) {
+ // case 'group': {
+ // return <InputObject name={name} readonly={readonly}
+
+ // onChange={(updater: any): void => valueHandler((prev: any) => ({
...prev, [name]: updater(prev[name]) }))}
+ // />
+ // }
+ // case 'array': return <InputArray name={name} readonly={readonly} />;
+ // case 'amount': {
+ // if (config.currency) {
+ // return <InputWithAddon name={name} readonly={readonly}
addon={config.currency} onChange={(v: string): void =>
values.onChange(`${config.currency}:${v}`)} value={values.value?.split(':')[1]}
/>
+ // }
+ // return <Input name={name} readonly={readonly} />;
+ // }
+ // case 'url': return <InputWithAddon name={name} readonly={readonly}
addon={`${backend.url}/private/instances/`} />;
+ // case 'secured': return <InputSecured name={name} readonly={readonly} />;
+ // case 'duration': return <InputWithAddon name={name} readonly={readonly}
addon={readableDuration(values.value?.d_ms)} atTheEnd
value={`${values.value?.d_ms / 1000 || ''}`} onChange={(v: string): void =>
values.onChange({ d_ms: (parseInt(v, 10) * 1000) || undefined } as any)} />;
+ // default: return <Input name={name} readonly={readonly} />;
+
+ // }
+// }
diff --git a/packages/frontend/src/components/yup/YupInput.tsx
b/packages/frontend/src/components/form/Input.tsx
similarity index 73%
rename from packages/frontend/src/components/yup/YupInput.tsx
rename to packages/frontend/src/components/form/Input.tsx
index aba089d..32afa6f 100644
--- a/packages/frontend/src/components/yup/YupInput.tsx
+++ b/packages/frontend/src/components/form/Input.tsx
@@ -20,16 +20,16 @@
*/
import { h, VNode } from "preact";
import { Message, useMessage } from "preact-messages";
+import { useField } from "./Field";
-interface Props {
- name: string;
- value: string;
+interface Props<T> {
+ name: T;
readonly?: boolean;
- errors: any;
- onChange: any;
}
-export function YupInput({ name, readonly, value, errors, onChange }: Props):
VNode {
+export function Input<T>({ name, readonly }: Props<keyof T>): VNode {
+ const { error, value, onChange, toStr, fromStr } = useField<T>(name);
+
const placeholder = useMessage(`fields.instance.${name}.placeholder`);
const tooltip = useMessage(`fields.instance.${name}.tooltip`);
@@ -45,14 +45,14 @@ export function YupInput({ name, readonly, value, errors,
onChange }: Props): VN
<div class="field-body">
<div class="field">
<p class="control">
- <input class={errors[name] ? "input is-danger" : "input"} type="text"
+ <input class={error ? "input is-danger" : "input"} type="text"
placeholder={placeholder} readonly={readonly}
- name={name} value={value} disabled={readonly}
- onChange={(e): void => onChange(e.currentTarget.value)} />
+ name={String(name)} value={toStr(value)} disabled={readonly}
+ onChange={(e): void => onChange(fromStr(e.currentTarget.value))} />
<Message id={`fields.instance.${name}.help`}> </Message>
</p>
- {errors[name] ? <p class="help is-danger">
- <Message id={`validation.${errors[name].type}`}
fields={errors[name].params}>{errors[name].message} </Message>
+ {error ? <p class="help is-danger">
+ <Message id={`validation.${error.type}`}
fields={error.params}>{error.message} </Message>
</p> : null}
</div>
</div>
diff --git a/packages/frontend/src/components/yup/YupInputArray.tsx
b/packages/frontend/src/components/form/InputArray.tsx
similarity index 73%
rename from packages/frontend/src/components/yup/YupInputArray.tsx
rename to packages/frontend/src/components/form/InputArray.tsx
index c9387fe..592831e 100644
--- a/packages/frontend/src/components/yup/YupInputArray.tsx
+++ b/packages/frontend/src/components/form/InputArray.tsx
@@ -21,20 +21,20 @@
import { h, VNode } from "preact";
import { Message, useMessage } from "preact-messages";
import { useState } from "preact/hooks";
+import { useField } from "./Field";
-export interface Props {
- name: string;
- value: string;
+export interface Props<T> {
+ name: T;
readonly?: boolean;
- errors: any;
- onChange: any;
}
-export function YupInputArray({ name, readonly, value, errors, onChange }:
Props): VNode {
+export function InputArray<T>({ name, readonly }: Props<keyof T>): VNode {
+ const { error, value, onChange, fromStr, toStr } = useField<T>(name);
+
const placeholder = useMessage(`fields.instance.${name}.placeholder`);
const tooltip = useMessage(`fields.instance.${name}.tooltip`);
- const array = value as unknown as string[] || [];
+ const array: any[] = (value ? value! : []) as any;
const [currentValue, setCurrentValue] = useState('');
return <div class="field is-horizontal">
@@ -50,27 +50,27 @@ export function YupInputArray({ name, readonly, value,
errors, onChange }: Props
<div class="field">
<div class="field has-addons">
<p class="control">
- <input class={errors[name] ? "input is-danger" : "input"}
type="text"
+ <input class={error ? "input is-danger" : "input"} type="text"
placeholder={placeholder} readonly={readonly} disabled={readonly}
- name={name} value={currentValue}
+ name={String(name)} value={currentValue}
onChange={(e): void => setCurrentValue(e.currentTarget.value)} />
<Message id={`fields.instance.${name}.help`}> </Message>
</p>
<p class="control">
<button class="button is-info" onClick={(): void => {
- onChange([currentValue, ...array]);
+ onChange([fromStr(currentValue), ...array] as any);
setCurrentValue('');
}}>add</button>
</p>
</div>
- {errors[name] ? <p class="help is-danger">
- <Message id={`validation.${errors[name].type}`}
fields={errors[name].params}>{errors[name].message}</Message>
+ {error ? <p class="help is-danger">
+ <Message id={`validation.${error.type}`}
fields={error.params}>{error.message}</Message>
</p> : null}
{array.map(v => <div class="tags has-addons">
- <span class="tag is-medium is-info">{v}</span>
+ <span class="tag is-medium is-info">{toStr(v)}</span>
<a class="tag is-medium is-danger is-delete" onClick={() => {
- onChange(array.filter(f => f !== v));
- setCurrentValue(v);
+ onChange(array.filter(f => f !== v) as any);
+ setCurrentValue( toStr(v) );
}} />
</div>
)}
diff --git a/packages/frontend/src/routes/instances/list/DeleteModal.tsx
b/packages/frontend/src/components/form/InputCurrency.tsx
similarity index 57%
rename from packages/frontend/src/routes/instances/list/DeleteModal.tsx
rename to packages/frontend/src/components/form/InputCurrency.tsx
index f526456..e8082ea 100644
--- a/packages/frontend/src/routes/instances/list/DeleteModal.tsx
+++ b/packages/frontend/src/components/form/InputCurrency.tsx
@@ -18,19 +18,21 @@
*
* @author Sebastian Javier Marchano (sebasjm)
*/
+import { h } from "preact";
+import { Amount } from "../../declaration";
+import { InputWithAddon } from "./InputWithAddon";
+import { useField } from "./Field";
-import { h, VNode } from "preact";
-import { ConfirmModal } from "../../../components/modal";
-
-interface Props {
- element: {id: string, name: string};
- onCancel: () => void;
- onConfirm: (id: string) => void;
+export interface Props<T> {
+ name: keyof T;
+ readonly?: boolean;
+ currency: string;
}
-export function DeleteModal({ element, onCancel, onConfirm }: Props): VNode {
- return <ConfirmModal description="delete_instance" danger active
onCancel={onCancel} onConfirm={() => onConfirm(element.id)}>
- <p>This will permanently delete instance "{element.name}" with id
<b>{element.id}</b></p>
- <p>Please confirm this action</p>
- </ConfirmModal>
+export function InputCurrency<T>({ name, readonly, currency }: Props<T>) {
+ return <InputWithAddon<T> name={name} readonly={readonly} addon={currency}
+ toStr={(v?: Amount) => v?.split(':')[1] || ''}
+ fromStr={(v: string) => `${currency}:${v}`}
+ />
}
+
diff --git a/packages/frontend/src/components/form/InputDuration.tsx
b/packages/frontend/src/components/form/InputDuration.tsx
new file mode 100644
index 0000000..65359d5
--- /dev/null
+++ b/packages/frontend/src/components/form/InputDuration.tsx
@@ -0,0 +1,48 @@
+/*
+ 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 { h, VNode } from "preact";
+import { RelativeTime } from "../../declaration";
+import { InputWithAddon } from "./InputWithAddon";
+import { formatDuration, intervalToDuration } from "date-fns";
+import { useField } from "./Field";
+
+export interface Props<T> {
+ name: keyof T;
+ readonly?: boolean;
+}
+
+export function InputDuration<T>({ name, readonly }: Props<T>) {
+ const { value } = useField<T>(name);
+ return <InputWithAddon<T> name={name} readonly={readonly}
addon={readableDuration( value as any )} atTheEnd
+ toStr={(v?: RelativeTime) => `${(v && v.d_ms !== "forever" && v.d_ms ?
v.d_ms / 1000 : '')}`}
+ fromStr={(v: string) => ({ d_ms: (parseInt(v, 10) * 1000) || undefined })}
+ />
+}
+
+function readableDuration(duration?: RelativeTime): string {
+ if (!duration) return ""
+ if (duration.d_ms === "forever") return "forever"
+ try {
+ return formatDuration(intervalToDuration({ start: 0, end: duration.d_ms }))
+ } catch (e) {
+ return ''
+ }
+}
diff --git a/packages/frontend/src/components/yup/YupObjectInput.tsx
b/packages/frontend/src/components/form/InputGroup.tsx
similarity index 72%
rename from packages/frontend/src/components/yup/YupObjectInput.tsx
rename to packages/frontend/src/components/form/InputGroup.tsx
index 76598e0..0a3f26d 100644
--- a/packages/frontend/src/components/yup/YupObjectInput.tsx
+++ b/packages/frontend/src/components/form/InputGroup.tsx
@@ -21,23 +21,18 @@
import { h, VNode } from "preact";
import { Message } from "preact-messages";
import { useState } from "preact/hooks";
-import { YupField } from "./YupField";
-export interface PropsObject {
- name: string;
- info: any;
- value: any;
- errors: any;
- onChange: any;
- readonly?: boolean;
+export interface Props<T> {
+ name: keyof T;
+ children: VNode[] | VNode;
}
-export function YupObjectInput({ name, info, value, errors, onChange, readonly
}: PropsObject): VNode {
+export function InputGroup<T>({ name, children }: Props<T>): VNode {
const [active, setActive] = useState(false);
return <div class="card">
<header class="card-header">
<p class="card-header-title">
- <Message id={`fields.instance.${name}.label`} />
+ <Message id={`fields.instance.${String(name)}.label`} />
</p>
<button class="card-header-icon" aria-label="more options" onClick={():
void => setActive(!active)}>
<span class="icon">
@@ -49,10 +44,7 @@ export function YupObjectInput({ name, info, value, errors,
onChange, readonly }
</header>
<div class={active ? "card-content" : "is-hidden"}>
<div class="content">
- {Object.keys(info.fields).map(f => <YupField name={`${name}.${f}`}
- field={f} errors={errors} object={value}
- valueHandler={onChange} info={info.fields[f]}
- readonly={readonly} />)}
+ {children}
</div>
</div>
</div>;
diff --git a/packages/frontend/src/components/yup/YupInputSecured.tsx
b/packages/frontend/src/components/form/InputSecured.tsx
similarity index 76%
rename from packages/frontend/src/components/yup/YupInputSecured.tsx
rename to packages/frontend/src/components/form/InputSecured.tsx
index 5fac0dc..34f815a 100644
--- a/packages/frontend/src/components/yup/YupInputSecured.tsx
+++ b/packages/frontend/src/components/form/InputSecured.tsx
@@ -21,16 +21,17 @@
import { h, VNode } from "preact";
import { Message, useMessage } from "preact-messages";
import { useState } from "preact/hooks";
+import { DeleteModal } from "../modal";
+import { useField } from "./Field";
-export interface Props {
- name: string;
- value: string;
+export interface Props<T> {
+ name: keyof T;
readonly?: boolean;
- errors: any;
- onChange: any;
}
-export function YupInputSecured({ name, readonly, value, errors, onChange }:
Props): VNode {
+export function InputSecured<T>({ name, readonly }: Props<T>): VNode {
+ const {error, value, onChange, toStr, fromStr } = useField<T>(name);
+
const placeholder = useMessage(`fields.instance.${name}.placeholder`, {});
const tooltip = useMessage(`fields.instance.${name}.tooltip`, {});
@@ -49,19 +50,19 @@ export function YupInputSecured({ name, readonly, value,
errors, onChange }: Pro
<div class="field">
<div class="field has-addons">
<label class="b-checkbox checkbox">
- <input type="checkbox" checked={active} onClick={(): void => {
onChange(''); setActive(!active); }} />
+ <input type="checkbox" checked={active} onClick={(): void => {
onChange(fromStr('')); setActive(!active); }} />
<span class="check" />
</label>
<p class="control">
<input class="input" type="text"
placeholder={placeholder} readonly={readonly || !active}
disabled={readonly || !active}
- name={name} value={value}
- onChange={(e): void => onChange(e.currentTarget.value)} />
+ name={String(name)} value={toStr(value)}
+ onChange={(e): void => onChange(fromStr(e.currentTarget.value))}
/>
<Message id={`fields.instance.${name}.help`}> </Message>
</p>
</div>
- {errors[name] ? <p class="help is-danger"><Message
id={`validation.${errors[name].type}`}
fields={errors[name].params}>{errors[name].message}</Message></p> : null}
+ {error ? <p class="help is-danger"><Message
id={`validation.${error.type}`}
fields={error.params}>{error.message}</Message></p> : null}
</div>
</div>
</div>;
diff --git a/packages/frontend/src/components/yup/YupInputWithAddon.tsx
b/packages/frontend/src/components/form/InputWithAddon.tsx
similarity index 69%
rename from packages/frontend/src/components/yup/YupInputWithAddon.tsx
rename to packages/frontend/src/components/form/InputWithAddon.tsx
index f789de7..e387ab4 100644
--- a/packages/frontend/src/components/yup/YupInputWithAddon.tsx
+++ b/packages/frontend/src/components/form/InputWithAddon.tsx
@@ -20,18 +20,23 @@
*/
import { h, VNode } from "preact";
import { Message, useMessage } from "preact-messages";
+import { useField } from "./Field";
-export interface Props {
- name: string;
- value: string;
+export interface Props<T> {
+ name: keyof T;
readonly?: boolean;
- errors: any;
- onChange: any;
- addon: string;
+ addon: string;
atTheEnd?: boolean;
+ toStr?: (v?: any) => string;
+ fromStr?: (s: string) => any;
}
-export function YupInputWithAddon({ name, readonly, value, errors, onChange,
addon, atTheEnd }: Props): VNode {
+const defaultToString = (f?: any):string => f || ''
+const defaultFromString = (v: string):any => v as any
+
+export function InputWithAddon<T>({ name, readonly, addon, atTheEnd, toStr =
defaultToString, fromStr = defaultFromString }: Props<T>): VNode {
+ const { error, value, onChange } = useField<T>(name);
+
const placeholder = useMessage(`fields.instance.${name}.placeholder`);
const tooltip = useMessage(`fields.instance.${name}.tooltip`);
@@ -51,17 +56,17 @@ export function YupInputWithAddon({ name, readonly, value,
errors, onChange, add
<a class="button is-static">{addon}</a>
</div>}
<p class="control is-expanded">
- <input class={errors[name] ? "input is-danger" : "input"}
type="text"
+ <input class={error ? "input is-danger" : "input"} type="text"
placeholder={placeholder} readonly={readonly} disabled={readonly}
- name={name} value={value}
- onChange={(e): void => onChange(e.currentTarget.value)} />
+ name={String(name)} value={toStr(value)}
+ onChange={(e): void => onChange(fromStr(e.currentTarget.value))}
/>
<Message id={`fields.instance.${name}.help`}> </Message>
</p>
{atTheEnd && <div class="control">
<a class="button is-static">{addon}</a>
</div>}
</div>
- {errors[name] ? <p class="help is-danger"><Message
id={`validation.${errors[name].type}`}
fields={errors[name].params}>{errors[name].message}</Message></p> : null}
+ {error ? <p class="help is-danger"><Message
id={`validation.${error.type}`}
fields={error.params}>{error.message}</Message></p> : null}
</div>
</div>
</div>;
diff --git a/packages/frontend/src/components/modal/index.tsx
b/packages/frontend/src/components/modal/index.tsx
index 0c04a4f..682c1ed 100644
--- a/packages/frontend/src/components/modal/index.tsx
+++ b/packages/frontend/src/components/modal/index.tsx
@@ -22,6 +22,9 @@
import { h, VNode } from "preact";
import { Message } from "preact-messages";
+import { useState } from "preact/hooks";
+import { FormProvider } from "../form/Field";
+import { Input } from "../form/Input";
interface Props {
active?: boolean;
@@ -30,14 +33,15 @@ interface Props {
onConfirm?: () => void;
children?: VNode[];
danger?: boolean;
+ disabled?: boolean;
}
-export function ConfirmModal({ active, description, onCancel, onConfirm,
children, danger }: Props): VNode {
+export function ConfirmModal({ active, description, onCancel, onConfirm,
children, danger, disabled }: Props): VNode {
return <div class={active ? "modal is-active" : "modal"}>
<div class="modal-background " onClick={onCancel} />
<div class="modal-card">
<header class="modal-card-head">
- <p class="modal-card-title"> <Message id="confirm_modal.title" /> {
!description ? null : <Message id={`confirm_modal.${description}`} /> }</p>
+ {!description ? null : <p class="modal-card-title"> <Message
id={description} /></p> }
<button class="delete " aria-label="close" onClick={onCancel} />
</header>
<section class="modal-card-body">
@@ -45,9 +49,77 @@ export function ConfirmModal({ active, description,
onCancel, onConfirm, childre
</section>
<footer class="modal-card-foot">
<button class="button " onClick={onCancel} ><Message id="Cancel"
/></button>
- <button class={danger ? "button is-danger " : "button is-info "}
onClick={onConfirm} ><Message id="Confirm" /></button>
+ <button class={danger ? "button is-danger " : "button is-info "}
disabled={disabled} onClick={onConfirm} ><Message id="Confirm" /></button>
</footer>
</div>
<button class="modal-close is-large " aria-label="close"
onClick={onCancel} />
</div>
-}
\ No newline at end of file
+}
+
+export function ClearConfirmModal({ description, onCancel, onClear, onConfirm,
children, disabled }: Props & { onClear?: () => void }): VNode {
+ return <div class="modal is-active">
+ <div class="modal-background " onClick={onCancel} />
+ <div class="modal-card">
+ <header class="modal-card-head">
+ {!description ? null : <p class="modal-card-title"> <Message
id={description} /></p> }
+ <button class="delete " aria-label="close" onClick={onCancel} />
+ </header>
+ <section class="modal-card-body">
+ {children}
+ </section>
+ <footer class="modal-card-foot">
+ <button class="button " onClick={onCancel} ><Message id="Cancel"
/></button>
+ <button class="button is-danger" onClick={onClear} disabled={disabled}
><Message id="Clear" /></button>
+ <button class="button is-info" onClick={onConfirm} disabled={disabled}
><Message id="Confirm" /></button>
+ </footer>
+ </div>
+ <button class="modal-close is-large " aria-label="close"
onClick={onCancel} />
+ </div>
+}
+
+interface DeleteModalProps {
+ element: { id: string, name: string };
+ onCancel: () => void;
+ onConfirm: (id: string) => void;
+}
+
+export function DeleteModal({ element, onCancel, onConfirm }:
DeleteModalProps): VNode {
+ return <ConfirmModal description="delete_instance" danger active
onCancel={onCancel} onConfirm={() => onConfirm(element.id)}>
+ <p>This will permanently delete instance "{element.name}" with id
<b>{element.id}</b></p>
+ <p>Please confirm this action</p>
+ </ConfirmModal>
+}
+
+interface UpdateTokenModalProps {
+ element: { id: string, name: string };
+ oldToken: string;
+ onCancel: () => void;
+ onConfirm: (value: string) => void;
+ onClear: () => void;
+}
+
+export function UpdateTokenModal({ element, onCancel, onClear, onConfirm,
oldToken }: UpdateTokenModalProps): VNode {
+ type State = {old_token: string, new_token: string}
+ const [form, setValue] = useState<Partial<State>>({
+ old_token: '', new_token: ''
+ })
+
+ const errors = {
+ old_token: oldToken !== form.old_token ? { message: 'should be the same' }
: undefined,
+ new_token: !form.new_token ? { message: 'should be the same' } : (
form.new_token === form.old_token ? { message: 'cant repeat' } : undefined ),
+ }
+
+ return <ClearConfirmModal description="update_token"
+ onCancel={onCancel}
+ onConfirm={() => onConfirm(form.new_token!)}
+ onClear={onClear}
+ disabled={!!errors.new_token || !!errors.old_token}
+ >
+ <p>You are updating the authorization token from instance {element.name}
with id <b>{element.id}</b></p>
+ <FormProvider errors={errors} object={form} valueHandler={setValue}>
+ <Input name="old_token" />
+ <Input name="new_token" />
+ </FormProvider>
+ <p>Clearing the auth token will mean public access to the instance</p>
+ </ClearConfirmModal>
+}
diff --git a/packages/frontend/src/components/yup/YupField.tsx
b/packages/frontend/src/components/yup/YupField.tsx
deleted file mode 100644
index 5000055..0000000
--- a/packages/frontend/src/components/yup/YupField.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- 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 { h, VNode } from "preact";
-import { StateUpdater, useContext } from "preact/hooks";
-import { intervalToDuration, formatDuration } from 'date-fns'
-import { BackendContext, ConfigContext } from '../../context/backend';
-import { YupObjectInput } from "./YupObjectInput";
-import { YupInput } from "./YupInput";
-import { YupInputArray } from "./YupInputArray";
-import { YupInputWithAddon } from "./YupInputWithAddon";
-import { YupInputSecured } from "./YupInputSecured";
-
-function readableDuration(duration?: number): string {
- if (!duration) return ""
- return formatDuration(intervalToDuration({ start: 0, end: duration }))
-}
-
-interface Props {
- name: string;
- field: string;
- errors: any;
- object: any;
- valueHandler: StateUpdater<any>;
- info: any;
- readonly?: boolean;
-}
-export function YupField({ name, field, errors, object, valueHandler, info,
readonly }: Props): VNode {
- const updateField = (f: string) => (v: string): void => valueHandler((prev:
any) => ({ ...prev, [f]: v }))
- const values = {
- name, errors,
- readonly: readonly || info?.meta?.readonly,
- value: object && object[field],
- onChange: updateField(field)
- }
- const backend = useContext(BackendContext)
- const config = useContext(ConfigContext)
-
- switch (info.meta?.type) {
- case 'group': {
- return <YupObjectInput name={name}
- info={info} errors={errors}
- value={object && object[field]}
- readonly={values.readonly}
- onChange={(updater: any): void => valueHandler((prev: any) => ({
...prev, [field]: updater(prev[field]) }))}
- />
- }
- case 'array': return <YupInputArray {...values} />;
- case 'amount': {
- if (config.currency) {
- return <YupInputWithAddon {...values} addon={config.currency}
onChange={(v: string): void => values.onChange(`${config.currency}:${v}`)}
value={values.value?.split(':')[1]} />
- }
- return <YupInput {...values} />;
- }
- case 'url': return <YupInputWithAddon {...values}
addon={`${backend.url}/private/instances/`} />;
- case 'secured': return <YupInputSecured {...values} />;
- case 'duration': return <YupInputWithAddon {...values}
addon={readableDuration(values.value?.d_ms)} atTheEnd
value={`${values.value?.d_ms / 1000 || ''}`} onChange={(v: string): void =>
values.onChange({ d_ms: (parseInt(v, 10) * 1000) || undefined } as any)} />;
- default: return <YupInput {...values} />;
-
- }
-}
diff --git a/packages/frontend/src/context/backend.ts
b/packages/frontend/src/context/backend.ts
index 31d9e62..896037b 100644
--- a/packages/frontend/src/context/backend.ts
+++ b/packages/frontend/src/context/backend.ts
@@ -28,8 +28,8 @@ export interface BackendContextType {
}
export interface ConfigContextType {
- currency?: string;
- version?: string;
+ currency: string;
+ version: string;
}
export interface InstanceContextType {
@@ -48,10 +48,7 @@ export const BackendContext =
createContext<BackendContextType>({
setLang: () => null,
})
-export const ConfigContext = createContext<ConfigContextType>({
- currency: undefined,
- version: undefined,
-})
+export const ConfigContext = createContext<ConfigContextType>(null!)
export const InstanceContext = createContext<InstanceContextType>({
id: '',
diff --git a/packages/frontend/src/hooks/backend.ts
b/packages/frontend/src/hooks/backend.ts
index e2b696b..96a1b73 100644
--- a/packages/frontend/src/hooks/backend.ts
+++ b/packages/frontend/src/hooks/backend.ts
@@ -85,6 +85,8 @@ interface BackendMutateAPI {
interface BackendInstaceMutateAPI {
updateInstance: (data:
MerchantBackend.Instances.InstanceReconfigurationMessage) => Promise<void>;
deleteInstance: () => Promise<void>;
+ clearToken: () => Promise<void>;
+ setNewToken: (token: string) => Promise<void>;
}
export function useBackendMutateAPI(): BackendMutateAPI {
@@ -127,7 +129,27 @@ export function useBackendInstanceMutateAPI():
BackendInstaceMutateAPI {
mutate(`/private/instances/${id}`, null)
}
- return { updateInstance, deleteInstance }
+ const clearToken = async (): Promise<void> => {
+ await request(`${url}/private/instances/${id}`, {
+ method: 'patch',
+ token,
+ data: { auth_token: null }
+ })
+
+ mutate(`/private/instances/${id}`, null)
+ }
+
+ const setNewToken = async (token: string): Promise<void> => {
+ await request(`${url}/private/instances/${id}`, {
+ method: 'patch',
+ token,
+ data: { auth_token: token }
+ })
+
+ mutate(`/private/instances/${id}`, null)
+ }
+
+ return { updateInstance, deleteInstance, setNewToken, clearToken }
}
export function useBackendInstances():
HttpResponse<MerchantBackend.Instances.InstancesResponse> {
diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx
index 6a6444f..335d9ba 100644
--- a/packages/frontend/src/index.tsx
+++ b/packages/frontend/src/index.tsx
@@ -95,7 +95,7 @@ function ApplicationStatusRoutes(): VNode {
const addTokenCleanerNemo = useCallback((c: () => void) => {
addTokenCleaner(c) }, [cleaner])
return <div id="app">
- <ConfigContext.Provider value={backendConfig.data || {}}>
+ <ConfigContext.Provider value={backendConfig.data || { currency: '',
version: '' }}>
<NavigationBar lang={lang} setLang={setLang} onLogout={() => {
cleaners.forEach(c => c()) }} />
<Sidebar />
<Notifications notifications={notifications}
removeNotification={removeNotification} />
diff --git a/packages/frontend/src/messages/en.po
b/packages/frontend/src/messages/en.po
index c8819bd..8ee89c4 100644
--- a/packages/frontend/src/messages/en.po
+++ b/packages/frontend/src/messages/en.po
@@ -151,6 +151,9 @@ msgstr "Update this instance"
msgid "Cancel"
msgstr "Cancel"
+msgid "Clear"
+msgstr "Clear"
+
msgid "Confirm"
msgstr "Confirm"
@@ -160,6 +163,16 @@ msgstr "English [en]"
msgid "es"
msgstr "EspaƱol [es]"
+
+msgid "fields.instance.old_token.label"
+msgstr "Old token"
+
+msgid "fields.instance.new_token.label"
+msgstr "New token"
+
+msgid "validations."
+msgstr "New token"
+
msgid "fields.instance.id.label"
msgstr "Id"
@@ -191,4 +204,3 @@ msgstr "Instance details"
-
diff --git a/packages/frontend/src/routes/instances/create/CreatePage.tsx
b/packages/frontend/src/routes/instances/create/CreatePage.tsx
index c779f94..b1531ce 100644
--- a/packages/frontend/src/routes/instances/create/CreatePage.tsx
+++ b/packages/frontend/src/routes/instances/create/CreatePage.tsx
@@ -20,15 +20,25 @@
*/
import { h, VNode } from "preact";
-import { useState } from "preact/hooks";
-import { MerchantBackend } from "../../../declaration";
+import { useContext, useState } from "preact/hooks";
+import { Amount, MerchantBackend, RelativeTime } from "../../../declaration";
import * as yup from 'yup';
-import { YupField } from "../../../components/yup/YupField"
+import { FormErrors, FormProvider } from "../../../components/form/Field"
import { InstanceCreateSchema as schema } from '../../../schemas'
import { Message } from "preact-messages";
+import { Input } from "../../../components/form/Input";
+import { InputSecured } from "../../../components/form/InputSecured";
+import { InputWithAddon } from "../../../components/form/InputWithAddon";
+import { InputGroup } from "../../../components/form/InputGroup";
+import { BackendContext, ConfigContext } from "../../../context/backend";
+import { InputArray } from "../../../components/form/InputArray";
+import { InputDuration } from "../../../components/form/InputDuration";
+import { InputCurrency } from "../../../components/form/InputCurrency";
+
+type Entity = MerchantBackend.Instances.InstanceConfigurationMessage
interface Props {
- onCreate: (d: MerchantBackend.Instances.InstanceConfigurationMessage) =>
void;
+ onCreate: (d: Entity) => void;
isLoading: boolean;
onBack: () => void;
}
@@ -37,7 +47,7 @@ interface KeyValue {
[key: string]: string;
}
-function with_defaults():
Partial<MerchantBackend.Instances.InstanceConfigurationMessage> {
+function with_defaults(): Partial<Entity> {
return {
default_pay_delay: { d_ms: 1000 },
default_wire_fee_amortization: 10,
@@ -47,12 +57,12 @@ function with_defaults():
Partial<MerchantBackend.Instances.InstanceConfiguratio
export function CreatePage({ onCreate, isLoading, onBack }: Props): VNode {
const [value, valueHandler] = useState(with_defaults())
- const [errors, setErrors] = useState<KeyValue>({})
+ const [errors, setErrors] = useState<FormErrors<Entity>>({})
const submit = (): void => {
try {
schema.validateSync(value, { abortEarly: false })
- onCreate(schema.cast(value) as
MerchantBackend.Instances.InstanceConfigurationMessage);
+ onCreate(schema.cast(value) as Entity);
onBack()
} catch (err) {
const errors = err.inner as yup.ValidationError[]
@@ -60,6 +70,9 @@ export function CreatePage({ onCreate, isLoading, onBack }:
Props): VNode {
setErrors(pathMessages)
}
}
+ const backend = useContext(BackendContext)
+ const config = useContext(ConfigContext)
+
return <div>
<section class="section is-title-bar">
@@ -96,11 +109,35 @@ export function CreatePage({ onCreate, isLoading, onBack
}: Props): VNode {
<div class="columns">
<div class="column" />
<div class="column is-two-thirds">
- {Object.keys(schema.fields)
- .map(f => <YupField name={f}
- field={f} errors={errors} object={value}
- valueHandler={valueHandler} info={schema.fields[f].describe()}
- />)}
+ <FormProvider<Entity> errors={errors} object={value}
valueHandler={valueHandler} >
+
+ <InputWithAddon<Entity> name="id"
addon={`${backend.url}/private/instances/`} />
+
+ <Input<Entity> name="name" />
+
+ <InputSecured<Entity> name="auth_token" />
+
+ <InputArray<Entity> name="payto_uris" />
+
+ <InputCurrency<Entity> name="default_max_deposit_fee"
currency={config.currency} />
+
+ <InputCurrency<Entity> name="default_max_wire_fee"
currency={config.currency} />
+
+ <Input<Entity> name="default_wire_fee_amortization" />
+
+ <InputGroup name="address">
+ <Input<Entity> name="name" />
+ </InputGroup>
+
+ <InputGroup name="jurisdiction">
+ <Input<Entity> name="name" />
+ </InputGroup>
+
+ <InputDuration<Entity> name="default_pay_delay" />
+
+ <InputDuration<Entity> name="default_wire_transfer_delay" />
+
+ </FormProvider>
<div class="buttons is-right">
<button class="button" onClick={onBack} ><Message id="Cancel"
/></button>
<button class="button is-success" onClick={submit} ><Message
id="Confirm" /></button>
diff --git a/packages/frontend/src/routes/instances/details/DetailPage.tsx
b/packages/frontend/src/routes/instances/details/DetailPage.tsx
index 908366c..9eccd99 100644
--- a/packages/frontend/src/routes/instances/details/DetailPage.tsx
+++ b/packages/frontend/src/routes/instances/details/DetailPage.tsx
@@ -22,10 +22,12 @@
import { h, VNode } from "preact";
import { useState } from "preact/hooks";
import { MerchantBackend } from "../../../declaration";
-import { YupField } from "../../../components/yup/YupField"
import { InstanceSchema as schema } from '../../../schemas'
import { Message } from "preact-messages";
+import { Input } from "../../../components/form/Input";
+import { FormProvider } from "../../../components/form/Field";
+type Entity = MerchantBackend.Instances.InstanceReconfigurationMessage;
interface Props {
onUpdate: () => void;
onDelete: () => void;
@@ -37,7 +39,7 @@ interface KeyValue {
[key: string]: string;
}
-function convert(from: MerchantBackend.Instances.QueryInstancesResponse):
MerchantBackend.Instances.InstanceReconfigurationMessage {
+function convert(from: MerchantBackend.Instances.QueryInstancesResponse):
Entity {
const { accounts, ...rest } = from
const payto_uris = accounts.filter(a => a.active).map(a => a.payto_uri)
const defaults = {
@@ -49,7 +51,7 @@ function convert(from:
MerchantBackend.Instances.QueryInstancesResponse): Mercha
}
export function DetailPage({ onUpdate, isLoading, selected, onDelete }:
Props): VNode {
- const [value, valueHandler] = useState(convert(selected))
+ const [value, valueHandler] = useState<Partial<Entity>>(convert(selected))
const [errors, setErrors] = useState<KeyValue>({})
return <div>
@@ -88,12 +90,12 @@ export function DetailPage({ onUpdate, isLoading, selected,
onDelete }: Props):
<div class="columns">
<div class="column" />
<div class="column is-6">
- {Object.keys(schema.pick(['name','address']).fields)
- .map(f => <YupField name={f}
- field={f} errors={errors} object={value}
- valueHandler={valueHandler} info={schema.fields[f].describe()}
- readonly
- />)}
+ <FormProvider<Entity> errors={errors} object={value}
valueHandler={valueHandler} >
+
+ <Input<Entity> name="name" readonly />
+ <Input<Entity> name="payto_uris" readonly />
+
+ </FormProvider>
<div class="buttons is-right">
<button class="button is-danger" onClick={() => onDelete()} >
<span class="icon"><i class="mdi mdi-delete" /></span>
diff --git a/packages/frontend/src/routes/instances/details/index.tsx
b/packages/frontend/src/routes/instances/details/index.tsx
index d16565b..81c51d0 100644
--- a/packages/frontend/src/routes/instances/details/index.tsx
+++ b/packages/frontend/src/routes/instances/details/index.tsx
@@ -18,8 +18,8 @@ import { useContext, useState } from "preact/hooks";
import { InstanceContext } from "../../../context/backend";
import { Notification } from "../../../utils/types";
import { useBackendInstance, useBackendInstanceMutateAPI, SwrError } from
"../../../hooks/backend";
-import { DeleteModal } from "../list/DeleteModal";
import { DetailPage } from "./DetailPage";
+import { DeleteModal } from "../../../components/modal";
interface Props {
onUnauthorized: () => VNode;
diff --git a/packages/frontend/src/routes/instances/list/CardTable.tsx
b/packages/frontend/src/routes/instances/list/CardTable.tsx
deleted file mode 100644
index 53a6f54..0000000
--- a/packages/frontend/src/routes/instances/list/CardTable.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- 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 { h, VNode } from "preact";
-import { Message } from "preact-messages";
-import { useEffect, useState } from "preact/hooks";
-import { MerchantBackend } from "../../../declaration";
-import { EmptyTable } from "./EmptyTable";
-import { Table } from "./Table";
-
-interface Props {
- instances: MerchantBackend.Instances.Instance[];
- onUpdate: (id: string) => void;
- onDelete: (id: MerchantBackend.Instances.Instance) => void;
- onCreate: () => void;
- selected?: boolean;
-}
-
-interface Actions {
- element: MerchantBackend.Instances.Instance;
- type: 'DELETE' | 'UPDATE';
-}
-
-function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
- return value !== null && value !== undefined;
-}
-
-function buildActions(intances: MerchantBackend.Instances.Instance[],
selected: string[], action: 'DELETE'): Actions[] {
- return selected.map(id => intances.find(i => i.id === id))
- .filter(notEmpty)
- .map(id => ({ element: id, type: action }))
-}
-
-export function CardTable({ instances, onCreate, onUpdate, onDelete, selected
}: Props): VNode {
- const [actionQueue, actionQueueHandler] = useState<Actions[]>([]);
- const [rowSelection, rowSelectionHandler] = useState<string[]>([])
-
- useEffect(() => {
- if (actionQueue.length > 0 && !selected && actionQueue[0].type ==
'DELETE') {
- onDelete(actionQueue[0].element)
- actionQueueHandler(actionQueue.slice(1))
- }
- }, [actionQueue, selected, onDelete])
-
- useEffect(() => {
- if (actionQueue.length > 0 && !selected && actionQueue[0].type ==
'UPDATE') {
- onUpdate(actionQueue[0].element.id)
- actionQueueHandler(actionQueue.slice(1))
- }
- }, [actionQueue, selected, onUpdate])
-
-
- return <div class="card has-table">
- <header class="card-header">
- <p class="card-header-title"><span class="icon"><i class="mdi
mdi-account-multiple" /></span><Message id="Instances" /></p>
-
- <div class="card-header-icon" aria-label="more options">
-
- <button class={rowSelection.length > 0 ? "button is-danger" :
"is-hidden"}
- type="button" onClick={(): void =>
actionQueueHandler(buildActions(instances, rowSelection, 'DELETE'))} >
- <span class="icon"><i class="mdi mdi-trash-can" /></span>
- </button>
- </div>
- <div class="card-header-icon" aria-label="more options">
- <button class="button is-info" type="button" onClick={onCreate}>
- <span class="icon is-small" ><i class="mdi mdi-plus mdi-36px"
/></span>
- </button>
- </div>
-
- </header>
- <div class="card-content">
- <div class="b-table has-pagination">
- <div class="table-wrapper has-mobile-cards">
- {instances.length > 0 ?
- <Table instances={instances} onUpdate={onUpdate}
onDelete={onDelete} rowSelection={rowSelection}
rowSelectionHandler={rowSelectionHandler} /> :
- <EmptyTable />
- }
- </div>
- </div>
- </div>
- </div>
-}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instances/list/EmptyTable.tsx
b/packages/frontend/src/routes/instances/list/EmptyTable.tsx
deleted file mode 100644
index e1de4da..0000000
--- a/packages/frontend/src/routes/instances/list/EmptyTable.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- 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 { h, VNode } from "preact";
-import { Message, useMessageTemplate } from "preact-messages";
-
-export function EmptyTable(): VNode {
- return <div class="content has-text-grey has-text-centered">
- <p>
- <span class="icon is-large"><i class="mdi mdi-emoticon-sad mdi-48px"
/></span>
- </p>
- <p><Message id="There is no instances yet, add more pressing the + sign"
/></p>
- </div>
-}
diff --git a/packages/frontend/src/routes/instances/list/Table.tsx
b/packages/frontend/src/routes/instances/list/Table.tsx
index 961832e..507bd2f 100644
--- a/packages/frontend/src/routes/instances/list/Table.tsx
+++ b/packages/frontend/src/routes/instances/list/Table.tsx
@@ -21,10 +21,67 @@
import { h, VNode } from "preact"
import { Message } from "preact-messages"
-import { StateUpdater } from "preact/hooks"
+import { StateUpdater, useEffect, useState } from "preact/hooks"
import { MerchantBackend } from "../../../declaration"
interface Props {
+ instances: MerchantBackend.Instances.Instance[];
+ onUpdate: (id: string) => void;
+ onDelete: (id: MerchantBackend.Instances.Instance) => void;
+ onCreate: () => void;
+ selected?: boolean;
+}
+
+export function CardTable({ instances, onCreate, onUpdate, onDelete, selected
}: Props): VNode {
+ const [actionQueue, actionQueueHandler] = useState<Actions[]>([]);
+ const [rowSelection, rowSelectionHandler] = useState<string[]>([])
+
+ useEffect(() => {
+ if (actionQueue.length > 0 && !selected && actionQueue[0].type ==
'DELETE') {
+ onDelete(actionQueue[0].element)
+ actionQueueHandler(actionQueue.slice(1))
+ }
+ }, [actionQueue, selected, onDelete])
+
+ useEffect(() => {
+ if (actionQueue.length > 0 && !selected && actionQueue[0].type ==
'UPDATE') {
+ onUpdate(actionQueue[0].element.id)
+ actionQueueHandler(actionQueue.slice(1))
+ }
+ }, [actionQueue, selected, onUpdate])
+
+
+ return <div class="card has-table">
+ <header class="card-header">
+ <p class="card-header-title"><span class="icon"><i class="mdi
mdi-account-multiple" /></span><Message id="Instances" /></p>
+
+ <div class="card-header-icon" aria-label="more options">
+
+ <button class={rowSelection.length > 0 ? "button is-danger" :
"is-hidden"}
+ type="button" onClick={(): void =>
actionQueueHandler(buildActions(instances, rowSelection, 'DELETE'))} >
+ <span class="icon"><i class="mdi mdi-trash-can" /></span>
+ </button>
+ </div>
+ <div class="card-header-icon" aria-label="more options">
+ <button class="button is-info" type="button" onClick={onCreate}>
+ <span class="icon is-small" ><i class="mdi mdi-plus mdi-36px"
/></span>
+ </button>
+ </div>
+
+ </header>
+ <div class="card-content">
+ <div class="b-table has-pagination">
+ <div class="table-wrapper has-mobile-cards">
+ {instances.length > 0 ?
+ <Table instances={instances} onUpdate={onUpdate}
onDelete={onDelete} rowSelection={rowSelection}
rowSelectionHandler={rowSelectionHandler} /> :
+ <EmptyTable />
+ }
+ </div>
+ </div>
+ </div>
+ </div>
+}
+interface TableProps {
rowSelection: string[];
instances: MerchantBackend.Instances.Instance[];
onUpdate: (id: string) => void;
@@ -36,7 +93,7 @@ function toggleSelected<T>(id: T): (prev: T[]) => T[] {
return (prev: T[]): T[] => prev.indexOf(id) == -1 ? [...prev, id] :
prev.filter(e => e != id)
}
-export function Table({ rowSelection, rowSelectionHandler, instances,
onUpdate, onDelete }: Props): VNode {
+function Table({ rowSelection, rowSelectionHandler, instances, onUpdate,
onDelete }: TableProps): VNode {
return (
<table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
<thead>
@@ -82,4 +139,31 @@ export function Table({ rowSelection, rowSelectionHandler,
instances, onUpdate,
</tbody>
</table>)
-}
\ No newline at end of file
+}
+
+function EmptyTable(): VNode {
+ return <div class="content has-text-grey has-text-centered">
+ <p>
+ <span class="icon is-large"><i class="mdi mdi-emoticon-sad mdi-48px"
/></span>
+ </p>
+ <p><Message id="There is no instances yet, add more pressing the + sign"
/></p>
+ </div>
+}
+
+
+interface Actions {
+ element: MerchantBackend.Instances.Instance;
+ type: 'DELETE' | 'UPDATE';
+}
+
+function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
+ return value !== null && value !== undefined;
+}
+
+function buildActions(intances: MerchantBackend.Instances.Instance[],
selected: string[], action: 'DELETE'): Actions[] {
+ return selected.map(id => intances.find(i => i.id === id))
+ .filter(notEmpty)
+ .map(id => ({ element: id, type: action }))
+}
+
+
diff --git a/packages/frontend/src/routes/instances/list/View.tsx
b/packages/frontend/src/routes/instances/list/View.tsx
index f54e64a..d224980 100644
--- a/packages/frontend/src/routes/instances/list/View.tsx
+++ b/packages/frontend/src/routes/instances/list/View.tsx
@@ -21,7 +21,7 @@
import { h, VNode } from "preact";
import { MerchantBackend } from "../../../declaration";
-import { CardTable } from './CardTable';
+import { CardTable } from './Table';
import { Message } from "preact-messages";
interface Props {
diff --git a/packages/frontend/src/routes/instances/list/index.tsx
b/packages/frontend/src/routes/instances/list/index.tsx
index 76947d7..274d707 100644
--- a/packages/frontend/src/routes/instances/list/index.tsx
+++ b/packages/frontend/src/routes/instances/list/index.tsx
@@ -25,7 +25,8 @@ import { useBackendInstances, useBackendInstanceMutateAPI,
SwrError } from '../.
import { useState } from 'preact/hooks';
import { MerchantBackend } from '../../../declaration';
import { Notification } from '../../../utils/types';
-import { DeleteModal } from './DeleteModal';
+import { DeleteModal } from '../../../components/modal';
+
interface Props {
pushNotification: (n: Notification) => void;
onUnauthorized: () => VNode;
diff --git a/packages/frontend/src/routes/instances/update/UpdatePage.tsx
b/packages/frontend/src/routes/instances/update/UpdatePage.tsx
index 8cbd2d6..5c81d68 100644
--- a/packages/frontend/src/routes/instances/update/UpdatePage.tsx
+++ b/packages/frontend/src/routes/instances/update/UpdatePage.tsx
@@ -20,25 +20,33 @@
*/
import { h, VNode } from "preact";
-import { useState } from "preact/hooks";
-import { MerchantBackend } from "../../../declaration";
+import { useContext, useState } from "preact/hooks";
+import { Amount, MerchantBackend, RelativeTime } from "../../../declaration";
import * as yup from 'yup';
-import { YupField } from "../../../components/yup/YupField"
+import { FormProvider, FormErrors } from "../../../components/form/Field"
+import { InputGroup } from "../../../components/form/InputGroup"
+
import { InstanceUpdateSchema as schema } from '../../../schemas'
import { Message } from "preact-messages";
+import { Input } from "../../../components/form/Input";
+import { InputSecured } from "../../../components/form/InputSecured";
+import { InputWithAddon } from "../../../components/form/InputWithAddon";
+import { BackendContext, ConfigContext } from "../../../context/backend";
+import { intervalToDuration, formatDuration } from 'date-fns'
+import { InputArray } from "../../../components/form/InputArray";
+import { InputDuration } from "../../../components/form/InputDuration";
+import { InputCurrency } from "../../../components/form/InputCurrency";
+
+type Entity = MerchantBackend.Instances.InstanceReconfigurationMessage
interface Props {
- onUpdate: (d: MerchantBackend.Instances.InstanceReconfigurationMessage) =>
void;
+ onUpdate: (d: Entity) => void;
selected: MerchantBackend.Instances.QueryInstancesResponse;
isLoading: boolean;
onBack: () => void;
}
-interface KeyValue {
- [key: string]: string;
-}
-
-function convert(from: MerchantBackend.Instances.QueryInstancesResponse):
MerchantBackend.Instances.InstanceReconfigurationMessage {
+function convert(from: MerchantBackend.Instances.QueryInstancesResponse):
Entity {
const { accounts, ...rest } = from
const payto_uris = accounts.filter(a => a.active).map(a => a.payto_uri)
const defaults = {
@@ -49,9 +57,11 @@ function convert(from:
MerchantBackend.Instances.QueryInstancesResponse): Mercha
return { ...defaults, ...rest, payto_uris };
}
+
+
export function UpdatePage({ onUpdate, isLoading, selected, onBack }: Props):
VNode {
- const [value, valueHandler] = useState(convert(selected))
- const [errors, setErrors] = useState<KeyValue>({})
+ const [value, valueHandler] = useState<Partial<Entity>>(convert(selected))
+ const [errors, setErrors] = useState<FormErrors<Entity>>({})
const submit = (): void => {
try {
@@ -64,6 +74,7 @@ export function UpdatePage({ onUpdate, isLoading, selected,
onBack }: Props): VN
setErrors(pathMessages)
}
}
+ const config = useContext(ConfigContext)
return <div>
<section class="section is-title-bar">
@@ -101,12 +112,35 @@ export function UpdatePage({ onUpdate, isLoading,
selected, onBack }: Props): VN
<div class="columns">
<div class="column" />
<div class="column is-two-thirds">
- {Object.keys(schema.fields)
- .map(f => <YupField name={f}
- field={f} errors={errors} object={value}
- valueHandler={valueHandler} info={schema.fields[f].describe()}
- />)}
- <div class="buttons is-right">
+ <FormProvider<Entity> errors={errors} object={value}
valueHandler={valueHandler} >
+
+ <Input<Entity> name="name" />
+
+ <InputSecured<Entity> name="auth_token" />
+
+ <InputArray<Entity> name="payto_uris" />
+
+ <InputCurrency<Entity> name="default_max_deposit_fee"
currency={config.currency} />
+
+ <InputCurrency<Entity> name="default_max_wire_fee"
currency={config.currency} />
+
+ <Input<Entity> name="default_wire_fee_amortization" />
+
+ <InputGroup name="address">
+ <Input<Entity> name="name" />
+ </InputGroup>
+
+ <InputGroup name="jurisdiction">
+ <Input<Entity> name="name" />
+ </InputGroup>
+
+ <InputDuration<Entity> name="default_pay_delay" />
+
+ <InputDuration<Entity> name="default_wire_transfer_delay" />
+
+ </FormProvider>
+
+ <div class="buttons is-right">
<button class="button" onClick={onBack} ><Message id="Cancel"
/></button>
<button class="button is-success" onClick={submit} ><Message
id="Confirm" /></button>
</div>
@@ -117,5 +151,4 @@ export function UpdatePage({ onUpdate, isLoading, selected,
onBack }: Props): VN
</div>
- // </ConfirmModal>
-}
\ No newline at end of file
+}
diff --git a/packages/frontend/src/routes/instances/update/index.tsx
b/packages/frontend/src/routes/instances/update/index.tsx
index f27908c..aea77f9 100644
--- a/packages/frontend/src/routes/instances/update/index.tsx
+++ b/packages/frontend/src/routes/instances/update/index.tsx
@@ -13,7 +13,10 @@
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/>
*/
-import { h, VNode } from "preact";
+import { Fragment, h, VNode } from "preact";
+import { useContext, useState } from "preact/hooks";
+import { UpdateTokenModal } from "../../../components/modal";
+import { InstanceContext } from "../../../context/backend";
import { MerchantBackend } from "../../../declaration";
import { SwrError, useBackendInstance, useBackendInstanceMutateAPI } from
"../../../hooks/backend";
import { UpdatePage } from "./UpdatePage";
@@ -30,22 +33,34 @@ interface Props {
}
export default function Update({ onBack, onConfirm, onLoadError,
onUpdateError, onUnauthorized }: Props): VNode {
- const { updateInstance } = useBackendInstanceMutateAPI();
+ const { updateInstance, setNewToken, clearToken } =
useBackendInstanceMutateAPI();
+ const [updatingToken, setUpdatingToken] = useState<string | null>(null)
const details = useBackendInstance()
+ const { id } = useContext(InstanceContext)
if (!details.data) {
if (details.unauthorized) return onUnauthorized()
if (details.error) return onLoadError(details.error)
return <div>
loading ....
- </div>
+ </div>
}
- return <UpdatePage
- onBack={onBack}
- isLoading={false}
- selected={details.data}
- onUpdate={(d: MerchantBackend.Instances.InstanceReconfigurationMessage):
Promise<void> => {
- return updateInstance(d).then(onConfirm).catch(onUpdateError)
- }} />
+ return <Fragment>
+ <UpdatePage
+ onBack={onBack}
+ isLoading={false}
+ selected={details.data}
+ onUpdate={(d: MerchantBackend.Instances.InstanceReconfigurationMessage):
Promise<void> => {
+ return updateInstance(d).then(onConfirm).catch(onUpdateError)
+ }} />
+ <input type="checkbox" checked={updatingToken !== null} onClick={() =>
setUpdatingToken(!updatingToken ? "caca": null)} />
+ {updatingToken && <UpdateTokenModal
+ oldToken={updatingToken}
+ element={{ id, name: details.data.name }}
+ onCancel={() => setUpdatingToken(null)}
+ onClear={() => clearToken()}
+ onConfirm={(newToken) => setNewToken(newToken) }
+ />}
+ </Fragment>
}
\ 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: refactor YUP, just using it to build errors object,
gnunet <=