gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant-backoffice] 01/03: refactor router


From: gnunet
Subject: [taler-merchant-backoffice] 01/03: refactor router
Date: Mon, 22 Feb 2021 23:09:13 +0100

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

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

commit 59e4d7e1873e701ef6041219bce0e0431e659de5
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Mon Feb 22 01:28:39 2021 -0300

    refactor router
---
 CHANGELOG.md                                       |   5 +-
 packages/frontend/src/components/auth/index.tsx    |  32 ++--
 packages/frontend/src/components/footer/index.tsx  |  49 -----
 packages/frontend/src/components/modal/index.tsx   |   4 +-
 packages/frontend/src/components/navbar/index.tsx  |   9 +-
 .../notifications/Notifications.stories.tsx        |  59 +++---
 .../src/components/notifications/index.tsx         |   4 +-
 packages/frontend/src/components/yup/YupField.tsx  |  27 +--
 packages/frontend/src/context/backend.ts           |  28 ++-
 packages/frontend/src/hooks/backend.ts             | 105 ++++++-----
 packages/frontend/src/hooks/index.ts               |  51 +++--
 packages/frontend/src/hooks/notifications.ts       |  22 ++-
 packages/frontend/src/i18n/index.ts                |   6 +-
 packages/frontend/src/index.tsx                    | 207 ++++++++++++++++++---
 .../instances/{ => create}/Create.stories.tsx      |   0
 .../routes/instances/{ => create}/CreatePage.tsx   |  16 +-
 .../frontend/src/routes/instances/create/index.tsx |  21 +++
 .../{UpdatePage.tsx => details/DetailPage.tsx}     |  35 ++--
 .../src/routes/instances/details/index.tsx         |  52 ++++++
 packages/frontend/src/routes/instances/index.tsx   |  97 ----------
 .../src/routes/instances/{ => list}/CardTable.tsx  |  19 +-
 .../routes/instances/{ => list}/DeleteModal.tsx    |   9 +-
 .../src/routes/instances/{ => list}/EmptyTable.tsx |   0
 .../src/routes/instances/{ => list}/Table.tsx      |  11 +-
 .../routes/instances/{ => list}/View.stories.tsx   |   0
 .../src/routes/instances/{ => list}/View.tsx       |  32 +---
 .../frontend/src/routes/instances/list/index.tsx   | 108 +++++++++++
 .../routes/instances/{ => update}/UpdatePage.tsx   |  21 ++-
 .../frontend/src/routes/instances/update/index.tsx |  37 ++++
 packages/frontend/src/routes/login/index.tsx       |   9 +
 packages/frontend/src/scss/main.scss               |  11 +-
 packages/frontend/tests/header.test.tsx            |  11 +-
 pnpm-lock.yaml                                     |  86 ++++-----
 33 files changed, 723 insertions(+), 460 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e093a1f..9aedc98 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,14 +27,11 @@ and this project adheres to [Semantic 
Versioning](https://semver.org/spec/v2.0.0
  - remove checkbox from auth token, use button (manage auth)
  - prepend payto:// to account
  - validate on change everything
- - remove footer
+ <!-- - remove footer -->
 
  - implement proper error handling
- - PATCH payto uri not working as expeced: re-enable, creating with multiple 
uris
  - replace Yup and type definition with a taler-library for the purpose (first 
wait Florian to refactor wallet core)
  - add more doc style comments 
- - check the field names in forms dont break spaces
- - update spanish lang
  - save every auth token of different instances
  - configure eslint
  - configure prettier
diff --git a/packages/frontend/src/components/auth/index.tsx 
b/packages/frontend/src/components/auth/index.tsx
index 6ec69c0..8c0a20b 100644
--- a/packages/frontend/src/components/auth/index.tsx
+++ b/packages/frontend/src/components/auth/index.tsx
@@ -20,40 +20,41 @@
 */
 
 import { h, VNode } from "preact";
-import { useState } from "preact/hooks";
-import { useBackend } from "../../hooks";
+import { useContext, useState } from "preact/hooks";
+import { BackendContext } from "../../context/backend";
 
 interface Props {
-  onConfirm?: () => void;
+  onConfirm: (backend: string, token?: string) => void;
 }
 
-export function LoginPage({ onConfirm }: Props): VNode {
-  const [backend, setBackend] = useBackend()
-  const [url, setUrl] = useState(backend.url)
-  const [token, setToken] = useState(backend.token)
-  const [changeToken, setChangeToken] = useState(false)
+export function LoginModal({ onConfirm }: 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)
 
   return <div class="modal is-active is-clipped">
     <div class="modal-background" />
     <div class="modal-card">
       <header class="modal-card-head">
-        <p class="modal-card-title">Authentication required</p>
+        <p class="modal-card-title">Login required</p>
       </header>
       <section class="modal-card-body">
         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">Change Token</label>
+            <label class="label">Updte token</label>
           </div>
           <div class="field-body">
             <div class="field has-addons">
               <label class="b-checkbox checkbox">
-                <input type="checkbox" checked={changeToken} onClick={(): void 
=> setChangeToken(!changeToken)} />
+                <input type="checkbox" checked={updatingToken} 
onClick={toggleUpdatingToken} />
                 <span class="check" />
               </label>
 
               <p class="control is-expanded">
-                <input class="input" type="text" placeholder={changeToken ? 
"set new token" : "hidden token value"} disabled={!changeToken} name="id" 
value={token} onInput={(e): void => setToken(e?.currentTarget.value)} />
+                <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)} />
               </p>
             </div>
           </div>
