gnunet-svn
[Top][All Lists]
Advanced

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

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


From: gnunet
Subject: [taler-merchant-backoffice] branch master updated (b1509bc -> e76acb3)
Date: Mon, 08 Mar 2021 13:19:56 +0100

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

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

    from b1509bc  fix linter
     new 3cacc8a  split pages into admin and instance groups
     new e76acb3  skeletons for order, products, transfers and tips

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


Summary of changes:
 CHANGELOG.md                                       |  22 +-
 packages/frontend/src/AdminRoutes.tsx              |  96 +++-
 packages/frontend/src/InstanceRoutes.tsx           |  93 +++-
 .../frontend/src/components/menu/NavigationBar.tsx |   2 +-
 packages/frontend/src/components/menu/SideBar.tsx  |  30 +-
 packages/frontend/src/context/backend.ts           |   2 +
 packages/frontend/src/declaration.d.ts             | 614 ++++++++++++++++++++-
 packages/frontend/src/hooks/backend.ts             | 288 +++++++++-
 packages/frontend/src/hooks/index.ts               |  22 +-
 packages/frontend/src/index.tsx                    |  32 +-
 packages/frontend/src/messages/en.po               |  30 +
 .../{instances => admin}/create/Create.stories.tsx |   0
 .../{instances => admin}/create/CreatePage.tsx     |   0
 .../routes/{instances => admin}/create/index.tsx   |   0
 .../src/routes/{instances => admin}/list/Table.tsx |  15 +-
 .../{instances => admin}/list/View.stories.tsx     |   0
 .../src/routes/{instances => admin}/list/View.tsx  |   1 -
 .../src/routes/{instances => admin}/list/index.tsx |   0
 .../{instances => instance}/details/DetailPage.tsx |   0
 .../{instances => instance}/details/index.tsx      |  14 +-
 .../src/routes/instance/orders/create/index.tsx    |   5 +
 .../{instances => instance/orders}/list/Table.tsx  |  47 +-
 .../src/routes/instance/orders/list/index.tsx      |  35 ++
 .../src/routes/instance/orders/update/index.tsx    |   5 +
 .../src/routes/instance/products/create/index.tsx  |   5 +
 .../products}/list/Table.tsx                       |  47 +-
 .../src/routes/instance/products/list/index.tsx    |  44 ++
 .../src/routes/instance/products/update/index.tsx  |   5 +
 .../src/routes/instance/tips/create/index.tsx      |   5 +
 .../{instances => instance/tips}/list/Table.tsx    |  45 +-
 .../src/routes/instance/tips/list/index.tsx        |  37 ++
 .../src/routes/instance/tips/update/index.tsx      |   5 +
 .../src/routes/instance/transfers/create/index.tsx |   5 +
 .../transfers}/list/Table.tsx                      |  37 +-
 .../src/routes/instance/transfers/list/index.tsx   |  36 ++
 .../src/routes/instance/transfers/update/index.tsx |   5 +
 .../{instances => instance}/update/UpdatePage.tsx  |   0
 .../{instances => instance}/update/index.tsx       |   4 +-
 packages/frontend/src/utils/functions.ts           |  13 +-
 packages/frontend/src/utils/table.ts               |  20 +
 40 files changed, 1496 insertions(+), 170 deletions(-)
 rename packages/frontend/src/routes/{instances => 
admin}/create/Create.stories.tsx (100%)
 rename packages/frontend/src/routes/{instances => admin}/create/CreatePage.tsx 
(100%)
 rename packages/frontend/src/routes/{instances => admin}/create/index.tsx 
(100%)
 copy packages/frontend/src/routes/{instances => admin}/list/Table.tsx (93%)
 rename packages/frontend/src/routes/{instances => admin}/list/View.stories.tsx 
(100%)
 rename packages/frontend/src/routes/{instances => admin}/list/View.tsx (97%)
 rename packages/frontend/src/routes/{instances => admin}/list/index.tsx (100%)
 rename packages/frontend/src/routes/{instances => 
instance}/details/DetailPage.tsx (100%)
 rename packages/frontend/src/routes/{instances => instance}/details/index.tsx 
(86%)
 create mode 100644 
packages/frontend/src/routes/instance/orders/create/index.tsx
 copy packages/frontend/src/routes/{instances => 
instance/orders}/list/Table.tsx (80%)
 create mode 100644 packages/frontend/src/routes/instance/orders/list/index.tsx
 create mode 100644 
packages/frontend/src/routes/instance/orders/update/index.tsx
 create mode 100644 
packages/frontend/src/routes/instance/products/create/index.tsx
 copy packages/frontend/src/routes/{instances => 
instance/products}/list/Table.tsx (78%)
 create mode 100644 
packages/frontend/src/routes/instance/products/list/index.tsx
 create mode 100644 
packages/frontend/src/routes/instance/products/update/index.tsx
 create mode 100644 packages/frontend/src/routes/instance/tips/create/index.tsx
 copy packages/frontend/src/routes/{instances => instance/tips}/list/Table.tsx 
(81%)
 create mode 100644 packages/frontend/src/routes/instance/tips/list/index.tsx
 create mode 100644 packages/frontend/src/routes/instance/tips/update/index.tsx
 create mode 100644 
packages/frontend/src/routes/instance/transfers/create/index.tsx
 rename packages/frontend/src/routes/{instances => 
instance/transfers}/list/Table.tsx (84%)
 create mode 100644 
packages/frontend/src/routes/instance/transfers/list/index.tsx
 create mode 100644 
packages/frontend/src/routes/instance/transfers/update/index.tsx
 rename packages/frontend/src/routes/{instances => 
instance}/update/UpdatePage.tsx (100%)
 rename packages/frontend/src/routes/{instances => instance}/update/index.tsx 
