gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant-backoffice] branch master updated: severals fixes


From: gnunet
Subject: [taler-merchant-backoffice] branch master updated: severals fixes
Date: Thu, 25 Feb 2021 20:48:21 +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 877bcbb  severals fixes
877bcbb is described below

commit 877bcbb0a5ed1c560ee8ff41270dc6d0664c98e9
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Thu Feb 25 16:48:10 2021 -0300

    severals fixes
---
 CHANGELOG.md                                       | 16 +++++--
 packages/frontend/src/ApplicationReadyRoutes.tsx   |  2 +-
 packages/frontend/src/InstanceRoutes.tsx           | 10 ++---
 packages/frontend/src/components/auth/index.tsx    | 35 +++++++--------
 packages/frontend/src/components/form/Field.tsx    |  7 ++-
 .../frontend/src/components/form/InputArray.tsx    | 34 ++++++++++++---
 .../frontend/src/components/form/InputCurrency.tsx |  2 +-
 .../frontend/src/components/form/InputDuration.tsx |  2 +-
 .../form/{InputCurrency.tsx => InputPayto.tsx}     | 18 ++++----
 .../src/components/form/InputWithAddon.tsx         | 14 +++---
 packages/frontend/src/components/navbar/index.tsx  | 51 ++++++++++++++++------
 packages/frontend/src/components/sidebar/index.tsx | 34 ++++++++++-----
 packages/frontend/src/hooks/backend.ts             |  4 +-
 packages/frontend/src/index.tsx                    | 16 ++++---
 packages/frontend/src/messages/en.po               |  8 +++-
 .../src/routes/instances/create/CreatePage.tsx     |  9 ++--
 .../frontend/src/routes/instances/list/Table.tsx   |  9 +---
 .../src/routes/instances/update/UpdatePage.tsx     |  6 +--
 packages/frontend/src/scss/_theme-default.scss     | 30 ++++++++-----
 packages/frontend/src/utils/constants.ts           |  3 +-
 20 files changed, 194 insertions(+), 116 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4ea2679..431498d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,16 +6,14 @@ and this project adheres to [Semantic 
Versioning](https://semver.org/spec/v2.0.0
 
 ## [Future work]
  - date format (error handling)
- - bug: set text int the intpu date (seconds)
  - connection errors
  - allow point separator for amounts
  - prevent letters to be input in numbers
  - red color when input is invalid (onchange)
- - prepend payto:// to account
  - 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
+ - submit form on key press == enter
 
  - add order section
  - add product section
@@ -27,7 +25,7 @@ and this project adheres to [Semantic 
Versioning](https://semver.org/spec/v2.0.0
  - configure prettier
  - prune scss styles to reduce size
 
-## [Unreleased]
+## [0.0.2] - 2021-02-25
  - REFACTOR: remove react-i18n and implement messageformat
  - REFACTOR: routes definitions to allow nested routes and tokens
  - REFACTOR: remove yup from input form defitions
@@ -44,6 +42,16 @@ and this project adheres to [Semantic 
Versioning](https://semver.org/spec/v2.0.0
  - show the connection state (url, currency, version) in the sidebar
  - add backend url without slash
  - added linter rule for source header
+ - bug: set text int the intpu date (seconds)
+ - row in the list instance are now clickable
+ - re implemented the language selector, remove the current lang from the 
dropdown
+ - remove payment adress and public key from instance listing
+ - fix bug on CORS error
+ - moved the login button to the sidebar
+ - remove the details page, go directly to the update page
+ - login modal: url before token, and removed the checkbox
+ - added payto:// to the field
+ - validate payto_uris on add
 
 ## [0.0.1] - 2021-02-18
 ### Changed
diff --git a/packages/frontend/src/ApplicationReadyRoutes.tsx 
b/packages/frontend/src/ApplicationReadyRoutes.tsx
index a245e27..61c56ef 100644
--- a/packages/frontend/src/ApplicationReadyRoutes.tsx
+++ b/packages/frontend/src/ApplicationReadyRoutes.tsx
@@ -53,7 +53,7 @@ export function ApplicationReadyRoutes({ pushNotification, 
addTokenCleaner }: {
       }}
 
       onUpdate={(id: string): void => {
-        route(`/instance/${id}/`);
+        route(`/instance/${id}/update`);
       }}
 
       onUnauthorized={() => <LoginPage
diff --git a/packages/frontend/src/InstanceRoutes.tsx 
b/packages/frontend/src/InstanceRoutes.tsx
index e0f4452..ae5610b 100644
--- a/packages/frontend/src/InstanceRoutes.tsx
+++ b/packages/frontend/src/InstanceRoutes.tsx
@@ -57,7 +57,7 @@ export function InstanceRoutes({ id, pushNotification, 
addTokenCleaner }: Props)
 
   return <InstanceContext.Provider value={{ id, token }}>
     <Router>
-      <Route path={InstancePaths.details}
+      {/* <Route path={InstancePaths.details}
         component={InstanceDetailsPage}
         pushNotification={pushNotification}
 
@@ -77,7 +77,7 @@ export function InstanceRoutes({ id, pushNotification, 
addTokenCleaner }: Props)
           pushNotification({ message: i18n`update_load_error`, type: 'ERROR', 
params: e });
           route(`/instance/${id}/`);
           return <div />;
-        }} />
+        }} /> */}
 
       <Route path={InstancePaths.update}
         component={InstanceUpdatePage}
@@ -87,15 +87,15 @@ export function InstanceRoutes({ id, pushNotification, 
addTokenCleaner }: Props)
           onConfirm={updateLoginStatus} />}
         onLoadError={(e: SwrError) => {
           pushNotification({ message: i18n`update_load_error`, type: 'ERROR', 
params: e });
-          route(`/instance/${id}/`);
+          route(`/instances`);
           return <div />;
         }}
         onBack={() => {
-          route(`/instance/${id}/`);
+          route(`/instances`);
         }}
         onConfirm={() => {
           pushNotification({ message: i18n`create_success`, type: 'SUCCESS' });
-          route(`/instance/${id}/`);
+          route(`/instances`);
         }}
         onUpdateError={(e: Error) => {
           pushNotification({ message: i18n`update_error`, type: 'ERROR', 
params: e });
diff --git a/packages/frontend/src/components/auth/index.tsx 
b/packages/frontend/src/components/auth/index.tsx
index dc5c84a..3f33993 100644
--- a/packages/frontend/src/components/auth/index.tsx
+++ b/packages/frontend/src/components/auth/index.tsx
@@ -32,10 +32,10 @@ interface Props {
 
 export function LoginModal({ onConfirm, withMessage }: Props): VNode {
   const backend = useContext(BackendContext)
-  const [updatingToken, setUpdatingToken] = useState(false)
   const [token, setToken] = useState(backend.token || '')
   const [url, setURL] = useState(backend.url)
-  const toggleUpdatingToken = (): void => setUpdatingToken(v => !v)
+  // const [updatingToken, setUpdatingToken] = useState(false)
+  // const toggleUpdatingToken = (): void => setUpdatingToken(v => !v)
 
   return <div class="modal is-active is-clipped">
     <div class="modal-background" />
@@ -43,11 +43,11 @@ export function LoginModal({ onConfirm, withMessage }: 
Props): VNode {
       {withMessage && <div class={withMessage.type === 'ERROR' ? "notification 
is-danger" : "notification is-info"}>
         <div class="columns is-vcentered">
           <div class="column is-12">
-              <div>
-                <p><strong>{withMessage.message}</strong></p>
-                {withMessage.description}
-              </div>
+            <div>
+              <p><strong>{withMessage.message}</strong></p>
+              {withMessage.description}
             </div>
+          </div>
         </div>
       </div>}
       <header class="modal-card-head">
@@ -57,29 +57,30 @@ export function LoginModal({ onConfirm, withMessage }: 
Props): VNode {
         Please enter your auth token. Token should have "secret-token:" and 
start with Bearer or ApiKey
         <div class="field is-horizontal">
           <div class="field-label is-normal">
-            <label class="label">Updte token</label>
+            <label class="label">URL</label>
           </div>
           <div class="field-body">
-            <div class="field has-addons">
-              <label class="b-checkbox checkbox">
-                <input type="checkbox" checked={updatingToken} 
onClick={toggleUpdatingToken} />
-                <span class="check" />
-              </label>
-
+            <div class="field">
               <p class="control is-expanded">
-                <input class="input" type="text" placeholder={updatingToken ? 
"set new token" : "hidden token value"} disabled={!updatingToken} name="id" 
value={token} onInput={(e): void => setToken(e?.currentTarget.value)} />
+                <input class="input" type="text" placeholder="set new url" 
name="id" value={url} onInput={(e): void => setURL(e?.currentTarget.value)} />
               </p>
             </div>
           </div>
         </div>
         <div class="field is-horizontal">
           <div class="field-label is-normal">
-            <label class="label">URL</label>
+            <label class="label">Token</label>
           </div>
           <div class="field-body">
+            {/* <div class="field has-addons">
+              <label class="b-checkbox checkbox">
+                <input type="checkbox" checked={updatingToken} 
onClick={toggleUpdatingToken} />
+                <span class="check" />
+              </label> */}
+
             <div class="field">
               <p class="control is-expanded">
-                <input class="input" type="text" placeholder="set new url" 
name="id" value={url} onInput={(e): void => setURL(e?.currentTarget.value)} />
+                <input class="input" type="text" placeholder={true ? "set new 
token" : "hidden token value"} name="id" value={token} onInput={(e): void => 
setToken(e?.currentTarget.value)} />
               </p>
             </div>
           </div>
@@ -87,7 +88,7 @@ export function LoginModal({ onConfirm, withMessage }: 
Props): VNode {
       </section>
       <footer class="modal-card-foot " style={{ justifyContent: 'flex-end' }}>
         <button class="button is-info" onClick={(): void => {
-          onConfirm(url, updatingToken && token ? token : undefined);
+          onConfirm(url, token ? token : undefined);
         }} >Confirm</button>
       </footer>
     </div>
diff --git a/packages/frontend/src/components/form/Field.tsx 
b/packages/frontend/src/components/form/Field.tsx
index 08b6361..9ae7967 100644
--- a/packages/frontend/src/components/form/Field.tsx
+++ b/packages/frontend/src/components/form/Field.tsx
@@ -76,7 +76,12 @@ interface ProviderProps<T> {
 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}
+    <form onSubmit={(e) => {
+      e.preventDefault()
+      valueHandler(object)
+    }}>
+      {children}
+    </form>
   </FormContext.Provider>
 }
 
diff --git a/packages/frontend/src/components/form/InputArray.tsx 
b/packages/frontend/src/components/form/InputArray.tsx
index 592831e..d7aef82 100644
--- a/packages/frontend/src/components/form/InputArray.tsx
+++ b/packages/frontend/src/components/form/InputArray.tsx
@@ -19,23 +19,33 @@
 * @author Sebastian Javier Marchano (sebasjm)
 */
 import { h, VNode } from "preact";
-import { Message, useMessage } from "preact-messages";
+import { Message, useMessage, useMessageTemplate } from "preact-messages";
 import { useState } from "preact/hooks";
-import { useField } from "./Field";
+import { FormErrors, useField, ValidationError } from "./Field";
 
 export interface Props<T> {
   name: T;
   readonly?: boolean;
+  isValid: (e: any) => boolean;
+  addonBefore?: string;
+  toStr?: (v?: any) => string;
+  fromStr?: (s: string) => any;
 }
 
-export function InputArray<T>({ name, readonly }: Props<keyof T>): VNode {
-  const { error, value, onChange, fromStr, toStr } = useField<T>(name);
+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 {
+  const { error: formError, value, onChange } = useField<T>(name);
+  const [localError, setLocalError] = useState<ValidationError | null>(null)
   const placeholder = useMessage(`fields.instance.${name}.placeholder`);
   const tooltip = useMessage(`fields.instance.${name}.tooltip`);
 
+  const error = formError || localError
+
   const array: any[] = (value ? value! : []) as any;
   const [currentValue, setCurrentValue] = useState('');
+  const i18n = useMessageTemplate();
 
   return <div class="field is-horizontal">
     <div class="field-label is-normal">
@@ -49,6 +59,9 @@ export function InputArray<T>({ name, readonly }: Props<keyof 
T>): VNode {
     <div class="field-body">
       <div class="field">
         <div class="field has-addons">
+          {addonBefore && <div class="control">
+            <a class="button is-static">{addonBefore}</a>
+          </div>}
           <p class="control">
             <input class={error ? "input is-danger" : "input"} type="text"
               placeholder={placeholder} readonly={readonly} disabled={readonly}
@@ -58,7 +71,14 @@ export function InputArray<T>({ name, readonly }: 
Props<keyof T>): VNode {
           </p>
           <p class="control">
             <button class="button is-info" onClick={(): void => {
-              onChange([fromStr(currentValue), ...array] as any);
+              const v = fromStr(currentValue)
+              if (!isValid(v)) {
+                setLocalError({ message: i18n`The value ${v} is invalid for a 
payment url` })
+                return;
+              } else {
+                setLocalError(null)
+              }
+              onChange([v, ...array] as any);
               setCurrentValue('');
             }}>add</button>
           </p>
@@ -67,10 +87,10 @@ export function InputArray<T>({ name, readonly }: 
Props<keyof T>): VNode {
           <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">{toStr(v)}</span>
+          <span class="tag is-medium is-info">{v}</span>
           <a class="tag is-medium is-danger is-delete" onClick={() => {
             onChange(array.filter(f => f !== v) as any);
-            setCurrentValue( toStr(v) );
+            setCurrentValue(toStr(v));
           }} />
         </div>
         )}
diff --git a/packages/frontend/src/components/form/InputCurrency.tsx 
b/packages/frontend/src/components/form/InputCurrency.tsx
index e8082ea..9b425c8 100644
--- a/packages/frontend/src/components/form/InputCurrency.tsx
+++ b/packages/frontend/src/components/form/InputCurrency.tsx
@@ -30,7 +30,7 @@ export interface Props<T> {
 }
 
 export function InputCurrency<T>({ name, readonly, currency }: Props<T>) {
-  return <InputWithAddon<T> name={name} readonly={readonly} addon={currency}
+  return <InputWithAddon<T> name={name} readonly={readonly} 
addonBefore={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
index 65359d5..a08719a 100644
--- a/packages/frontend/src/components/form/InputDuration.tsx
+++ b/packages/frontend/src/components/form/InputDuration.tsx
@@ -31,7 +31,7 @@ export interface Props<T> {
 
 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
+  return <InputWithAddon<T> name={name} readonly={readonly} 
addonAfter={readableDuration( value as any )}
     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/InputCurrency.tsx 
b/packages/frontend/src/components/form/InputPayto.tsx
similarity index 65%
copy from packages/frontend/src/components/form/InputCurrency.tsx
copy to packages/frontend/src/components/form/InputPayto.tsx
index e8082ea..f6717cb 100644
--- a/packages/frontend/src/components/form/InputCurrency.tsx
+++ b/packages/frontend/src/components/form/InputPayto.tsx
@@ -19,20 +19,22 @@
 * @author Sebastian Javier Marchano (sebasjm)
 */
 import { h } from "preact";
-import { Amount } from "../../declaration";
-import { InputWithAddon } from "./InputWithAddon";
-import { useField } from "./Field";
+import { InputArray } from "./InputArray";
+import { PAYTO_REGEX } from "../../utils/constants";
 
 export interface Props<T> {
   name: keyof T;
   readonly?: boolean;
-  currency: string;
 }
 
-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}`}
+const PAYTO_START_REGEX = /^payto:\/\//
+
+export function InputPayto<T>({ name, readonly }: Props<T>) {
+  return <InputArray<T> name={name} readonly={readonly} 
+    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/InputWithAddon.tsx 
b/packages/frontend/src/components/form/InputWithAddon.tsx
index e387ab4..8f33d74 100644
--- a/packages/frontend/src/components/form/InputWithAddon.tsx
+++ b/packages/frontend/src/components/form/InputWithAddon.tsx
@@ -25,8 +25,8 @@ import { useField } from "./Field";
 export interface Props<T> {
   name: keyof T;
   readonly?: boolean;
-  addon: string;
-  atTheEnd?: boolean;
+  addonBefore?: string;
+  addonAfter?: string;
   toStr?: (v?: any) => string;
   fromStr?: (s: string) => any;
 }
@@ -34,7 +34,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, addon, atTheEnd, toStr = 
defaultToString, fromStr = defaultFromString }: Props<T>): VNode {
+export function InputWithAddon<T>({ name, readonly, addonBefore, addonAfter, 
toStr = defaultToString, fromStr = defaultFromString }: Props<T>): VNode {
   const { error, value, onChange } = useField<T>(name);
 
   const placeholder = useMessage(`fields.instance.${name}.placeholder`);
@@ -52,8 +52,8 @@ export function InputWithAddon<T>({ name, readonly, addon, 
atTheEnd, toStr = def
     <div class="field-body">
       <div class="field">
         <div class="field has-addons">
-          {!atTheEnd && <div class="control">
-            <a class="button is-static">{addon}</a>
+          {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"
@@ -62,8 +62,8 @@ export function InputWithAddon<T>({ name, readonly, addon, 
atTheEnd, toStr = def
               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>
+          {addonAfter && <div class="control">
+            <a class="button is-static">{addonAfter}</a>
           </div>}
         </div>
         {error ? <p class="help is-danger"><Message 
id={`validation.${error.type}`} 
fields={error.params}>{error.message}</Message></p> : null}
diff --git a/packages/frontend/src/components/navbar/index.tsx 
b/packages/frontend/src/components/navbar/index.tsx
index be381fa..eb7857e 100644
--- a/packages/frontend/src/components/navbar/index.tsx
+++ b/packages/frontend/src/components/navbar/index.tsx
@@ -23,15 +23,30 @@ import { h, VNode } from 'preact';
 import * as messages from '../../messages'
 import logo from '../../assets/logo.jpeg';
 import langIcon from '../../assets/icons/languageicon.svg'
-import { Message } from 'preact-messages';
+import { useState } from 'preact/hooks';
 
 interface Props {
   lang: string;
   setLang: (l: string) => void;
-  onLogout: () => void;
 }
 
-export function NavigationBar({ lang, setLang, onLogout }: Props): VNode {
+type LangsNames = {
+  [P in keyof typeof messages]: string
+}
+
+const names: LangsNames = {
+  es: 'EspaƱol [es]', 
+  en: 'English [en]',
+}
+
+function getLangName(s: keyof LangsNames | string) {
+  if (s === 'es' || s === 'en') return names[s]
+  return s
+}
+
+
+export function NavigationBar({ lang, setLang }: Props): VNode {
+  const [updatingLang, setUpdatingLang] = useState(false)
   return (<nav class="navbar is-fixed-top" role="navigation" aria-label="main 
navigation">
     <div class="navbar-brand">
       <a class="navbar-item" href="https://taler.net";>
@@ -49,20 +64,28 @@ export function NavigationBar({ lang, setLang, onLogout }: 
Props): VNode {
 
       <div class="navbar-end">
         <div class="navbar-item">
-          <div class="control has-icons-left">
-            <div class="select">
-              <select onChange={(e): void => setLang(e.currentTarget.value)}>
-                {Object.keys(messages).map(l => <option selected={lang === l} 
value={l}><Message id={l} /></option>)}
-              </select>
-            </div>
-            <div class="icon is-small is-left">
-              <img src={langIcon} />
+
+          <div class="dropdown is-active ">
+            <div class="dropdown-trigger">
+              <button class="button" aria-haspopup="true" 
aria-controls="dropdown-menu" onClick={() => setUpdatingLang(!updatingLang)}>
+                <div class="icon is-small is-left">
+                  <img src={langIcon} />
+                </div>
+                <span>{getLangName(lang)}</span>
+                <div class="icon is-right">
+                  <i class="mdi mdi-chevron-down" />
+                </div>
+              </button>
             </div>
+            { updatingLang && <div class="dropdown-menu" id="dropdown-menu" 
role="menu">
+              <div class="dropdown-content">
+                {Object.keys(messages)
+                  .filter((l) => l !== lang)
+                  .map(l => <a class="dropdown-item" value={l} onClick={() => 
{setLang(l); setUpdatingLang(false)}}>{getLangName(l)}</a>)}
+              </div>
+            </div> }
           </div>
         </div>
-        <div class="navbar-item">
-          <button class="button is-primary" onClick={(): void => 
onLogout()}>Log out</button>
-        </div>
       </div>
     </div>
   </nav>
diff --git a/packages/frontend/src/components/sidebar/index.tsx 
b/packages/frontend/src/components/sidebar/index.tsx
index 0d1877f..8931292 100644
--- a/packages/frontend/src/components/sidebar/index.tsx
+++ b/packages/frontend/src/components/sidebar/index.tsx
@@ -24,7 +24,11 @@ import { h, VNode } from 'preact';
 import { useContext } from 'preact/hooks';
 import { BackendContext, ConfigContext } from '../../context/backend';
 
-export function Sidebar(): VNode {
+interface Props {
+  onLogout: () => void;
+}
+
+export function Sidebar({ onLogout }: Props): VNode {
   const config = useContext(ConfigContext);
   const backend = useContext(BackendContext);
   return (
@@ -74,21 +78,27 @@ export function Sidebar(): VNode {
         <p class="menu-label">Connection</p>
         <ul class="menu-list">
           <li>
-            <a>
-            <span class="icon"><i class="mdi mdi-currency-eur" /></span>
-            <span class="menu-item-label">{config.currency}</span>
-            </a>
+            <div>
+              <span class="icon"><i class="mdi mdi-currency-eur" /></span>
+              <span class="menu-item-label">{config.currency}</span>
+            </div>
           </li>
           <li>
-          <a>
-            <span class="icon"><i class="mdi mdi-web" /></span>
-            <span class="menu-item-label">{backend.url}</span>
-            </a>
+            <div>
+              <span class="icon"><i class="mdi mdi-web" /></span>
+              <span class="menu-item-label">{backend.url}</span>
+            </div>
+          </li>
+          <li>
+            <div>
+              <span class="icon">V</span>
+              <span class="menu-item-label">{config.version}</span>
+            </div>
           </li>
           <li>
-          <a>
-            <span class="icon">V</span>
-            <span class="menu-item-label">{config.version}</span>
+            <a class="has-icon is-state-info is-hoverable" onClick={(): void 
=> onLogout()}>
+              <span class="icon"><i class="mdi mdi-logout default"></i></span>
+              <span class="menu-item-label">Log out</span>
             </a>
           </li>
         </ul>
diff --git a/packages/frontend/src/hooks/backend.ts 
b/packages/frontend/src/hooks/backend.ts
index 96a1b73..8781e87 100644
--- a/packages/frontend/src/hooks/backend.ts
+++ b/packages/frontend/src/hooks/backend.ts
@@ -169,9 +169,7 @@ export function useBackendInstance(): 
HttpResponse<MerchantBackend.Instances.Que
 
 export function useBackendConfig(): 
HttpResponse<MerchantBackend.VersionResponse> {
   const { url, token } = useContext(BackendContext)
-  const { data, error } = useSWR<MerchantBackend.VersionResponse, 
SwrError>(['/config', token, url], fetcher, {
-    shouldRetryOnError: false
-  })
+  const { data, error } = useSWR<MerchantBackend.VersionResponse, 
SwrError>(['/config', token, url], fetcher)
 
   return { data, unauthorized: error?.status === 401, error }
 }
diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx
index 335d9ba..1645196 100644
--- a/packages/frontend/src/index.tsx
+++ b/packages/frontend/src/index.tsx
@@ -22,7 +22,7 @@
 import "./scss/main.scss"
 
 import { h, VNode } from 'preact';
-import { useCallback, useContext, useEffect, useState } from "preact/hooks";
+import { useCallback, useContext, useEffect, useMemo, useState } from 
"preact/hooks";
 import { Route, route } from 'preact-router';
 import { MessageProvider, useMessageTemplate } from 'preact-messages';
 
@@ -47,7 +47,6 @@ export enum RootPaths {
 }
 
 export enum InstancePaths {
-  details = '/instance/:id/',
   update = '/instance/:id/update',
 }
 
@@ -94,12 +93,17 @@ function ApplicationStatusRoutes(): VNode {
   const addTokenCleaner = (c: () => void) => setCleaners(cs => [...cs, c])
   const addTokenCleanerNemo = useCallback((c: () => void) => { 
addTokenCleaner(c) }, [cleaner])
 
+  const ctx = useMemo(() => ({ currency: backendConfig.data?.currency || '', 
version: backendConfig.data?.version || '' }), [backendConfig.data?.currency, 
backendConfig.data?.version])
+
   return <div id="app">
-    <ConfigContext.Provider value={backendConfig.data || { currency: '', 
version: '' }}>
-      <NavigationBar lang={lang} setLang={setLang} onLogout={() => { 
cleaners.forEach(c => c()) }} />
-      <Sidebar />
+    <ConfigContext.Provider value={ctx}>
+      <NavigationBar lang={lang} setLang={setLang} />
+      <Sidebar onLogout={() => {
+        cleaners.forEach(c => c())
+        route(RootPaths.list_instances)
+      }} />
       <Notifications notifications={notifications} 
removeNotification={removeNotification} />
-      {!backendConfig.data ?
+      {!ctx.currency ?
         <Route default component={LoginWithError} /> :
         <Route default component={ApplicationReadyRoutes} 
pushNotification={pushNotification} addTokenCleaner={addTokenCleanerNemo} />
       }
diff --git a/packages/frontend/src/messages/en.po 
b/packages/frontend/src/messages/en.po
index 8ee89c4..7511c90 100644
--- a/packages/frontend/src/messages/en.po
+++ b/packages/frontend/src/messages/en.po
@@ -20,6 +20,12 @@ msgstr "List of configured instances"
 msgid "There is no instances yet, add more pressing the + sign"
 msgstr "There is no instances yet, add more pressing the + sign"
 
+msgid "Invalid payment address"
+msgstr "Invalid payment address"
+
+msgid "The value %s is invalid for a payment url"
+msgstr "The value \"%s\" is invalid for a payment url"
+
 #  msgctxt "fields.instance.name"
 #  msgid "placeholder"
 #  msgstr ""
@@ -50,7 +56,7 @@ msgid "fields.instance.payto_uris.label"
 msgstr "Account address"
 
 msgid "fields.instance.payto_uris.help"
-msgstr "payto://x-taler-bank/bank.taler:5882/blogger"
+msgstr "x-taler-bank/bank.taler:5882/blogger"
 
 msgid "fields.instance.default_max_deposit_fee.label"
 msgstr "Max deposit fee label"
diff --git a/packages/frontend/src/routes/instances/create/CreatePage.tsx 
b/packages/frontend/src/routes/instances/create/CreatePage.tsx
index b1531ce..2a595a6 100644
--- a/packages/frontend/src/routes/instances/create/CreatePage.tsx
+++ b/packages/frontend/src/routes/instances/create/CreatePage.tsx
@@ -21,7 +21,7 @@
 
 import { h, VNode } from "preact";
 import { useContext, useState } from "preact/hooks";
-import { Amount, MerchantBackend, RelativeTime } from "../../../declaration";
+import { MerchantBackend } from "../../../declaration";
 import * as yup from 'yup';
 import { FormErrors, FormProvider } from "../../../components/form/Field"
 import { InstanceCreateSchema as schema } from '../../../schemas'
@@ -31,9 +31,9 @@ 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";
+import { InputPayto } from "../../../components/form/InputPayto";
 
 type Entity = MerchantBackend.Instances.InstanceConfigurationMessage
 
@@ -63,7 +63,6 @@ export function CreatePage({ onCreate, isLoading, onBack }: 
Props): VNode {
     try {
       schema.validateSync(value, { abortEarly: false })
       onCreate(schema.cast(value) as Entity);
-      onBack()
     } catch (err) {
       const errors = err.inner as yup.ValidationError[]
       const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev : ({ 
...prev, [cur.path]: { type: cur.type, params: cur.params, message: cur.message 
} }), {})
@@ -111,13 +110,13 @@ export function CreatePage({ onCreate, isLoading, onBack 
}: Props): VNode {
         <div class="column is-two-thirds">
           <FormProvider<Entity> errors={errors} object={value} 
valueHandler={valueHandler} >
 
-            <InputWithAddon<Entity> name="id" 
addon={`${backend.url}/private/instances/`} />
+            <InputWithAddon<Entity> name="id" 
addonBefore={`${backend.url}/private/instances/`} />
 
             <Input<Entity> name="name" />
 
             <InputSecured<Entity> name="auth_token" />
 
-            <InputArray<Entity> name="payto_uris" />
+            <InputPayto<Entity> name="payto_uris" />
 
             <InputCurrency<Entity> name="default_max_deposit_fee" 
currency={config.currency} />
 
diff --git a/packages/frontend/src/routes/instances/list/Table.tsx 
b/packages/frontend/src/routes/instances/list/Table.tsx
index 507bd2f..3270806 100644
--- a/packages/frontend/src/routes/instances/list/Table.tsx
+++ b/packages/frontend/src/routes/instances/list/Table.tsx
@@ -106,14 +106,12 @@ function Table({ rowSelection, rowSelectionHandler, 
instances, onUpdate, onDelet
           </th>
           <th><Message id="fields.instance.id.label" /></th>
           <th><Message id="fields.instance.name.label" /></th>
-          <th><Message id="fields.instance.merchant_pub.label" /></th>
-          <th><Message id="fields.instance.payment_targets.label" /></th>
           <th />
         </tr>
       </thead>
       <tbody>
         {instances.map(i => {
-          return <tr>
+          return <tr onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}}>
             <td class="is-checkbox-cell">
               <label class="b-checkbox checkbox">
                 <input type="checkbox" checked={rowSelection.indexOf(i.id) != 
-1} onClick={(): void => rowSelectionHandler(toggleSelected(i.id))} />
@@ -122,13 +120,8 @@ function Table({ rowSelection, rowSelectionHandler, 
instances, onUpdate, onDelet
             </td>
             <td >{i.id}</td>
             <td >{i.name}</td>
-            <td >{i.merchant_pub}</td>
-            <td >{i.payment_targets}</td>
             <td class="is-actions-cell">
               <div class="buttons is-right">
-                <button class="button is-small is-link" type="button" 
onClick={(): void => onUpdate(i.id)}>
-                  <span class="icon"><i class="mdi mdi-eye" /></span>
-                </button>
                 <button class="button is-small is-danger jb-modal" 
type="button" onClick={(): void => onDelete(i)}>
                   <span class="icon"><i class="mdi mdi-trash-can" /></span>
                 </button>
diff --git a/packages/frontend/src/routes/instances/update/UpdatePage.tsx 
b/packages/frontend/src/routes/instances/update/UpdatePage.tsx
index 5c81d68..647f9da 100644
--- a/packages/frontend/src/routes/instances/update/UpdatePage.tsx
+++ b/packages/frontend/src/routes/instances/update/UpdatePage.tsx
@@ -21,7 +21,7 @@
 
 import { h, VNode } from "preact";
 import { useContext, useState } from "preact/hooks";
-import { Amount, MerchantBackend, RelativeTime } from "../../../declaration";
+import { MerchantBackend } from "../../../declaration";
 import * as yup from 'yup';
 import { FormProvider, FormErrors } from "../../../components/form/Field"
 import { InputGroup } from "../../../components/form/InputGroup"
@@ -30,9 +30,7 @@ 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 { ConfigContext } from "../../../context/backend";
 import { InputArray } from "../../../components/form/InputArray";
 import { InputDuration } from "../../../components/form/InputDuration";
 import { InputCurrency } from "../../../components/form/InputCurrency";
diff --git a/packages/frontend/src/scss/_theme-default.scss 
b/packages/frontend/src/scss/_theme-default.scss
index 3ad807b..091c42d 100644
--- a/packages/frontend/src/scss/_theme-default.scss
+++ b/packages/frontend/src/scss/_theme-default.scss
@@ -14,7 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
- /**
+/**
  *
  * @author Sebastian Javier Marchano (sebasjm)
  */
@@ -31,7 +31,7 @@ $family-sans-serif: "Nunito", sans-serif;
 
 /* Base color */
 $base-color: #2e323a;
-$base-color-light: rgba(24, 28, 33, .06);
+$base-color-light: rgba(24, 28, 33, 0.06);
 
 /* General overrides */
 $primary: $turquoise;
@@ -44,14 +44,14 @@ $hr-height: 1px;
 /* NavBar: specifics */
 $navbar-input-color: $grey-darker;
 $navbar-input-placeholder-color: $grey-lighter;
-$navbar-box-shadow: 0 1px 0 rgba(24,28,33,.04);
-$navbar-divider-border: 1px solid rgba($grey-lighter, .25);
-$navbar-item-h-padding: $default-padding * .75;
+$navbar-box-shadow: 0 1px 0 rgba(24, 28, 33, 0.04);
+$navbar-divider-border: 1px solid rgba($grey-lighter, 0.25);
+$navbar-item-h-padding: $default-padding * 0.75;
 $navbar-avatar-size: 1.75rem;
 
 /* Aside: Bulma override */
 $menu-item-radius: 0;
-$menu-list-link-padding: $size-base * .5 0;
+$menu-list-link-padding: $size-base * 0.5 0;
 $menu-label-color: lighten($base-color, 25%);
 $menu-item-color: lighten($base-color, 30%);
 $menu-item-hover-color: $white;
@@ -63,7 +63,7 @@ $menu-item-active-background-color: darken($base-color, 2.5%);
 $aside-width: $size-base * 14;
 $aside-mobile-width: $size-base * 15;
 $aside-icon-width: $size-base * 3;
-$aside-submenu-font-size: $size-base * .95;
+$aside-submenu-font-size: $size-base * 0.95;
 $aside-box-shadow: none;
 $aside-background-color: $base-color;
 $aside-tools-background-color: darken($aside-background-color, 10%);
@@ -107,11 +107,11 @@ $modal-card-width-mobile: 90vw;
 $modal-card-foot-background-color: $white-ter;
 
 /* Notification: Bulma override */
-$notification-padding: $default-padding * .75 $default-padding;
+$notification-padding: $default-padding * 0.75 $default-padding;
 
 /* Footer: Bulma override */
 $footer-background-color: $white;
-$footer-padding: $default-padding * .33 $default-padding;
+$footer-padding: $default-padding * 0.33 $default-padding;
 
 /* Footer: specifics */
 $footer-logo-height: $size-base * 2;
@@ -120,7 +120,17 @@ $footer-logo-height: $size-base * 2;
 $progress-bar-background-color: $grey-lighter;
 
 /* Icon: specifics */
-$icon-update-mark-size: $size-base * .5;
+$icon-update-mark-size: $size-base * 0.5;
 $icon-update-mark-color: $yellow;
 
 $input-disabled-border-color: $grey-lighter;
+$table-row-hover-background-color: hsl(0, 0%, 80%);
+
+.menu-list {
+  div {
+    border-radius: $menu-item-radius;
+    color: $menu-item-color;
+    display: block;
+    padding: $menu-list-link-padding;
+  }
+}
diff --git a/packages/frontend/src/utils/constants.ts 
b/packages/frontend/src/utils/constants.ts
index adc25a4..a560612 100644
--- a/packages/frontend/src/utils/constants.ts
+++ b/packages/frontend/src/utils/constants.ts
@@ -19,5 +19,6 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-export const 
PAYTO_REGEX=/^payto:\/\/[a-zA-Z][a-zA-Z0-9-.]*(\/[a-zA-Z0-9\-\.\~\(\)@_%:!$&'*+,;=]*)*\??((amount|receiver-name|sender-name|instruction|message)=[a-zA-Z0-9\-\.\~\(\)@_%:!$'*+,;=]*&?)*$/
+ //https://tools.ietf.org/html/rfc8905
+export const 
PAYTO_REGEX=/^payto:\/\/[a-zA-Z][a-zA-Z0-9-.]+(\/[a-zA-Z0-9\-\.\~\(\)@_%:!$&'*+,;=]*)*\??((amount|receiver-name|sender-name|instruction|message)=[a-zA-Z0-9\-\.\~\(\)@_%:!$'*+,;=]*&?)*$/
 export const AMOUNT_REGEX=/^[a-zA-Z][a-zA-Z]*:[0-9][0-9,]*\.?[0-9,]*$/

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