@@ -65,16 +66,15 @@ export function LoginPage({ onConfirm }: Props): VNode {
           <div class="field-body">
             <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="set new url" 
name="id" value={url} onInput={(e): void => setURL(e?.currentTarget.value)} />
               </p>
             </div>
           </div>
         </div>
       </section>
-      <footer class="modal-card-foot">
+      <footer class="modal-card-foot " style={{justifyContent: 'flex-end'}}>
         <button class="button is-info" onClick={(): void => {
-          setBackend({ token, url });
-          onConfirm && onConfirm();
+          onConfirm(url, updatingToken && token ? token : undefined);
         }} >Confirm</button>
       </footer>
     </div>
diff --git a/packages/frontend/src/components/footer/index.tsx 
b/packages/frontend/src/components/footer/index.tsx
deleted file mode 100644
index 6a31e88..0000000
--- a/packages/frontend/src/components/footer/index.tsx
+++ /dev/null
@@ -1,49 +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';
-
-export function Footer(): VNode {
-  return (
-    <footer class="footer">
-      <div class="container-fluid">
-        <div class="level">
-          <div class="level-left">
-            <div class="level-item">copyleft</div>
-            <div class="level-item">
-              <a href="https://taler.net/"; style="height: 20px">
-                Taler
-              </a>
-            </div>
-          </div>
-          <div class="level-right">
-            <div class="level-item">
-              <div class="logo">
-                sebasjm
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
-    </footer>
-  )
-}
-
diff --git a/packages/frontend/src/components/modal/index.tsx 
b/packages/frontend/src/components/modal/index.tsx
index ec6d681..e3c957c 100644
--- a/packages/frontend/src/components/modal/index.tsx
+++ b/packages/frontend/src/components/modal/index.tsx
@@ -43,8 +43,8 @@ export function ConfirmModal({ active, description, onCancel, 
onConfirm, childre
         {children}
       </section>
       <footer class="modal-card-foot">
-        <button class="button " onClick={onCancel} ><Text id="cancel" 
/></button>
-        <button class={danger ? "button is-danger " : "button is-info "} 
onClick={onConfirm} ><Text id="confirm" /></button>
+        <button class="button " onClick={onCancel} ><Text id="text.cancel" 
/></button>
+        <button class={danger ? "button is-danger " : "button is-info "} 
onClick={onConfirm} ><Text id="text.confirm" /></button>
       </footer>
     </div>
     <button class="modal-close is-large " aria-label="close" 
onClick={onCancel} />
diff --git a/packages/frontend/src/components/navbar/index.tsx 
b/packages/frontend/src/components/navbar/index.tsx
index 626f471..ec00c70 100644
--- a/packages/frontend/src/components/navbar/index.tsx
+++ b/packages/frontend/src/components/navbar/index.tsx
@@ -20,8 +20,6 @@
 */
 
 import { h, VNode } from 'preact';
-import { useState } from 'preact/hooks';
-import { LoginPage } from '../auth';
 import { translations } from '../../i18n'
 // TODO: Fix compilation problem
 // import * as logo from '../../assets/logo.jpeg';
@@ -29,10 +27,10 @@ import { translations } from '../../i18n'
 interface Props {
   lang: string;
   setLang: (l: string) => void;
+  onLogout: () => void;
 }
 
-export function NavigationBar({ lang, setLang }: Props): VNode {
-  const [showLogin, setShowLogin] = useState(false)
+export function NavigationBar({ lang, setLang, onLogout }: Props): VNode {
   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";>
@@ -62,11 +60,10 @@ export function NavigationBar({ lang, setLang }: Props): 
VNode {
           </div>
         </div>
         <div class="navbar-item">
-          <button class="button is-primary" onClick={(): void => 
setShowLogin(true)}>Change access</button>
+          <button class="button is-primary" onClick={(): void => 
onLogout()}>Log out</button>
         </div>
       </div>
     </div>
-    {showLogin && <LoginPage onConfirm={(): void => setShowLogin(false)} />}
   </nav>
   );
 }
\ No newline at end of file
diff --git 
a/packages/frontend/src/components/notifications/Notifications.stories.tsx 
b/packages/frontend/src/components/notifications/Notifications.stories.tsx
index 88549fe..734644d 100644
--- a/packages/frontend/src/components/notifications/Notifications.stories.tsx
+++ b/packages/frontend/src/components/notifications/Notifications.stories.tsx
@@ -26,40 +26,29 @@ import { Notifications } from './index'
 export default {
   title: 'Components/Notification',
   component: Notifications,
+  argTypes: {
+    removeNotification: { action: 'removeNotification' },
+  },
 };
 
-export const NotificationInfo = (): VNode => {
-  return <div>
-    <Notifications notifications={[{
-      messageId: 'unauthorized',
-      type: 'INFO',
-    }]} />
-  </div>
-};
-
-export const NotificationWarn = (): VNode => {
-  return <div>
-    <Notifications notifications={[{
-      messageId: 'unauthorized',
-      type: 'WARN',
-    }]} />
-  </div>
-};
-
-export const NotificationError = (): VNode => {
-  return <div>
-    <Notifications notifications={[{
-      messageId: 'unauthorized',
-      type: 'ERROR',
-    }]} />
-  </div>
-};
-
-export const NotificationSuccess = (): VNode => {
-  return <div>
-    <Notifications notifications={[{
-      messageId: 'unauthorized',
-      type: 'SUCCESS',
-    }]} />
-  </div>
-};
+export const Info = (a: any) => <Notifications {...a} />;
+Info.args = {
+  notifications: [{
+    messageId: 'unauthorized',
+    type: 'INFO',
+  }]
+}
+export const Warn = (a: any) => <Notifications {...a} />;
+Warn.args = {
+  notifications: [{
+    messageId: 'unauthorized',
+    type: 'WARN',
+  }]
+}
+export const Error = (a: any) => <Notifications {...a} />;
+Error.args = {
+  notifications: [{
+    messageId: 'unauthorized',
+    type: 'ERROR',
+  }]
+}
diff --git a/packages/frontend/src/components/notifications/index.tsx 
b/packages/frontend/src/components/notifications/index.tsx
index 7c32ed2..a581823 100644
--- a/packages/frontend/src/components/notifications/index.tsx
+++ b/packages/frontend/src/components/notifications/index.tsx
@@ -25,6 +25,7 @@ import { MessageType, Notification } from "../../declaration";
 
 interface Props {
   notifications: Notification[];
+  removeNotification: (n: Notification) => void;
 }
 
 function messageStyle(type: MessageType): string {
@@ -37,11 +38,12 @@ function messageStyle(type: MessageType): string {
   }
 }
 
-export function Notifications({ notifications }: Props): VNode {
+export function Notifications({ notifications, removeNotification }: Props): 
VNode {
   return <div class="toast">
     {notifications.map(n => <article class={messageStyle(n.type)}>
       <div class="message-header">
         <p><Text id={`notification.${n.messageId}.title`} /> </p>
+        <button class="delete" onClick={()=> removeNotification(n)} />
       </div>
       <div class="message-body">
         <Text id={`notification.${n.messageId}.description`} fields={n.params} 
/>
diff --git a/packages/frontend/src/components/yup/YupField.tsx 
b/packages/frontend/src/components/yup/YupField.tsx
index 5bbcf33..ea15bda 100644
--- a/packages/frontend/src/components/yup/YupField.tsx
+++ b/packages/frontend/src/components/yup/YupField.tsx
@@ -23,8 +23,10 @@ import { h, VNode } from "preact";
 import { Text, useText } from "preact-i18n";
 import { StateUpdater, useContext, useState } from "preact/hooks";
 import { intervalToDuration, formatDuration } from 'date-fns'
+import { BackendContext, ConfigContext } from '../../context/backend';
 
-function readableDuration(duration: number): string {
+function readableDuration(duration?: number): string {
+  if (!duration) return ""
   return formatDuration(intervalToDuration({ start: 0, end: duration }))
 }
 
@@ -55,8 +57,6 @@ interface Props {
   valueHandler: StateUpdater<any>;
   info: any;
 }
-import { ConfigContext } from '../../context/backend';
-
 export function YupField({ name, field, errors, object, valueHandler, info }: 
Props): VNode {
   const updateField = (f: string) => (v: string): void => valueHandler((prev: 
any) => ({ ...prev, [f]: v }))
   const values = {
@@ -65,24 +65,27 @@ export function YupField({ name, field, errors, object, 
valueHandler, info }: Pr
     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]}
-      onChange={(updater: any): void => valueHandler((prev: any) => ({ 
...prev, [field]: updater(prev[field]) }))}
-    />
+    case 'group': {
+      return <YupObjectInput name={name}
+        info={info} errors={errors}
+        value={object && object[field]}
+        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} />;      
+      }
+      return <YupInput {...values} />;
     }
-    case 'url': return <YupInputWithAddon {...values} 
addon={`${config.backendURL}/private/instances/`} />;
+    case 'url': return <YupInputWithAddon {...values} 
addon={`${backend.url}/private/instances/`} />;
     case 'secured': return <YupInputSecured {...values} />;
-    case 'duration': return <YupInputWithAddon 
addon={readableDuration(values.value?.d_ms)} atTheEnd {...values} 
value={`${values.value?.d_ms / 1000 || ''}`} onChange={(v: string): void => 
values.onChange({ d_ms: (parseInt(v, 10) * 1000) || undefined } as any)} />;
+    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 beb6a1b..64e4dd2 100644
--- a/packages/frontend/src/context/backend.ts
+++ b/packages/frontend/src/context/backend.ts
@@ -1,11 +1,29 @@
 import { createContext } from 'preact'
 
-interface GlobalContext {
-  backendURL: string;
+export interface BackendContextType {
+  url: string;
+  token?: string;
+  changeBackend: (url: string) => void;
+  clearToken: () => void;
+  updateToken: (token:string) => void;
+  lang: string;
+  setLang: (lang: string) => void;
+}
+
+export interface ConfigContextType {
   currency?: string;
 }
 
-export const ConfigContext = createContext<GlobalContext>({
-  backendURL: '',
-  currency: '',
+export const BackendContext = createContext<BackendContextType>({
+  url: '',
+  lang: 'en',
+  token: undefined,
+  changeBackend: () => null,
+  clearToken: () => null,
+  updateToken: () => null,
+  setLang: () => null,
+})
+
+export const ConfigContext = createContext<ConfigContextType>({
+  currency: undefined,
 })
diff --git a/packages/frontend/src/hooks/backend.ts 
b/packages/frontend/src/hooks/backend.ts
index 0804c7b..c1118f9 100644
--- a/packages/frontend/src/hooks/backend.ts
+++ b/packages/frontend/src/hooks/backend.ts
@@ -14,14 +14,16 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
- /**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
 
 import useSWR, { mutate } from 'swr';
 import axios from 'axios'
 import { MerchantBackend } from '../declaration';
+import { useContext } from 'preact/hooks';
+import { BackendContext } from '../context/backend';
 
 type HttpResponse<T> = HttpResponseOk<T> | HttpResponseError<T>;
 
@@ -30,82 +32,101 @@ interface HttpResponseOk<T> {
 }
 interface HttpResponseError<T> {
   data: undefined;
-  needsAuth: boolean;
+  unauthorized: boolean;
   error: Error;
 }
 
 
 type Methods = 'get' | 'post' | 'patch' | 'delete' | 'put';
 
-async function request(url: string, method?: Methods, data?: any): 
Promise<any> {
-  const backend = localStorage.getItem('backend-url')
-  const token = localStorage.getItem('backend-token')
-  const headers = token ? { Authorization: `${token}` } : undefined
+interface RequestOptions {
+  method?: Methods;
+  token?: string;
+  data?: any;
+}
+
+async function request(url: string, options: RequestOptions = {}): 
Promise<any> {
+  const headers = options.token ? { Authorization: `${options.token}` } : 
undefined
 
   try {
     const res = await axios({
-      method: method || 'get',
-      url: `${backend}${url}`,
+      method: options.method || 'get',
+      url,
       responseType: 'json',
       headers,
-      data
+      data: options.data
     })
     return res.data
   } catch (e) {
     const info = e.response?.data
     const status = e.response?.status
-    throw { info, status, error:e, backend, hasToken: !!token }
+    throw { info, status, error: e, backend: url, hasToken: !!options.token }
   }
 
 }
 
-async function fetcher(url: string): Promise<any> {
-  return request(url, 'get')
+function fetcher(url: string, token: string, backend: string) {
+  return request(`${backend}${url}`, { token })
 }
 
-interface WithCreate<T> {
-  create: (data: T) => Promise<void>;
-}
-interface WithUpdate<T> {
-  update: (id: string, data: T) => Promise<void>;
-}
-interface WithDelete {
-  delete: (id: string) => Promise<void>;
+interface BackendMutateAPI {
+  createInstance: (data: 
MerchantBackend.Instances.InstanceConfigurationMessage) => Promise<void>;
+  updateInstance: (id: string, data: 
MerchantBackend.Instances.InstanceReconfigurationMessage) => Promise<void>;
+  deleteInstance: (id: string) => Promise<void>;
 }
 
-export function useBackendInstances(): 
HttpResponse<MerchantBackend.Instances.InstancesResponse> & 
WithCreate<MerchantBackend.Instances.InstanceConfigurationMessage> {
-  const { data, error } = 
useSWR<MerchantBackend.Instances.InstancesResponse>('/private/instances', 
fetcher)
+export function useBackendMutateAPI(): BackendMutateAPI {
+  const { url, token } = useContext(BackendContext)
 
-  const create = async (instance: 
MerchantBackend.Instances.InstanceConfigurationMessage): Promise<void> => {
-    await request('/private/instances', 'post', instance)
+  const createInstance = async (instance: 
MerchantBackend.Instances.InstanceConfigurationMessage): Promise<void> => {
+    await request(`${url}/private/instances`, {
+      method: 'post',
+      token,
+      data: instance
+    })
 
     mutate('/private/instances')
   }
-
-  return { data, needsAuth: error?.status === 401, error, create }
-}
-
-export function useBackendInstance(id: string | null): 
HttpResponse<MerchantBackend.Instances.QueryInstancesResponse> & 
WithUpdate<MerchantBackend.Instances.InstanceReconfigurationMessage> & 
WithDelete {
-  const { data, error } = 
useSWR<MerchantBackend.Instances.QueryInstancesResponse>(id ? 
`/private/instances/${id}` : null, fetcher)
-
-  const update = async (updateId: string, instance: 
MerchantBackend.Instances.InstanceReconfigurationMessage): Promise<void> => {
-    await request(`/private/instances/${updateId}`, 'patch', instance)
+  const updateInstance = async (updateId: string, instance: 
MerchantBackend.Instances.InstanceReconfigurationMessage): Promise<void> => {
+    await request(`${url}/private/instances/${updateId}`, {
+      method: 'patch',
+      token,
+      data: instance
+    })
 
     mutate('/private/instances', null)
     mutate(`/private/instances/${updateId}`, null)
   };
-  const _delete = async (deleteId: string): Promise<void> => {
-    await request(`/private/instances/${deleteId}`, 'delete')
+  const deleteInstance = async (deleteId: string): Promise<void> => {
+    await request(`${url}/private/instances/${deleteId}`, {
+      method: 'delete',
+      token,
+    })
 
     mutate('/private/instances', null)
     mutate(`/private/instances/${deleteId}`, null)
   }
 
-  return { data, needsAuth: error?.status === 401, error, update, delete: 
_delete }
+  return { createInstance, updateInstance, deleteInstance }
+}
+
+export function useBackendInstances(): 
HttpResponse<MerchantBackend.Instances.InstancesResponse> {
+  const { url, token } = useContext(BackendContext)
+  const { data, error } = 
useSWR<MerchantBackend.Instances.InstancesResponse>(['/private/instances', 
token, url], fetcher)
+
+  return { data, unauthorized: error?.status === 401, error }
+}
+
+export function useBackendInstance(id: string | null): 
HttpResponse<MerchantBackend.Instances.QueryInstancesResponse> {
+  const { url, token } = useContext(BackendContext)
+  const { data, error } = 
useSWR<MerchantBackend.Instances.QueryInstancesResponse>(id ? 
[`/private/instances/${id}`, token, url] : null, fetcher)
+
+  return { data, unauthorized: error?.status === 401, error }
 }
 
-export function useBackendConfig(): 
HttpResponse<MerchantBackend.VersionResponse>  {
-  const { data, error } = useSWR<MerchantBackend.VersionResponse>(`/config`, 
fetcher)
+export function useBackendConfig(): 
HttpResponse<MerchantBackend.VersionResponse> {
+  const { url, token } = useContext(BackendContext)
+  const { data, error } = useSWR<MerchantBackend.VersionResponse>(['/config', 
token, url], fetcher)
 
-  return { data, needsAuth: error?.status === 401, error }
+  return { data, unauthorized: error?.status === 401, error }
 }
diff --git a/packages/frontend/src/hooks/index.ts 
b/packages/frontend/src/hooks/index.ts
index 267b765..12d65d4 100644
--- a/packages/frontend/src/hooks/index.ts
+++ b/packages/frontend/src/hooks/index.ts
@@ -19,29 +19,52 @@
 * @author Sebastian Javier Marchano (sebasjm)
 */
 
-import { StateUpdater, useState } from "preact/hooks";
+import { StateUpdater, useEffect, useState } from "preact/hooks";
 import { mutate } from 'swr';
 
-interface State {
-  token?: string;
-  url: string;
+export function useBackendURL(): [string, StateUpdater<string>] {
+  return useNotNullLocalStorage('backend-url', window.location.origin)
+}
+export function useBackendDefaultToken(): [string | undefined, 
StateUpdater<string | undefined>] {
+  return useLocalStorage('backend-token')
 }
 
-export function useBackend(): [State, StateUpdater<State>] {
-  const [url, setUrl] = useLocalStorage('backend-url', window.location.origin)
-  const [token, setToken] = useLocalStorage('backend-token')
-
-  const updater: StateUpdater<State> = (value: State | ((value: State) => 
State)) => {
-    const valueToStore = value instanceof Function ? value({ token, url: url 
|| window.location.origin }) : value;
-    setUrl(valueToStore.url)
-    setToken(valueToStore.token)
+export function useBackendInstanceToken(id: string): [string | undefined, 
StateUpdater<string | undefined>, VoidFunction] {
+  const [token, setToken] = useLocalStorage(`backend-token-${id}`)
+  const [ids, setIds] = useLocalStorage(`backend-token-ids`)
 
-    mutate('/private/instances', null)
+  function clearAllTokens() {
+    // TODO: refactor this
+    ids?.split(',').map(i => localStorage.removeItem(`backend-token-${i}`))
   }
 
-  return [{ token, url: url || window.location.origin }, updater]
+  useEffect(() => {
+    setIds((ids: string | undefined): string | undefined => {
+      if (!ids) return ids
+      const all = ids.split(',')
+      if (all.includes(id)) return ids
+      return all.concat(id).filter(Boolean).join(',')
+    })
+  }, [id, setIds])
+  
+  return [token, setToken, clearAllTokens]
 }
 
+// export function useBackend(): [State, StateUpdater<State>] {
+//   const [url, setUrl] = useLocalStorage('backend-url', 
window.location.origin)
+//   const [token, setToken] = useLocalStorage('backend-token')
+
+//   const updater: StateUpdater<State> = (value: State | ((value: State) => 
State)) => {
+//     const valueToStore = value instanceof Function ? value({ token, url: 
url || window.location.origin }) : value;
+//     setUrl(valueToStore.url)
+//     setToken(valueToStore.token)
+
+//     mutate('/private/instances', null)
+//   }
+
+//   return [{ token, url: url || window.location.origin }, updater]
+// }
+
 export function useLang(): [string, StateUpdater<string>] {
   return useNotNullLocalStorage('lang-preference', typeof window !== 
"undefined" ? navigator.language || (navigator as any).userLanguage : 'en')
 }
diff --git a/packages/frontend/src/hooks/notifications.ts 
b/packages/frontend/src/hooks/notifications.ts
index 986ddaf..2a3cb3f 100644
--- a/packages/frontend/src/hooks/notifications.ts
+++ b/packages/frontend/src/hooks/notifications.ts
@@ -14,10 +14,10 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
- /**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
 
 import { useState } from "preact/hooks";
 import { Notification } from '../declaration';
@@ -25,16 +25,24 @@ import { Notification } from '../declaration';
 interface Result {
   notifications: Notification[];
   pushNotification: (n: Notification) => void;
+  removeNotification: (n: Notification) => void;
 }
 
+type NotificationWithDate = Notification & { since: Date }
+
 export function useNotifications(timeout = 3000): Result {
-  const [notifications, setNotifications] = useState<(Notification & { since: 
Date })[]>([])
+  const [notifications, setNotifications] = 
useState<(NotificationWithDate)[]>([])
+
   const pushNotification = (n: Notification): void => {
     const entry = { ...n, since: new Date() }
     setNotifications(ns => [...ns, entry])
-    setTimeout(() => {
+    if (n.type !== 'ERROR') setTimeout(() => {
       setNotifications(ns => ns.filter(x => x.since !== entry.since))
     }, timeout)
   }
-  return { notifications, pushNotification }
+
+  const removeNotification = (notif: Notification) => {
+    setNotifications((ns: NotificationWithDate[]) => ns.filter(n => n !== 
notif))
+  }
+  return { notifications, pushNotification, removeNotification }
 }
diff --git a/packages/frontend/src/i18n/index.ts 
b/packages/frontend/src/i18n/index.ts
index 27b9f72..cdc6984 100644
--- a/packages/frontend/src/i18n/index.ts
+++ b/packages/frontend/src/i18n/index.ts
@@ -150,8 +150,6 @@ export const translations = {
         description: 'the delete process completed'
       },
     },
-    cancel: 'cancel',
-    confirm: 'confirm',
     fields: {
       instance: {
         id: {
@@ -271,6 +269,10 @@ export const translations = {
       list_of_configured_instances: 'List of configured instances',
       create_new_instance: 'Create new instance',
       
+      cancel: 'cancel',
+      confirm: 'confirm',
+      delete: 'delete',
+      update: 'update',
       instance: {
         empty_list: 'No instance configured yet, setup one pressing the + 
button',
       }
diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx
index c613514..07d34ac 100644
--- a/packages/frontend/src/index.tsx
+++ b/packages/frontend/src/index.tsx
@@ -22,43 +22,200 @@
 import "./scss/main.scss"
 
 import { h, VNode } from 'preact';
-import { Route, Router } from 'preact-router';
+import { useContext, useEffect } from "preact/hooks";
+import { Route, Router, route } from 'preact-router';
 import { IntlProvider } from 'preact-i18n';
 
-import { Footer } from './components/footer';
+import { Notification } from "./declaration";
 import { Sidebar } from './components/sidebar';
 import { NavigationBar } from './components/navbar';
 import { Notifications } from './components/notifications';
 import { translations } from './i18n';
-import { useBackend, useLang } from './hooks';
-
-import NotFoundPage from './routes/notfound';
-import Instances from './routes/instances';
+import { useBackendURL, useBackendDefaultToken, useLang, 
useBackendInstanceToken } from './hooks';
 import { useNotifications } from "./hooks/notifications";
-import { ConfigContext } from './context/backend';
+import { BackendContext } from './context/backend';
 import { useBackendConfig } from "./hooks/backend";
 
-export default function App(): VNode {
-  const { notifications, pushNotification } = useNotifications()
+import NotFoundPage from './routes/notfound';
+import Login from './routes/login';
+import Instances from './routes/instances/list';
+import Create from "./routes/instances/create";
+import Details from "./routes/instances/details";
+import Update from "./routes/instances/update";
+
+enum RootPages {
+  root = '/',
+  instances = '/instances',
+  new = '/new',
+  instance_id_route = '/instance/:id/:rest*',
+}
+
+enum InstancePages {
+  details = '/instance/:id/',
+  update = '/instance/:id/update',
+}
+
+function Redirect({ to }: { to: string }): null {
+  useEffect(() => {
+    route(to, true)
+  })
+  return null
+}
+
+
+
+function AppRouting(): VNode {
+  const { notifications, pushNotification, removeNotification } = 
useNotifications()
+  const { lang, setLang, clearToken, changeBackend, updateToken } = 
useContext(BackendContext)
+  const backendConfig = useBackendConfig();
+
+  const error = backendConfig.data || backendConfig.error
+
+  useEffect(() =>{
+    pushNotification({
+      messageId: 'error',
+      type: 'ERROR',
+      params: error
+    })
+  }, [error, pushNotification])
+
+  return <div id="app">
+    <NavigationBar lang={lang} setLang={setLang} onLogout={clearToken} />
+    <Sidebar />
+    <Notifications notifications={notifications} 
removeNotification={removeNotification} />
+    {!backendConfig.data ?
+      <Route default 
+        component={Login}
+
+        onConfirm={(url: string, token?: string) => {
+          changeBackend(url)
+          if (token) updateToken(token)
+          route(RootPages.instances)
+        }} /> :
+      <Route default component={AppReady} pushNotification={pushNotification} 
/>
+    }
+  </div>
+}
+
+function AppReady({ pushNotification }: { pushNotification: (n: Notification) 
=> void }): VNode {
+  const { changeBackend, updateToken } = useContext(BackendContext)
+
+  const updateLoginStatus = (url: string, token?: string) => {
+    changeBackend(url)
+    if (token) updateToken(token)
+  }
+
+  return <Router>
+    <Route path={RootPages.root} component={Redirect} to={RootPages.instances} 
/>
+
+    <Route path={RootPages.instances}
+      component={Instances}
+
+      onCreate={() => {
+        route(RootPages.new)
+      }}
+
+      onUpdate={(id: string): void => {
+        route(`/instance/${id}/update`)
+      }}
+
+      onUnauthorized={() => <Login onConfirm={updateLoginStatus} />}
+
+      onError={(error: Error) => {
+        pushNotification({ messageId: 'error', params: error, type: 'ERROR' })
+        return <div />
+      }}
+    />
+
+    <Route path={RootPages.new}
+      component={Create}
+      onBack={() => route(RootPages.instances)}
+
+      onConfirm={() => {
+        pushNotification({ messageId: 'create_success', type: 'SUCCESS' })
+        route(RootPages.instances)
+      }}
+
+      onError={(error: any) => {
+        pushNotification({ messageId: 'create_error', type: 'ERROR', params: 
error })
+      }}
+    />
+
+    <Route path={RootPages.instance_id_route} component={SubPages} 
pushNotification={pushNotification} />
+
+    <Route default component={NotFoundPage} />
+
+  </Router>
+}
+
+function useBackendContextState() {
   const [lang, setLang] = useLang()
-  const [{url: backendURL}] = useBackend();
-  const { data } = useBackendConfig();
+  const [url, changeBackend] = useBackendURL();
+  const [token, updateToken] = useBackendDefaultToken();
+  const clearToken = () => updateToken(undefined)
+
+  return { url, token, changeBackend, clearToken, updateToken, lang, setLang }
+}
 
+export default function Application(): VNode {
+  const state = useBackendContextState()
   return (
-    <ConfigContext.Provider value={{backendURL, currency: data && 
data.currency}}>
-
-      <IntlProvider definition={(translations as any)[lang] || 
translations.en}>
-        <div id="app">
-          <NavigationBar lang={lang} setLang={setLang} />
-          <Sidebar />
-          <Notifications notifications={notifications} />
-          <Router>
-            <Route path="/" component={Instances} 
pushNotification={pushNotification} />
-            <Route default component={NotFoundPage} />
-          </Router>
-          <Footer />
-        </div>
+    <BackendContext.Provider value={state}>
+      <IntlProvider definition={(translations as any)[state.lang] || 
translations.en}>
+        <AppRouting />
       </IntlProvider >
-    </ConfigContext.Provider>
+    </BackendContext.Provider>
   );
+}
+interface SubPagesProps {
+  id: string;
+  pushNotification: (n: Notification) => void;
+}
+
+function SubPages({ id, pushNotification }: SubPagesProps): VNode {
+  const [, updateToken] = useBackendInstanceToken(id);
+  const { changeBackend } = useContext(BackendContext)
+
+  const updateLoginStatus = (url: string, token?: string) => {
+    changeBackend(url)
+    if (token) updateToken(token)
+  }
+
+  return <Router>
+    <Route path={InstancePages.details}
+      component={Details}
+      onUnauthorized={() => <Login onConfirm={updateLoginStatus} />}
+      onUpdate={() => {
+        route(`/instance/${id}/update`)
+      }}
+      onLoadError={(e: Error) => {
+        pushNotification({ messageId: 'update_load_error', type: 'ERROR', 
params: e })
+        route(`/instance/${id}/`)
+        return <div />
+      }}
+
+    />
+
+    <Route path={InstancePages.update}
+      component={Update}
+      onUnauthorized={() => <Login onConfirm={updateLoginStatus} />}
+      onLoadError={(e: Error) => {
+        pushNotification({ messageId: 'update_load_error', type: 'ERROR', 
params: e })
+        route(`/instance/${id}/`)
+        return <div />
+      }}
+      onBack={() => {
+        route(`/instance/${id}/`)
+      }}
+      onConfirm={() => {
+        pushNotification({ messageId: 'create_success', type: 'SUCCESS' })
+        route(`/instance/${id}/`)
+      }}
+      onUpdateError={(e: Error) => {
+        pushNotification({ messageId: 'update_error', type: 'ERROR', params: e 
})
+      }}
+    />
+
+    <Route default component={NotFoundPage} />
+  </Router>
 }
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instances/Create.stories.tsx 
b/packages/frontend/src/routes/instances/create/Create.stories.tsx
similarity index 100%
rename from packages/frontend/src/routes/instances/Create.stories.tsx
rename to packages/frontend/src/routes/instances/create/Create.stories.tsx
diff --git a/packages/frontend/src/routes/instances/CreatePage.tsx 
b/packages/frontend/src/routes/instances/create/CreatePage.tsx
similarity index 89%
rename from packages/frontend/src/routes/instances/CreatePage.tsx
rename to packages/frontend/src/routes/instances/create/CreatePage.tsx
index 7f16c2d..44aa1cc 100644
--- a/packages/frontend/src/routes/instances/CreatePage.tsx
+++ b/packages/frontend/src/routes/instances/create/CreatePage.tsx
@@ -21,16 +21,16 @@
 
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
-import { MerchantBackend } from "../../declaration";
+import { MerchantBackend } from "../../../declaration";
 import * as yup from 'yup';
-import { YupField } from "../../components/yup/YupField"
-import { InstanceCreateSchema as schema } from '../../schemas'
+import { YupField } from "../../../components/yup/YupField"
+import { InstanceCreateSchema as schema } from '../../../schemas'
 import { Text } from "preact-i18n";
 
 interface Props {
   onCreate: (d: MerchantBackend.Instances.InstanceConfigurationMessage) => 
void;
   isLoading: boolean;
-  goBack: () => void;
+  onBack: () => void;
 }
 
 interface KeyValue {
@@ -45,7 +45,7 @@ function with_defaults(): 
Partial<MerchantBackend.Instances.InstanceConfiguratio
   };
 }
 
-export function CreatePage({ onCreate, isLoading, goBack }: Props): VNode {
+export function CreatePage({ onCreate, isLoading, onBack }: Props): VNode {
   const [value, valueHandler] = useState(with_defaults())
   const [errors, setErrors] = useState<KeyValue>({})
 
@@ -53,7 +53,7 @@ export function CreatePage({ onCreate, isLoading, goBack }: 
Props): VNode {
     try {
       schema.validateSync(value, { abortEarly: false })
       onCreate(schema.cast(value) as 
MerchantBackend.Instances.InstanceConfigurationMessage);
-      goBack()
+      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 
} }), {})
@@ -102,8 +102,8 @@ export function CreatePage({ onCreate, isLoading, goBack }: 
Props): VNode {
               valueHandler={valueHandler} info={schema.fields[f].describe()}
             />)}
           <div class="buttons is-right">
-            <button class="button" onClick={goBack} ><Text id="cancel" 
/></button>
-            <button class="button is-success" onClick={submit} ><Text 
id="confirm" /></button>
+            <button class="button" onClick={onBack} ><Text id="text.cancel" 
/></button>
+            <button class="button is-success" onClick={submit} ><Text 
id="text.confirm" /></button>
           </div>
         </div>
         <div class="column" />
diff --git a/packages/frontend/src/routes/instances/create/index.tsx 
b/packages/frontend/src/routes/instances/create/index.tsx
new file mode 100644
index 0000000..4af7b5a
--- /dev/null
+++ b/packages/frontend/src/routes/instances/create/index.tsx
@@ -0,0 +1,21 @@
+import { h, VNode } from "preact";
+import { MerchantBackend } from "../../../declaration";
+import { useBackendMutateAPI } from "../../../hooks/backend";
+import { CreatePage } from "./CreatePage";
+
+interface Props {
+  onBack: () => void;
+  onConfirm: () => void;
+  onError: (error: any) => void;
+}
+
+export default function Create({ onBack, onConfirm, onError }: Props): VNode {
+  const { createInstance } = useBackendMutateAPI();
+
+  return <CreatePage 
+    onBack={onBack}
+    isLoading={false}
+    onCreate={(d: MerchantBackend.Instances.InstanceConfigurationMessage): 
Promise<void> => {
+      return createInstance(d).then(onConfirm).catch(onError)
+    }} />
+}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instances/UpdatePage.tsx 
b/packages/frontend/src/routes/instances/details/DetailPage.tsx
similarity index 71%
copy from packages/frontend/src/routes/instances/UpdatePage.tsx
copy to packages/frontend/src/routes/instances/details/DetailPage.tsx
index feed125..f4ab4a7 100644
--- a/packages/frontend/src/routes/instances/UpdatePage.tsx
+++ b/packages/frontend/src/routes/instances/details/DetailPage.tsx
@@ -21,17 +21,17 @@
 
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
-import { MerchantBackend, WidthId } from "../../declaration";
-import * as yup from 'yup';
-import { YupField } from "../../components/yup/YupField"
-import { InstanceUpdateSchema as schema } from '../../schemas'
+import { MerchantBackend } from "../../../declaration";
+import { YupField } from "../../../components/yup/YupField"
+import { InstanceUpdateSchema as schema } from '../../../schemas'
 import { Text } from "preact-i18n";
 
 interface Props {
-  onUpdate: (id: string, d: 
MerchantBackend.Instances.InstanceReconfigurationMessage) => void;
-  selected: MerchantBackend.Instances.QueryInstancesResponse & WidthId;
+  onUpdate: () => void;
+  onDelete: () => void;
+  selected: MerchantBackend.Instances.QueryInstancesResponse;
+  id: string,
   isLoading: boolean;
-  goBack: () => void;
 }
 
 interface KeyValue {
@@ -49,22 +49,10 @@ function convert(from: 
MerchantBackend.Instances.QueryInstancesResponse): Mercha
   return { ...defaults, ...rest, payto_uris };
 }
 
-export function UpdatePage({ onUpdate, isLoading, selected, goBack }: Props): 
VNode {
+export function DetailPage({ onUpdate, isLoading, selected, onDelete }: 
Props): VNode {
   const [value, valueHandler] = useState(convert(selected))
   const [errors, setErrors] = useState<KeyValue>({})
 
-  const submit = (): void => {
-    try {
-      schema.validateSync(value, { abortEarly: false })
-      onUpdate(selected.id, schema.cast(value));
-      goBack()
-    } 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 
} }), {})
-      setErrors(pathMessages)
-    }
-  }
-
   return <div>
     <section class="section is-title-bar">
 
@@ -106,9 +94,9 @@ export function UpdatePage({ onUpdate, isLoading, selected, 
goBack }: Props): VN
               field={f} errors={errors} object={value}
               valueHandler={valueHandler} info={schema.fields[f].describe()}
             />)}
-         <div class="buttons is-right">
-            <button class="button" onClick={goBack} ><Text id="cancel" 
/></button>
-            <button class="button is-success" onClick={submit} ><Text 
id="confirm" /></button>
+          <div class="buttons is-right">
+            <button class="button is-danger" onClick={() => onDelete()} ><Text 
id="text.delete" /></button>
+            <button class="button is-success" onClick={() => onUpdate()} 
><Text id="text.update" /></button>
           </div>
         </div>
         <div class="column" />
@@ -117,5 +105,4 @@ export function UpdatePage({ onUpdate, isLoading, selected, 
goBack }: Props): VN
 
   </div>
 
-  // </ConfirmModal>
 }
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instances/details/index.tsx 
b/packages/frontend/src/routes/instances/details/index.tsx
new file mode 100644
index 0000000..177278d
--- /dev/null
+++ b/packages/frontend/src/routes/instances/details/index.tsx
@@ -0,0 +1,52 @@
+import { Fragment, h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { Notification } from "../../../declaration";
+import { useBackendInstance, useBackendMutateAPI } from 
"../../../hooks/backend";
+import { DeleteModal } from "../list/DeleteModal";
+import { DetailPage } from "./DetailPage";
+
+interface Props {
+  onUnauthorized: () => VNode;
+  onLoadError: (e: Error) => VNode;
+  onUpdate: () => void;
+  id: string;
+  pushNotification: (n: Notification) => void;
+}
+
+export default function Detail({ onUpdate, onLoadError, onUnauthorized, id, 
pushNotification }: Props): VNode {
+  const details = useBackendInstance(id)
+  const [deleting, setDeleting] = useState<boolean>(false)
+
+  const { deleteInstance } = useBackendMutateAPI()
+
+  if (!details.data) {
+    if (details.unauthorized) return onUnauthorized()
+    if (details.error) return onLoadError(details.error)
+    return <div>
+      loading ....
+    </div>
+  }
+
+  return <Fragment>
+    <DetailPage
+      isLoading={false}
+      selected={details.data} id={id}
+      onUpdate={onUpdate}
+      onDelete={() => setDeleting(true) }
+    />
+    {deleting && <DeleteModal 
+      element={{name: details.data.name, id }} 
+      onCancel={() => setDeleting(false) } 
+      onConfirm={async (id: string): Promise<void> => {
+        try {
+          await deleteInstance(id)
+          pushNotification({ messageId: 'delete_success', type: 'SUCCESS' })
+        } catch (error) {
+          pushNotification({ messageId: 'delete_error', type: 'ERROR', params: 
error })
+        }
+        setDeleting(false)
+    }}
+    />}
+
+  </Fragment>
+}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instances/index.tsx 
b/packages/frontend/src/routes/instances/index.tsx
deleted file mode 100644
index e44b087..0000000
--- a/packages/frontend/src/routes/instances/index.tsx
+++ /dev/null
@@ -1,97 +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 { View } from './View';
-import { LoginPage } from '../../components/auth';
-import { useBackendInstance, useBackendInstances } from '../../hooks/backend';
-import { useEffect, useState } from 'preact/hooks';
-import { Notification } from '../../declaration';
-import { CreatePage } from './CreatePage';
-import { UpdatePage } from './UpdatePage';
-
-interface Props {
-  pushNotification: (n: Notification) => void;
-}
-
-export default function Instances({ pushNotification }: Props): VNode {
-  const list = useBackendInstances()
-  const [selectedId, select] = useState<string | null>(null)
-  const details = useBackendInstance(selectedId)
-  const [create, setCreate] = useState<boolean>(false)
-  const [update, setUpdate] = useState<boolean>(false)
-
-
-  const requiresToken = (!list.data && list.needsAuth) || (selectedId != null 
&& !details.data && details.needsAuth)
-  const isLoadingTheList = (!list.data && !list.error)
-  const isLoadingTheDetails = (!details.data && !details.error)
-
-  const genericError = !list.data && list.error || !details.data && 
details.error
-
-  useEffect(() => {
-    if (requiresToken) {
-      pushNotification({ messageId: 'unauthorized', type: 'ERROR' })
-    } else if (genericError) {
-      pushNotification({ messageId: 'error', params: genericError, type: 
'ERROR' })
-    }
-  }, [requiresToken, genericError])
-
-
-  if (requiresToken) {
-    return <LoginPage />
-  }
-
-  if (create) {
-    return <CreatePage
-      goBack={() => setCreate(false)}
-      isLoading={false}
-
-      onCreate={(d): Promise<void> => list.create(d)
-        .then((): void => pushNotification({ messageId: 'create_success', 
type: 'SUCCESS' }))
-        .catch((error): void => pushNotification({ messageId: 'create_error', 
type: 'ERROR', params: error }))
-      }
-    />
-  }
-
-  if (update && details.data && selectedId) {
-    return <UpdatePage
-      goBack={() => setUpdate(false)}
-      isLoading={false}
-      selected={{ ...details.data, id: selectedId }}
-      onUpdate={(id, d): Promise<void> => details.update(id, d)
-        .then((): void => pushNotification({ messageId: 'update_success', 
type: 'SUCCESS' }))
-        .catch((error): void => pushNotification({ messageId: 'update_error', 
type: 'ERROR', params: error }))
-      }
-    />
-  }
-
-  return <View instances={list.data?.instances || []}
-    isLoading={isLoadingTheList || isLoadingTheDetails}
-    onCreate={setCreate}
-    onUpdate={setUpdate}
-    onDelete={(id): Promise<void> => details.delete(id)
-      .then((): void => pushNotification({ messageId: 'delete_success', type: 
'SUCCESS' }))
-      .catch((error): void => pushNotification({ messageId: 'delete_error', 
type: 'ERROR', params: error }))
-    }
-    onSelect={select}
-    selected={!details.data || !selectedId ? undefined : { ...details.data, 
id: selectedId }}
-  />;
-}
diff --git a/packages/frontend/src/routes/instances/CardTable.tsx 
b/packages/frontend/src/routes/instances/list/CardTable.tsx
similarity index 82%
rename from packages/frontend/src/routes/instances/CardTable.tsx
rename to packages/frontend/src/routes/instances/list/CardTable.tsx
index 2a4e9d5..46c32ff 100644
--- a/packages/frontend/src/routes/instances/CardTable.tsx
+++ b/packages/frontend/src/routes/instances/list/CardTable.tsx
@@ -22,15 +22,16 @@
 import { h, VNode } from "preact";
 import { Text } from "preact-i18n";
 import { useEffect, useState } from "preact/hooks";
-import { MerchantBackend, WidthId as WithId } from "../../declaration";
+import { MerchantBackend } from "../../../declaration";
 import { EmptyTable } from "./EmptyTable";
 import { Table } from "./Table";
 
 interface Props {
   instances: MerchantBackend.Instances.Instance[];
-  onSelect: (id: string | null, action: 'UPDATE' | 'DELETE') => void;
+  onUpdate: (id: string) => void;
+  onDelete: (id: MerchantBackend.Instances.Instance) => void;
   onCreate: () => void;
-  selected: MerchantBackend.Instances.QueryInstancesResponse & WithId | 
undefined;
+  selected?: boolean;
 }
 
 interface Actions {
@@ -48,23 +49,23 @@ function buildActions(intances: 
MerchantBackend.Instances.Instance[], selected:
     .map(id => ({ element: id, type: action }))
 }
 
-export function CardTable({ instances, onCreate, onSelect, selected }: Props): 
VNode {
+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') {
-      onSelect(actionQueue[0].element.id, 'DELETE')
+      onDelete(actionQueue[0].element)
       actionQueueHandler(actionQueue.slice(1))
     }
-  }, [actionQueue, selected, onSelect])
+  }, [actionQueue, selected, onDelete])
 
   useEffect(() => {
     if (actionQueue.length > 0 && !selected && actionQueue[0].type == 
'UPDATE') {
-      onSelect(actionQueue[0].element.id, 'UPDATE')
+      onUpdate(actionQueue[0].element.id)
       actionQueueHandler(actionQueue.slice(1))
     }
-  }, [actionQueue, selected, onSelect])
+  }, [actionQueue, selected, onUpdate])
 
 
   return <div class="card has-table">
@@ -89,7 +90,7 @@ export function CardTable({ instances, onCreate, onSelect, 
selected }: Props): V
       <div class="b-table has-pagination">
         <div class="table-wrapper has-mobile-cards">
           {instances.length > 0 ?
-            <Table instances={instances} onSelect={onSelect} 
rowSelection={rowSelection} rowSelectionHandler={rowSelectionHandler} /> :
+            <Table instances={instances} onUpdate={onUpdate} 
onDelete={onDelete} rowSelection={rowSelection} 
rowSelectionHandler={rowSelectionHandler} /> :
             <EmptyTable />
           }
         </div>
diff --git a/packages/frontend/src/routes/instances/DeleteModal.tsx 
b/packages/frontend/src/routes/instances/list/DeleteModal.tsx
similarity index 76%
rename from packages/frontend/src/routes/instances/DeleteModal.tsx
rename to packages/frontend/src/routes/instances/list/DeleteModal.tsx
index a682d12..f526456 100644
--- a/packages/frontend/src/routes/instances/DeleteModal.tsx
+++ b/packages/frontend/src/routes/instances/list/DeleteModal.tsx
@@ -20,17 +20,16 @@
 */
 
 import { h, VNode } from "preact";
-import { MerchantBackend, WidthId } from "../../declaration";
-import { ConfirmModal } from "../../components/modal";
+import { ConfirmModal } from "../../../components/modal";
 
 interface Props {
-  element: MerchantBackend.Instances.QueryInstancesResponse & WidthId;
+  element: {id: string, name: string};
   onCancel: () => void;
-  onConfirm: (i: MerchantBackend.Instances.QueryInstancesResponse & WidthId) 
=> void;
+  onConfirm: (id: string) => void;
 }
 
 export function DeleteModal({ element, onCancel, onConfirm }: Props): VNode {
-  return <ConfirmModal description="delete_instance" danger active 
onCancel={onCancel} onConfirm={() => onConfirm(element)}>
+  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>
diff --git a/packages/frontend/src/routes/instances/EmptyTable.tsx 
b/packages/frontend/src/routes/instances/list/EmptyTable.tsx
similarity index 100%
rename from packages/frontend/src/routes/instances/EmptyTable.tsx
rename to packages/frontend/src/routes/instances/list/EmptyTable.tsx
diff --git a/packages/frontend/src/routes/instances/Table.tsx 
b/packages/frontend/src/routes/instances/list/Table.tsx
similarity index 91%
rename from packages/frontend/src/routes/instances/Table.tsx
rename to packages/frontend/src/routes/instances/list/Table.tsx
index 4faf6b5..eb44447 100644
--- a/packages/frontend/src/routes/instances/Table.tsx
+++ b/packages/frontend/src/routes/instances/list/Table.tsx
@@ -22,12 +22,13 @@
 import { h, VNode } from "preact"
 import { Text } from "preact-i18n"
 import { StateUpdater } from "preact/hooks"
-import { MerchantBackend } from "../../declaration"
+import { MerchantBackend } from "../../../declaration"
 
 interface Props {
   rowSelection: string[];
   instances: MerchantBackend.Instances.Instance[];
-  onSelect: (id: string | null, action: 'UPDATE' | 'DELETE') => void;
+  onUpdate: (id: string) => void;
+  onDelete: (id: MerchantBackend.Instances.Instance) => void;
   rowSelectionHandler: StateUpdater<string[]>;
 }
 
@@ -35,7 +36,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, onSelect 
}: Props): VNode {
+export function Table({ rowSelection, rowSelectionHandler, instances, 
onUpdate, onDelete }: Props): VNode {
   return (
     <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
       <thead>
@@ -68,10 +69,10 @@ export function Table({ rowSelection, rowSelectionHandler, 
instances, onSelect }
             <td >{i.payment_targets}</td>
             <td class="is-actions-cell">
               <div class="buttons is-right">
-                <button class="button is-small is-primary" type="button" 
onClick={(): void => onSelect(i.id, 'UPDATE')}>
+                <button class="button is-small is-primary" 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 => onSelect(i.id, 'DELETE')}>
+                <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>
               </div>
diff --git a/packages/frontend/src/routes/instances/View.stories.tsx 
b/packages/frontend/src/routes/instances/list/View.stories.tsx
similarity index 100%
rename from packages/frontend/src/routes/instances/View.stories.tsx
rename to packages/frontend/src/routes/instances/list/View.stories.tsx
diff --git a/packages/frontend/src/routes/instances/View.tsx 
b/packages/frontend/src/routes/instances/list/View.tsx
similarity index 65%
rename from packages/frontend/src/routes/instances/View.tsx
rename to packages/frontend/src/routes/instances/list/View.tsx
index 438e54f..48b2a46 100644
--- a/packages/frontend/src/routes/instances/View.tsx
+++ b/packages/frontend/src/routes/instances/list/View.tsx
@@ -20,30 +20,20 @@
 */
 
 import { h, VNode } from "preact";
-import { useState } from "preact/hooks";
-import { MerchantBackend, WidthId } from "../../declaration";
+import { MerchantBackend } from "../../../declaration";
 import { CardTable } from './CardTable';
-import { DeleteModal } from './DeleteModal'
 import { Text } from "preact-i18n";
 
 interface Props {
   instances: MerchantBackend.Instances.Instance[];
-  onCreate: (s: boolean) => void;
-  onUpdate: (s: boolean) => void;
-  onDelete: (id: string) => void;
-  onSelect: (id: string | null) => void;
-  selected: MerchantBackend.Instances.QueryInstancesResponse & WidthId | 
undefined;
+  onCreate: () => void;
+  onUpdate: (id: string) => void;
+  onDelete: (id: MerchantBackend.Instances.Instance) => void;
+  selected?: boolean;
   isLoading: boolean;
 }
 
-export function View({ instances, isLoading, onCreate, onDelete, onSelect, 
onUpdate, selected }: Props): VNode {
-  const [action, setAction] = useState<'UPDATE' | 'DELETE' | null>(null)
-
-  const onSelectAction = (id: string | null, action?: 'UPDATE' | 'DELETE'): 
void => {
-    onSelect(id)
-    setAction(action || null)
-    if (action === 'UPDATE') onUpdate(true)
-  }
+export function View({ instances, isLoading, onCreate, onDelete, onUpdate, 
selected }: Props): VNode {
 
   return <div id="app">
     <section class="section is-title-bar">
@@ -77,16 +67,8 @@ export function View({ instances, isLoading, onCreate, 
onDelete, onSelect, onUpd
       </div>
     </section>
     <section class="section is-main-section">
-      <CardTable instances={instances} onSelect={onSelectAction} 
selected={selected} onCreate={(): void => onCreate(true)} />
+      <CardTable instances={instances} onDelete={onDelete} onUpdate={onUpdate} 
selected={selected} onCreate={onCreate} />
     </section>
 
-    {selected && action === 'DELETE' ?
-      <DeleteModal element={selected} onCancel={(): void => 
onSelectAction(null)} onConfirm={(i): void => {
-        onDelete(i.id)
-        onSelectAction(null);
-      }}
-      />
-      : null}
-
   </div >
 }
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instances/list/index.tsx 
b/packages/frontend/src/routes/instances/list/index.tsx
new file mode 100644
index 0000000..b9eedc9
--- /dev/null
+++ b/packages/frontend/src/routes/instances/list/index.tsx
@@ -0,0 +1,108 @@
+/*
+ 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 { Fragment, h, VNode } from 'preact';
+import { View } from './View';
+import { useBackendInstances, useBackendMutateAPI } from 
'../../../hooks/backend';
+import { useState } from 'preact/hooks';
+import { MerchantBackend, Notification } from '../../../declaration';
+import { DeleteModal } from './DeleteModal';
+interface Props {
+  pushNotification: (n: Notification) => void;
+  onUnauthorized: () => VNode;
+  onError: (e: Error) => VNode;
+  onCreate: () => void;
+  onUpdate: (id: string) => void;
+}
+
+export default function Instances({ pushNotification, onUnauthorized, onError, 
onCreate, onUpdate }: Props): VNode {
+  const list = useBackendInstances()
+  const [deleting, setDeleting] = useState<MerchantBackend.Instances.Instance 
| null>(null)
+  // const details = useBackendInstance(selectedId)
+  // const [create, setCreate] = useState<boolean>(false)
+  // const [update, setUpdate] = useState<boolean>(false)
+  const { deleteInstance } = useBackendMutateAPI()
+
+  // || (selectedId != null && !details.data && details.unauthorized)
+  const isLoadingTheList = (!list.data && !list.error)
+  // const isLoadingTheDetails = (!details.data && !details.error)
+  const error = !list.data && list.error
+  // || !details.data && details.error
+
+  if (!list.data) {
+    if (list.unauthorized) return onUnauthorized()
+    if (list.error) return onError(list.error)    
+  }
+
+  // if (create) {
+  //   return <CreatePage
+  //     goBack={() => setCreate(false)}
+  //     isLoading={false}
+
+  //     onCreate={(d): Promise<void> => list.create(d)
+  //       .then((): void => pushNotification({ messageId: 'create_success', 
type: 'SUCCESS' }))
+  //       .catch((error): void => pushNotification({ messageId: 
'create_error', type: 'ERROR', params: error }))
+  //     }
+  //   />
+  // }
+
+  // if (update && details.data && selectedId) {
+  //   return <UpdatePage
+  //     goBack={() => setUpdate(false)}
+  //     isLoading={false}
+  //     selected={{ ...details.data, id: selectedId }}
+  //     onUpdate={(id, d): Promise<void> => details.update(id, d)
+  //       .then((): void => pushNotification({ messageId: 'update_success', 
type: 'SUCCESS' }))
+  //       .catch((error): void => pushNotification({ messageId: 
'update_error', type: 'ERROR', params: error }))
+  //     }
+  //   />
+  // }
+  // {selected && action === 'DELETE' ?
+
+  // : null}
+
+  // (id): Promise<void> => deleteInstance(id)
+  // .then((): void => pushNotification({ messageId: 'delete_success', type: 
'SUCCESS' }))
+  // .catch((error): void => pushNotification({ messageId: 'delete_error', 
type: 'ERROR', params: error }))
+
+  return <Fragment>
+    <View instances={list.data?.instances || []}
+      isLoading={isLoadingTheList}
+      onDelete={setDeleting}
+      onCreate={onCreate}
+      onUpdate={onUpdate}
+      selected={!!deleting}
+    />
+    {deleting && <DeleteModal 
+      element={deleting} 
+      onCancel={() => setDeleting(null) } 
+      onConfirm={async (id: string): Promise<void> => {
+        try {
+          await deleteInstance(id)
+          pushNotification({ messageId: 'delete_success', type: 'SUCCESS' })
+        } catch (e) {
+          pushNotification({ messageId: 'delete_error', type: 'ERROR', params: 
error })
+        }
+        setDeleting(null)
+    }}
+    />}
+  </Fragment>;
+}
diff --git a/packages/frontend/src/routes/instances/UpdatePage.tsx 
b/packages/frontend/src/routes/instances/update/UpdatePage.tsx
similarity index 86%
rename from packages/frontend/src/routes/instances/UpdatePage.tsx
rename to packages/frontend/src/routes/instances/update/UpdatePage.tsx
index feed125..f99f797 100644
--- a/packages/frontend/src/routes/instances/UpdatePage.tsx
+++ b/packages/frontend/src/routes/instances/update/UpdatePage.tsx
@@ -21,17 +21,18 @@
 
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
-import { MerchantBackend, WidthId } from "../../declaration";
+import { MerchantBackend } from "../../../declaration";
 import * as yup from 'yup';
-import { YupField } from "../../components/yup/YupField"
-import { InstanceUpdateSchema as schema } from '../../schemas'
+import { YupField } from "../../../components/yup/YupField"
+import { InstanceUpdateSchema as schema } from '../../../schemas'
 import { Text } from "preact-i18n";
 
 interface Props {
   onUpdate: (id: string, d: 
MerchantBackend.Instances.InstanceReconfigurationMessage) => void;
-  selected: MerchantBackend.Instances.QueryInstancesResponse & WidthId;
+  selected: MerchantBackend.Instances.QueryInstancesResponse;
+  id: string,
   isLoading: boolean;
-  goBack: () => void;
+  onBack: () => void;
 }
 
 interface KeyValue {
@@ -49,15 +50,15 @@ function convert(from: 
MerchantBackend.Instances.QueryInstancesResponse): Mercha
   return { ...defaults, ...rest, payto_uris };
 }
 
-export function UpdatePage({ onUpdate, isLoading, selected, goBack }: Props): 
VNode {
+export function UpdatePage({ onUpdate, isLoading, selected, id, onBack }: 
Props): VNode {
   const [value, valueHandler] = useState(convert(selected))
   const [errors, setErrors] = useState<KeyValue>({})
 
   const submit = (): void => {
     try {
       schema.validateSync(value, { abortEarly: false })
-      onUpdate(selected.id, schema.cast(value));
-      goBack()
+      onUpdate(id, schema.cast(value));
+      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 
} }), {})
@@ -107,8 +108,8 @@ export function UpdatePage({ onUpdate, isLoading, selected, 
goBack }: Props): VN
               valueHandler={valueHandler} info={schema.fields[f].describe()}
             />)}
          <div class="buttons is-right">
-            <button class="button" onClick={goBack} ><Text id="cancel" 
/></button>
-            <button class="button is-success" onClick={submit} ><Text 
id="confirm" /></button>
+            <button class="button" onClick={onBack} ><Text id="text.cancel" 
/></button>
+            <button class="button is-success" onClick={submit} ><Text 
id="text.confirm" /></button>
           </div>
         </div>
         <div class="column" />
diff --git a/packages/frontend/src/routes/instances/update/index.tsx 
b/packages/frontend/src/routes/instances/update/index.tsx
new file mode 100644
index 0000000..8147ff8
--- /dev/null
+++ b/packages/frontend/src/routes/instances/update/index.tsx
@@ -0,0 +1,37 @@
+import { h, VNode } from "preact";
+import { MerchantBackend } from "../../../declaration";
+import { useBackendInstance, useBackendMutateAPI } from 
"../../../hooks/backend";
+import { UpdatePage } from "./UpdatePage";
+
+interface Props {
+  onBack: () => void;
+  onConfirm: () => void;
+  pushNotification: (n: Notification) => void;
+
+  onUnauthorized: () => VNode;
+  onLoadError: (e: Error) => VNode;
+  onUpdateError: (e: Error) => void;
+
+  id: string;
+}
+
+export default function Update({ onBack, onConfirm, onLoadError, 
onUpdateError, onUnauthorized, id }: Props): VNode {
+  const { updateInstance } = useBackendMutateAPI();
+  const details = useBackendInstance(id)
+
+  if (!details.data) {
+    if (details.unauthorized) return onUnauthorized()
+    if (details.error) return onLoadError(details.error)
+    return <div>
+      loading ....
+    </div>  
+  }
+
+  return <UpdatePage 
+    onBack={onBack}
+    isLoading={false}
+    selected={details.data} id={id}
+    onUpdate={(id: string, d: 
MerchantBackend.Instances.InstanceReconfigurationMessage): Promise<void> => {
+      return updateInstance(id, d).then(onConfirm).catch(onUpdateError)
+  }} />
+}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/login/index.tsx 
b/packages/frontend/src/routes/login/index.tsx
new file mode 100644
index 0000000..1651bf7
--- /dev/null
+++ b/packages/frontend/src/routes/login/index.tsx
@@ -0,0 +1,9 @@
+import { h, VNode } from "preact";
+import { LoginModal } from '../../components/auth';
+
+interface Props {
+  onConfirm: (url: string, token?: string) => void;
+}
+export default function LoginPage({onConfirm}: Props):VNode {
+  return <LoginModal onConfirm={onConfirm} />
+} 
\ No newline at end of file
diff --git a/packages/frontend/src/scss/main.scss 
b/packages/frontend/src/scss/main.scss
index 4696247..9e9e5c0 100644
--- a/packages/frontend/src/scss/main.scss
+++ b/packages/frontend/src/scss/main.scss
@@ -48,17 +48,16 @@
 @import 
"../../node_modules/@creativebulma/bulma-tooltip/dist/bulma-tooltip.min.css";
 
 .toast {
-  position: fixed;
-  right: 10px;
-  top: 10px;
+  position: absolute;
+  width: 60%;
+  margin-left: 10%;
+  margin-right: 10%;
   z-index: 999;
 
   display: flex;
   flex-direction: column;
   padding: 15px;
-  text-align: right;
-  align-items: flex-end;
-  width: auto;
+  text-align: center;
   pointer-events: none;
 }
 
diff --git a/packages/frontend/tests/header.test.tsx 
b/packages/frontend/tests/header.test.tsx
index 04932b7..2eefda3 100644
--- a/packages/frontend/tests/header.test.tsx
+++ b/packages/frontend/tests/header.test.tsx
@@ -20,14 +20,13 @@
 */
 
 import { h } from 'preact';
-import { Footer } from '../src/components/footer';
+import { Sidebar } from '../src/components/sidebar';
 // See: https://github.com/preactjs/enzyme-adapter-preact-pure
 import { shallow } from 'enzyme';
 
-describe('Initial Test of the Footer', () => {
-    test('Footer renders an anchor with Taler text', () => {
-        const context = shallow(<Footer />);
-        expect(context.find('a').text()).toBe('Taler');
-        expect(context.find('footer').length).toBe(1);
+describe('Initial Test of the Sidebar', () => {
+    test('Sidbar renders anchors with text', () => {
+        const context = shallow(<Sidebar />);
+        expect(context.find('a').map( a => a.text())).toEqual(["Instances", 
"Details", "Orders", "Inventory", "Tipping", "About"]);
     });
 });
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cc9e8b1..242438d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -97,7 +97,7 @@ importers:
       typedoc: ^0.20.25
       typescript: ^4.1.3
       yup: ^0.32.8
-lockfileVersion: 5.1
+lockfileVersion: 5.2
 packages:
   /@babel/code-frame/7.12.11:
     dependencies:
@@ -1546,7 +1546,7 @@ packages:
       jest-haste-map: 26.6.2
       jest-message-util: 26.6.2
       jest-regex-util: 26.0.0
-      jest-resolve: 26.6.2_jest-resolve@26.6.2
+      jest-resolve: 26.6.2
       jest-resolve-dependencies: 26.6.3
       jest-runner: 26.6.3
       jest-runtime: 26.6.3
@@ -1616,7 +1616,7 @@ packages:
       istanbul-lib-source-maps: 4.0.0
       istanbul-reports: 3.0.2
       jest-haste-map: 26.6.2
-      jest-resolve: 26.6.2_jest-resolve@26.6.2
+      jest-resolve: 26.6.2
       jest-util: 26.6.2
       jest-worker: 26.6.2
       slash: 3.0.0
@@ -1833,7 +1833,7 @@ packages:
       '@prefresh/core': 0.8.1_preact@10.5.12
       '@prefresh/utils': 0.3.1
       preact: 10.5.12
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     peerDependencies:
       preact: ^10.4.0
@@ -2536,7 +2536,7 @@ packages:
       unfetch: 4.2.0
       url-loader: 4.1.1_file-loader@6.2.0+webpack@4.46.0
       util-deprecate: 1.0.2
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
       webpack-dev-middleware: 3.7.3_webpack@4.46.0
       webpack-filter-warnings-plugin: 1.2.1_webpack@4.46.0
       webpack-hot-middleware: 2.25.0
@@ -2646,7 +2646,7 @@ packages:
       unfetch: 4.2.0
       url-loader: 4.1.1_file-loader@6.2.0+webpack@4.46.0
       util-deprecate: 1.0.2
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
       webpack-dev-middleware: 3.7.3_webpack@4.46.0
       webpack-filter-warnings-plugin: 1.2.1_webpack@4.46.0
       webpack-hot-middleware: 2.25.0
@@ -4150,7 +4150,7 @@ packages:
     dependencies:
       chalk: 2.4.1
       deepcopy: 1.0.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     peerDependencies:
       webpack: ^4.28.4
@@ -4217,7 +4217,7 @@ packages:
       loader-utils: 1.4.0
       make-dir: 3.1.0
       schema-utils: 2.7.1
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     engines:
       node: '>= 8.9'
@@ -5564,7 +5564,7 @@ packages:
       find-cache-dir: 3.3.1
       schema-utils: 2.7.1
       serialize-javascript: 4.0.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
       webpack-sources: 1.4.3
     dev: true
     engines:
@@ -5730,7 +5730,7 @@ packages:
       p-limit: 2.3.0
       schema-utils: 1.0.0
       serialize-javascript: 4.0.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
       webpack-log: 2.0.0
     dev: true
     engines:
@@ -5752,7 +5752,7 @@ packages:
     resolution:
       integrity: 
sha512-V5qQZVAr9K0xu7jXg1M7qTEwuxUgqr7dUOezGaNa7i+Xn9oXAU/d1fzqD9ObuwpVQOaorO5s70ckyi1woP9lVA==
   /core-js/2.6.12:
-    deprecated: 'core-js@<3 is no longer maintained and not recommended for 
usage due to the number of issues. Please, upgrade your dependencies to the 
actual version of core-js@3.'
+    deprecated: core-js@<3 is no longer maintained and not recommended for 
usage due to the number of issues. Please, upgrade your dependencies to the 
actual version of core-js@3.
     dev: true
     requiresBuild: true
     resolution:
@@ -5985,7 +5985,7 @@ packages:
       postcss-value-parser: 4.1.0
       schema-utils: 2.7.1
       semver: 6.3.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     engines:
       node: '>= 8.9.0'
@@ -6676,7 +6676,7 @@ packages:
   /dotenv-webpack/1.8.0_webpack@4.46.0:
     dependencies:
       dotenv-defaults: 1.1.1
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     peerDependencies:
       webpack: ^1 || ^2 || ^3 || ^4
@@ -7603,7 +7603,7 @@ packages:
     dependencies:
       loader-utils: 2.0.0
       schema-utils: 3.0.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     engines:
       node: '>= 10.13.0'
@@ -8679,7 +8679,7 @@ packages:
       tapable: 1.1.3
       toposort: 1.0.7
       util.promisify: 1.0.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     engines:
       node: '>=6.9'
@@ -8698,7 +8698,7 @@ packages:
       pretty-error: 2.1.2
       tapable: 1.1.3
       util.promisify: 1.0.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     engines:
       node: '>=6.9'
@@ -9801,7 +9801,7 @@ packages:
       jest-get-type: 26.3.0
       jest-jasmine2: 26.6.3
       jest-regex-util: 26.0.0
-      jest-resolve: 26.6.2_jest-resolve@26.6.2
+      jest-resolve: 26.6.2
       jest-util: 26.6.2
       jest-validate: 26.6.2
       micromatch: 4.0.2
@@ -9974,7 +9974,7 @@ packages:
       integrity: 
sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==
   /jest-pnp-resolver/1.2.2_jest-resolve@26.6.2:
     dependencies:
-      jest-resolve: 26.6.2_jest-resolve@26.6.2
+      jest-resolve: 26.6.2
     dev: true
     engines:
       node: '>=6'
@@ -10022,7 +10022,7 @@ packages:
       node: '>= 10.14.2'
     resolution:
       integrity: 
sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==
-  /jest-resolve/26.6.2_jest-resolve@26.6.2:
+  /jest-resolve/26.6.2:
     dependencies:
       '@jest/types': 26.6.2
       chalk: 4.1.0
@@ -10035,8 +10035,6 @@ packages:
     dev: true
     engines:
       node: '>= 10.14.2'
-    peerDependencies:
-      jest-resolve: '*'
     resolution:
       integrity: 
sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==
   /jest-runner/26.6.3:
@@ -10055,7 +10053,7 @@ packages:
       jest-haste-map: 26.6.2
       jest-leak-detector: 26.6.2
       jest-message-util: 26.6.2
-      jest-resolve: 26.6.2_jest-resolve@26.6.2
+      jest-resolve: 26.6.2
       jest-runtime: 26.6.3
       jest-util: 26.6.2
       jest-worker: 26.6.2
@@ -10088,7 +10086,7 @@ packages:
       jest-message-util: 26.6.2
       jest-mock: 26.6.2
       jest-regex-util: 26.0.0
-      jest-resolve: 26.6.2_jest-resolve@26.6.2
+      jest-resolve: 26.6.2
       jest-snapshot: 26.6.2
       jest-util: 26.6.2
       jest-validate: 26.6.2
@@ -10124,7 +10122,7 @@ packages:
       jest-haste-map: 26.6.2
       jest-matcher-utils: 26.6.2
       jest-message-util: 26.6.2
-      jest-resolve: 26.6.2_jest-resolve@26.6.2
+      jest-resolve: 26.6.2
       natural-compare: 1.4.0
       pretty-format: 26.6.2
       semver: 7.3.4
@@ -11132,7 +11130,7 @@ packages:
       loader-utils: 1.4.0
       normalize-url: 1.9.1
       schema-utils: 1.0.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
       webpack-sources: 1.4.3
     dev: true
     engines:
@@ -11795,7 +11793,7 @@ packages:
     dependencies:
       cssnano: 4.1.10
       last-call-webpack-plugin: 3.0.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     peerDependencies:
       webpack: ^4.0.0
@@ -12803,7 +12801,7 @@ packages:
       update-notifier: 4.1.3
       url-loader: 4.1.1_file-loader@6.2.0+webpack@4.46.0
       validate-npm-package-name: 3.0.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
       webpack-bundle-analyzer: 3.9.0
       webpack-dev-server: 3.11.2_webpack@4.46.0
       webpack-fix-style-only-entries: 0.5.2
@@ -12962,7 +12960,7 @@ packages:
     dependencies:
       chalk: 3.0.0
       progress: 2.0.3
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     peerDependencies:
       webpack: ^1.3.0 || ^2 || ^3 || ^4 || ^5
@@ -13225,7 +13223,7 @@ packages:
     dependencies:
       loader-utils: 2.0.0
       schema-utils: 3.0.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     engines:
       node: '>= 10.13.0'
@@ -13873,7 +13871,7 @@ packages:
       request-promise-core: 1.1.4_request@2.88.2
       stealthy-require: 1.1.1
       tough-cookie: 2.5.0
-    deprecated: 'request-promise-native has been deprecated because it extends 
the now deprecated request package, see 
https://github.com/request/request/issues/3142'
+    deprecated: request-promise-native has been deprecated because it extends 
the now deprecated request package, see 
https://github.com/request/request/issues/3142
     dev: true
     engines:
       node: '>=0.12.0'
@@ -13903,7 +13901,7 @@ packages:
       tough-cookie: 2.5.0
       tunnel-agent: 0.6.0
       uuid: 3.4.0
-    deprecated: 'request has been deprecated, see 
https://github.com/request/request/issues/3142'
+    deprecated: request has been deprecated, see 
https://github.com/request/request/issues/3142
     dev: true
     engines:
       node: '>= 6'
@@ -13968,7 +13966,7 @@ packages:
     resolution:
       integrity: 
sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
   /resolve-url/0.2.1:
-    deprecated: 'https://github.com/lydell/resolve-url#deprecated'
+    deprecated: https://github.com/lydell/resolve-url#deprecated
     dev: true
     resolution:
       integrity: sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
@@ -14552,7 +14550,7 @@ packages:
       minimatch: 3.0.4
       pretty-bytes: 5.5.0
       util.promisify: 1.1.1
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     peerDependencies:
       webpack: '*'
@@ -15099,7 +15097,7 @@ packages:
     dependencies:
       loader-utils: 2.0.0
       schema-utils: 2.7.1
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     engines:
       node: '>= 8.9.0'
@@ -15321,7 +15319,7 @@ packages:
       serialize-javascript: 4.0.0
       source-map: 0.6.1
       terser: 4.8.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
       webpack-sources: 1.4.3
       worker-farm: 1.7.0
     dev: true
@@ -15341,7 +15339,7 @@ packages:
       serialize-javascript: 4.0.0
       source-map: 0.6.1
       terser: 4.8.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
       webpack-sources: 1.4.3
     dev: true
     engines:
@@ -16015,7 +16013,7 @@ packages:
     resolution:
       integrity: 
sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
   /urix/0.1.0:
-    deprecated: 'Please see https://github.com/lydell/urix#deprecated'
+    deprecated: Please see https://github.com/lydell/urix#deprecated
     dev: true
     resolution:
       integrity: sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
@@ -16025,7 +16023,7 @@ packages:
       loader-utils: 2.0.0
       mime-types: 2.1.29
       schema-utils: 3.0.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     engines:
       node: '>= 10.13.0'
@@ -16368,7 +16366,7 @@ packages:
       mime: 2.5.2
       mkdirp: 0.5.5
       range-parser: 1.2.1
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
       webpack-log: 2.0.0
     dev: true
     engines:
@@ -16408,7 +16406,7 @@ packages:
       strip-ansi: 3.0.1
       supports-color: 6.1.0
       url: 0.11.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
       webpack-dev-middleware: 3.7.3_webpack@4.46.0
       webpack-log: 2.0.0
       ws: 6.2.1
@@ -16427,7 +16425,7 @@ packages:
       integrity: 
sha512-A80BkuHRQfCiNtGBS1EMf2ChTUs0x+B3wGDFmOeT4rmJOHhHTCH2naNxIHhmkr0/UillP4U3yeIyv1pNp+QDLQ==
   /webpack-filter-warnings-plugin/1.2.1_webpack@4.46.0:
     dependencies:
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     engines:
       node: '>= 4.3 < 5.0.0 || >= 5.10'
@@ -16482,7 +16480,7 @@ packages:
     dev: true
     resolution:
       integrity: 
sha512-kDUmfm3BZrei0y+1NTHJInejzxfhtU8eDj2M7OKb2IWrPFAeO1SOH2KuQ68MSZu9IGEHcxbkKKR1v18FrUSOmA==
-  /webpack/4.46.0_webpack@4.46.0:
+  /webpack/4.46.0:
     dependencies:
       '@webassemblyjs/ast': 1.9.0
       '@webassemblyjs/helper-module-context': 1.9.0
@@ -16506,14 +16504,12 @@ packages:
       tapable: 1.1.3
       terser-webpack-plugin: 1.4.5_webpack@4.46.0
       watchpack: 1.7.5
-      webpack: 4.46.0_webpack@4.46.0
       webpack-sources: 1.4.3
     dev: true
     engines:
       node: '>=6.11.5'
     hasBin: true
     peerDependencies:
-      webpack: '*'
       webpack-cli: '*'
       webpack-command: '*'
     peerDependenciesMeta:
@@ -16745,7 +16741,7 @@ packages:
       fast-json-stable-stringify: 2.1.0
       source-map-url: 0.4.1
       upath: 1.2.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
       webpack-sources: 1.4.3
       workbox-build: 5.1.4
     dev: true

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