(95%)
 create mode 100644 packages/frontend/src/utils/table.ts

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 228bfbc..b0c25c4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,11 +12,7 @@ and this project adheres to [Semantic 
Versioning](https://semver.org/spec/v2.0.0
  - red color when input is invalid (onchange)
  - validate everything using onChange
  - feature: input as date format
- - bug: there is missing a mutate call when updating to remove the instance 
from cache
 
- - add order section
- - add product section
- - add tips section
  - implement better error handling (improve creation of duplicated instances)
  - replace Yup and type definition with a taler-library for the purpose (first 
wait Florian to refactor wallet core)
  - add more doc style comments 
@@ -24,8 +20,25 @@ and this project adheres to [Semantic 
Versioning](https://semver.org/spec/v2.0.0
  - configure prettier
  - prune scss styles to reduce size
  - create a loading page to be use when the data is not ready
+ - some way to copy the url of a created instance
+ - change the admin title to "instances" if we are listing the instances and 
"settings: $ID" on updating instances
+ - fix mobile: some things are still on the left
+ - update title with: Taler Backoffice: $PAGE_TITLE
+ - instance id in instance list should be clickable
+ - edit button to go to instance settings
+ - notifications should tale place between title and content, and not disapear
+ - confirmation page when creating instances
+ - if there is enough space for tables in mobile, make the scrollables
  
 ## [Unreleased]
+ - add order section
+ - add product section
+ - add tips section
+ - add transfers section
+ - initial state before login
+ - logout takes you to a initial state, not showing error messages
+
+## [0.0.3] - 2021-03-04
  - submit form on key press == enter
  - version of backoffice in sidebar
  - fixed login dialog on mobile
@@ -39,6 +52,7 @@ and this project adheres to [Semantic 
Versioning](https://semver.org/spec/v2.0.0
  - remove checkbox from auth token, use button (manage auth)
  - auth token config as popup with 3 actions (clear (sure?), cancel, set token)
  - new password enpoint
+ - bug: there is missing a mutate call when updating to remove the instance 
from cache
  
 
 ## [0.0.2] - 2021-02-25
diff --git a/packages/frontend/src/AdminRoutes.tsx 
b/packages/frontend/src/AdminRoutes.tsx
index 54fbc43..6b63239 100644
--- a/packages/frontend/src/AdminRoutes.tsx
+++ b/packages/frontend/src/AdminRoutes.tsx
@@ -15,15 +15,33 @@
  */
 import { h, VNode } from "preact";
 import Router, { route, Route } from "preact-router";
-import { RootPaths, Redirect } from "./index";
-import NotFoundPage from './routes/notfound';
-import InstanceListPage from './routes/instances/list';
-import InstanceCreatePage from "./routes/instances/create";
+import { RootPaths, Redirect, InstancePaths } from "./index";
 import { MerchantBackend } from "./declaration";
 import { useMessageTemplate } from "preact-messages";
 import { Notification } from "./utils/types";
 import { InstanceRoutes } from "./InstanceRoutes";
 
+import InstanceListPage from './routes/admin/list';
+import InstanceCreatePage from "./routes/admin/create";
+import NotFoundPage from './routes/notfound';
+
+import ProductListPage from './routes/instance/products/list'
+import ProductCreatePage from './routes/instance/products/create'
+import ProductUpdatePage from './routes/instance/products/update'
+
+import OrderListPage from './routes/instance/orders/list'
+import OrderCreatePage from './routes/instance/orders/create'
+import OrderUpdatePage from './routes/instance/orders/update'
+
+import TipListPage from './routes/instance/tips/list'
+import TipCreatePage from './routes/instance/tips/create'
+import TipUpdatePage from './routes/instance/tips/update'
+
+import TransferListPage from './routes/instance/transfers/list'
+import TransferCreatePage from './routes/instance/transfers/create'
+import LoginPage from "./routes/login";
+import { SwrError } from "./hooks/backend";
+
 interface Props {
   pushNotification: (n: Notification) => void;
   instances: MerchantBackend.Instances.Instance[]
@@ -31,6 +49,10 @@ interface Props {
 }
 export function AdminRoutes({ instances, pushNotification, addTokenCleaner }: 
Props): VNode {
   const i18n = useMessageTemplate();
+  
+  // const [token, updateToken] = useBackendInstanceToken(id);
+  // const { changeBackend } = useBackendContext();
+  const updateLoginStatus = () => null;
 
   return <Router>
     <Route path={RootPaths.root} component={Redirect} 
to={RootPaths.list_instances} />
@@ -77,6 +99,72 @@ export function AdminRoutes({ instances, pushNotification, 
addTokenCleaner }: Pr
       parent="/instance/:id"
     />
 
+    <Route path={InstancePaths.product_list}
+      component={ProductListPage}
+      onUnauthorized={() => <LoginPage
+        withMessage={{ message: i18n`Access denied`, description: i18n`Check 
your token is valid`, type: 'ERROR', }}
+        onConfirm={updateLoginStatus} />}
+
+      onLoadError={(error: SwrError) => <LoginPage
+        withMessage={{ message: i18n`Problem reaching the server`, 
description: i18n`Got message: ${error.message} from: ${error.backend} 
(hasToken: ${error.hasToken})`, type: 'ERROR', }}
+        onConfirm={updateLoginStatus} />}
+    />
+    <Route path={InstancePaths.product_update}
+      component={ProductUpdatePage}
+    />
+    <Route path={InstancePaths.product_new}
+      component={ProductCreatePage}
+    />
+
+    <Route path={InstancePaths.order_list}
+      component={OrderListPage}
+      onUnauthorized={() => <LoginPage
+        withMessage={{ message: i18n`Access denied`, description: i18n`Check 
your token is valid`, type: 'ERROR', }}
+        onConfirm={updateLoginStatus} />}
+
+      onLoadError={(error: SwrError) => <LoginPage
+        withMessage={{ message: i18n`Problem reaching the server`, 
description: i18n`Got message: ${error.message} from: ${error.backend} 
(hasToken: ${error.hasToken})`, type: 'ERROR', }}
+        onConfirm={updateLoginStatus} />}
+    />
+    <Route path={InstancePaths.order_update}
+      component={OrderUpdatePage}
+    />
+    <Route path={InstancePaths.order_new}
+      component={OrderCreatePage}
+    />
+
+    <Route path={InstancePaths.tips_list}
+      component={TipListPage}
+      onUnauthorized={() => <LoginPage
+        withMessage={{ message: i18n`Access denied`, description: i18n`Check 
your token is valid`, type: 'ERROR', }}
+        onConfirm={updateLoginStatus} />}
+
+      onLoadError={(error: SwrError) => <LoginPage
+        withMessage={{ message: i18n`Problem reaching the server`, 
description: i18n`Got message: ${error.message} from: ${error.backend} 
(hasToken: ${error.hasToken})`, type: 'ERROR', }}
+        onConfirm={updateLoginStatus} />}
+    />
+    <Route path={InstancePaths.tips_update}
+      component={TipUpdatePage}
+    />
+    <Route path={InstancePaths.tips_new}
+      component={TipCreatePage}
+    />
+
+    <Route path={InstancePaths.transfers_list}
+      component={TransferListPage}
+      onUnauthorized={() => <LoginPage
+        withMessage={{ message: i18n`Access denied`, description: i18n`Check 
your token is valid`, type: 'ERROR', }}
+        onConfirm={updateLoginStatus} />}
+
+      onLoadError={(error: SwrError) => <LoginPage
+        withMessage={{ message: i18n`Problem reaching the server`, 
description: i18n`Got message: ${error.message} from: ${error.backend} 
(hasToken: ${error.hasToken})`, type: 'ERROR', }}
+        onConfirm={updateLoginStatus} />}
+    />
+    <Route path={InstancePaths.transfers_new}
+      component={TransferCreatePage}
+    />
+
+
     <Route default component={NotFoundPage} />
 
   </Router>
diff --git a/packages/frontend/src/InstanceRoutes.tsx 
b/packages/frontend/src/InstanceRoutes.tsx
index b3adc92..175b465 100644
--- a/packages/frontend/src/InstanceRoutes.tsx
+++ b/packages/frontend/src/InstanceRoutes.tsx
@@ -28,10 +28,26 @@ import { InstanceContextProvider, useBackendContext } from 
'./context/backend';
 import { SwrError } from "./hooks/backend";
 import { InstancePaths } from "./index";
 import { Notification } from './utils/types';
-import NotFoundPage from './routes/notfound';
+
 import LoginPage from './routes/login';
-import InstanceUpdatePage from "./routes/instances/update";
-import DetailPage from './routes/instances/details';
+import InstanceUpdatePage from "./routes/instance/update";
+import DetailPage from './routes/instance/details';
+import NotFoundPage from './routes/notfound';
+
+import ProductListPage from './routes/instance/products/list'
+import ProductCreatePage from './routes/instance/products/create'
+import ProductUpdatePage from './routes/instance/products/update'
+
+import OrderListPage from './routes/instance/orders/list'
+import OrderCreatePage from './routes/instance/orders/create'
+import OrderUpdatePage from './routes/instance/orders/update'
+
+import TipListPage from './routes/instance/tips/list'
+import TipCreatePage from './routes/instance/tips/create'
+import TipUpdatePage from './routes/instance/tips/update'
+
+import TransferListPage from './routes/instance/transfers/list'
+import TransferCreatePage from './routes/instance/transfers/create'
 
 export interface Props {
   id: string;
@@ -60,7 +76,7 @@ export function InstanceRoutes({ id, pushNotification, 
addTokenCleaner, parent }
 
   return <InstanceContextProvider value={value}>
     <Router>
-      <Route path={(!parent? "" : parent) + InstancePaths.details}
+      <Route path={(!parent ? "" : parent) + InstancePaths.details}
         component={DetailPage}
 
         onUnauthorized={() => <LoginPage
@@ -70,9 +86,9 @@ export function InstanceRoutes({ id, pushNotification, 
addTokenCleaner, parent }
         onLoadError={(error: SwrError) => <LoginPage
           withMessage={{ message: i18n`Problem reaching the server`, 
description: i18n`Got message: ${error.message} from: ${error.backend} 
(hasToken: ${error.hasToken})`, type: 'ERROR', }}
           onConfirm={updateLoginStatus} />}
-      />     
+      />
 
-      <Route path={(!parent? "" : parent) + InstancePaths.update}
+      <Route path={(!parent ? "" : parent) + InstancePaths.update}
         component={InstanceUpdatePage}
 
         onUnauthorized={() => <LoginPage
@@ -97,6 +113,71 @@ export function InstanceRoutes({ id, pushNotification, 
addTokenCleaner, parent }
         }}
       />
 
+      <Route path={(!parent ? "" : parent) + InstancePaths.product_list}
+        component={ProductListPage}
+        onUnauthorized={() => <LoginPage
+          withMessage={{ message: i18n`Access denied`, description: i18n`Check 
your token is valid`, type: 'ERROR', }}
+          onConfirm={updateLoginStatus} />}
+
+        onLoadError={(error: SwrError) => <LoginPage
+          withMessage={{ message: i18n`Problem reaching the server`, 
description: i18n`Got message: ${error.message} from: ${error.backend} 
(hasToken: ${error.hasToken})`, type: 'ERROR', }}
+          onConfirm={updateLoginStatus} />}
+      />
+      <Route path={(!parent ? "" : parent) + InstancePaths.product_update}
+        component={ProductUpdatePage}
+      />
+      <Route path={(!parent ? "" : parent) + InstancePaths.product_new}
+        component={ProductCreatePage}
+      />
+
+      <Route path={(!parent ? "" : parent) + InstancePaths.order_list}
+        component={OrderListPage}
+        onUnauthorized={() => <LoginPage
+          withMessage={{ message: i18n`Access denied`, description: i18n`Check 
your token is valid`, type: 'ERROR', }}
+          onConfirm={updateLoginStatus} />}
+
+        onLoadError={(error: SwrError) => <LoginPage
+          withMessage={{ message: i18n`Problem reaching the server`, 
description: i18n`Got message: ${error.message} from: ${error.backend} 
(hasToken: ${error.hasToken})`, type: 'ERROR', }}
+          onConfirm={updateLoginStatus} />}
+      />
+      <Route path={(!parent ? "" : parent) + InstancePaths.order_update}
+        component={OrderUpdatePage}
+      />
+      <Route path={(!parent ? "" : parent) + InstancePaths.order_new}
+        component={OrderCreatePage}
+      />
+
+      <Route path={(!parent ? "" : parent) + InstancePaths.tips_list}
+        component={TipListPage}
+        onUnauthorized={() => <LoginPage
+          withMessage={{ message: i18n`Access denied`, description: i18n`Check 
your token is valid`, type: 'ERROR', }}
+          onConfirm={updateLoginStatus} />}
+
+        onLoadError={(error: SwrError) => <LoginPage
+          withMessage={{ message: i18n`Problem reaching the server`, 
description: i18n`Got message: ${error.message} from: ${error.backend} 
(hasToken: ${error.hasToken})`, type: 'ERROR', }}
+          onConfirm={updateLoginStatus} />}
+      />
+      <Route path={(!parent ? "" : parent) + InstancePaths.tips_update}
+        component={TipUpdatePage}
+      />
+      <Route path={(!parent ? "" : parent) + InstancePaths.tips_new}
+        component={TipCreatePage}
+      />
+
+      <Route path={(!parent ? "" : parent) + InstancePaths.transfers_list}
+        component={TransferListPage}
+        onUnauthorized={() => <LoginPage
+          withMessage={{ message: i18n`Access denied`, description: i18n`Check 
your token is valid`, type: 'ERROR', }}
+          onConfirm={updateLoginStatus} />}
+
+        onLoadError={(error: SwrError) => <LoginPage
+          withMessage={{ message: i18n`Problem reaching the server`, 
description: i18n`Got message: ${error.message} from: ${error.backend} 
(hasToken: ${error.hasToken})`, type: 'ERROR', }}
+          onConfirm={updateLoginStatus} />}
+      />
+      <Route path={(!parent ? "" : parent) + InstancePaths.transfers_new}
+        component={TransferCreatePage}
+      />
+
       <Route default component={NotFoundPage} />
     </Router>
   </InstanceContextProvider>;
diff --git a/packages/frontend/src/components/menu/NavigationBar.tsx 
b/packages/frontend/src/components/menu/NavigationBar.tsx
index 22d432e..e1bb4c7 100644
--- a/packages/frontend/src/components/menu/NavigationBar.tsx
+++ b/packages/frontend/src/components/menu/NavigationBar.tsx
@@ -48,7 +48,7 @@ export function NavigationBar({ onMobileMenu, title }: 
Props): VNode {
         <img src={logo} style={{ height: 50, maxHeight: 50 }} />
       </a>
       <div class="navbar-end">
-        <div class="navbar-item">
+        <div class="navbar-item" style={{ paddingTop: 4, paddingBottom: 4 }}>
           <LangSelector />
         </div>
       </div>
diff --git a/packages/frontend/src/components/menu/SideBar.tsx 
b/packages/frontend/src/components/menu/SideBar.tsx
index 5967117..e534c77 100644
--- a/packages/frontend/src/components/menu/SideBar.tsx
+++ b/packages/frontend/src/components/menu/SideBar.tsx
@@ -60,28 +60,36 @@ export function Sidebar({ mobile, instance, onLogout }: 
Props): VNode {
         </Fragment>}
         <p class="menu-label">Instance</p>
         <ul class="menu-list">
-          { instance && <li>
-            <a href="/update" class="has-icon">
-              <span class="icon"><i class="mdi mdi-square-edit-outline" 
/></span>
-              <span class="menu-item-label">Settings</span>
-            </a>
-          </li> }
+          {instance && <Fragment>
+            <li>
+              <a href="/update" class="has-icon">
+                <span class="icon"><i class="mdi mdi-square-edit-outline" 
/></span>
+                <span class="menu-item-label">Settings</span>
+              </a>
+            </li>
+          </Fragment>}
           <li>
-            <a href="/forms" class="has-icon">
+            <a href="/o" class="has-icon">
               <span class="icon"><i class="mdi mdi-square-edit-outline" 
/></span>
               <span class="menu-item-label">Orders</span>
             </a>
           </li>
           <li>
-            <a href="/profile" class="has-icon">
+            <a href="/p" class="has-icon">
+              <span class="icon"><i class="mdi mdi-account-circle" /></span>
+              <span class="menu-item-label">Products</span>
+            </a>
+          </li>
+          <li>
+            <a href="/t" class="has-icon">
               <span class="icon"><i class="mdi mdi-account-circle" /></span>
-              <span class="menu-item-label">Inventory</span>
+              <span class="menu-item-label">Transfers</span>
             </a>
           </li>
           <li>
-            <a href="/profile" class="has-icon">
+            <a href="/r" class="has-icon">
               <span class="icon"><i class="mdi mdi-account-circle" /></span>
-              <span class="menu-item-label">Tipping</span>
+              <span class="menu-item-label">Tips</span>
             </a>
           </li>
         </ul>
diff --git a/packages/frontend/src/context/backend.ts 
b/packages/frontend/src/context/backend.ts
index 5b9b648..55eba5d 100644
--- a/packages/frontend/src/context/backend.ts
+++ b/packages/frontend/src/context/backend.ts
@@ -19,6 +19,7 @@ import { StateUpdater, useContext } from 'preact/hooks'
 export interface BackendContextType {
   url: string;
   token?: string;
+  triedToLog: boolean;
   changeBackend: (url: string) => void;
   resetBackend: () => void;
   // clearTokens: () => void;
@@ -43,6 +44,7 @@ const BackendContext = createContext<BackendContextType>({
   url: '',
   lang: 'en',
   token: undefined,
+  triedToLog: false,
   changeBackend: () => null,
   resetBackend: () => null,
   // clearTokens: () => null,
diff --git a/packages/frontend/src/declaration.d.ts 
b/packages/frontend/src/declaration.d.ts
index 6a08212..c0e541f 100644
--- a/packages/frontend/src/declaration.d.ts
+++ b/packages/frontend/src/declaration.d.ts
@@ -21,8 +21,13 @@
 
 
 
+type HashCode = string;
 type EddsaPublicKey = string;
+type EddsaSignature = string;
+type WireTransferIdentifierRawP = string;
 type RelativeTime = Duration;
+type ImageDataUrl = string;
+
 interface Timestamp {
     // Milliseconds since epoch, or the special
     // value "forever" to represent an event that will
@@ -54,6 +59,64 @@ export namespace MerchantBackend {
         tax: Amount;
     }
 
+    interface Auditor {
+        // official name
+        name: string;
+
+        // Auditor's public key
+        auditor_pub: EddsaPublicKey;
+
+        // Base URL of the auditor
+        url: string;
+    }
+    interface Exchange {
+        // the exchange's base URL
+        url: string;
+
+        // master public key of the exchange
+        master_pub: EddsaPublicKey;
+    }
+
+    interface Product {
+        // merchant-internal identifier for the product.
+        product_id?: string;
+
+        // Human-readable product description.
+        description: string;
+
+        // Map from IETF BCP 47 language tags to localized descriptions
+        description_i18n?: { [lang_tag: string]: string };
+
+        // The number of units of the product to deliver to the customer.
+        quantity?: Integer;
+
+        // The unit in which the product is measured (liters, kilograms, 
packages, etc.)
+        unit?: string;
+
+        // The price of the product; this is the total price for quantity 
times unit of this product.
+        price?: Amount;
+
+        // An optional base64-encoded product image
+        image?: ImageDataUrl;
+
+        // a list of taxes paid by the merchant for this product. Can be empty.
+        taxes?: Tax[];
+
+        // time indicating when this product should be delivered
+        delivery_date?: Timestamp;
+    }
+    interface Merchant {
+        // label for a location with the business address of the merchant
+        address: Location;
+
+        // the merchant's legal name of business
+        name: string;
+
+        // label for a location that denotes the jurisdiction for disputes.
+        // Some of the typical fields for a location (such as a street 
address) may be absent.
+        jurisdiction: Location;
+    }
+
     interface VersionResponse {
         // libtool-style representation of the Merchant protocol version, see
         // 
https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
@@ -305,7 +368,7 @@ export namespace MerchantBackend {
 
     }
 
-    namespace Inventory {
+    namespace Products {
         // POST /private/products
         interface ProductAddDetail {
 
@@ -464,4 +527,553 @@ export namespace MerchantBackend {
         //   DELETE /private/products/$PRODUCT_ID
 
     }
+
+    namespace Orders {
+        interface OrderHistory {
+            // timestamp-sorted array of all orders matching the query.
+            // The order of the sorting depends on the sign of delta.
+            orders: OrderHistoryEntry[];
+        }
+        interface OrderHistoryEntry {
+
+            // order ID of the transaction related to this entry.
+            order_id: string;
+
+            // row ID of the order in the database
+            row_id: number;
+
+            // when the order was created
+            timestamp: Timestamp;
+
+            // the amount of money the order is for
+            amount: Amount;
+
+            // the summary of the order
+            summary: string;
+
+            // whether some part of the order is refundable,
+            // that is the refund deadline has not yet expired
+            // and the total amount refunded so far is below
+            // the value of the original transaction.
+            refundable: boolean;
+
+            // whether the order has been paid or not
+            paid: boolean;
+        }
+
+        interface PostOrderRequest {
+            // The order must at least contain the minimal
+            // order detail, but can override all
+            order: Order;
+
+            // if set, the backend will then set the refund deadline to the 
current
+            // time plus the specified delay.  If it's not set, refunds will 
not be
+            // possible.
+            refund_delay?: RelativeTime;
+
+            // specifies the payment target preferred by the client. Can be 
used
+            // to select among the various (active) wire methods supported by 
the instance.
+            payment_target?: string;
+
+            // specifies that some products are to be included in the
+            // order from the inventory.  For these inventory management
+            // is performed (so the products must be in stock) and
+            // details are completed from the product data of the backend.
+            inventory_products?: MinimalInventoryProduct[];
+
+            // Specifies a lock identifier that was used to
+            // lock a product in the inventory.  Only useful if
+            // manage_inventory is set.  Used in case a frontend
+            // reserved quantities of the individual products while
+            // the shopping card was being built.  Multiple UUIDs can
+            // be used in case different UUIDs were used for different
+            // products (i.e. in case the user started with multiple
+            // shopping sessions that were combined during checkout).
+            lock_uuids?: UUID[];
+
+            // Should a token for claiming the order be generated?
+            // False can make sense if the ORDER_ID is sufficiently
+            // high entropy to prevent adversarial claims (like it is
+            // if the backend auto-generates one). Default is 'true'.
+            create_token?: boolean;
+
+        }
+        type Order = MinimalOrderDetail | ContractTerms;
+
+        interface MinimalOrderDetail {
+            // Amount to be paid by the customer
+            amount: Amount;
+
+            // Short summary of the order
+            summary: string;
+
+            // URL that will show that the order was successful after
+            // it has been paid for.  Optional. When POSTing to the
+            // merchant, the placeholder "${ORDER_ID}" will be
+            // replaced with the actual order ID (useful if the
+            // order ID is generated server-side and needs to be
+            // in the URL).
+            fulfillment_url?: string;
+        }
+
+        // FIXME: Where is this being used?
+        // type ProductSpecification = (MinimalInventoryProduct | Product);
+
+        interface MinimalInventoryProduct {
+            // Which product is requested (here mandatory!)
+            product_id: string;
+
+            // How many units of the product are requested
+            quantity: Integer;
+        }
+        interface PostOrderResponse {
+            // Order ID of the response that was just created
+            order_id: string;
+
+            // Token that authorizes the wallet to claim the order.
+            // Provided only if "create_token" was set to 'true'
+            // in the request.
+            token?: ClaimToken;
+        }
+        interface OutOfStockResponse {
+
+            // Product ID of an out-of-stock item
+            product_id: string;
+
+            // Requested quantity
+            requested_quantity: Integer;
+
+            // Available quantity (must be below requested_quanitity)
+            available_quantity: Integer;
+
+            // When do we expect the product to be again in stock?
+            // Optional, not given if unknown.
+            restock_expected?: Timestamp;
+        }
+
+        interface ForgetRequest {
+
+            // Array of valid JSON paths to forgettable fields in the order's
+            // contract terms.
+            fields: string[];
+        }
+        interface RefundRequest {
+            // Amount to be refunded
+            refund: Amount;
+
+            // Human-readable refund justification
+            reason: string;
+        }
+        interface MerchantRefundResponse {
+
+            // URL (handled by the backend) that the wallet should access to
+            // trigger refund processing.
+            // taler://refund/...
+            taler_refund_uri: string;
+
+            // Contract hash that a client may need to authenticate an
+            // HTTP request to obtain the above URI in a wallet-friendly way.
+            h_contract: HashCode;
+        }
+
+    }
+
+    namespace Tips {
+
+        // GET /private/reserves
+        interface TippingReserveStatus {
+            // Array of all known reserves (possibly empty!)
+            reserves: ReserveStatusEntry[];
+        }
+        interface ReserveStatusEntry {
+            // Public key of the reserve
+            reserve_pub: EddsaPublicKey;
+
+            // Timestamp when it was established
+            creation_time: Timestamp;
+
+            // Timestamp when it expires
+            expiration_time: Timestamp;
+
+            // Initial amount as per reserve creation call
+            merchant_initial_amount: Amount;
+
+            // Initial amount as per exchange, 0 if exchange did
+            // not confirm reserve creation yet.
+            exchange_initial_amount: Amount;
+
+            // Amount picked up so far.
+            pickup_amount: Amount;
+
+            // Amount approved for tips that exceeds the pickup_amount.
+            committed_amount: Amount;
+
+            // Is this reserve active (false if it was deleted but not purged)
+            active: boolean;
+        }
+
+        interface ReserveCreateRequest {
+            // Amount that the merchant promises to put into the reserve
+            initial_balance: Amount;
+
+            // Exchange the merchant intends to use for tipping
+            exchange_url: string;
+
+            // Desired wire method, for example "iban" or "x-taler-bank"
+            wire_method: string;
+        }
+        interface ReserveCreateConfirmation {
+            // Public key identifying the reserve
+            reserve_pub: EddsaPublicKey;
+
+            // Wire account of the exchange where to transfer the funds
+            payto_uri: string;
+        }
+        interface TipCreateRequest {
+            // Amount that the customer should be tipped
+            amount: Amount;
+
+            // Justification for giving the tip
+            justification: string;
+
+            // URL that the user should be directed to after tipping,
+            // will be included in the tip_token.
+            next_url: string;
+        }
+        interface TipCreateConfirmation {
+            // Unique tip identifier for the tip that was created.
+            tip_id: HashCode;
+
+            // taler://tip URI for the tip
+            taler_tip_uri: string;
+
+            // URL that will directly trigger processing
+            // the tip when the browser is redirected to it
+            tip_status_url: string;
+
+            // when does the tip expire
+            tip_expiration: Timestamp;
+        }
+
+    }
+
+    namespace Transfers {
+
+        interface TransferList {
+            // list of all the transfers that fit the filter that we know
+            transfers: TransferDetails[];
+        }
+        interface TransferDetails {
+            // how much was wired to the merchant (minus fees)
+            credit_amount: Amount;
+
+            // raw wire transfer identifier identifying the wire transfer (a 
base32-encoded value)
+            wtid: string;
+
+            // target account that received the wire transfer
+            payto_uri: string;
+
+            // base URL of the exchange that made the wire transfer
+            exchange_url: string;
+
+            // Serial number identifying the transfer in the merchant backend.
+            // Used for filgering via offset.
+            transfer_serial_id: number;
+
+            // Time of the execution of the wire transfer by the exchange, 
according to the exchange
+            // Only provided if we did get an answer from the exchange.
+            execution_time?: Timestamp;
+
+            // True if we checked the exchange's answer and are happy with it.
+            // False if we have an answer and are unhappy, missing if we
+            // do not have an answer from the exchange.
+            verified?: boolean;
+
+            // True if the merchant uses the POST /transfers API to confirm
+            // that this wire transfer took place (and it is thus not
+            // something merely claimed by the exchange).
+            confirmed?: boolean;
+        }
+
+        interface TransferInformation {
+            // how much was wired to the merchant (minus fees)
+            credit_amount: Amount;
+
+            // raw wire transfer identifier identifying the wire transfer (a 
base32-encoded value)
+            wtid: WireTransferIdentifierRawP;
+
+            // target account that received the wire transfer
+            payto_uri: string;
+
+            // base URL of the exchange that made the wire transfer
+            exchange_url: string;
+        }
+        interface MerchantTrackTransferResponse {
+            // Total amount transferred
+            total: Amount;
+
+            // Applicable wire fee that was charged
+            wire_fee: Amount;
+
+            // Time of the execution of the wire transfer by the exchange, 
according to the exchange
+            execution_time: Timestamp;
+
+            // details about the deposits
+            deposits_sums: MerchantTrackTransferDetail[];
+        }
+        interface MerchantTrackTransferDetail {
+            // Business activity associated with the wire transferred amount
+            // deposit_value.
+            order_id: string;
+
+            // The total amount the exchange paid back for order_id.
+            deposit_value: Amount;
+
+            // applicable fees for the deposit
+            deposit_fee: Amount;
+        }
+
+        type ExchangeConflictDetails = WireFeeConflictDetails | 
TrackTransferConflictDetails
+        // Note: this is not the full 'proof' of missbehavior, as
+        // the bogus message from the exchange with a signature
+        // over the 'different' wire fee is missing.
+        //
+        // This information is NOT provided by the current implementation,
+        // because this would be quite expensive to generate and is
+        // hardly needed _here_. Once we add automated reports for
+        // the Taler auditor, we need to generate this data anyway
+        // and should probably return it here as well.
+        interface WireFeeConflictDetails {
+            // Numerical error code:
+            code: "TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_BAD_WIRE_FEE";
+
+            // Text describing the issue for humans.
+            hint: string;
+
+
+            // Wire fee (wrongly) charged by the exchange, breaking the
+            // contract affirmed by the exchange_sig.
+            wire_fee: Amount;
+
+            // Timestamp of the wire transfer
+            execution_time: Timestamp;
+
+            // The expected wire fee (as signed by the exchange)
+            expected_wire_fee: Amount;
+
+            // Expected closing fee (needed to verify signature)
+            expected_closing_fee: Amount;
+
+            // Start date of the expected fee structure
+            start_date: Timestamp;
+
+            // End date of the expected fee structure
+            end_date: Timestamp;
+
+            // Signature of the exchange affirming the expected fee structure
+            master_sig: EddsaSignature;
+
+            // Master public key of the exchange
+            master_pub: EddsaPublicKey;
+        }
+        interface TrackTransferConflictDetails {
+            // Numerical error code
+            code: 
"TALER_EC_MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_REPORTS";
+
+            // Text describing the issue for humans.
+            hint: string;
+
+            // Offset in the exchange_transfer where the
+            // exchange's response fails to match the exchange_deposit_proof.
+            conflict_offset: number;
+
+            // The response from the exchange which tells us when the
+            // coin was returned to us, except that it does not match
+            // the expected value of the coin.
+            //
+            // This field is NOT provided by the current implementation,
+            // because this would be quite expensive to generate and is
+            // hardly needed _here_. Once we add automated reports for
+            // the Taler auditor, we need to generate this data anyway
+            // and should probably return it here as well.
+            // exchange_transfer?: TrackTransferResponse;
+
+            // Public key of the exchange used to sign the response to
+            // our deposit request.
+            deposit_exchange_pub: EddsaPublicKey;
+
+            // Signature of the exchange signing the (conflicting) response.
+            // Signs over a struct TALER_DepositConfirmationPS.
+            deposit_exchange_sig: EddsaSignature;
+
+            // Hash of the merchant's bank account the wire transfer went to
+            h_wire: HashCode;
+
+            // Hash of the contract terms with the conflicting deposit.
+            h_contract_terms: HashCode;
+
+            // At what time the exchange received the deposit.  Needed
+            // to verify the \exchange_sig\.
+            deposit_timestamp: Timestamp;
+
+            // At what time the refund possibility expired (needed to verify 
exchange_sig).
+            refund_deadline: Timestamp;
+
+            // Public key of the coin for which we have conflicting 
information.
+            coin_pub: EddsaPublicKey;
+
+            // Amount the exchange counted the coin for in the transfer.
+            amount_with_fee: Amount;
+
+            // Expected value of the coin.
+            coin_value: Amount;
+
+            // Expected deposit fee of the coin.
+            coin_fee: Amount;
+
+            // Expected deposit fee of the coin.
+            deposit_fee: Amount;
+
+        }
+
+        // interface TrackTransferProof {
+        //     // signature from the exchange made with purpose
+        //     // TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT
+        //     exchange_sig: EddsaSignature;
+
+        //     // public EdDSA key of the exchange that was used to generate 
the signature.
+        //     // Should match one of the exchange's signing keys from /keys.  
Again given
+        //     // explicitly as the client might otherwise be confused by 
clock skew as to
+        //     // which signing key was used.
+        //     exchange_pub: EddsaSignature;
+
+        //     // hash of the wire details (identical for all deposits)
+        //     // Needed to check the exchange_sig
+        //     h_wire: HashCode;
+        // }
+
+    }
+
+
+    interface ContractTerms {
+        // Human-readable description of the whole purchase
+        summary: string;
+
+        // Map from IETF BCP 47 language tags to localized summaries
+        summary_i18n?: { [lang_tag: string]: string };
+
+        // Unique, free-form identifier for the proposal.
+        // Must be unique within a merchant instance.
+        // For merchants that do not store proposals in their DB
+        // before the customer paid for them, the order_id can be used
+        // by the frontend to restore a proposal from the information
+        // encoded in it (such as a short product identifier and timestamp).
+        order_id: string;
+
+        // Total price for the transaction.
+        // The exchange will subtract deposit fees from that amount
+        // before transferring it to the merchant.
+        amount: Amount;
+
+        // The URL for this purchase.  Every time is is visited, the merchant
+        // will send back to the customer the same proposal.  Clearly, this URL
+        // can be bookmarked and shared by users.
+        fulfillment_url?: string;
+
+        // Maximum total deposit fee accepted by the merchant for this contract
+        max_fee: Amount;
+
+        // Maximum wire fee accepted by the merchant (customer share to be
+        // divided by the 'wire_fee_amortization' factor, and further reduced
+        // if deposit fees are below 'max_fee').  Default if missing is zero.
+        max_wire_fee: Amount;
+
+        // Over how many customer transactions does the merchant expect to
+        // amortize wire fees on average?  If the exchange's wire fee is
+        // above 'max_wire_fee', the difference is divided by this number
+        // to compute the expected customer's contribution to the wire fee.
+        // The customer's contribution may further be reduced by the difference
+        // between the 'max_fee' and the sum of the actual deposit fees.
+        // Optional, default value if missing is 1.  0 and negative values are
+        // invalid and also interpreted as 1.
+        wire_fee_amortization: number;
+
+        // List of products that are part of the purchase (see Product).
+        products: Product[];
+
+        // Time when this contract was generated
+        timestamp: Timestamp;
+
+        // After this deadline has passed, no refunds will be accepted.
+        refund_deadline: Timestamp;
+
+        // After this deadline, the merchant won't accept payments for the 
contact
+        pay_deadline: Timestamp;
+
+        // Transfer deadline for the exchange.  Must be in the
+        // deposit permissions of coins used to pay for this order.
+        wire_transfer_deadline: Timestamp;
+
+        // Merchant's public key used to sign this proposal; this information
+        // is typically added by the backend Note that this can be an 
ephemeral key.
+        merchant_pub: EddsaPublicKey;
+
+        // Base URL of the (public!) merchant backend API.
+        // Must be an absolute URL that ends with a slash.
+        merchant_base_url: string;
+
+        // More info about the merchant, see below
+        merchant: Merchant;
+
+        // The hash of the merchant instance's wire details.
+        h_wire: HashCode;
+
+        // Wire transfer method identifier for the wire method associated with 
h_wire.
+        // The wallet may only select exchanges via a matching auditor if the
+        // exchange also supports this wire method.
+        // The wire transfer fees must be added based on this wire transfer 
method.
+        wire_method: string;
+
+        // Any exchanges audited by these auditors are accepted by the 
merchant.
+        auditors: Auditor[];
+
+        // Exchanges that the merchant accepts even if it does not accept any 
auditors that audit them.
+        exchanges: Exchange[];
+
+        // Delivery location for (all!) products.
+        delivery_location?: Location;
+
+        // Time indicating when the order should be delivered.
+        // May be overwritten by individual products.
+        delivery_date?: Timestamp;
+
+        // Nonce generated by the wallet and echoed by the merchant
+        // in this field when the proposal is generated.
+        nonce: string;
+
+        // Specifies for how long the wallet should try to get an
+        // automatic refund for the purchase. If this field is
+        // present, the wallet should wait for a few seconds after
+        // the purchase and then automatically attempt to obtain
+        // a refund.  The wallet should probe until "delay"
+        // after the payment was successful (i.e. via long polling
+        // or via explicit requests with exponential back-off).
+        //
+        // In particular, if the wallet is offline
+        // at that time, it MUST repeat the request until it gets
+        // one response from the merchant after the delay has expired.
+        // If the refund is granted, the wallet MUST automatically
+        // recover the payment.  This is used in case a merchant
+        // knows that it might be unable to satisfy the contract and
+        // desires for the wallet to attempt to get the refund without any
+        // customer interaction.  Note that it is NOT an error if the
+        // merchant does not grant a refund.
+        auto_refund?: RelativeTime;
+
+        // Extra data that is only interpreted by the merchant frontend.
+        // Useful when the merchant needs to store extra information on a
+        // contract without storing it separately in their database.
+        extra?: any;
+    }
+
 }
diff --git a/packages/frontend/src/hooks/backend.ts 
b/packages/frontend/src/hooks/backend.ts
index 56c85e0..f5ed418 100644
--- a/packages/frontend/src/hooks/backend.ts
+++ b/packages/frontend/src/hooks/backend.ts
@@ -52,6 +52,7 @@ interface RequestOptions {
   method?: Methods;
   token?: string;
   data?: any;
+  params?: any;
 }
 
 
@@ -69,11 +70,12 @@ async function request(url: string, options: RequestOptions 
= {}): Promise<any>
 
 
     const res = await axios({
-      method: options.method || 'get',
       url,
       responseType: 'json',
       headers,
-      data: options.data
+      method: options.method || 'get',
+      data: options.data,
+      params: options.params
     })
     return res.data
   } catch (e) {
@@ -88,6 +90,10 @@ function fetcher(url: string, token: string, backend: 
string) {
   return request(`${backend}${url}`, { token })
 }
 
+function transferFetcher(url: string, token: string, backend: string) {
+  return request(`${backend}${url}`, { token, params: { payto_uri: '' } })
+}
+
 interface AdminMutateAPI {
   createInstance: (data: 
MerchantBackend.Instances.InstanceConfigurationMessage) => Promise<void>;
   deleteInstance: (id: string) => Promise<void>;
@@ -117,6 +123,218 @@ export function useAdminMutateAPI(): AdminMutateAPI {
   return { createInstance, deleteInstance }
 }
 
+interface ProductMutateAPI {
+  createProduct: (data: MerchantBackend.Products.ProductAddDetail) => 
Promise<void>;
+  updateProduct: (id: string, data: 
MerchantBackend.Products.ProductPatchDetail) => Promise<void>;
+  deleteProduct: (id: string) => Promise<void>;
+  lockProduct: (id: string, data: MerchantBackend.Products.LockRequest) => 
Promise<void>;
+}
+
+
+export function useProductMutateAPI(): ProductMutateAPI {
+  const { url: baseUrl, token: adminToken } = useBackendContext()
+  const { token: instanceToken, id, admin } = useInstanceContext()
+
+  const { url, token } = !admin ? {
+    url: baseUrl, token: adminToken
+  } : {
+      url: `${baseUrl}/instances/${id}`, token: instanceToken
+    }
+
+
+  const createProduct = async (data: 
MerchantBackend.Products.ProductAddDetail): Promise<void> => {
+    await request(`${url}/private/products`, {
+      method: 'post',
+      token,
+      data
+    })
+
+    if (adminToken) mutate(['/private/products', adminToken, baseUrl], null)
+    mutate([`/private/products`, token, url], null)
+  }
+
+  const updateProduct = async (productId: string, data: 
MerchantBackend.Products.ProductPatchDetail): Promise<void> => {
+    await request(`${url}/private/products/${productId}`, {
+      method: 'patch',
+      token,
+      data
+    })
+
+    if (adminToken) mutate(['/private/products', adminToken, baseUrl], null)
+    mutate([`/private/products`, token, url], null)
+  }
+
+  const deleteProduct = async (productId: string): Promise<void> => {
+    await request(`${url}/private/products/${productId}`, {
+      method: 'delete',
+      token,
+    })
+
+    if (adminToken) mutate(['/private/products', adminToken, baseUrl], null)
+    mutate([`/private/products`, token, url], null)
+  }
+
+  const lockProduct = async (productId: string, data: 
MerchantBackend.Products.LockRequest): Promise<void> => {
+    await request(`${url}/private/products/${productId}/lock`, {
+      method: 'post',
+      token,
+      data
+    })
+
+    if (adminToken) mutate(['/private/products', adminToken, baseUrl], null)
+    mutate([`/private/products`, token, url], null)
+  }
+
+  return { createProduct, updateProduct, deleteProduct, lockProduct }
+}
+
+interface OrderMutateAPI {
+  //FIXME: add OutOfStockResponse on 410
+  createOrder: (data: MerchantBackend.Orders.PostOrderRequest) => 
Promise<MerchantBackend.Orders.PostOrderResponse>;
+  forgetOrder: (id: string, data: MerchantBackend.Orders.ForgetRequest) => 
Promise<void>;
+  deleteOrder: (id: string) => Promise<void>;
+}
+
+export function useOrderMutateAPI(): OrderMutateAPI {
+  const { url: baseUrl, token: adminToken } = useBackendContext()
+  const { token: instanceToken, id, admin } = useInstanceContext()
+
+  const { url, token } = !admin ? {
+    url: baseUrl, token: adminToken
+  } : {
+      url: `${baseUrl}/instances/${id}`, token: instanceToken
+    }
+
+  const createOrder = async (data: MerchantBackend.Orders.PostOrderRequest): 
Promise<MerchantBackend.Orders.PostOrderResponse> => {
+    const res = await request(`${url}/private/orders`, {
+      method: 'post',
+      token,
+      data
+    })
+
+    if (adminToken) mutate(['/private/orders', adminToken, baseUrl], null)
+    mutate([`/private/orders`, token, url], null)
+    return res
+  }
+  const forgetOrder = async (orderId: string, data: 
MerchantBackend.Orders.ForgetRequest): Promise<void> => {
+    await request(`${url}/private/orders/${orderId}/forget`, {
+      method: 'patch',
+      token,
+      data
+    })
+
+    if (adminToken) mutate(['/private/orders', adminToken, baseUrl], null)
+    mutate([`/private/orders`, token, url], null)
+  }
+  const deleteOrder = async (orderId: string): Promise<void> => {
+    await request(`${url}/private/orders/${orderId}`, {
+      method: 'delete',
+      token
+    })
+
+    if (adminToken) mutate(['/private/orders', adminToken, baseUrl], null)
+    mutate([`/private/orders`, token, url], null)
+  }
+  return { createOrder, forgetOrder, deleteOrder }
+}
+
+interface TransferMutateAPI {
+  informTransfer: (data: MerchantBackend.Transfers.TransferInformation) => 
Promise<MerchantBackend.Transfers.MerchantTrackTransferResponse>;
+}
+
+export function useTransferMutateAPI(): TransferMutateAPI {
+  const { url: baseUrl, token: adminToken } = useBackendContext()
+  const { token: instanceToken, id, admin } = useInstanceContext()
+
+  const { url, token } = !admin ? {
+    url: baseUrl, token: adminToken
+  } : {
+      url: `${baseUrl}/instances/${id}`, token: instanceToken
+    }
+
+  const informTransfer = async (data: 
MerchantBackend.Transfers.TransferInformation): 
Promise<MerchantBackend.Transfers.MerchantTrackTransferResponse> => {
+    const res = await request(`${url}/private/transfers`, {
+      method: 'post',
+      token,
+      data
+    })
+
+    if (adminToken) mutate(['/private/transfers', adminToken, baseUrl], null)
+    mutate([`/private/transfers`, token, url], null)
+    return res
+  }
+
+  return { informTransfer }
+}
+
+interface TipsMutateAPI {
+  createReserve: (data: MerchantBackend.Tips.ReserveCreateRequest) => 
Promise<MerchantBackend.Tips.ReserveCreateConfirmation>;
+  authorizeTipReserve: (id: string, data: 
MerchantBackend.Tips.TipCreateRequest) => 
Promise<MerchantBackend.Tips.TipCreateConfirmation>;
+  authorizeTip: (data: MerchantBackend.Tips.TipCreateRequest) => 
Promise<MerchantBackend.Tips.TipCreateConfirmation>;
+  deleteReserve: (id: string) => Promise<void>;
+}
+
+export function useTipsMutateAPI(): TipsMutateAPI {
+  const { url: baseUrl, token: adminToken } = useBackendContext()
+  const { token: instanceToken, id, admin } = useInstanceContext()
+
+  const { url, token } = !admin ? {
+    url: baseUrl, token: adminToken
+  } : {
+      url: `${baseUrl}/instances/${id}`, token: instanceToken
+    }
+
+  //reserves
+  const createReserve = async (data: 
MerchantBackend.Tips.ReserveCreateRequest): 
Promise<MerchantBackend.Tips.ReserveCreateConfirmation> => {
+    const res = await request(`${url}/private/reserves`, {
+      method: 'post',
+      token,
+      data
+    })
+
+    if (adminToken) mutate(['/private/reserves', adminToken, baseUrl], null)
+    mutate([`/private/reserves`, token, url], null)
+    return res
+  }
+
+  const authorizeTipReserve = async (pub: string, data: 
MerchantBackend.Tips.TipCreateRequest): 
Promise<MerchantBackend.Tips.TipCreateConfirmation> => {
+    const res = await request(`${url}/private/reserves/${pub}/authorize-tip`, {
+      method: 'post',
+      token,
+      data
+    })
+
+    if (adminToken) mutate(['/private/reserves', adminToken, baseUrl], null)
+    mutate([`/private/reserves`, token, url], null)
+    return res
+  }
+
+  const authorizeTip = async (data: MerchantBackend.Tips.TipCreateRequest): 
Promise<MerchantBackend.Tips.TipCreateConfirmation> => {
+    const res = await request(`${url}/private/tips`, {
+      method: 'post',
+      token,
+      data
+    })
+
+    if (adminToken) mutate(['/private/reserves', adminToken, baseUrl], null)
+    mutate([`/private/reserves`, token, url], null)
+    return res
+  }
+
+  const deleteReserve = async (pub: string): Promise<void> => {
+    await request(`${url}/private/reserves/${pub}`, {
+      method: 'delete',
+      token,
+    })
+
+    if (adminToken) mutate(['/private/reserves', adminToken, baseUrl], null)
+    mutate([`/private/reserves`, token, url], null)
+  }
+
+
+  return { createReserve, authorizeTip, authorizeTipReserve, deleteReserve }
+}
+
 interface InstaceMutateAPI {
   updateInstance: (data: 
MerchantBackend.Instances.InstanceReconfigurationMessage, a?: 
MerchantBackend.Instances.InstanceAuthConfigurationMessage) => Promise<void>;
   deleteInstance: () => Promise<void>;
@@ -128,7 +346,7 @@ export function useInstanceMutateAPI(): InstaceMutateAPI {
   const { url: baseUrl, token: adminToken } = useBackendContext()
   const { token, id, admin } = useInstanceContext()
 
-  const url = !admin ? baseUrl: `${baseUrl}/instances/${id}`
+  const url = !admin ? baseUrl : `${baseUrl}/instances/${id}`
 
   const updateInstance = async (instance: 
MerchantBackend.Instances.InstanceReconfigurationMessage, auth?: 
MerchantBackend.Instances.InstanceAuthConfigurationMessage): Promise<void> => {
     await request(`${url}/private/`, {
@@ -187,17 +405,77 @@ export function useBackendInstances(): 
HttpResponse<MerchantBackend.Instances.In
   return { data, unauthorized: error?.status === 401, notfound: error?.status 
=== 404, error }
 }
 
-export function useBackendInstance(): 
HttpResponse<MerchantBackend.Instances.QueryInstancesResponse> {
+export function useInstanceDetails(): 
HttpResponse<MerchantBackend.Instances.QueryInstancesResponse> {
   const { url: baseUrl } = useBackendContext();
   const { token, id, admin } = useInstanceContext();
 
-  const url = !admin ? baseUrl: `${baseUrl}/instances/${id}`
+  const url = !admin ? baseUrl : `${baseUrl}/instances/${id}`
 
   const { data, error } = 
useSWR<MerchantBackend.Instances.QueryInstancesResponse, 
SwrError>([`/private/`, token, url], fetcher)
 
   return { data, unauthorized: error?.status === 401, notfound: error?.status 
=== 404, error }
 }
 
+export function useInstanceProducts(): 
HttpResponse<MerchantBackend.Products.InventorySummaryResponse> {
+  const { url: baseUrl, token: baseToken } = useBackendContext();
+  const { token: instanceToken, id, admin } = useInstanceContext();
+
+  const { url, token } = !admin ? {
+    url: baseUrl, token: baseToken
+  } : {
+      url: `${baseUrl}/instances/${id}`, token: instanceToken
+    }
+
+  const { data, error } = 
useSWR<MerchantBackend.Products.InventorySummaryResponse, 
SwrError>([`/private/products`, token, url], fetcher)
+
+  return { data, unauthorized: error?.status === 401, notfound: error?.status 
=== 404, error }
+}
+
+export function useInstanceOrders(): 
HttpResponse<MerchantBackend.Orders.OrderHistory> {
+  const { url: baseUrl, token: baseToken } = useBackendContext();
+  const { token: instanceToken, id, admin } = useInstanceContext();
+
+  const { url, token } = !admin ? {
+    url: baseUrl, token: baseToken
+  } : {
+      url: `${baseUrl}/instances/${id}`, token: instanceToken
+    }
+
+  const { data, error } = useSWR<MerchantBackend.Orders.OrderHistory, 
SwrError>([`/private/orders`, token, url], fetcher)
+
+  return { data, unauthorized: error?.status === 401, notfound: error?.status 
=== 404, error }
+}
+
+export function useInstanceTips(): 
HttpResponse<MerchantBackend.Tips.TippingReserveStatus> {
+  const { url: baseUrl, token: baseToken } = useBackendContext();
+  const { token: instanceToken, id, admin } = useInstanceContext();
+
+  const { url, token } = !admin ? {
+    url: baseUrl, token: baseToken
+  } : {
+      url: `${baseUrl}/instances/${id}`, token: instanceToken
+    }
+
+  const { data, error } = useSWR<MerchantBackend.Tips.TippingReserveStatus, 
SwrError>([`/private/reserves`, token, url], fetcher)
+
+  return { data, unauthorized: error?.status === 401, notfound: error?.status 
=== 404, error }
+}
+
+export function useInstanceTransfers(): 
HttpResponse<MerchantBackend.Transfers.TransferList> {
+  const { url: baseUrl, token: baseToken } = useBackendContext();
+  const { token: instanceToken, id, admin } = useInstanceContext();
+
+  const { url, token } = !admin ? {
+    url: baseUrl, token: baseToken
+  } : {
+      url: `${baseUrl}/instances/${id}`, token: instanceToken
+    }
+
+  const { data, error } = useSWR<MerchantBackend.Transfers.TransferList, 
SwrError>([`/private/transfers`, token, url], transferFetcher)
+
+  return { data, unauthorized: error?.status === 401, notfound: error?.status 
=== 404, error }
+}
+
 export function useBackendConfig(): 
HttpResponse<MerchantBackend.VersionResponse> {
   const { url, token } = useBackendContext()
   const { data, error } = useSWR<MerchantBackend.VersionResponse, 
SwrError>(['/config', token, url], fetcher)
diff --git a/packages/frontend/src/hooks/index.ts 
b/packages/frontend/src/hooks/index.ts
index 514a458..68a3c5b 100644
--- a/packages/frontend/src/hooks/index.ts
+++ b/packages/frontend/src/hooks/index.ts
@@ -25,18 +25,28 @@ import { ValueOrFunction } from '../utils/types';
 
 export function useBackendContextState() {
   const [lang, setLang] = useLang()
-  const [url, changeBackend, resetBackend] = useBackendURL();
+  const [url, triedToLog, changeBackend, resetBackend] = useBackendURL();
   const [token, updateToken] = useBackendDefaultToken();
 
-  return { url, token, changeBackend, updateToken, lang, setLang, resetBackend 
}
+
+  return { url, token, triedToLog, changeBackend, updateToken, lang, setLang, 
resetBackend }
 }
 
-export function useBackendURL(): [string, StateUpdater<string>, () => void] {
+export function useBackendURL(): [string, boolean, StateUpdater<string>, () => 
void] {
   const [value, setter] = useNotNullLocalStorage('backend-url', typeof window 
!== 'undefined' ? window.location.origin : '')
-  const checkedSetter = (v: ValueOrFunction<string>) => setter(p => (v 
instanceof Function ? v(p) : v).replace(/\/$/, ''))
-  const reset = () => checkedSetter(typeof window !== 'undefined' ? 
window.location.origin : '')
-  return [value, checkedSetter, reset]
+  const [triedToLog, setTriedToLog] = useLocalStorage('tried-login')
+
+  const checkedSetter = (v: ValueOrFunction<string>) => {
+    setTriedToLog('yes')
+    return setter(p => (v instanceof Function ? v(p) : v).replace(/\/$/, ''))
+  }
+  
+  const resetBackend = () => {
+    setTriedToLog(undefined)
+  }
+  return [value, !!triedToLog, checkedSetter, resetBackend]
 }
+
 export function useBackendDefaultToken(): [string | undefined, 
StateUpdater<string | undefined>] {
   return useLocalStorage('backend-token')
 }
diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx
index f2f50b3..29aec14 100644
--- a/packages/frontend/src/index.tsx
+++ b/packages/frontend/src/index.tsx
@@ -48,6 +48,21 @@ export enum RootPaths {
 export enum InstancePaths {
   details = '/',
   update = '/update',
+
+  product_list = '/p',
+  product_update = '/p/:pid/update',
+  product_new = '/p/new',
+
+  order_list = '/o',
+  order_update = '/p/:oid/update',
+  order_new = '/o/new',
+
+  tips_list = '/r',
+  tips_update = '/r/:rid/update',
+  tips_new = '/r/new',
+
+  transfers_list = '/t',
+  transfers_new = '/t/new',
 }
 
 export function Redirect({ to }: { to: string }): null {
@@ -71,7 +86,7 @@ export default function Application(): VNode {
 
 function ApplicationStatusRoutes(): VNode {
   const { notifications, pushNotification, removeNotification } = 
useNotifications()
-  const { changeBackend, updateToken, resetBackend } = useBackendContext()
+  const { changeBackend, triedToLog, updateToken, resetBackend } = 
useBackendContext()
   const backendConfig = useBackendConfig();
   const i18n = useMessageTemplate()
 
@@ -88,6 +103,19 @@ function ApplicationStatusRoutes(): VNode {
   const v = `${backendConfig.data?.currency} ${backendConfig.data?.version}`
   const ctx = useMemo(() => ({ currency: backendConfig.data?.currency || '', 
version: backendConfig.data?.version || '' }), [v])
 
+  if (!triedToLog) {
+    return <div id="app">
+      <Menu />
+      <LoginPage
+        onConfirm={(url: string, token?: string) => {
+          changeBackend(url)
+          if (token) updateToken(token)
+          route(RootPaths.list_instances)
+        }}
+      />
+    </div>
+  }
+
   if (!backendConfig.data) {
 
     if (!backendConfig.error) return <div class="is-loading" />
@@ -125,7 +153,7 @@ function ApplicationStatusRoutes(): VNode {
   return <div id="app" class="has-navbar-fixed-top">
     <ConfigContextProvider value={ctx}>
       <Notifications notifications={notifications} 
removeNotification={removeNotification} />
-      <Route default component={ApplicationReadyRoutes} 
pushNotification={pushNotification} addTokenCleaner={addTokenCleanerMemo} 
clearAllTokens={clearAllTokens} /> :
+      <Route default component={ApplicationReadyRoutes} 
pushNotification={pushNotification} addTokenCleaner={addTokenCleanerMemo} 
clearAllTokens={clearAllTokens} />
     </ConfigContextProvider>
   </div>
 }
diff --git a/packages/frontend/src/messages/en.po 
b/packages/frontend/src/messages/en.po
index 717e4f0..d12bbaf 100644
--- a/packages/frontend/src/messages/en.po
+++ b/packages/frontend/src/messages/en.po
@@ -218,5 +218,35 @@ msgstr "Login required"
 msgid "Please enter your auth token. Token should have \"secret-token:\" and 
start with Bearer or ApiKey"
 msgstr "Please enter your auth token. Token should have \"secret-token:\" and 
start with Bearer or ApiKey"
 
+msgid "Orders"
+msgstr "Orders"
 
+msgid "fields.order.amount.label"
+msgstr "Amount"
 
+msgid "fields.order.summary.label"
+msgstr "Summary"
+
+msgid "fields.order.paid.label"
+msgstr "Paid"
+
+msgid "Products"
+msgstr "Products"
+
+msgid "fields.product.id.label"
+msgstr "Id"
+
+msgid "Transfers"
+msgstr "Transfers"
+
+msgid "Tips"
+msgstr "Tips"
+
+msgid "fields.tips.committed_amount.label"
+msgstr "Commited Amount"
+
+msgid "fields.tips.exchange_initial_amount.label"
+msgstr "Exchange Initial Amount"
+
+msgid "fields.tips.merchant_initial_amount.label"
+msgstr "Merchant Initial Amount"
diff --git a/packages/frontend/src/routes/instances/create/Create.stories.tsx 
b/packages/frontend/src/routes/admin/create/Create.stories.tsx
similarity index 100%
rename from packages/frontend/src/routes/instances/create/Create.stories.tsx
rename to packages/frontend/src/routes/admin/create/Create.stories.tsx
diff --git a/packages/frontend/src/routes/instances/create/CreatePage.tsx 
b/packages/frontend/src/routes/admin/create/CreatePage.tsx
similarity index 100%
rename from packages/frontend/src/routes/instances/create/CreatePage.tsx
rename to packages/frontend/src/routes/admin/create/CreatePage.tsx
diff --git a/packages/frontend/src/routes/instances/create/index.tsx 
b/packages/frontend/src/routes/admin/create/index.tsx
similarity index 100%
rename from packages/frontend/src/routes/instances/create/index.tsx
rename to packages/frontend/src/routes/admin/create/index.tsx
diff --git a/packages/frontend/src/routes/instances/list/Table.tsx 
b/packages/frontend/src/routes/admin/list/Table.tsx
similarity index 93%
copy from packages/frontend/src/routes/instances/list/Table.tsx
copy to packages/frontend/src/routes/admin/list/Table.tsx
index 2c00668..4831ed3 100644
--- a/packages/frontend/src/routes/instances/list/Table.tsx
+++ b/packages/frontend/src/routes/admin/list/Table.tsx
@@ -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 { h, VNode } from "preact"
 import { Message } from "preact-messages"
@@ -118,10 +118,13 @@ function Table({ rowSelection, rowSelectionHandler, 
instances, onUpdate, onDelet
                 <span class="check" />
               </label>
             </td>
-            <td onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}} >{i.id}</td>
-            <td onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}} >{i.name}</td>
+            <td><a onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}} >{i.id}</a></td>
+            <td >{i.name}</td>
             <td class="is-actions-cell">
               <div class="buttons is-right">
+                <button class="button is-small is-success jb-modal" 
type="button" onClick={(): void => onUpdate(i.id)}>
+                  <span class="icon"><i class="mdi mdi-pen" /></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/list/View.stories.tsx 
b/packages/frontend/src/routes/admin/list/View.stories.tsx
similarity index 100%
rename from packages/frontend/src/routes/instances/list/View.stories.tsx
rename to packages/frontend/src/routes/admin/list/View.stories.tsx
diff --git a/packages/frontend/src/routes/instances/list/View.tsx 
b/packages/frontend/src/routes/admin/list/View.tsx
similarity index 97%
rename from packages/frontend/src/routes/instances/list/View.tsx
rename to packages/frontend/src/routes/admin/list/View.tsx
index a66fb5e..26c2eca 100644
--- a/packages/frontend/src/routes/instances/list/View.tsx
+++ b/packages/frontend/src/routes/admin/list/View.tsx
@@ -22,7 +22,6 @@
 import { h, VNode } from "preact";
 import { MerchantBackend } from "../../../declaration";
 import { CardTable } from './Table';
-import { Message } from "preact-messages";
 
 interface Props {
   instances: MerchantBackend.Instances.Instance[];
diff --git a/packages/frontend/src/routes/instances/list/index.tsx 
b/packages/frontend/src/routes/admin/list/index.tsx
similarity index 100%
rename from packages/frontend/src/routes/instances/list/index.tsx
rename to packages/frontend/src/routes/admin/list/index.tsx
diff --git a/packages/frontend/src/routes/instances/details/DetailPage.tsx 
b/packages/frontend/src/routes/instance/details/DetailPage.tsx
similarity index 100%
rename from packages/frontend/src/routes/instances/details/DetailPage.tsx
rename to packages/frontend/src/routes/instance/details/DetailPage.tsx
diff --git a/packages/frontend/src/routes/instances/details/index.tsx 
b/packages/frontend/src/routes/instance/details/index.tsx
similarity index 86%
rename from packages/frontend/src/routes/instances/details/index.tsx
rename to packages/frontend/src/routes/instance/details/index.tsx
index e0a3248..492878c 100644
--- a/packages/frontend/src/routes/instances/details/index.tsx
+++ b/packages/frontend/src/routes/instance/details/index.tsx
@@ -17,7 +17,7 @@ import { Fragment, h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { useInstanceContext } from "../../../context/backend";
 import { Notification } from "../../../utils/types";
-import { useBackendInstance, useInstanceMutateAPI, SwrError } from 
"../../../hooks/backend";
+import { useInstanceDetails, useInstanceMutateAPI, SwrError } from 
"../../../hooks/backend";
 import { DetailPage } from "./DetailPage";
 import { DeleteModal } from "../../../components/modal";
 
@@ -31,14 +31,14 @@ interface Props {
 
 export default function Detail({ onUpdate, onLoadError, onUnauthorized, 
pushNotification, onDelete }: Props): VNode {
   const { id } = useInstanceContext()
-  const details = useBackendInstance()
+  const result = useInstanceDetails()
   const [deleting, setDeleting] = useState<boolean>(false)
 
   const { deleteInstance } = useInstanceMutateAPI()
 
-  if (!details.data) {
-    if (details.unauthorized) return onUnauthorized()
-    if (details.error) return onLoadError(details.error)
+  if (!result.data) {
+    if (result.unauthorized) return onUnauthorized()
+    if (result.error) return onLoadError(result.error)
     return <div>
       loading ....
     </div>
@@ -46,12 +46,12 @@ export default function Detail({ onUpdate, onLoadError, 
onUnauthorized, pushNoti
 
   return <Fragment>
     <DetailPage
-      selected={details.data}
+      selected={result.data}
       onUpdate={onUpdate}
       onDelete={() => setDeleting(true)}
     />
     {deleting && <DeleteModal
-      element={{ name: details.data.name, id }}
+      element={{ name: result.data.name, id }}
       onCancel={() => setDeleting(false)}
       onConfirm={async (): Promise<void> => {
         try {
diff --git a/packages/frontend/src/routes/instance/orders/create/index.tsx 
b/packages/frontend/src/routes/instance/orders/create/index.tsx
new file mode 100644
index 0000000..2d84be3
--- /dev/null
+++ b/packages/frontend/src/routes/instance/orders/create/index.tsx
@@ -0,0 +1,5 @@
+import { h, VNode } from 'preact';
+
+export default function ():VNode {
+  return <div>order create page</div>
+}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instances/list/Table.tsx 
b/packages/frontend/src/routes/instance/orders/list/Table.tsx
similarity index 80%
copy from packages/frontend/src/routes/instances/list/Table.tsx
copy to packages/frontend/src/routes/instance/orders/list/Table.tsx
index 2c00668..ff2f50e 100644
--- a/packages/frontend/src/routes/instances/list/Table.tsx
+++ b/packages/frontend/src/routes/instance/orders/list/Table.tsx
@@ -22,18 +22,21 @@
 import { h, VNode } from "preact"
 import { Message } from "preact-messages"
 import { StateUpdater, useEffect, useState } from "preact/hooks"
-import { MerchantBackend } from "../../../declaration"
+import { MerchantBackend, WidthId } from "../../../../declaration"
+import { Actions, buildActions } from "../../../../utils/table";
+
+type Entity = MerchantBackend.Orders.OrderHistoryEntry & {id: string}
 
 interface Props {
-  instances: MerchantBackend.Instances.Instance[];
+  instances: Entity[];
   onUpdate: (id: string) => void;
-  onDelete: (id: MerchantBackend.Instances.Instance) => void;
+  onDelete: (id: Entity) => void;
   onCreate: () => void;
   selected?: boolean;
 }
 
 export function CardTable({ instances, onCreate, onUpdate, onDelete, selected 
}: Props): VNode {
-  const [actionQueue, actionQueueHandler] = useState<Actions[]>([]);
+  const [actionQueue, actionQueueHandler] = useState<Actions<Entity>[]>([]);
   const [rowSelection, rowSelectionHandler] = useState<string[]>([])
 
   useEffect(() => {
@@ -53,12 +56,14 @@ export function CardTable({ instances, onCreate, onUpdate, 
onDelete, selected }:
 
   return <div class="card has-table">
     <header class="card-header">
-      <p class="card-header-title"><span class="icon"><i class="mdi 
mdi-account-multiple" /></span><Message id="Instances" /></p>
+      <p class="card-header-title"><span class="icon"><i class="mdi 
mdi-account-multiple" /></span><Message id="Orders" /></p>
 
       <div class="card-header-icon" aria-label="more options">
 
         <button class={rowSelection.length > 0 ? "button is-danger" : 
"is-hidden"}
-          type="button" onClick={(): void => 
actionQueueHandler(buildActions(instances, rowSelection, 'DELETE'))} >
+          type="button" 
+          onClick={(): void => actionQueueHandler(buildActions(instances, 
rowSelection, 'DELETE'))} 
+          >
           <span class="icon"><i class="mdi mdi-trash-can" /></span>
         </button>
       </div>
@@ -83,9 +88,9 @@ export function CardTable({ instances, onCreate, onUpdate, 
onDelete, selected }:
 }
 interface TableProps {
   rowSelection: string[];
-  instances: MerchantBackend.Instances.Instance[];
+  instances: Entity[];
   onUpdate: (id: string) => void;
-  onDelete: (id: MerchantBackend.Instances.Instance) => void;
+  onDelete: (id: Entity) => void;
   rowSelectionHandler: StateUpdater<string[]>;
 }
 
@@ -104,8 +109,9 @@ function Table({ rowSelection, rowSelectionHandler, 
instances, onUpdate, onDelet
               <span class="check" />
             </label>
           </th>
-          <th><Message id="fields.instance.id.label" /></th>
-          <th><Message id="fields.instance.name.label" /></th>
+          <th><Message id="fields.order.amount.label" /></th>
+          <th><Message id="fields.order.summary.label" /></th>
+          <th><Message id="fields.order.paid.label" /></th>
           <th />
         </tr>
       </thead>
@@ -118,8 +124,9 @@ function Table({ rowSelection, rowSelectionHandler, 
instances, onUpdate, onDelet
                 <span class="check" />
               </label>
             </td>
-            <td onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}} >{i.id}</td>
-            <td onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}} >{i.name}</td>
+            <td onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}} >{i.amount}</td>
+            <td onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}} >{i.summary}</td>
+            <td onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}} >{i.paid}</td>
             <td class="is-actions-cell">
               <div class="buttons is-right">
                 <button class="button is-small is-danger jb-modal" 
type="button" onClick={(): void => onDelete(i)}>
@@ -144,19 +151,3 @@ function EmptyTable(): VNode {
 }
 
 
-interface Actions {
-  element: MerchantBackend.Instances.Instance;
-  type: 'DELETE' | 'UPDATE';
-}
-
-function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
-  return value !== null && value !== undefined;
-}
-
-function buildActions(intances: MerchantBackend.Instances.Instance[], 
selected: string[], action: 'DELETE'): Actions[] {
-  return selected.map(id => intances.find(i => i.id === id))
-    .filter(notEmpty)
-    .map(id => ({ element: id, type: action }))
-}
-
-
diff --git a/packages/frontend/src/routes/instance/orders/list/index.tsx 
b/packages/frontend/src/routes/instance/orders/list/index.tsx
new file mode 100644
index 0000000..d1cff4c
--- /dev/null
+++ b/packages/frontend/src/routes/instance/orders/list/index.tsx
@@ -0,0 +1,35 @@
+import { h, VNode } from 'preact';
+import { useConfigContext } from '../../../../context/backend';
+import { MerchantBackend } from '../../../../declaration';
+import { SwrError, useInstanceOrders, useOrderMutateAPI, useProductMutateAPI } 
from '../../../../hooks/backend';
+import { CardTable } from './Table';
+
+interface Props {
+  onUnauthorized: () => VNode;
+  onLoadError: (e: SwrError) => VNode;
+  onCreate: () => void;
+}
+export default function ({ onUnauthorized, onLoadError, onCreate }: Props): 
VNode {
+  const result = useInstanceOrders()
+  const { createOrder, deleteOrder } = useOrderMutateAPI()
+  const { currency } = useConfigContext()
+  if (!result.data) {
+    if (result.unauthorized) return onUnauthorized()
+    if (result.error) return onLoadError(result.error)
+    return <div>
+      loading ....
+    </div>
+  }
+  return <section class="section is-main-section">
+    <CardTable instances={result.data.orders.map(o => ({ ...o, id: o.order_id 
}))}
+      onCreate={() => createOrder({
+        order: {
+          amount: `${currency}:${Math.floor(Math.random() * 20 + 1)}`,
+          summary: `some summary with a random number 
${Math.floor(Math.random() * 20 + 1)}`,
+        }
+      })}
+      onDelete={(order: MerchantBackend.Orders.OrderHistoryEntry) => 
deleteOrder(order.order_id)}
+      onUpdate={() => null}
+    />
+  </section>
+}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instance/orders/update/index.tsx 
b/packages/frontend/src/routes/instance/orders/update/index.tsx
new file mode 100644
index 0000000..a1f58d3
--- /dev/null
+++ b/packages/frontend/src/routes/instance/orders/update/index.tsx
@@ -0,0 +1,5 @@
+import { h, VNode } from 'preact';
+
+export default function ():VNode {
+  return <div>order update page</div>
+}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instance/products/create/index.tsx 
b/packages/frontend/src/routes/instance/products/create/index.tsx
new file mode 100644
index 0000000..cb2a82c
--- /dev/null
+++ b/packages/frontend/src/routes/instance/products/create/index.tsx
@@ -0,0 +1,5 @@
+import { h, VNode } from 'preact';
+
+export default function ():VNode {
+  return <div>product list page</div>
+}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instances/list/Table.tsx 
b/packages/frontend/src/routes/instance/products/list/Table.tsx
similarity index 78%
copy from packages/frontend/src/routes/instances/list/Table.tsx
copy to packages/frontend/src/routes/instance/products/list/Table.tsx
index 2c00668..ab3795b 100644
--- a/packages/frontend/src/routes/instances/list/Table.tsx
+++ b/packages/frontend/src/routes/instance/products/list/Table.tsx
@@ -14,26 +14,29 @@
  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 { h, VNode } from "preact"
 import { Message } from "preact-messages"
 import { StateUpdater, useEffect, useState } from "preact/hooks"
-import { MerchantBackend } from "../../../declaration"
+import { MerchantBackend } from "../../../../declaration"
+import { Actions, buildActions } from "../../../../utils/table"
+
+type Entity = MerchantBackend.Products.InventoryEntry & { id: string }
 
 interface Props {
-  instances: MerchantBackend.Instances.Instance[];
+  instances: Entity[];
   onUpdate: (id: string) => void;
-  onDelete: (id: MerchantBackend.Instances.Instance) => void;
+  onDelete: (id: Entity) => void;
   onCreate: () => void;
   selected?: boolean;
 }
 
 export function CardTable({ instances, onCreate, onUpdate, onDelete, selected 
}: Props): VNode {
-  const [actionQueue, actionQueueHandler] = useState<Actions[]>([]);
+  const [actionQueue, actionQueueHandler] = useState<Actions<Entity>[]>([]);
   const [rowSelection, rowSelectionHandler] = useState<string[]>([])
 
   useEffect(() => {
@@ -53,7 +56,7 @@ export function CardTable({ instances, onCreate, onUpdate, 
onDelete, selected }:
 
   return <div class="card has-table">
     <header class="card-header">
-      <p class="card-header-title"><span class="icon"><i class="mdi 
mdi-account-multiple" /></span><Message id="Instances" /></p>
+      <p class="card-header-title"><span class="icon"><i class="mdi 
mdi-account-multiple" /></span><Message id="Products" /></p>
 
       <div class="card-header-icon" aria-label="more options">
 
@@ -83,9 +86,9 @@ export function CardTable({ instances, onCreate, onUpdate, 
onDelete, selected }:
 }
 interface TableProps {
   rowSelection: string[];
-  instances: MerchantBackend.Instances.Instance[];
+  instances: Entity[];
   onUpdate: (id: string) => void;
-  onDelete: (id: MerchantBackend.Instances.Instance) => void;
+  onDelete: (id: Entity) => void;
   rowSelectionHandler: StateUpdater<string[]>;
 }
 
@@ -104,8 +107,7 @@ function Table({ rowSelection, rowSelectionHandler, 
instances, onUpdate, onDelet
               <span class="check" />
             </label>
           </th>
-          <th><Message id="fields.instance.id.label" /></th>
-          <th><Message id="fields.instance.name.label" /></th>
+          <th><Message id="fields.product.id.label" /></th>
           <th />
         </tr>
       </thead>
@@ -118,8 +120,7 @@ function Table({ rowSelection, rowSelectionHandler, 
instances, onUpdate, onDelet
                 <span class="check" />
               </label>
             </td>
-            <td onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}} >{i.id}</td>
-            <td onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}} >{i.name}</td>
+            <td onClick={(): void => onUpdate(i.id)} style={{ cursor: 
'pointer' }} >{i.id}</td>
             <td class="is-actions-cell">
               <div class="buttons is-right">
                 <button class="button is-small is-danger jb-modal" 
type="button" onClick={(): void => onDelete(i)}>
@@ -144,19 +145,3 @@ function EmptyTable(): VNode {
 }
 
 
-interface Actions {
-  element: MerchantBackend.Instances.Instance;
-  type: 'DELETE' | 'UPDATE';
-}
-
-function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
-  return value !== null && value !== undefined;
-}
-
-function buildActions(intances: MerchantBackend.Instances.Instance[], 
selected: string[], action: 'DELETE'): Actions[] {
-  return selected.map(id => intances.find(i => i.id === id))
-    .filter(notEmpty)
-    .map(id => ({ element: id, type: action }))
-}
-
-
diff --git a/packages/frontend/src/routes/instance/products/list/index.tsx 
b/packages/frontend/src/routes/instance/products/list/index.tsx
new file mode 100644
index 0000000..a7f271e
--- /dev/null
+++ b/packages/frontend/src/routes/instance/products/list/index.tsx
@@ -0,0 +1,44 @@
+import { h, VNode } from 'preact';
+import { create } from 'yup/lib/Reference';
+import { SwrError, useInstanceProducts, useProductMutateAPI } from 
'../../../../hooks/backend';
+import { CardTable } from './Table';
+import logo from '../../../../assets/logo.jpeg';
+import { useConfigContext } from '../../../../context/backend';
+import { MerchantBackend } from '../../../../declaration';
+
+interface Props {
+  onUnauthorized: () => VNode;
+  onLoadError: (e: SwrError) => VNode;
+}
+export default function ({ onUnauthorized, onLoadError }: Props): VNode {
+  const result = useInstanceProducts()
+  const { createProduct, deleteProduct } = useProductMutateAPI()
+  const { currency } = useConfigContext()
+  if (!result.data) {
+    if (result.unauthorized) return onUnauthorized()
+    if (result.error) return onLoadError(result.error)
+    return <div>
+      loading ....
+    </div>
+  }
+  return <section class="section is-main-section">
+    <CardTable instances={result.data.products.map(o => ({ ...o, id: 
o.product_id }))}
+      onCreate={() => createProduct({
+        product_id: `${Math.floor(Math.random() * 999999 + 1)}`,
+        address: {},
+        description: '',
+        description_i18n: {
+          en: '', es: ''
+        },
+        image: {} as string, //WTF? 
+        price: `${currency}:${Math.floor(Math.random() * 20 + 1)}`,
+        taxes: [],
+        total_stock: Math.floor(Math.random() * 20 + 1),
+        unit: 'units',
+        next_restock: { t_ms: 'never' }, //WTF? should not be required
+      })}
+      onDelete={(prod: MerchantBackend.Products.InventoryEntry) => 
deleteProduct(prod.product_id)}
+      onUpdate={() => null}
+    />
+  </section>
+}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instance/products/update/index.tsx 
b/packages/frontend/src/routes/instance/products/update/index.tsx
new file mode 100644
index 0000000..f91bf13
--- /dev/null
+++ b/packages/frontend/src/routes/instance/products/update/index.tsx
@@ -0,0 +1,5 @@
+import { h, VNode } from 'preact';
+
+export default function ():VNode {
+  return <div>product update page</div>
+}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instance/tips/create/index.tsx 
b/packages/frontend/src/routes/instance/tips/create/index.tsx
new file mode 100644
index 0000000..39608c3
--- /dev/null
+++ b/packages/frontend/src/routes/instance/tips/create/index.tsx
@@ -0,0 +1,5 @@
+import { h, VNode } from 'preact';
+
+export default function ():VNode {
+  return <div>tip create page</div>
+}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instances/list/Table.tsx 
b/packages/frontend/src/routes/instance/tips/list/Table.tsx
similarity index 81%
copy from packages/frontend/src/routes/instances/list/Table.tsx
copy to packages/frontend/src/routes/instance/tips/list/Table.tsx
index 2c00668..bd818fd 100644
--- a/packages/frontend/src/routes/instances/list/Table.tsx
+++ b/packages/frontend/src/routes/instance/tips/list/Table.tsx
@@ -22,18 +22,21 @@
 import { h, VNode } from "preact"
 import { Message } from "preact-messages"
 import { StateUpdater, useEffect, useState } from "preact/hooks"
-import { MerchantBackend } from "../../../declaration"
+import { MerchantBackend } from "../../../../declaration"
+import { Actions, buildActions } from "../../../../utils/table"
+
+type Entity = MerchantBackend.Tips.ReserveStatusEntry & { id: string }
 
 interface Props {
-  instances: MerchantBackend.Instances.Instance[];
+  instances: Entity[];
   onUpdate: (id: string) => void;
-  onDelete: (id: MerchantBackend.Instances.Instance) => void;
+  onDelete: (id: Entity) => void;
   onCreate: () => void;
   selected?: boolean;
 }
 
 export function CardTable({ instances, onCreate, onUpdate, onDelete, selected 
}: Props): VNode {
-  const [actionQueue, actionQueueHandler] = useState<Actions[]>([]);
+  const [actionQueue, actionQueueHandler] = useState<Actions<Entity>[]>([]);
   const [rowSelection, rowSelectionHandler] = useState<string[]>([])
 
   useEffect(() => {
@@ -53,7 +56,7 @@ export function CardTable({ instances, onCreate, onUpdate, 
onDelete, selected }:
 
   return <div class="card has-table">
     <header class="card-header">
-      <p class="card-header-title"><span class="icon"><i class="mdi 
mdi-account-multiple" /></span><Message id="Instances" /></p>
+      <p class="card-header-title"><span class="icon"><i class="mdi 
mdi-account-multiple" /></span><Message id="Tips" /></p>
 
       <div class="card-header-icon" aria-label="more options">
 
@@ -83,9 +86,9 @@ export function CardTable({ instances, onCreate, onUpdate, 
onDelete, selected }:
 }
 interface TableProps {
   rowSelection: string[];
-  instances: MerchantBackend.Instances.Instance[];
+  instances: Entity[];
   onUpdate: (id: string) => void;
-  onDelete: (id: MerchantBackend.Instances.Instance) => void;
+  onDelete: (id: Entity) => void;
   rowSelectionHandler: StateUpdater<string[]>;
 }
 
@@ -104,8 +107,9 @@ function Table({ rowSelection, rowSelectionHandler, 
instances, onUpdate, onDelet
               <span class="check" />
             </label>
           </th>
-          <th><Message id="fields.instance.id.label" /></th>
-          <th><Message id="fields.instance.name.label" /></th>
+          <th><Message id="fields.tips.committed_amount.label" /></th>
+          <th><Message id="fields.tips.exchange_initial_amount.label" /></th>
+          <th><Message id="fields.tips.merchant_initial_amount.label" /></th>
           <th />
         </tr>
       </thead>
@@ -118,8 +122,9 @@ function Table({ rowSelection, rowSelectionHandler, 
instances, onUpdate, onDelet
                 <span class="check" />
               </label>
             </td>
-            <td onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}} >{i.id}</td>
-            <td onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}} >{i.name}</td>
+            <td onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}} >{i.committed_amount}</td>
+            <td onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}} >{i.exchange_initial_amount}</td>
+            <td onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}} >{i.merchant_initial_amount}</td>
             <td class="is-actions-cell">
               <div class="buttons is-right">
                 <button class="button is-small is-danger jb-modal" 
type="button" onClick={(): void => onDelete(i)}>
@@ -142,21 +147,3 @@ function EmptyTable(): VNode {
     <p><Message id="There is no instances yet, add more pressing the + sign" 
/></p>
   </div>
 }
-
-
-interface Actions {
-  element: MerchantBackend.Instances.Instance;
-  type: 'DELETE' | 'UPDATE';
-}
-
-function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
-  return value !== null && value !== undefined;
-}
-
-function buildActions(intances: MerchantBackend.Instances.Instance[], 
selected: string[], action: 'DELETE'): Actions[] {
-  return selected.map(id => intances.find(i => i.id === id))
-    .filter(notEmpty)
-    .map(id => ({ element: id, type: action }))
-}
-
-
diff --git a/packages/frontend/src/routes/instance/tips/list/index.tsx 
b/packages/frontend/src/routes/instance/tips/list/index.tsx
new file mode 100644
index 0000000..9c7ea6d
--- /dev/null
+++ b/packages/frontend/src/routes/instance/tips/list/index.tsx
@@ -0,0 +1,37 @@
+import { h, VNode } from 'preact';
+import { useConfigContext } from '../../../../context/backend';
+import { MerchantBackend } from '../../../../declaration';
+import { SwrError, useInstanceMutateAPI, useInstanceTips, useTipsMutateAPI } 
from '../../../../hooks/backend';
+import { CardTable } from './Table';
+
+interface Props {
+  onUnauthorized: () => VNode;
+  onLoadError: (e: SwrError) => VNode;
+}
+export default function ({ onUnauthorized, onLoadError }: Props): VNode {
+  const result = useInstanceTips()
+  const { createReserve, deleteReserve } = useTipsMutateAPI()
+  const { currency } = useConfigContext()
+  if (!result.data) {
+    if (result.unauthorized) return onUnauthorized()
+    if (result.error) return onLoadError(result.error)
+    return <div>
+      loading ....
+    </div>
+  }
+  return <section class="section is-main-section">
+    <CardTable instances={result.data.reserves.filter(r => r.active).map(o => 
({ ...o, id: o.reserve_pub }))}
+      onCreate={() => createReserve({
+        // explode with basic
+        wire_method: 'x-taler-bank',
+        initial_balance: `${currency}:${Math.floor(Math.random() * 20 + 1)}`,
+        //explode with 1
+        // hangs with /asd/asd/
+        // http://localhost:8081/
+        exchange_url: 'http://exchange.taler:8081',
+      })}
+      onDelete={(reserve: MerchantBackend.Tips.ReserveStatusEntry) => 
deleteReserve(reserve.reserve_pub)}
+      onUpdate={() => null}
+    />
+  </section>
+}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instance/tips/update/index.tsx 
b/packages/frontend/src/routes/instance/tips/update/index.tsx
new file mode 100644
index 0000000..dc4f045
--- /dev/null
+++ b/packages/frontend/src/routes/instance/tips/update/index.tsx
@@ -0,0 +1,5 @@
+import { h, VNode } from 'preact';
+
+export default function ():VNode {
+  return <div>tip update page</div>
+}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instance/transfers/create/index.tsx 
b/packages/frontend/src/routes/instance/transfers/create/index.tsx
new file mode 100644
index 0000000..797ac19
--- /dev/null
+++ b/packages/frontend/src/routes/instance/transfers/create/index.tsx
@@ -0,0 +1,5 @@
+import { h, VNode } from 'preact';
+
+export default function ():VNode {
+  return <div>transfer create page</div>
+}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instances/list/Table.tsx 
b/packages/frontend/src/routes/instance/transfers/list/Table.tsx
similarity index 84%
rename from packages/frontend/src/routes/instances/list/Table.tsx
rename to packages/frontend/src/routes/instance/transfers/list/Table.tsx
index 2c00668..b6586ba 100644
--- a/packages/frontend/src/routes/instances/list/Table.tsx
+++ b/packages/frontend/src/routes/instance/transfers/list/Table.tsx
@@ -22,18 +22,21 @@
 import { h, VNode } from "preact"
 import { Message } from "preact-messages"
 import { StateUpdater, useEffect, useState } from "preact/hooks"
-import { MerchantBackend } from "../../../declaration"
+import { MerchantBackend } from "../../../../declaration"
+import { Actions, buildActions } from "../../../../utils/table"
+
+type Entity = MerchantBackend.Transfers.TransferDetails & { id: string }
 
 interface Props {
-  instances: MerchantBackend.Instances.Instance[];
+  instances: Entity[];
   onUpdate: (id: string) => void;
-  onDelete: (id: MerchantBackend.Instances.Instance) => void;
+  onDelete: (id: Entity) => void;
   onCreate: () => void;
   selected?: boolean;
 }
 
 export function CardTable({ instances, onCreate, onUpdate, onDelete, selected 
}: Props): VNode {
-  const [actionQueue, actionQueueHandler] = useState<Actions[]>([]);
+  const [actionQueue, actionQueueHandler] = useState<Actions<Entity>[]>([]);
   const [rowSelection, rowSelectionHandler] = useState<string[]>([])
 
   useEffect(() => {
@@ -53,7 +56,7 @@ export function CardTable({ instances, onCreate, onUpdate, 
onDelete, selected }:
 
   return <div class="card has-table">
     <header class="card-header">
-      <p class="card-header-title"><span class="icon"><i class="mdi 
mdi-account-multiple" /></span><Message id="Instances" /></p>
+      <p class="card-header-title"><span class="icon"><i class="mdi 
mdi-account-multiple" /></span><Message id="Transfers" /></p>
 
       <div class="card-header-icon" aria-label="more options">
 
@@ -83,9 +86,9 @@ export function CardTable({ instances, onCreate, onUpdate, 
onDelete, selected }:
 }
 interface TableProps {
   rowSelection: string[];
-  instances: MerchantBackend.Instances.Instance[];
+  instances: Entity[];
   onUpdate: (id: string) => void;
-  onDelete: (id: MerchantBackend.Instances.Instance) => void;
+  onDelete: (id: Entity) => void;
   rowSelectionHandler: StateUpdater<string[]>;
 }
 
@@ -118,8 +121,8 @@ function Table({ rowSelection, rowSelectionHandler, 
instances, onUpdate, onDelet
                 <span class="check" />
               </label>
             </td>
-            <td onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}} >{i.id}</td>
-            <td onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}} >{i.name}</td>
+            <td onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}} >{i.credit_amount}</td>
+            <td onClick={(): void => onUpdate(i.id)} style={{cursor: 
'pointer'}} >{i.exchange_url}</td>
             <td class="is-actions-cell">
               <div class="buttons is-right">
                 <button class="button is-small is-danger jb-modal" 
type="button" onClick={(): void => onDelete(i)}>
@@ -144,19 +147,3 @@ function EmptyTable(): VNode {
 }
 
 
-interface Actions {
-  element: MerchantBackend.Instances.Instance;
-  type: 'DELETE' | 'UPDATE';
-}
-
-function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
-  return value !== null && value !== undefined;
-}
-
-function buildActions(intances: MerchantBackend.Instances.Instance[], 
selected: string[], action: 'DELETE'): Actions[] {
-  return selected.map(id => intances.find(i => i.id === id))
-    .filter(notEmpty)
-    .map(id => ({ element: id, type: action }))
-}
-
-
diff --git a/packages/frontend/src/routes/instance/transfers/list/index.tsx 
b/packages/frontend/src/routes/instance/transfers/list/index.tsx
new file mode 100644
index 0000000..488130c
--- /dev/null
+++ b/packages/frontend/src/routes/instance/transfers/list/index.tsx
@@ -0,0 +1,36 @@
+import { h, VNode } from 'preact';
+import { useConfigContext } from '../../../../context/backend';
+import { SwrError, useInstanceTransfers, useTransferMutateAPI } from 
'../../../../hooks/backend';
+import { CardTable } from './Table';
+
+interface Props {
+  onUnauthorized: () => VNode;
+  onLoadError: (e: SwrError) => VNode;
+}
+export default function ({ onUnauthorized, onLoadError }: Props): VNode {
+  const result = useInstanceTransfers()
+  const { informTransfer } = useTransferMutateAPI()
+  const { currency } = useConfigContext()
+  if (!result.data) {
+    if (result.unauthorized) return onUnauthorized()
+    if (result.error) return onLoadError(result.error)
+    return <div>
+      loading ....
+    </div>
+  }
+  return <section class="section is-main-section">
+    <CardTable instances={result.data.transfers.map(o => ({ ...o, id: 
String(o.transfer_serial_id) }))}
+      onCreate={() => informTransfer({
+        wtid: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
+
+        // exchange: payto://x-taler-bank/bank.taler:5882/exchangeminator
+        // payto://x-taler-bank/bank.taler:5882/9?subject=qwe&amount=COL:10
+        payto_uri: 'payto://x-taler-bank/bank.taler:5882/blogger',
+        exchange_url: 'http://exchange.taler:8081/',
+        credit_amount: 'COL:2'
+      })}
+      onDelete={() => null}
+      onUpdate={() => null}
+    />
+  </section>
+}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instance/transfers/update/index.tsx 
b/packages/frontend/src/routes/instance/transfers/update/index.tsx
new file mode 100644
index 0000000..a1f58d3
--- /dev/null
+++ b/packages/frontend/src/routes/instance/transfers/update/index.tsx
@@ -0,0 +1,5 @@
+import { h, VNode } from 'preact';
+
+export default function ():VNode {
+  return <div>order update page</div>
+}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instances/update/UpdatePage.tsx 
b/packages/frontend/src/routes/instance/update/UpdatePage.tsx
similarity index 100%
rename from packages/frontend/src/routes/instances/update/UpdatePage.tsx
rename to packages/frontend/src/routes/instance/update/UpdatePage.tsx
diff --git a/packages/frontend/src/routes/instances/update/index.tsx 
b/packages/frontend/src/routes/instance/update/index.tsx
similarity index 95%
rename from packages/frontend/src/routes/instances/update/index.tsx
rename to packages/frontend/src/routes/instance/update/index.tsx
index 2f75258..51bb6a5 100644
--- a/packages/frontend/src/routes/instances/update/index.tsx
+++ b/packages/frontend/src/routes/instance/update/index.tsx
@@ -18,7 +18,7 @@ import { useState } from "preact/hooks";
 import { UpdateTokenModal } from "../../../components/modal";
 import { useInstanceContext } from "../../../context/backend";
 import { MerchantBackend } from "../../../declaration";
-import { SwrError, useBackendInstance, useInstanceMutateAPI } from 
"../../../hooks/backend";
+import { SwrError, useInstanceDetails, useInstanceMutateAPI } from 
"../../../hooks/backend";
 import { UpdatePage } from "./UpdatePage";
 
 interface Props {
@@ -35,7 +35,7 @@ interface Props {
 export default function Update({ onBack, onConfirm, onLoadError, 
onUpdateError, onUnauthorized }: Props): VNode {
   const { updateInstance, setNewToken, clearToken } = useInstanceMutateAPI();
   const [updatingToken, setUpdatingToken] = useState<boolean>(false)
-  const details = useBackendInstance()
+  const details = useInstanceDetails()
   const { id, token } = useInstanceContext()
 
   if (!details.data) {
diff --git a/packages/frontend/src/utils/functions.ts 
b/packages/frontend/src/utils/functions.ts
index f51aacf..7550e68 100644
--- a/packages/frontend/src/utils/functions.ts
+++ b/packages/frontend/src/utils/functions.ts
@@ -20,7 +20,18 @@ export function hasKey<O>(obj: O, key: string | number | 
symbol): key is keyof O
   return key in obj
 }
 
+declare global {
+  interface Window { MerchantBackoffice: any; }
+}
+
+if (typeof window !== "undefined") {
+  window.MerchantBackoffice = window.MerchantBackoffice || {
+    missing_locales: [],
+    getMissingTranslation: () => Array.from(new 
Set(window.MerchantBackoffice.missing_locales)).filter(i => i).map(i => `msgid 
"${i}"\nmsgstr ""\n`).join('\n')
+  };
+}
+
 export function onTranslationError(error: MessageError) {
   if (typeof window === "undefined") return;
-  (window as any)['missing_locale'] = ([] as string[]).concat((window as 
any)['missing_locale']).concat(error.path.join())
+  window.MerchantBackoffice.missing_locales = 
window.MerchantBackoffice.missing_locales.concat(error.path.join())
 }
diff --git a/packages/frontend/src/utils/table.ts 
b/packages/frontend/src/utils/table.ts
new file mode 100644
index 0000000..d9e3d53
--- /dev/null
+++ b/packages/frontend/src/utils/table.ts
@@ -0,0 +1,20 @@
+
+
+export interface Actions<T extends WithId> {
+  element: T;
+  type: 'DELETE' | 'UPDATE';
+}
+
+function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
+  return value !== null && value !== undefined;
+}
+
+interface WithId {
+  id: string
+}
+
+export function buildActions<T extends WithId>(intances: T[], selected: 
string[], action: 'DELETE'): Actions<T>[] {
+  return selected.map(id => intances.find(i => i.id === id))
+    .filter(notEmpty)
+    .map(id => ({ element: id, type: action }))
+}

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