gnunet-svn
[Top][All Lists]
Advanced

[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.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]