gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant-backoffice] branch master updated (e76acb3 -> 43ca7d9)


From: gnunet
Subject: [taler-merchant-backoffice] branch master updated (e76acb3 -> 43ca7d9)
Date: Wed, 10 Mar 2021 19:47:52 +0100

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

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

    from e76acb3  skeletons for order, products, transfers and tips
     new 224cbe6  updated form for deep object
     new 43ca7d9  tie title to the path #6790

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


Summary of changes:
 CHANGELOG.md                                       |   4 +-
 packages/frontend/package.json                     |   2 +-
 packages/frontend/src/AdminRoutes.tsx              |  34 +++---
 packages/frontend/src/ApplicationReadyRoutes.tsx   |   4 +-
 packages/frontend/src/InstanceRoutes.tsx           |  21 +++-
 packages/frontend/src/components/form/Field.tsx    |  23 +++-
 packages/frontend/src/components/form/Input.tsx    |  24 +++-
 .../frontend/src/components/form/InputArray.tsx    |  20 ++--
 .../frontend/src/components/form/InputCurrency.tsx |   5 +-
 .../frontend/src/components/form/InputDuration.tsx |   6 +-
 .../frontend/src/components/form/InputPayto.tsx    |   2 +-
 .../frontend/src/components/form/InputSecured.tsx  | 125 +++++++++++----------
 .../src/components/form/InputWithAddon.tsx         |  10 +-
 packages/frontend/src/components/menu/index.tsx    |  59 ++++++++--
 packages/frontend/src/index.tsx                    |  39 +------
 packages/frontend/src/routes/admin/list/Table.tsx  |   7 +-
 .../src/routes/instance/update/UpdatePage.tsx      |  43 +++++--
 .../frontend/src/routes/instance/update/index.tsx  |  14 +--
 18 files changed, 271 insertions(+), 171 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index b0c25c4..f2e3fe2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,9 +21,7 @@ and this project adheres to [Semantic 
Versioning](https://semver.org/spec/v2.0.0
  - prune scss styles to reduce size
  - create a loading page to be use when the data is not ready
  - some way to copy the url of a created instance
- - change the admin title to "instances" if we are listing the instances and 
"settings: $ID" on updating instances
  - fix mobile: some things are still on the left
- - update title with: Taler Backoffice: $PAGE_TITLE
  - instance id in instance list should be clickable
  - edit button to go to instance settings
  - notifications should tale place between title and content, and not disapear
@@ -37,6 +35,8 @@ and this project adheres to [Semantic 
Versioning](https://semver.org/spec/v2.0.0
  - add transfers section
  - initial state before login
  - logout takes you to a initial state, not showing error messages
+ - change the admin title to "instances" if we are listing the instances and 
"settings: $ID" on updating instances
+ - update title with: Taler Backoffice: $PAGE_TITLE
 
 ## [0.0.3] - 2021-03-04
  - submit form on key press == enter
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index f9d6bd1..5cbab9e 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -1,7 +1,7 @@
 {
   "private": true,
   "name": "merchant-backoffice",
-  "version": "0.0.1",
+  "version": "0.0.4",
   "license": "MIT",
   "scripts": {
     "build": "preact build --no-sw --no-esm",
diff --git a/packages/frontend/src/AdminRoutes.tsx 
b/packages/frontend/src/AdminRoutes.tsx
index 6b63239..b5631f5 100644
--- a/packages/frontend/src/AdminRoutes.tsx
+++ b/packages/frontend/src/AdminRoutes.tsx
@@ -15,11 +15,11 @@
  */
 import { h, VNode } from "preact";
 import Router, { route, Route } from "preact-router";
-import { RootPaths, Redirect, InstancePaths } from "./index";
+import { Redirect } from "./index";
 import { MerchantBackend } from "./declaration";
 import { useMessageTemplate } from "preact-messages";
 import { Notification } from "./utils/types";
-import { InstanceRoutes } from "./InstanceRoutes";
+import { InstancePaths, InstanceRoutes } from "./InstanceRoutes";
 
 import InstanceListPage from './routes/admin/list';
 import InstanceCreatePage from "./routes/admin/create";
@@ -42,6 +42,13 @@ import TransferCreatePage from 
'./routes/instance/transfers/create'
 import LoginPage from "./routes/login";
 import { SwrError } from "./hooks/backend";
 
+export enum AdminPaths {
+  root = '/',
+  list_instances = '/instances',
+  new_instance = '/new',
+  instance_id_route = '/instance/:id/:rest*',
+}
+
 interface Props {
   pushNotification: (n: Notification) => void;
   instances: MerchantBackend.Instances.Instance[]
@@ -55,12 +62,12 @@ export function AdminRoutes({ instances, pushNotification, 
addTokenCleaner }: Pr
   const updateLoginStatus = () => null;
 
   return <Router>
-    <Route path={RootPaths.root} component={Redirect} 
to={RootPaths.list_instances} />
+    <Route path={AdminPaths.root} component={Redirect} 
to={AdminPaths.list_instances} />
 
-    <Route path={RootPaths.list_instances} component={InstanceListPage}
+    <Route path={AdminPaths.list_instances} component={InstanceListPage}
 
       onCreate={() => {
-        route(RootPaths.new_instance);
+        route(AdminPaths.new_instance);
       }}
 
       instances={instances}
@@ -69,31 +76,24 @@ export function AdminRoutes({ instances, pushNotification, 
addTokenCleaner }: Pr
         route(`/instance/${id}/update`);
       }}
 
-    // onUnauthorized={() => <LoginPage
-    //   withMessage={{ message: i18n`Access denied`, description: i18n`Check 
your token is valid`, type: 'ERROR', }}
-    //   onConfirm={updateLoginStatus} />}
-
-    // onQueryError={(error: SwrError) => <LoginPage
-    //   withMessage={{ message: i18n`Problem reaching the server`, 
description: i18n`Got message: ${error.message} from: ${error.backend} 
(hasToken: ${error.hasToken})`, type: 'ERROR', }}
-    //   onConfirm={updateLoginStatus} />}
-
     />
 
-    <Route path={RootPaths.new_instance} component={InstanceCreatePage}
+    <Route path={AdminPaths.new_instance} component={InstanceCreatePage}
 
-      onBack={() => route(RootPaths.list_instances)}
+      onBack={() => route(AdminPaths.list_instances)}
 
       onConfirm={() => {
         pushNotification({ message: i18n`create_success`, type: 'SUCCESS' });
-        route(RootPaths.list_instances);
+        route(AdminPaths.list_instances);
       }}
 
       onError={(error: any) => {
         pushNotification({ message: i18n`create_error`, type: 'ERROR' });
       }}
+      
     />
 
-    <Route path={RootPaths.instance_id_route} component={InstanceRoutes}
+    <Route path={AdminPaths.instance_id_route} component={InstanceRoutes}
       pushNotification={pushNotification}
       addTokenCleaner={addTokenCleaner}
       parent="/instance/:id"
diff --git a/packages/frontend/src/ApplicationReadyRoutes.tsx 
b/packages/frontend/src/ApplicationReadyRoutes.tsx
index 0b0fc92..446cd56 100644
--- a/packages/frontend/src/ApplicationReadyRoutes.tsx
+++ b/packages/frontend/src/ApplicationReadyRoutes.tsx
@@ -47,7 +47,7 @@ export function ApplicationReadyRoutes({ pushNotification, 
addTokenCleaner, clea
   if (!list.data) {
     if (list.unauthorized) {
       return <Fragment>
-        <Menu onLogout={() => {
+        <Menu title="Login" onLogout={() => {
           clearAllTokens();
           route('/')
         }} />
@@ -78,7 +78,7 @@ export function ApplicationReadyRoutes({ pushNotification, 
addTokenCleaner, clea
         // query to /config is ok but the URL
         // doest not match with our pattern
         return <Fragment>
-          <Menu onLogout={() => {
+          <Menu title="Error" onLogout={() => {
             clearAllTokens();
             route('/')
           }} />
diff --git a/packages/frontend/src/InstanceRoutes.tsx 
b/packages/frontend/src/InstanceRoutes.tsx
index 175b465..a243fd6 100644
--- a/packages/frontend/src/InstanceRoutes.tsx
+++ b/packages/frontend/src/InstanceRoutes.tsx
@@ -26,7 +26,6 @@ import { useMessageTemplate } from 'preact-messages';
 import { useBackendInstanceToken } from './hooks';
 import { InstanceContextProvider, useBackendContext } from './context/backend';
 import { SwrError } from "./hooks/backend";
-import { InstancePaths } from "./index";
 import { Notification } from './utils/types';
 
 import LoginPage from './routes/login';
@@ -49,6 +48,26 @@ import TipUpdatePage from './routes/instance/tips/update'
 import TransferListPage from './routes/instance/transfers/list'
 import TransferCreatePage from './routes/instance/transfers/create'
 
+export enum InstancePaths {
+  details = '/',
+  update = '/update',
+
+  product_list = '/p',
+  product_update = '/p/:pid/update',
+  product_new = '/p/new',
+
+  order_list = '/o',
+  order_update = '/p/:oid/update',
+  order_new = '/o/new',
+
+  tips_list = '/r',
+  tips_update = '/r/:rid/update',
+  tips_new = '/r/new',
+
+  transfers_list = '/t',
+  transfers_new = '/t/new',
+}
+
 export interface Props {
   id: string;
   pushNotification: (n: Notification) => void;
diff --git a/packages/frontend/src/components/form/Field.tsx 
b/packages/frontend/src/components/form/Field.tsx
index 1561459..43c04f7 100644
--- a/packages/frontend/src/components/form/Field.tsx
+++ b/packages/frontend/src/components/form/Field.tsx
@@ -74,14 +74,31 @@ export function FormProvider<T>({ object = {}, errors = {}, 
valueHandler, childr
   </FormContext.Provider>
 }
 
+/**
+ * read the field of an object an support accesing it using '.'
+ * 
+ * @param object 
+ * @param name 
+ * @returns 
+ */
+const readField = (object: any, name: string) => {
+  return name.split('.').reduce((prev, current) => prev[current], object)
+}
+
+const setValueDeeper = (object: any, names: string[], value: any): any => {
+  if (names.length === 0) return value
+  const [head, ...rest] = names
+  return {...object, [head]: setValueDeeper(object[head], rest, value) }
+}
+
 export function useField<T>(name: keyof T) {
   const { errors, object, initial, toStr, fromStr, valueHandler } = 
useContext<FormType<T>>(FormContext)
   type P = typeof name
   type V = T[P]
 
-  const updateField = (f: P) => (v: V): void => {
+  const updateField = (field: P) => (value: V): void => {
     return valueHandler((prev) => {
-      return ({ ...prev, [f]: v })
+      return setValueDeeper(prev, String(field).split('.'), value)
     })
   }
   
@@ -90,7 +107,7 @@ export function useField<T>(name: keyof T) {
     
   return {
     error: errors[name],
-    value: object[name],
+    value: readField(object, String(name)),
     initial: initial[name],
     onChange: updateField(name),
     toStr: toStr[name] ? toStr[name]! : defaultToString,
diff --git a/packages/frontend/src/components/form/Input.tsx 
b/packages/frontend/src/components/form/Input.tsx
index 32afa6f..565e17b 100644
--- a/packages/frontend/src/components/form/Input.tsx
+++ b/packages/frontend/src/components/form/Input.tsx
@@ -25,10 +25,21 @@ import { useField } from "./Field";
 interface Props<T> {
   name: T;
   readonly?: boolean;
+  inputType?: 'text' | 'number' | 'multiline';
+  expand?: boolean;
+  toStr?: (v?: any) => string;
+  fromStr?: (s: string) => any;
 }
 
-export function Input<T>({ name, readonly }: Props<keyof T>): VNode {
-  const { error, value, onChange, toStr, fromStr } = useField<T>(name);
+const defaultToString = (f?: any): string => f || ''
+const defaultFromString = (v: string): any => v as any
+
+const TextInput = ({inputType, error, ...rest}:any) => inputType === 
'multiline' ? 
+  <textarea {...rest} class={error ? "textarea is-danger" : "textarea"} 
rows="3"  /> : 
+  <input {...rest} class={error ? "input is-danger" : "input"} 
type={inputType} />;
+
+export function Input<T>({ name, readonly, expand, inputType, fromStr = 
defaultFromString, toStr = defaultToString }: Props<keyof T>): VNode {
+  const { error, value, onChange } = useField<T>(name);
 
   const placeholder = useMessage(`fields.instance.${name}.placeholder`);
   const tooltip = useMessage(`fields.instance.${name}.tooltip`);
@@ -42,13 +53,14 @@ export function Input<T>({ name, readonly }: Props<keyof 
T>): VNode {
         </span>}
       </label>
     </div>
-    <div class="field-body">
+    <div class="field-body is-flex-grow-3">
       <div class="field">
-        <p class="control">
-          <input class={error ? "input is-danger" : "input"} type="text"
+      <p class={ expand ? "control is-expanded" : "control" }>
+          <TextInput error={error}
+            inputType={inputType}
             placeholder={placeholder} readonly={readonly}
             name={String(name)} value={toStr(value)} disabled={readonly}
-            onChange={(e): void => onChange(fromStr(e.currentTarget.value))} />
+            onChange={(e:h.JSX.TargetedEvent<HTMLInputElement>): void => 
onChange(fromStr(e.currentTarget.value))} />
           <Message id={`fields.instance.${name}.help`}> </Message>
         </p>
         {error ? <p class="help is-danger">
diff --git a/packages/frontend/src/components/form/InputArray.tsx 
b/packages/frontend/src/components/form/InputArray.tsx
index 47f6bea..80feec8 100644
--- a/packages/frontend/src/components/form/InputArray.tsx
+++ b/packages/frontend/src/components/form/InputArray.tsx
@@ -26,7 +26,7 @@ import { FormErrors, useField, ValidationError } from 
"./Field";
 export interface Props<T> {
   name: T;
   readonly?: boolean;
-  isValid: (e: any) => boolean;
+  isValid?: (e: any) => boolean;
   addonBefore?: string;
   toStr?: (v?: any) => string;
   fromStr?: (s: string) => any;
@@ -35,7 +35,7 @@ export interface Props<T> {
 const defaultToString = (f?: any): string => f || ''
 const defaultFromString = (v: string): any => v as any
 
-export function InputArray<T>({ name, readonly, addonBefore, isValid, fromStr 
= defaultFromString, toStr = defaultToString }: Props<keyof T>): VNode {
+export function InputArray<T>({ name, readonly, addonBefore, isValid = () => 
true, fromStr = defaultFromString, toStr = defaultToString }: Props<keyof T>): 
VNode {
   const { error: formError, value, onChange } = useField<T>(name);
   const [localError, setLocalError] = useState<ValidationError | null>(null)
   const placeholder = useMessage(`fields.instance.${name}.placeholder`);
@@ -56,13 +56,13 @@ export function InputArray<T>({ name, readonly, 
addonBefore, isValid, fromStr =
         </span>}
       </label>
     </div>
-    <div class="field-body">
+    <div class="field-body is-flex-grow-3">
       <div class="field">
         <div class="field has-addons">
           {addonBefore && <div class="control">
             <a class="button is-static">{addonBefore}</a>
           </div>}
-          <p class="control">
+          <p class="control is-expanded">
             <input class={error ? "input is-danger" : "input"} type="text"
               placeholder={placeholder} readonly={readonly} disabled={readonly}
               name={String(name)} value={currentValue}
@@ -75,9 +75,9 @@ export function InputArray<T>({ name, readonly, addonBefore, 
isValid, fromStr =
               if (!isValid(v)) {
                 setLocalError({ message: i18n`The value ${v} is invalid for a 
payment url` })
                 return;
-              } 
-                setLocalError(null)
-              
+              }
+              setLocalError(null)
+
               onChange([v, ...array] as any);
               setCurrentValue('');
             }}>add</button>
@@ -86,9 +86,9 @@ export function InputArray<T>({ name, readonly, addonBefore, 
isValid, fromStr =
         {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" 
style={{maxWidth:'90%'}}>{v}</span>
-          <a class="tag is-medium is-danger is-delete" onClick={() => {
+        {array.map(v => <div class="tags has-addons mt-3 mb-0">
+          <span class="tag is-medium is-info mb-0" style={{ maxWidth: '90%' 
}}>{v}</span>
+          <a class="tag is-medium is-danger is-delete mb-0" onClick={() => {
             onChange(array.filter(f => f !== v) as any);
             setCurrentValue(toStr(v));
           }} />
diff --git a/packages/frontend/src/components/form/InputCurrency.tsx 
b/packages/frontend/src/components/form/InputCurrency.tsx
index 9b425c8..72334b7 100644
--- a/packages/frontend/src/components/form/InputCurrency.tsx
+++ b/packages/frontend/src/components/form/InputCurrency.tsx
@@ -21,16 +21,17 @@
 import { h } from "preact";
 import { Amount } from "../../declaration";
 import { InputWithAddon } from "./InputWithAddon";
-import { useField } from "./Field";
 
 export interface Props<T> {
   name: keyof T;
   readonly?: boolean;
+  expand?: boolean;
   currency: string;
 }
 
-export function InputCurrency<T>({ name, readonly, currency }: Props<T>) {
+export function InputCurrency<T>({ name, readonly, expand, currency }: 
Props<T>) {
   return <InputWithAddon<T> name={name} readonly={readonly} 
addonBefore={currency}
+    inputType='number' expand={expand}
     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
index a08719a..18b55a3 100644
--- a/packages/frontend/src/components/form/InputDuration.tsx
+++ b/packages/frontend/src/components/form/InputDuration.tsx
@@ -26,12 +26,14 @@ import { useField } from "./Field";
 
 export interface Props<T> {
   name: keyof T;
+  expand?: boolean;
   readonly?: boolean;
 }
 
-export function InputDuration<T>({ name, readonly }: Props<T>) {
+export function InputDuration<T>({ name, expand, readonly }: Props<T>) {
   const { value } = useField<T>(name);
-  return <InputWithAddon<T> name={name} readonly={readonly} 
addonAfter={readableDuration( value as any )}
+  return <InputWithAddon<T> name={name} readonly={readonly} 
addonAfter={readableDuration(value as any)}
+    expand={expand}
     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 })}
   />
diff --git a/packages/frontend/src/components/form/InputPayto.tsx 
b/packages/frontend/src/components/form/InputPayto.tsx
index f6717cb..23de52a 100644
--- a/packages/frontend/src/components/form/InputPayto.tsx
+++ b/packages/frontend/src/components/form/InputPayto.tsx
@@ -31,7 +31,7 @@ const PAYTO_START_REGEX = /^payto:\/\//
 
 export function InputPayto<T>({ name, readonly }: Props<T>) {
   return <InputArray<T> name={name} readonly={readonly} 
-    addonBefore="payto://"
+    addonBefore="payto://" 
     isValid={(v) => v && PAYTO_REGEX.test(v) }
     toStr={(v?: string) => !v ? '': v.replace(PAYTO_START_REGEX, '')}
     fromStr={(v: string) => `payto://${v}` }
diff --git a/packages/frontend/src/components/form/InputSecured.tsx 
b/packages/frontend/src/components/form/InputSecured.tsx
index 01b0f19..83a3cbb 100644
--- a/packages/frontend/src/components/form/InputSecured.tsx
+++ b/packages/frontend/src/components/form/InputSecured.tsx
@@ -47,65 +47,76 @@ export function InputSecured<T>({ name, readonly }: 
Props<T>): VNode {
   const [active, setActive] = useState(false);
   const [newValue, setNuewValue] = useState("")
 
-  return <div class="field is-horizontal">
-    <div class="field-label is-normal">
-      <label class="label">
-        <Message id={`fields.instance.${name}.label`} />
-        {tooltip && <span class="icon" data-tooltip={tooltip}>
-          <i class="mdi mdi-information" />
-        </span>}
-      </label>
-    </div>
-    <div class="field-body">
-      {!active ?
-        <Fragment>
-          <div class="field has-addons">
-            <button class="button" onClick={(): void => { setActive(!active); 
}} >
-              <div class="icon is-left"><i class="mdi mdi-lock-reset" /></div>
-              <span>Manage token</span>
-            </button>
-            <TokenStatus prev={initial} post={value} />
-          </div>
-        </Fragment> :
-        <Fragment>
-          <div class="field has-addons">
-            <div class="control">
-              <a class="button is-static">secret-token:</a>
-            </div>
-            <div class="control">
-              <input class="input" type="password"
-                placeholder={placeholder} readonly={readonly || !active}
-                disabled={readonly || !active}
-                name={String(name)} value={newValue}
-                onInput={(e): void => {
-                  setNuewValue(e.currentTarget.value)
-                }} />
-              <Message id={`fields.instance.${name}.help`}> </Message>
-            </div>
-            <div class="control">
-              <button class="button is-info" disabled={fromStr(newValue) === 
value} onClick={(): void => { onChange(fromStr(newValue)); setActive(!active); 
setNuewValue(""); }} >
-                <div class="icon is-left"><i class="mdi mdi-lock-outline" 
/></div>
-                <span>Update</span>
+  return <Fragment>
+    <div class="field is-horizontal">
+      <div class="field-label is-normal">
+        <label class="label">
+          <Message id={`fields.instance.${name}.label`} />
+          {tooltip && <span class="icon" data-tooltip={tooltip}>
+            <i class="mdi mdi-information" />
+          </span>}
+        </label>
+      </div>
+      <div class="field-body is-flex-grow-3">
+        {!active ?
+          <Fragment>
+            <div class="field has-addons">
+              <button class="button" onClick={(): void => { 
setActive(!active); }} >
+                <div class="icon is-left"><i class="mdi mdi-lock-reset" 
/></div>
+                <span>Manage token</span>
               </button>
+              <TokenStatus prev={initial} post={value} />
             </div>
-          </div>
-          <div class="control">
-            <button class="button is-danger" disabled={null === value || 
undefined === value} onClick={(): void => { onChange(null!); 
setActive(!active); setNuewValue("");}} >
-              <div class="icon is-left"><i class="mdi mdi-lock-open-variant" 
/></div>
-              <span>Remove</span>
-            </button>
-          </div>
-          <div class="field ml-4">
-            <div class="control">
-              <button class="button " onClick={(): void => { 
onChange(initial!); setActive(!active); setNuewValue(""); }} >
-                <div class="icon is-left"><i class="mdi mdi-lock-open-variant" 
/></div>
-                <span>Cancel update</span>
-              </button>
+          </Fragment> :
+          <Fragment>
+            <div class="field has-addons">
+              <div class="control">
+                <a class="button is-static">secret-token:</a>
+              </div>
+              <div class="control is-expanded">
+                <input class="input" type="password"
+                  placeholder={placeholder} readonly={readonly || !active}
+                  disabled={readonly || !active}
+                  name={String(name)} value={newValue}
+                  onInput={(e): void => {
+                    setNuewValue(e.currentTarget.value)
+                  }} />
+                <Message id={`fields.instance.${name}.help`}> </Message>
+              </div>
+              <div class="control">
+                <button class="button is-info" disabled={fromStr(newValue) === 
value} onClick={(): void => { onChange(fromStr(newValue)); setActive(!active); 
setNuewValue(""); }} >
+                  <div class="icon is-left"><i class="mdi mdi-lock-outline" 
/></div>
+                  <span>Update</span>
+                </button>
+              </div>
             </div>
-          </div>
-        </Fragment>
-      }
-      {error ? <p class="help is-danger"><Message 
id={`validation.${error.type}`} 
fields={error.params}>{error.message}</Message></p> : null}
+          </Fragment>
+        }
+        {error ? <p class="help is-danger"><Message 
id={`validation.${error.type}`} 
fields={error.params}>{error.message}</Message></p> : null}
+      </div>
     </div>
-  </div>;
+    {active &&
+      <div class="field is-horizontal">
+        <div class="field-body is-flex-grow-3">
+          <div class="level" style={{width:'100%'}}>
+            <div class="level-right is-flex-grow-1">
+              <div class="level-item">
+                <button class="button is-danger" disabled={null === value || 
undefined === value} onClick={(): void => { onChange(null!); 
setActive(!active); setNuewValue(""); }} >
+                  <div class="icon is-left"><i class="mdi 
mdi-lock-open-variant" /></div>
+                  <span>Remove</span>
+                </button>
+              </div>
+              <div class="level-item">
+                <button class="button " onClick={(): void => { 
onChange(initial!); setActive(!active); setNuewValue(""); }} >
+                  <div class="icon is-left"><i class="mdi 
mdi-lock-open-variant" /></div>
+                  <span>Cancel</span>
+                </button>
+              </div>
+            </div>
+
+          </div>
+        </div>
+      </div>
+    }
+  </Fragment >;
 }
diff --git a/packages/frontend/src/components/form/InputWithAddon.tsx 
b/packages/frontend/src/components/form/InputWithAddon.tsx
index 8f33d74..f583f08 100644
--- a/packages/frontend/src/components/form/InputWithAddon.tsx
+++ b/packages/frontend/src/components/form/InputWithAddon.tsx
@@ -25,6 +25,8 @@ import { useField } from "./Field";
 export interface Props<T> {
   name: keyof T;
   readonly?: boolean;
+  expand?: boolean;
+  inputType?: 'text' | 'number';
   addonBefore?: string;
   addonAfter?: string;
   toStr?: (v?: any) => string;
@@ -34,7 +36,7 @@ export interface Props<T> {
 const defaultToString = (f?: any):string => f || ''
 const defaultFromString = (v: string):any => v as any
 
-export function InputWithAddon<T>({ name, readonly, addonBefore, addonAfter, 
toStr = defaultToString, fromStr = defaultFromString }: Props<T>): VNode {
+export function InputWithAddon<T>({ name, readonly, addonBefore, expand, 
inputType, addonAfter, toStr = defaultToString, fromStr = defaultFromString }: 
Props<T>): VNode {
   const { error, value, onChange } = useField<T>(name);
 
   const placeholder = useMessage(`fields.instance.${name}.placeholder`);
@@ -49,14 +51,14 @@ export function InputWithAddon<T>({ name, readonly, 
addonBefore, addonAfter, toS
         </span>}
       </label>
     </div>
-    <div class="field-body">
+    <div class="field-body is-flex-grow-3">
       <div class="field">
         <div class="field has-addons">
           {addonBefore && <div class="control">
             <a class="button is-static">{addonBefore}</a>
           </div>}
-          <p class="control is-expanded">
-            <input class={error ? "input is-danger" : "input"} type="text"
+          <p class={ expand ? "control is-expanded" : "control" }>
+            <input class={error ? "input is-danger" : "input"} type={inputType}
               placeholder={placeholder} readonly={readonly} disabled={readonly}
               name={String(name)} value={toStr(value)}
               onChange={(e): void => onChange(fromStr(e.currentTarget.value))} 
/>
diff --git a/packages/frontend/src/components/menu/index.tsx 
b/packages/frontend/src/components/menu/index.tsx
index 2de99f1..f76b07e 100644
--- a/packages/frontend/src/components/menu/index.tsx
+++ b/packages/frontend/src/components/menu/index.tsx
@@ -15,21 +15,66 @@
  */
 
 import { h, VNode } from "preact";
-import { useState } from "preact/hooks";
+import { useEffect, useState } from "preact/hooks";
+import { AdminPaths } from "../../AdminRoutes";
+import { InstancePaths } from "../../InstanceRoutes";
 import { NavigationBar } from "./NavigationBar";
 import { Sidebar } from "./SideBar";
+import Match from 'preact-router/match';
 
 interface Props {
+  title?: string;
   instance?: string;
   onLogout?: () => void;
 }
 
-export function Menu({ onLogout, instance }: Props): VNode {
+function getInstanceTitle(path: string, id: string): string {
+  console.log('-->', path, id);
+
+  switch (path) {
+    case InstancePaths.details: return `${id}`
+    case InstancePaths.update: return `${id}: Settings`
+    case InstancePaths.order_list: return `${id}: Orders`
+    case InstancePaths.order_new: return `${id}: New order`
+    case InstancePaths.order_update: return `${id}: Update order`
+    case InstancePaths.product_list: return `${id}: Products`
+    case InstancePaths.product_new: return `${id}: New product`
+    case InstancePaths.product_update: return `${id}: Update product`
+    case InstancePaths.tips_list: return `${id}: Tips`
+    case InstancePaths.tips_new: return `${id}: New tip`
+    case InstancePaths.tips_update: return `${id}: Update tip`
+    case InstancePaths.transfers_list: return `${id}: Transfers`
+    case InstancePaths.transfers_new: return `${id}: New Transfer`
+    default: return '';
+  }
+}
+
+const INSTANCE_ID_LOOKUP = /^\/instance\/([^/]*)\//
+function getAdminTitle(path: string) {
+  if (path === AdminPaths.new_instance) return `New instance`
+  if (path === AdminPaths.list_instances) return `Instances`
+  const match = INSTANCE_ID_LOOKUP.exec(path)
+  if (match && match[1]) return 
getInstanceTitle(path.replace(INSTANCE_ID_LOOKUP, '/'), match[1]);
+  return getInstanceTitle(path, 'default')
+}
+
+export function Menu({ onLogout, title, instance }: Props): VNode {
   const [mobileOpen, setMobileOpen] = useState(false)
-  const title = !onLogout ? "Welcome!" : (!instance ? "Admin" : instance)
-  return <div class={mobileOpen ? "has-aside-mobile-expanded" : ""} 
onClick={() => setMobileOpen(false)}>
-    <NavigationBar onMobileMenu={() => setMobileOpen(!mobileOpen)} 
title={title} />
-    {onLogout && <Sidebar onLogout={onLogout} instance={instance} 
mobile={mobileOpen} />}
-  </div>
+
+  return <Match>{({ path }: any) => {
+    const titleWithSubtitle = title ? title : (instance ? 
getInstanceTitle(path, instance) : getAdminTitle(path))
+
+    useEffect(() => {
+      document.title = 'Taler Backoffice: ' + titleWithSubtitle
+    }, [titleWithSubtitle])
+
+    return (
+      <div class={mobileOpen ? "has-aside-mobile-expanded" : ""} onClick={() 
=> setMobileOpen(false)}>
+        <NavigationBar onMobileMenu={() => setMobileOpen(!mobileOpen)} 
title={titleWithSubtitle} />
+        {onLogout && <Sidebar onLogout={onLogout} instance={instance} 
mobile={mobileOpen} />}
+      </div>
+    )
+  }}</Match>
+
 
 }
\ No newline at end of file
diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx
index 29aec14..d7a984b 100644
--- a/packages/frontend/src/index.tsx
+++ b/packages/frontend/src/index.tsx
@@ -38,33 +38,6 @@ import LoginPage from './routes/login';
 import { ApplicationReadyRoutes } from "./ApplicationReadyRoutes";
 import { Menu } from "./components/menu";
 
-export enum RootPaths {
-  root = '/',
-  list_instances = '/instances',
-  new_instance = '/new',
-  instance_id_route = '/instance/:id/:rest*',
-}
-
-export enum InstancePaths {
-  details = '/',
-  update = '/update',
-
-  product_list = '/p',
-  product_update = '/p/:pid/update',
-  product_new = '/p/new',
-
-  order_list = '/o',
-  order_update = '/p/:oid/update',
-  order_new = '/o/new',
-
-  tips_list = '/r',
-  tips_update = '/r/:rid/update',
-  tips_new = '/r/new',
-
-  transfers_list = '/t',
-  transfers_new = '/t/new',
-}
-
 export function Redirect({ to }: { to: string }): null {
   useEffect(() => {
     route(to, true)
@@ -105,12 +78,12 @@ function ApplicationStatusRoutes(): VNode {
 
   if (!triedToLog) {
     return <div id="app">
-      <Menu />
+      <Menu title="Welcome!" />
       <LoginPage
         onConfirm={(url: string, token?: string) => {
           changeBackend(url)
           if (token) updateToken(token)
-          route(RootPaths.list_instances)
+          route('/')
         }}
       />
     </div>
@@ -122,19 +95,19 @@ function ApplicationStatusRoutes(): VNode {
 
     if (backendConfig.unauthorized) {
       return <div id="app">
-        <Menu />
+        <Menu title="Login" />
         <LoginPage
           onConfirm={(url: string, token?: string) => {
             changeBackend(url)
             if (token) updateToken(token)
-            route(RootPaths.list_instances)
+            route('/')
           }}
         />
       </div>
     }
 
     return <div id="app">
-      <Menu />
+      <Menu title="Error" />
       <LoginPage
         withMessage={{
           message: i18n`Couldnt access the server`,
@@ -144,7 +117,7 @@ function ApplicationStatusRoutes(): VNode {
         onConfirm={(url: string, token?: string) => {
           changeBackend(url)
           if (token) updateToken(token)
-          route(RootPaths.list_instances)
+          route('/')
         }}
       />
     </div>
diff --git a/packages/frontend/src/routes/admin/list/Table.tsx 
b/packages/frontend/src/routes/admin/list/Table.tsx
index 4831ed3..024ef9c 100644
--- a/packages/frontend/src/routes/admin/list/Table.tsx
+++ b/packages/frontend/src/routes/admin/list/Table.tsx
@@ -53,7 +53,7 @@ export function CardTable({ instances, onCreate, onUpdate, 
onDelete, selected }:
 
   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>
+      <p class="card-header-title"><span class="icon"><i class="mdi 
mdi-desktop-mac" /></span><Message id="Instances" /></p>
 
       <div class="card-header-icon" aria-label="more options">
 
@@ -118,10 +118,13 @@ function Table({ rowSelection, rowSelectionHandler, 
instances, onUpdate, onDelet
                 <span class="check" />
               </label>
             </td>
-            <td><a onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}} >{i.id}</a></td>
+            <td><a onClick={(): void => onUpdate(i.id)} style={{ cursor: 
'pointer' }} >{i.id}</a></td>
             <td >{i.name}</td>
             <td class="is-actions-cell">
               <div class="buttons is-right">
+                <button class="button is-small is-success jb-modal" 
type="button" onClick={(): void => navigator.clipboard.writeText(i.id) as any}>
+                  <span class="icon"><i class="mdi mdi-content-copy" /></span>
+                </button>
                 <button class="button is-small is-success jb-modal" 
type="button" onClick={(): void => onUpdate(i.id)}>
                   <span class="icon"><i class="mdi mdi-pen" /></span>
                 </button>
diff --git a/packages/frontend/src/routes/instance/update/UpdatePage.tsx 
b/packages/frontend/src/routes/instance/update/UpdatePage.tsx
index 6fde628..ac6eec4 100644
--- a/packages/frontend/src/routes/instance/update/UpdatePage.tsx
+++ b/packages/frontend/src/routes/instance/update/UpdatePage.tsx
@@ -34,8 +34,9 @@ import { useConfigContext, useInstanceContext } from 
"../../../context/backend";
 import { InputDuration } from "../../../components/form/InputDuration";
 import { InputCurrency } from "../../../components/form/InputCurrency";
 import { InputPayto } from "../../../components/form/InputPayto";
+import { InputArray } from "../../../components/form/InputArray";
 
-type Entity = MerchantBackend.Instances.InstanceReconfigurationMessage & 
{auth_token?: string}
+type Entity = MerchantBackend.Instances.InstanceReconfigurationMessage & { 
auth_token?: string }
 
 interface Props {
   onUpdate: (d: Entity, auth?: 
MerchantBackend.Instances.InstanceAuthConfigurationMessage) => void;
@@ -73,11 +74,11 @@ export function UpdatePage({ onUpdate, isLoading, selected, 
onBack }: Props): VN
       // use conversion instead of this
       const newToken = value.auth_token;
       value.auth_token = undefined;
-      const auth: MerchantBackend.Instances.InstanceAuthConfigurationMessage | 
undefined = 
-      newToken === currentTokenValue ? undefined : (newToken === null ? 
-          { method: "external" } : 
+      const auth: MerchantBackend.Instances.InstanceAuthConfigurationMessage | 
undefined =
+        newToken === currentTokenValue ? undefined : (newToken === null ?
+          { method: "external" } :
           { method: "token", token: `secret-token:${newToken}` });
-      
+
       // remove above use conversion
       schema.validateSync(value, { abortEarly: false })
       onUpdate(schema.cast(value), auth);
@@ -94,7 +95,7 @@ export function UpdatePage({ onUpdate, isLoading, selected, 
onBack }: Props): VN
     <section class="section is-main-section">
       <div class="columns">
         <div class="column" />
-        <div class="column is-two-thirds">
+        <div class="column is-four-fifths">
           <FormProvider<Entity> errors={errors} object={value} 
valueHandler={valueHandler} >
 
             <Input<Entity> name="name" />
@@ -107,14 +108,38 @@ export function UpdatePage({ onUpdate, isLoading, 
selected, onBack }: Props): VN
 
             <InputCurrency<Entity> name="default_max_wire_fee" 
currency={config.currency} />
 
-            <Input<Entity> name="default_wire_fee_amortization" />
+            <Input<Entity> name="default_wire_fee_amortization" 
inputType="number" />
 
             <InputGroup name="address">
-              <Input<Entity> name="name" />
+              <Input name="address.country" />
+              <Input name="address.address_lines" inputType="multiline"
+                toStr={(v: string[] | undefined) => !v ? '' : v.join('\n')}
+                fromStr={(v: string) => v.split('\n')}
+              />
+              <Input name="address.building_number" />
+              <Input name="address.building_name" />
+              <Input name="address.street" />
+              <Input name="address.post_code" />
+              <Input name="address.town_location" />
+              <Input name="address.town" />
+              <Input name="address.district" />
+              <Input name="address.country_subdivision" />
             </InputGroup>
 
             <InputGroup name="jurisdiction">
-              <Input<Entity> name="name" />
+              <Input name="jurisdiction.country" />
+              <Input name="jurisdiction.address_lines" inputType="multiline"
+                toStr={(v: string[] | undefined) => !v ? '' : v.join('\n')}
+                fromStr={(v: string) => v.split('\n')}
+              />
+              <Input name="jurisdiction.building_number" />
+              <Input name="jurisdiction.building_name" />
+              <Input name="jurisdiction.street" />
+              <Input name="jurisdiction.post_code" />
+              <Input name="jurisdiction.town_location" />
+              <Input name="jurisdiction.town" />
+              <Input name="jurisdiction.district" />
+              <Input name="jurisdiction.country_subdivision" />
             </InputGroup>
 
             <InputDuration<Entity> name="default_pay_delay" />
diff --git a/packages/frontend/src/routes/instance/update/index.tsx 
b/packages/frontend/src/routes/instance/update/index.tsx
index 51bb6a5..3bed0cf 100644
--- a/packages/frontend/src/routes/instance/update/index.tsx
+++ b/packages/frontend/src/routes/instance/update/index.tsx
@@ -33,10 +33,8 @@ interface Props {
 }
 
 export default function Update({ onBack, onConfirm, onLoadError, 
onUpdateError, onUnauthorized }: Props): VNode {
-  const { updateInstance, setNewToken, clearToken } = useInstanceMutateAPI();
-  const [updatingToken, setUpdatingToken] = useState<boolean>(false)
+  const { updateInstance } = useInstanceMutateAPI();
   const details = useInstanceDetails()
-  const { id, token } = useInstanceContext()
 
   if (!details.data) {
     if (details.unauthorized) return onUnauthorized()
@@ -52,15 +50,7 @@ export default function Update({ onBack, onConfirm, 
onLoadError, onUpdateError,
       isLoading={false}
       selected={details.data}
       onUpdate={(d: MerchantBackend.Instances.InstanceReconfigurationMessage, 
t?: MerchantBackend.Instances.InstanceAuthConfigurationMessage): Promise<void> 
=> {
-        return updateInstance(d,t).then(onConfirm).catch(onUpdateError)
+        return updateInstance(d, t).then(onConfirm).catch(onUpdateError)
       }} />
-    <button class="button" onClick={() => setUpdatingToken(true)}>auth</button>
-    {updatingToken && <UpdateTokenModal
-      oldToken={token}
-      element={{ id, name: details.data.name }}
-      onCancel={() => setUpdatingToken(false)}
-      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]