gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant-backoffice] branch master updated: started to work on th


From: gnunet
Subject: [taler-merchant-backoffice] branch master updated: started to work on the product page
Date: Thu, 01 Apr 2021 17:36:43 +0200

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

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

The following commit(s) were added to refs/heads/master by this push:
     new b15cf80  started to work on the product page
b15cf80 is described below

commit b15cf80c3448501d042f34b542ed7c34314261c3
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Thu Apr 1 12:36:15 2021 -0300

    started to work on the product page
---
 packages/frontend/src/InstanceRoutes.tsx           | 445 +++++-------------
 packages/frontend/src/hooks/admin.ts               |  34 ++
 packages/frontend/src/hooks/backend.ts             | 506 +--------------------
 packages/frontend/src/hooks/instance.ts            |  97 ++++
 packages/frontend/src/hooks/order.ts               | 177 +++++++
 packages/frontend/src/hooks/product.ts             | 105 +++++
 packages/frontend/src/hooks/tips.ts                |  86 ++++
 packages/frontend/src/hooks/transfer.ts            |  54 +++
 packages/frontend/src/messages/en.po               |  21 +
 packages/frontend/src/paths/admin/create/index.tsx |   4 +-
 packages/frontend/src/paths/admin/list/index.tsx   |   6 +-
 .../frontend/src/paths/instance/details/index.tsx  |   6 +-
 .../orders/create/OrderCreatedSuccessfully.tsx     |   4 +-
 .../src/paths/instance/orders/create/index.tsx     |  10 +-
 .../src/paths/instance/orders/details/index.tsx    |   5 +-
 .../src/paths/instance/orders/list/index.tsx       |   6 +-
 .../src/paths/instance/products/list/Table.tsx     |  86 ++--
 .../src/paths/instance/products/list/index.tsx     |  19 +-
 .../src/paths/instance/tips/list/index.tsx         |   3 +-
 .../src/paths/instance/transfers/list/index.tsx    |   3 +-
 .../frontend/src/paths/instance/update/index.tsx   |   5 +-
 21 files changed, 810 insertions(+), 872 deletions(-)

diff --git a/packages/frontend/src/InstanceRoutes.tsx 
b/packages/frontend/src/InstanceRoutes.tsx
index 2114c6c..fb8e0ac 100644
--- a/packages/frontend/src/InstanceRoutes.tsx
+++ b/packages/frontend/src/InstanceRoutes.tsx
@@ -19,14 +19,14 @@
 * @author Sebastian Javier Marchano (sebasjm)
 */
 
-import { Fragment, h, VNode } from 'preact';
+import { AnyComponent, Fragment, FunctionalComponent, FunctionComponent, h, 
RenderableProps, VNode } from 'preact';
 import { useCallback, useEffect, useMemo } from "preact/hooks";
 import { Route, Router, route } from 'preact-router';
 import { useMessageTemplate } from 'preact-messages';
 import { createHashHistory } from 'history';
 import { useBackendDefaultToken, useBackendInstanceToken } from './hooks';
 import { InstanceContextProvider, useBackendContext } from './context/backend';
-import { SwrError, useInstanceDetails } from "./hooks/backend";
+import { SwrError } from "./hooks/backend";
 // import { Notification } from './utils/types';
 
 import LoginPage from './paths/login';
@@ -86,36 +86,6 @@ export interface Props {
   admin?: boolean;
 }
 
-function AdminInstanceUpdatePage({ id, ...rest }: { id: string } & 
InstanceUpdatePageProps) {
-  const [token, updateToken] = useBackendInstanceToken(id);
-  const value = useMemo(() => ({ id, token, admin: true }), [id, token])
-  const { changeBackend } = useBackendContext();
-  const updateLoginStatus = (url: string, token?: string) => {
-    changeBackend(url);
-    if (token)
-      updateToken(token);
-  };
-  const i18n = useMessageTemplate('');
-  return <InstanceContextProvider value={value}>
-    <InstanceUpdatePage {...rest}
-      onLoadError={(error: SwrError) => {
-        return <Fragment>
-          <NotificationCard notification={{ message: i18n`Problem reaching the 
server`, description: i18n`Got message: ${error.message} from: ${error.backend} 
(hasToken: ${error.hasToken})`, type: 'ERROR' }} />
-          <LoginPage onConfirm={updateLoginStatus} />
-        </Fragment>
-      }}
-
-      onUnauthorized={() => {
-        return <Fragment>
-          <NotificationCard notification={{ message: i18n`Access denied`, 
description: i18n`Check your token is valid`, type: 'ERROR', }} />
-          <LoginPage onConfirm={updateLoginStatus} />
-        </Fragment>
-      }}
-
-    />
-  </InstanceContextProvider>
-}
-
 export function InstanceRoutes({ id, admin }: Props): VNode {
   const [_, updateDefaultToken] = useBackendDefaultToken()
   const [token, updateToken] = useBackendInstanceToken(id);
@@ -139,325 +109,128 @@ export function InstanceRoutes({ id, admin }: Props): 
VNode {
 
   const value = useMemo(() => ({ id, token, admin }), [id, token])
 
-  return <InstanceContextProvider value={value}>
-    <Router history={createHashHistory()}>
-      {admin &&
-        <Route path={AdminPaths.list_instances} component={InstanceListPage}
-
-          onCreate={() => {
-            route(AdminPaths.new_instance);
-          }}
+  const LoginPageServerError = (error: SwrError) => <Fragment>
+    <NotificationCard notification={{ message: i18n`Problem reaching the 
server`, description: i18n`Got message: ${error.message} from: ${error.backend} 
(hasToken: ${error.hasToken})`, type: 'ERROR' }} />
+    <LoginPage onConfirm={updateLoginStatus} />
+  </Fragment>
 
-          onUpdate={(id: string): void => {
-            route(`/instance/${id}/update`);
-          }}
+  const LoginPageAccessDenied = () => <Fragment>
+    <NotificationCard notification={{ message: i18n`Access denied`, 
description: i18n`Check your token is valid`, type: 'ERROR', }} />
+    <LoginPage onConfirm={updateLoginStatus} />
+  </Fragment>
 
-          onUnauthorized={() => {
-            return <Fragment>
-              <NotificationCard notification={{ message: i18n`Access denied`, 
description: i18n`Check your token is valid`, type: 'ERROR' }} />
-              <LoginPage onConfirm={updateLoginStatus} />
-            </Fragment>
-          }}
+  function IfAdminCreateDefaultOr<T>(Next: FunctionComponent<any>) {
+    return (props?:T)=> {
+      if (admin) {
+        return <Fragment>
+          <NotificationCard notification={{
+            message: 'No default instance',
+            description: 'in order to use merchant backoffice, you should 
create the default instance',
+            type: 'INFO'
+          }} />
+          <InstanceCreatePage onError={() => null} forceId="default" 
onConfirm={() => {
+            route(AdminPaths.list_instances)
+          }} />
+        </Fragment>
+      }
+      if (props) {
+        return <Next {...props} />
+      } else {
+        return <Next />
+      }
+    }
+  }
 
-          onLoadError={(error: SwrError) => {
-            return <Fragment>
-              <NotificationCard notification={{ message: i18n`Problem reaching 
the server`, description: i18n`Got message: ${error.message} from: 
${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR' }} />
-              <LoginPage onConfirm={updateLoginStatus} />
-            </Fragment>
-          }}
+  return <InstanceContextProvider value={value}>
+    <Router history={createHashHistory()}>
+      <Route path="/" component={Redirect} to={InstancePaths.order_list} />
 
+      {/**
+       * Admin pages
+       */}
+      {admin &&
+        <Route path={AdminPaths.list_instances} component={InstanceListPage}
+          onCreate={() => { route(AdminPaths.new_instance) }}
+          onUpdate={(id: string): void => { route(`/instance/${id}/update`); }}
+          onUnauthorized={LoginPageAccessDenied}
+          onLoadError={LoginPageServerError}
         />
       }
 
       {admin &&
         <Route path={AdminPaths.new_instance} component={InstanceCreatePage}
-
           onBack={() => route(AdminPaths.list_instances)}
-
-          onConfirm={() => {
-            // pushNotification({ message: i18n`create_success`, type: 
'SUCCESS' });
-            route(AdminPaths.list_instances);
-          }}
-
-          onError={(error: any) => {
-            // pushNotification({ message: i18n`create_error`, type: 'ERROR' 
});
-          }}
-
+          onConfirm={() => { route(AdminPaths.list_instances); }}
+          onError={LoginPageServerError}
         />
       }
 
       {admin &&
         <Route path={AdminPaths.update_instance} 
component={AdminInstanceUpdatePage}
-
           onBack={() => route(AdminPaths.list_instances)}
-
-          onConfirm={() => {
-            // pushNotification({ message: i18n`create_success`, type: 
'SUCCESS' });
-            route(AdminPaths.list_instances);
-          }}
-
-          onUpdateError={(e: Error) => {
-            // pushNotification({ message: i18n`update_error`, type: 'ERROR' 
});
-          }}
-
+          onConfirm={() => { route(AdminPaths.list_instances); }}
+          onLoadError={LoginPageServerError}
         />
       }
 
-      <Route path="/" component={Redirect} to={InstancePaths.order_list} />
-
-      <Route path={InstancePaths.update}
-        component={InstanceUpdatePage}
-
-        onUnauthorized={() => {
-          return <Fragment>
-            <NotificationCard notification={{ message: i18n`Access denied`, 
description: i18n`Check your token is valid`, type: 'ERROR', }} />
-            <LoginPage onConfirm={updateLoginStatus} />
-          </Fragment>
-        }}
-
-        onLoadError={(error: SwrError) => {
-          if (admin) {
-            return <Fragment>
-              <NotificationCard notification={{
-                message: 'No default instance',
-                description: 'in order to use merchant backoffice, you should 
create the default instance',
-                type: 'INFO'
-              }} />
-              <InstanceCreatePage onError={() => null} forceId="default" 
onConfirm={() => {
-                route(AdminPaths.list_instances)
-              }} />
-            </Fragment>
-          }
-          return <Fragment>
-            <NotificationCard notification={{ message: i18n`Problem reaching 
the server`, description: i18n`Got message: ${error.message} from: 
${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR' }} />
-            <LoginPage onConfirm={updateLoginStatus} />
-          </Fragment>
-        }}
-
-        onNotFound={() => {
-          if (admin) {
-            return <Fragment>
-              <NotificationCard notification={{
-                message: 'No default instance',
-                description: 'in order to use merchant backoffice, you should 
create the default instance',
-                type: 'INFO'
-              }} />
-              <InstanceCreatePage onError={() => null} forceId="default" 
onConfirm={() => {
-                route(AdminPaths.list_instances)
-              }} />
-            </Fragment>
-          }
-          return <NotFoundPage />
-        }}
-
-        onBack={() => {
-          route(`/`);
-        }}
-
-        onConfirm={() => {
-          route(`/`);
-        }}
-
-        onUpdateError={(e: Error) => {
-        }}
+      {/**
+       * Update instance page
+       */}
+      <Route path={InstancePaths.update} component={InstanceUpdatePage}
+        onUnauthorized={LoginPageAccessDenied}
+        onLoadError={IfAdminCreateDefaultOr(LoginPageServerError)}
+        onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
+        onBack={() => {route(`/`);}}
+        onConfirm={() => {route(`/`);}}
+        onUpdateError={(e: Error) => {}}
       />
 
-      <Route path={InstancePaths.product_list}
-        component={ProductListPage}
-
-        onUnauthorized={() => {
-          return <Fragment>
-            <NotificationCard notification={{ message: i18n`Access denied`, 
description: i18n`Check your token is valid`, type: 'ERROR', }} />
-            <LoginPage onConfirm={updateLoginStatus} />
-          </Fragment>
-        }}
-
-        onNotFound={() => {
-          if (admin) {
-            return <Fragment>
-              <NotificationCard notification={{
-                message: 'No default instance',
-                description: 'in order to use merchant backoffice, you should 
create the default instance',
-                type: 'INFO'
-              }} />
-              <InstanceCreatePage onError={() => null} forceId="default" 
onConfirm={() => {
-                route(AdminPaths.list_instances)
-              }} />
-            </Fragment>
-          }
-          return <NotFoundPage />
-        }}
-
-        onLoadError={(error: SwrError) => {
-          return <Fragment>
-            <NotificationCard notification={{ message: i18n`Problem reaching 
the server`, description: i18n`Got message: ${error.message} from: 
${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR' }} />
-            <LoginPage onConfirm={updateLoginStatus} />
-          </Fragment>
-        }}
+      {/**
+       * Product pages
+       */}
+      <Route path={InstancePaths.product_list} component={ProductListPage}
+        onUnauthorized={LoginPageAccessDenied}
+        onLoadError={IfAdminCreateDefaultOr(LoginPageServerError)}
+        onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
       />
-      <Route path={InstancePaths.product_update}
-        component={ProductUpdatePage}
+      <Route path={InstancePaths.product_update} component={ProductUpdatePage}
       />
-      <Route path={InstancePaths.product_new}
-        component={ProductCreatePage}
+      <Route path={InstancePaths.product_new} component={ProductCreatePage}
       />
 
-      <Route path={InstancePaths.order_list}
-        component={OrderListPage}
-
-        onSelect={ (id:string) => {
-          route(InstancePaths.order_details.replace(':oid',id))
-        }}
-
-        onUnauthorized={() => {
-          return <Fragment>
-            <NotificationCard notification={{ message: i18n`Access denied`, 
description: i18n`Check your token is valid`, type: 'ERROR', }} />
-            <LoginPage onConfirm={updateLoginStatus} />
-          </Fragment>
-        }}
-
-        onNotFound={() => {
-          if (admin) {
-            return <Fragment>
-              <NotificationCard notification={{
-                message: 'No default instance',
-                description: 'in order to use merchant backoffice, you should 
create the default instance',
-                type: 'INFO'
-              }} />
-              <InstanceCreatePage onError={() => null} forceId="default" 
onConfirm={() => {
-                route(AdminPaths.list_instances)
-              }} />
-            </Fragment>
-          }
-          return <NotFoundPage />
-        }}
-
-        onLoadError={(error: SwrError) => {
-          return <Fragment>
-            <NotificationCard notification={{ message: i18n`Problem reaching 
the server`, description: i18n`Got message: ${error.message} from: 
${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR' }} />
-            <LoginPage onConfirm={updateLoginStatus} />
-          </Fragment>
-        }}
-
-        onCreate={() => {
-          route(InstancePaths.order_new)
-        }}
+      {/**
+       * Order pages
+       */}
+      <Route path={InstancePaths.order_list} component={OrderListPage}
+        onCreate={() => {route(InstancePaths.order_new)}}
+        onSelect={(id: string) => { 
route(InstancePaths.order_details.replace(':oid', id)) }}
+        onUnauthorized={LoginPageAccessDenied}
+        onLoadError={IfAdminCreateDefaultOr(LoginPageServerError)}
+        onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
       />
-
-      <Route path={InstancePaths.order_details}
-        component={OrderDetailsPage}
-        
-        onUnauthorized={() => {
-          return <Fragment>
-            <NotificationCard notification={{ message: i18n`Access denied`, 
description: i18n`Check your token is valid`, type: 'ERROR', }} />
-            <LoginPage onConfirm={updateLoginStatus} />
-          </Fragment>
-        }}
-
-        onNotFound={() => {
-          return <NotFoundPage />
-        }}
-
-        onLoadError={(error: SwrError) => {
-          return <Fragment>
-            <NotificationCard notification={{ message: i18n`Problem reaching 
the server`, description: i18n`Got message: ${error.message} from: 
${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR' }} />
-            <LoginPage onConfirm={updateLoginStatus} />
-          </Fragment>
-        }}
-
-        onBack={() => {
-          route(InstancePaths.order_list)
-        }}
-      />
-      <Route path={InstancePaths.order_new}
-        component={OrderCreatePage}
-        
-        onConfirm={() => {
-          route(InstancePaths.order_list)
-        }}
-
-        onBack={() => {
-          route(InstancePaths.order_list)
-        }}
-      />     
-       {/* 
-      <Route path={InstancePaths.tips_list}
-        component={TipListPage}
-
-        onUnauthorized={() => {
-          return <Fragment>
-            <NotificationCard notification={{ message: i18n`Access denied`, 
description: i18n`Check your token is valid`, type: 'ERROR', }} />
-            <LoginPage onConfirm={updateLoginStatus} />
-          </Fragment>
-        }}
-
-        onNotFound={() => {
-          if (admin) {
-            return <Fragment>
-              <NotificationCard notification={{
-                message: 'No default instance',
-                description: 'in order to use merchant backoffice, you should 
create the default instance',
-                type: 'INFO'
-              }} />
-              <InstanceCreatePage onError={() => null} onConfirm={() => null} 
/>
-            </Fragment>
-          }
-          return <NotFoundPage />
-        }}
-
-        onLoadError={(error: SwrError) => {
-          return <Fragment>
-            <NotificationCard notification={{ message: i18n`Problem reaching 
the server`, description: i18n`Got message: ${error.message} from: 
${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR' }} />
-            <LoginPage onConfirm={updateLoginStatus} />
-          </Fragment>
-        }}
+      <Route path={InstancePaths.order_details} component={OrderDetailsPage}
+        onUnauthorized={LoginPageAccessDenied}
+        onLoadError={IfAdminCreateDefaultOr(LoginPageServerError)}
+        onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
+        onBack={() => {route(InstancePaths.order_list)}}
       />
-      <Route path={InstancePaths.tips_update}
-        component={TipUpdatePage}
+      <Route path={InstancePaths.order_new} component={OrderCreatePage}
+        onConfirm={() => {route(InstancePaths.order_list)}}
+        onBack={() => {route(InstancePaths.order_list)}}
       />
-      <Route path={InstancePaths.tips_new}
-        component={TipCreatePage}
-      /> 
-      */}
-
-      <Route path={InstancePaths.transfers_list}
-        component={TransferListPage}
-
-        onUnauthorized={() => {
-          return <Fragment>
-            <NotificationCard notification={{ message: i18n`Access denied`, 
description: i18n`Check your token is valid`, type: 'ERROR', }} />
-            <LoginPage onConfirm={updateLoginStatus} />
-          </Fragment>
-        }}
-
-        onNotFound={() => {
-          if (admin) {
-            return <Fragment>
-              <NotificationCard notification={{
-                message: 'No default instance',
-                description: 'in order to use merchant backoffice, you should 
create the default instance',
-                type: 'INFO'
-              }} />
-              <InstanceCreatePage onError={() => null} forceId="default" 
onConfirm={() => {
-                route(AdminPaths.list_instances)
-              }} />
-            </Fragment>
-          }
-          return <NotFoundPage />
-        }}
-
-        onLoadError={(error: SwrError) => {
-          return <Fragment>
-            <NotificationCard notification={{ message: i18n`Problem reaching 
the server`, description: i18n`Got message: ${error.message} from: 
${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR' }} />
-            <LoginPage onConfirm={updateLoginStatus} />
-          </Fragment>
-        }}
+
+      {/**
+       * Transfer pages
+       */}
+      <Route path={InstancePaths.transfers_list} component={TransferListPage}
+        onUnauthorized={LoginPageAccessDenied}
+        onLoadError={IfAdminCreateDefaultOr(LoginPageServerError)}
+        onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
       />
-      {/* <Route path={InstancePaths.transfers_new}
-        component={TransferCreatePage}
-      /> */}
 
-      {/* example of loading page*/}
+      {/**
+       * Example pages
+       */}
       <Route path="/loading" component={Loading} />
       <Route default component={NotFoundPage} />
     </Router>
@@ -472,3 +245,33 @@ export function Redirect({ to }: { to: string }): null {
   return null
 }
 
+function AdminInstanceUpdatePage({ id, ...rest }: { id: string } & 
InstanceUpdatePageProps) {
+  const [token, updateToken] = useBackendInstanceToken(id);
+  const value = useMemo(() => ({ id, token, admin: true }), [id, token])
+  const { changeBackend } = useBackendContext();
+  const updateLoginStatus = (url: string, token?: string) => {
+    changeBackend(url);
+    if (token)
+      updateToken(token);
+  };
+  const i18n = useMessageTemplate('');
+  return <InstanceContextProvider value={value}>
+    <InstanceUpdatePage {...rest}
+      onLoadError={(error: SwrError) => {
+        return <Fragment>
+          <NotificationCard notification={{ message: i18n`Problem reaching the 
server`, description: i18n`Got message: ${error.message} from: ${error.backend} 
(hasToken: ${error.hasToken})`, type: 'ERROR' }} />
+          <LoginPage onConfirm={updateLoginStatus} />
+        </Fragment>
+      }}
+
+      onUnauthorized={() => {
+        return <Fragment>
+          <NotificationCard notification={{ message: i18n`Access denied`, 
description: i18n`Check your token is valid`, type: 'ERROR', }} />
+          <LoginPage onConfirm={updateLoginStatus} />
+        </Fragment>
+      }}
+
+    />
+  </InstanceContextProvider>
+}
+
diff --git a/packages/frontend/src/hooks/admin.ts 
b/packages/frontend/src/hooks/admin.ts
new file mode 100644
index 0000000..c12fcfd
--- /dev/null
+++ b/packages/frontend/src/hooks/admin.ts
@@ -0,0 +1,34 @@
+import { MerchantBackend } from '../declaration';
+import { useBackendContext } from '../context/backend';
+import { request, mutateAll } from './backend';
+
+
+export function useAdminAPI(): AdminAPI {
+  const { url, token } = useBackendContext();
+
+  const createInstance = async (instance: 
MerchantBackend.Instances.InstanceConfigurationMessage): Promise<void> => {
+    await request(`${url}/private/instances`, {
+      method: 'post',
+      token,
+      data: instance
+    });
+
+    mutateAll(/@"\/private\/instances"@/);
+  };
+
+  const deleteInstance = async (id: string): Promise<void> => {
+    await request(`${url}/private/instances/${id}`, {
+      method: 'delete',
+      token,
+    });
+
+    mutateAll(/@"\/private\/instances"@/);
+  };
+
+  return { createInstance, deleteInstance };
+}
+
+export interface AdminAPI {
+  createInstance: (data: 
MerchantBackend.Instances.InstanceConfigurationMessage) => Promise<void>;
+  deleteInstance: (id: string) => Promise<void>;
+}
diff --git a/packages/frontend/src/hooks/backend.ts 
b/packages/frontend/src/hooks/backend.ts
index e8b9af5..943d826 100644
--- a/packages/frontend/src/hooks/backend.ts
+++ b/packages/frontend/src/hooks/backend.ts
@@ -26,12 +26,16 @@ import { useBackendContext, useInstanceContext } from 
'../context/backend';
 import { useEffect, useMemo, useState } from 'preact/hooks';
 import { MAX_RESULT_SIZE, PAGE_SIZE } from '../utils/constants';
 import { add, addHours, addSeconds, format, max } from 'date-fns';
+import { OrderAPI } from './order';
 
-function mutateAll(re: RegExp) {
-  cache.keys().filter(key => re.test(key)).forEach(key => mutate(key, null))
+export function mutateAll(re: RegExp) {
+  cache.keys().filter(key => {
+    // console.log(key, re.test(key))
+    return re.test(key)
+  }).forEach(key => mutate(key, null))
 }
 
-type HttpResponse<T> = HttpResponseOk<T> | HttpResponseError;
+export type HttpResponse<T> = HttpResponseOk<T> | HttpResponseError;
 
 interface HttpResponseOk<T> {
   data: T;
@@ -74,8 +78,7 @@ interface RequestOptions {
 }
 
 
-
-async function request(url: string, options: RequestOptions = {}): 
Promise<any> {
+export async function request(url: string, options: RequestOptions = {}): 
Promise<any> {
   const headers = options.token ? { Authorization: `Bearer ${options.token}` } 
: undefined
 
   try {
@@ -97,6 +100,7 @@ async function request(url: string, options: RequestOptions 
= {}): Promise<any>
     })
     return res.data
   } catch (e) {
+    console.error(e)
     const info = e.response?.data
     const status = e.response?.status
     const hint = info?.hint
@@ -105,500 +109,10 @@ async function request(url: string, options: 
RequestOptions = {}): Promise<any>
 
 }
 
-function fetcher(url: string, token: string, backend: string) {
+export function fetcher(url: string, token: string, backend: string) {
   return request(`${backend}${url}`, { token })
 }
 
-type YesOrNo = 'yes' | 'no';
-
-function orderFetcher(url: string, token: string, backend: string, paid?: 
YesOrNo, refunded?: YesOrNo, wired?: YesOrNo, searchDate?: Date, delta?: 
number) {
-  const newDate = searchDate && addHours(searchDate, 3) // remove this, locale
-  // if we are 
-  const newDatePlus1SecIfNeeded = delta && delta < 0 && newDate ? 
addSeconds(newDate, 1) : newDate
-  const date = newDatePlus1SecIfNeeded ? format(newDatePlus1SecIfNeeded, 
'yyyy-MM-dd HH:mm:ss') : undefined
-  return request(`${backend}${url}`, { token, params: { paid, refunded, wired, 
delta, date } })
-}
-
-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>;
-}
-
-export function useAdminMutateAPI(): AdminMutateAPI {
-  const { url, token } = useBackendContext()
-
-  const createInstance = async (instance: 
MerchantBackend.Instances.InstanceConfigurationMessage): Promise<void> => {
-    await request(`${url}/private/instances`, {
-      method: 'post',
-      token,
-      data: instance
-    })
-
-    mutateAll(/@"\/private\/instances"@/)
-  }
-
-  const deleteInstance = async (id: string): Promise<void> => {
-    await request(`${url}/private/instances/${id}`, {
-      method: 'delete',
-      token,
-    })
-
-    mutateAll(/@"\/private\/instances"@/)
-  }
-
-  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
-    })
-
-    mutateAll(/@"\/private\/products"@/)
-  }
-
-  const updateProduct = async (productId: string, data: 
MerchantBackend.Products.ProductPatchDetail): Promise<void> => {
-    await request(`${url}/private/products/${productId}`, {
-      method: 'patch',
-      token,
-      data
-    })
-
-    mutateAll(/@"\/private\/products"@/)
-  }
-
-  const deleteProduct = async (productId: string): Promise<void> => {
-    await request(`${url}/private/products/${productId}`, {
-      method: 'delete',
-      token,
-    })
-
-    mutateAll(/@"\/private\/products"@/)
-  }
-
-  const lockProduct = async (productId: string, data: 
MerchantBackend.Products.LockRequest): Promise<void> => {
-    await request(`${url}/private/products/${productId}/lock`, {
-      method: 'post',
-      token,
-      data
-    })
-
-    mutateAll(/@"\/private\/products"@/)
-  }
-
-  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>;
-  refundOrder: (id: string, data: MerchantBackend.Orders.RefundRequest) => 
Promise<MerchantBackend.Orders.MerchantRefundResponse>;
-  deleteOrder: (id: string) => Promise<void>;
-  getPaymentURL: (id: string) => Promise<string>;
-}
-
-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
-    })
-
-    mutateAll(/@"\/private\/orders"@/)
-    return res
-  }
-  const refundOrder = async (orderId: string, data: 
MerchantBackend.Orders.RefundRequest): 
Promise<MerchantBackend.Orders.MerchantRefundResponse> => {
-    const res = await request(`${url}/private/orders/${orderId}/refund`, {
-      method: 'post',
-      token,
-      data
-    })
-
-    mutateAll(/@"\/private\/orders"@/)
-    return res
-  }
-
-  const forgetOrder = async (orderId: string, data: 
MerchantBackend.Orders.ForgetRequest): Promise<void> => {
-    await request(`${url}/private/orders/${orderId}/forget`, {
-      method: 'patch',
-      token,
-      data
-    })
-
-    mutateAll(/@"\/private\/orders"@/)
-  }
-  const deleteOrder = async (orderId: string): Promise<void> => {
-    await request(`${url}/private/orders/${orderId}`, {
-      method: 'delete',
-      token
-    })
-
-    mutateAll(/@"\/private\/orders"@/)
-  }
-
-  const getPaymentURL = async (orderId: string): Promise<string> => {
-    const data = await request(`${url}/private/orders/${orderId}`, {
-      method: 'get',
-      token
-    })
-    return data.taler_pay_uri || data.contract_terms?.fulfillment_url
-  }
-
-  return { createOrder, forgetOrder, deleteOrder, refundOrder, getPaymentURL }
-}
-
-export function useOrderDetails(oderId:string): 
HttpResponse<MerchantBackend.Orders.MerchantOrderStatusResponse> {
-  const { url: baseUrl } = useBackendContext();
-  const { token, id: instanceId, admin } = useInstanceContext();
-
-  const url = !admin ? baseUrl : `${baseUrl}/instances/${instanceId}`
-
-  const { data, error } = 
useSWR<MerchantBackend.Orders.MerchantOrderStatusResponse, 
SwrError>([`/private/orders/${oderId}`, token, url], fetcher)
-
-  return { data, unauthorized: error?.status === 401, notfound: error?.status 
=== 404, error }
-}
-
-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
-    })
-
-    mutateAll(/@"\/private\/transfers"@/)
-    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
-    })
-
-    mutateAll(/@"\/private\/reserves"@/)
-    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
-    })
-
-    mutateAll(/@"\/private\/reserves"@/)
-    return res
-  }
-
-  const authorizeTip = async (data: MerchantBackend.Tips.TipCreateRequest): 
Promise<MerchantBackend.Tips.TipCreateConfirmation> => {
-    const res = await request(`${url}/private/tips`, {
-      method: 'post',
-      token,
-      data
-    })
-
-    mutateAll(/@"\/private\/reserves"@/)
-    return res
-  }
-
-  const deleteReserve = async (pub: string): Promise<void> => {
-    await request(`${url}/private/reserves/${pub}`, {
-      method: 'delete',
-      token,
-    })
-
-    mutateAll(/@"\/private\/reserves"@/)
-  }
-
-
-  return { createReserve, authorizeTip, authorizeTipReserve, deleteReserve }
-}
-
-interface InstaceMutateAPI {
-  updateInstance: (data: 
MerchantBackend.Instances.InstanceReconfigurationMessage, a?: 
MerchantBackend.Instances.InstanceAuthConfigurationMessage) => Promise<void>;
-  deleteInstance: () => Promise<void>;
-  clearToken: () => Promise<void>;
-  setNewToken: (token: string) => Promise<void>;
-}
-
-export function useInstanceMutateAPI(): InstaceMutateAPI {
-  const { url: baseUrl, token: adminToken } = useBackendContext()
-  const { token, id, admin } = useInstanceContext()
-
-  const url = !admin ? baseUrl : `${baseUrl}/instances/${id}`
-
-  const updateInstance = async (instance: 
MerchantBackend.Instances.InstanceReconfigurationMessage, auth?: 
MerchantBackend.Instances.InstanceAuthConfigurationMessage): Promise<void> => {
-    await request(`${url}/private/`, {
-      method: 'patch',
-      token,
-      data: instance
-    })
-
-    if (auth) await request(`${url}/private/auth`, {
-      method: 'post',
-      token,
-      data: auth
-    })
-
-    if (adminToken) mutate(['/private/instances', adminToken, baseUrl], null)
-    mutate([`/private/`, token, url], null)
-  };
-
-  const deleteInstance = async (): Promise<void> => {
-    await request(`${url}/private/`, {
-      method: 'delete',
-      token: adminToken,
-    })
-
-    if (adminToken) mutate(['/private/instances', adminToken, baseUrl], null)
-    mutate([`/private/`, token, url], null)
-  }
-
-  const clearToken = async (): Promise<void> => {
-    await request(`${url}/private/auth`, {
-      method: 'post',
-      token,
-      data: { method: 'external' }
-    })
-
-    mutate([`/private/`, token, url], null)
-  }
-
-  const setNewToken = async (newToken: string): Promise<void> => {
-    await request(`${url}/private/auth`, {
-      method: 'post',
-      token,
-      data: { method: 'token', token: newToken }
-    })
-
-    mutate([`/private/`, token, url], null)
-  }
-
-  return { updateInstance, deleteInstance, setNewToken, clearToken }
-}
-
-export function useBackendInstances(): 
HttpResponse<MerchantBackend.Instances.InstancesResponse> {
-  const { url } = useBackendContext()
-  const { token } = useInstanceContext();
-
-  const { data, error } = useSWR<MerchantBackend.Instances.InstancesResponse, 
SwrError>(['/private/instances', token, url], fetcher)
-
-  return { data, unauthorized: error?.status === 401, notfound: error?.status 
=== 404, error }
-}
-
-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: 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.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 interface InstanceOrderFilter {
-  paid?: YesOrNo;
-  refunded?: YesOrNo;
-  wired?: YesOrNo;
-  date?: Date;
-}
-
-export function useInstanceOrders(args: InstanceOrderFilter, updateFilter: 
(d:Date)=>void): 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 [pageBefore, setPageBefore] = useState(1)
-  const [pageAfter, setPageAfter] = useState(1)
-
-  const totalAfter = pageAfter * PAGE_SIZE;
-  const totalBefore = args?.date ? pageBefore * PAGE_SIZE : 0;
-
-  /**
-   * FIXME: this can be cleaned up a little
-   * 
-   * the logic of double query should be inside the orderFetch so from the 
hook perspective and cache
-   * is just one query and one error status
-   */
-  const { data:beforeData, error:beforeError } = 
useSWR<MerchantBackend.Orders.OrderHistory, SwrError>(
-    [`/private/orders`, token, url, args?.paid, args?.refunded, args?.wired, 
args?.date, totalBefore],
-    orderFetcher,
-  )
-  const { data:afterData, error:afterError } = 
useSWR<MerchantBackend.Orders.OrderHistory, SwrError>(
-    [`/private/orders`, token, url, args?.paid, args?.refunded, args?.wired, 
args?.date, -totalAfter],
-    orderFetcher,
-  )
-
-  //this will save last result
-  const [lastBefore, setLastBefore] = 
useState<MerchantBackend.Orders.OrderHistory | undefined>(undefined)
-  const [lastAfter, setLastAfter] = 
useState<MerchantBackend.Orders.OrderHistory | undefined>(undefined)
-  useEffect(() => {
-    if (afterData) setLastAfter(afterData)
-    if (beforeData) setLastBefore(beforeData)
-  }, [afterData, beforeData])
-
-  // this has problems when there are some ids missing
-  const isReachingEnd = afterData && afterData.orders.length < totalAfter;
-  const isReachingStart = (!args?.date) || (beforeData && 
beforeData.orders.length < totalBefore);
-
-  const orders = !beforeData || !afterData ? undefined : (beforeData || 
lastBefore).orders.slice().reverse().concat((afterData || lastAfter).orders)
-  const unauthorized = beforeError?.status === 401 || afterError?.status === 
401
-  const notfound =  beforeError?.status === 404 || afterError?.status === 404
-
-  const loadMore = () => {
-    if (!orders) return
-    if (orders.length < MAX_RESULT_SIZE) {
-      setPageAfter(pageAfter + 1)
-    } else { 
-      const from = 
afterData?.orders?.[afterData?.orders?.length-1]?.timestamp?.t_ms
-      if (from) updateFilter(new Date(from))
-    }
-  }
-
-  const loadMorePrev = () => {
-    if (!orders) return
-    if (orders.length < MAX_RESULT_SIZE) {
-      setPageBefore(pageBefore + 1)
-    } else {
-      const from = 
beforeData?.orders?.[beforeData?.orders?.length-1]?.timestamp?.t_ms
-      if (from) updateFilter(new Date(from))
-    }
-  }
-
-  return { data: orders ? {orders} : undefined, loadMorePrev, loadMore, 
isReachingEnd, isReachingStart, unauthorized, notfound, error: beforeError ? 
beforeError : afterError }
-}
-
-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 useBackendInstancesTestForAdmin(): 
HttpResponse<MerchantBackend.Instances.InstancesResponse> {
   const { url, token } = useBackendContext()
   interface Result {
diff --git a/packages/frontend/src/hooks/instance.ts 
b/packages/frontend/src/hooks/instance.ts
new file mode 100644
index 0000000..31709bc
--- /dev/null
+++ b/packages/frontend/src/hooks/instance.ts
@@ -0,0 +1,97 @@
+import { MerchantBackend } from '../declaration';
+import { useBackendContext, useInstanceContext } from '../context/backend';
+import { fetcher, HttpResponse, request, SwrError } from './backend';
+import useSWR, { mutate } from 'swr';
+
+
+interface InstanceAPI {
+  updateInstance: (data: 
MerchantBackend.Instances.InstanceReconfigurationMessage, a?: 
MerchantBackend.Instances.InstanceAuthConfigurationMessage) => Promise<void>;
+  deleteInstance: () => Promise<void>;
+  clearToken: () => Promise<void>;
+  setNewToken: (token: string) => Promise<void>;
+}
+
+export function useInstanceAPI(): InstanceAPI {
+  const { url: baseUrl, token: adminToken } = useBackendContext()
+  const { token, id, admin } = useInstanceContext()
+
+  const url = !admin ? baseUrl : `${baseUrl}/instances/${id}`
+
+  const updateInstance = async (instance: 
MerchantBackend.Instances.InstanceReconfigurationMessage, auth?: 
MerchantBackend.Instances.InstanceAuthConfigurationMessage): Promise<void> => {
+    await request(`${url}/private/`, {
+      method: 'patch',
+      token,
+      data: instance
+    })
+
+    if (auth) await request(`${url}/private/auth`, {
+      method: 'post',
+      token,
+      data: auth
+    })
+
+    if (adminToken) mutate(['/private/instances', adminToken, baseUrl], null)
+    mutate([`/private/`, token, url], null)
+  };
+
+  const deleteInstance = async (): Promise<void> => {
+    await request(`${url}/private/`, {
+      method: 'delete',
+      token: adminToken,
+    })
+
+    if (adminToken) mutate(['/private/instances', adminToken, baseUrl], null)
+    mutate([`/private/`, token, url], null)
+  }
+
+  const clearToken = async (): Promise<void> => {
+    await request(`${url}/private/auth`, {
+      method: 'post',
+      token,
+      data: { method: 'external' }
+    })
+
+    mutate([`/private/`, token, url], null)
+  }
+
+  const setNewToken = async (newToken: string): Promise<void> => {
+    await request(`${url}/private/auth`, {
+      method: 'post',
+      token,
+      data: { method: 'token', token: newToken }
+    })
+
+    mutate([`/private/`, token, url], null)
+  }
+
+  return { updateInstance, deleteInstance, setNewToken, clearToken }
+}
+
+
+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: 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.Instances.QueryInstancesResponse, 
SwrError>([`/private/`, token, url], fetcher)
+
+  return { data, unauthorized: error?.status === 401, notfound: error?.status 
=== 404, error }
+}
+
+export function useBackendInstances(): 
HttpResponse<MerchantBackend.Instances.InstancesResponse> {
+  const { url } = useBackendContext()
+  const { token } = useInstanceContext();
+
+  const { data, error } = useSWR<MerchantBackend.Instances.InstancesResponse, 
SwrError>(['/private/instances', token, url], fetcher)
+
+  return { data, unauthorized: error?.status === 401, notfound: error?.status 
=== 404, error }
+}
+
diff --git a/packages/frontend/src/hooks/order.ts 
b/packages/frontend/src/hooks/order.ts
new file mode 100644
index 0000000..2b51366
--- /dev/null
+++ b/packages/frontend/src/hooks/order.ts
@@ -0,0 +1,177 @@
+import { addHours, addSeconds, format } from 'date-fns';
+import { useEffect, useState } from 'preact/hooks';
+import useSWR from 'swr';
+import { useBackendContext, useInstanceContext } from '../context/backend';
+import { MerchantBackend } from '../declaration';
+import { MAX_RESULT_SIZE, PAGE_SIZE } from '../utils/constants';
+import { fetcher, HttpResponse, mutateAll, request, SwrError } from 
'./backend';
+
+export interface OrderAPI {
+  //FIXME: add OutOfStockResponse on 410
+  createOrder: (data: MerchantBackend.Orders.PostOrderRequest) => 
Promise<MerchantBackend.Orders.PostOrderResponse>;
+  forgetOrder: (id: string, data: MerchantBackend.Orders.ForgetRequest) => 
Promise<void>;
+  refundOrder: (id: string, data: MerchantBackend.Orders.RefundRequest) => 
Promise<MerchantBackend.Orders.MerchantRefundResponse>;
+  deleteOrder: (id: string) => Promise<void>;
+  getPaymentURL: (id: string) => Promise<string>;
+}
+
+type YesOrNo = 'yes' | 'no';
+
+
+export function orderFetcher(url: string, token: string, backend: string, 
paid?: YesOrNo, refunded?: YesOrNo, wired?: YesOrNo, searchDate?: Date, delta?: 
number) {
+  const newDate = searchDate && addHours(searchDate, 3) // remove this, locale
+  // if we are 
+  const newDatePlus1SecIfNeeded = delta && delta < 0 && newDate ? 
addSeconds(newDate, 1) : newDate
+  const date = newDatePlus1SecIfNeeded ? format(newDatePlus1SecIfNeeded, 
'yyyy-MM-dd HH:mm:ss') : undefined
+  return request(`${backend}${url}`, { token, params: { paid, refunded, wired, 
delta, date } })
+}
+
+
+export function useOrderAPI(): OrderAPI {
+  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
+    })
+
+    mutateAll(/@"\/private\/orders"@/)
+    return res
+  }
+  const refundOrder = async (orderId: string, data: 
MerchantBackend.Orders.RefundRequest): 
Promise<MerchantBackend.Orders.MerchantRefundResponse> => {
+    const res = await request(`${url}/private/orders/${orderId}/refund`, {
+      method: 'post',
+      token,
+      data
+    })
+
+    mutateAll(/@"\/private\/orders"@/)
+    return res
+  }
+
+  const forgetOrder = async (orderId: string, data: 
MerchantBackend.Orders.ForgetRequest): Promise<void> => {
+    await request(`${url}/private/orders/${orderId}/forget`, {
+      method: 'patch',
+      token,
+      data
+    })
+
+    mutateAll(/@"\/private\/orders"@/)
+  }
+  const deleteOrder = async (orderId: string): Promise<void> => {
+    await request(`${url}/private/orders/${orderId}`, {
+      method: 'delete',
+      token
+    })
+
+    mutateAll(/@"\/private\/orders"@/)
+  }
+
+  const getPaymentURL = async (orderId: string): Promise<string> => {
+    const data = await request(`${url}/private/orders/${orderId}`, {
+      method: 'get',
+      token
+    })
+    return data.taler_pay_uri || data.contract_terms?.fulfillment_url
+  }
+
+  return { createOrder, forgetOrder, deleteOrder, refundOrder, getPaymentURL }
+}
+
+export function useOrderDetails(oderId:string): 
HttpResponse<MerchantBackend.Orders.MerchantOrderStatusResponse> {
+  const { url: baseUrl } = useBackendContext();
+  const { token, id: instanceId, admin } = useInstanceContext();
+
+  const url = !admin ? baseUrl : `${baseUrl}/instances/${instanceId}`
+
+  const { data, error } = 
useSWR<MerchantBackend.Orders.MerchantOrderStatusResponse, 
SwrError>([`/private/orders/${oderId}`, token, url], fetcher)
+
+  return { data, unauthorized: error?.status === 401, notfound: error?.status 
=== 404, error }
+}
+
+export interface InstanceOrderFilter {
+  paid?: YesOrNo;
+  refunded?: YesOrNo;
+  wired?: YesOrNo;
+  date?: Date;
+}
+
+export function useInstanceOrders(args: InstanceOrderFilter, updateFilter: 
(d:Date)=>void): 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 [pageBefore, setPageBefore] = useState(1)
+  const [pageAfter, setPageAfter] = useState(1)
+
+  const totalAfter = pageAfter * PAGE_SIZE;
+  const totalBefore = args?.date ? pageBefore * PAGE_SIZE : 0;
+
+  /**
+   * FIXME: this can be cleaned up a little
+   * 
+   * the logic of double query should be inside the orderFetch so from the 
hook perspective and cache
+   * is just one query and one error status
+   */
+  const { data:beforeData, error:beforeError } = 
useSWR<MerchantBackend.Orders.OrderHistory, SwrError>(
+    [`/private/orders`, token, url, args?.paid, args?.refunded, args?.wired, 
args?.date, totalBefore],
+    orderFetcher,
+  )
+  const { data:afterData, error:afterError } = 
useSWR<MerchantBackend.Orders.OrderHistory, SwrError>(
+    [`/private/orders`, token, url, args?.paid, args?.refunded, args?.wired, 
args?.date, -totalAfter],
+    orderFetcher,
+  )
+
+  //this will save last result
+  const [lastBefore, setLastBefore] = 
useState<MerchantBackend.Orders.OrderHistory | undefined>(undefined)
+  const [lastAfter, setLastAfter] = 
useState<MerchantBackend.Orders.OrderHistory | undefined>(undefined)
+  useEffect(() => {
+    if (afterData) setLastAfter(afterData)
+    if (beforeData) setLastBefore(beforeData)
+  }, [afterData, beforeData])
+
+  // this has problems when there are some ids missing
+  const isReachingEnd = afterData && afterData.orders.length < totalAfter;
+  const isReachingStart = (!args?.date) || (beforeData && 
beforeData.orders.length < totalBefore);
+
+  const orders = !beforeData || !afterData ? undefined : (beforeData || 
lastBefore).orders.slice().reverse().concat((afterData || lastAfter).orders)
+  const unauthorized = beforeError?.status === 401 || afterError?.status === 
401
+  const notfound =  beforeError?.status === 404 || afterError?.status === 404
+
+  const loadMore = () => {
+    if (!orders) return
+    if (orders.length < MAX_RESULT_SIZE) {
+      setPageAfter(pageAfter + 1)
+    } else { 
+      const from = 
afterData?.orders?.[afterData?.orders?.length-1]?.timestamp?.t_ms
+      if (from) updateFilter(new Date(from))
+    }
+  }
+
+  const loadMorePrev = () => {
+    if (!orders) return
+    if (orders.length < MAX_RESULT_SIZE) {
+      setPageBefore(pageBefore + 1)
+    } else {
+      const from = 
beforeData?.orders?.[beforeData?.orders?.length-1]?.timestamp?.t_ms
+      if (from) updateFilter(new Date(from))
+    }
+  }
+
+  return { data: orders ? {orders} : undefined, loadMorePrev, loadMore, 
isReachingEnd, isReachingStart, unauthorized, notfound, error: beforeError ? 
beforeError : afterError }
+}
+
diff --git a/packages/frontend/src/hooks/product.ts 
b/packages/frontend/src/hooks/product.ts
new file mode 100644
index 0000000..1909a18
--- /dev/null
+++ b/packages/frontend/src/hooks/product.ts
@@ -0,0 +1,105 @@
+import { useEffect } from 'preact/hooks';
+import useSWR, { useSWRInfinite } from 'swr';
+import { useBackendContext, useInstanceContext } from '../context/backend';
+import { MerchantBackend } from '../declaration';
+import { fetcher, HttpResponse, mutateAll, request, SwrError } from 
'./backend';
+
+
+export interface ProductAPI {
+  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 useProductAPI(): ProductAPI {
+  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
+    });
+
+    mutateAll(/@"\/private\/products"@/);
+  };
+
+  const updateProduct = async (productId: string, data: 
MerchantBackend.Products.ProductPatchDetail): Promise<void> => {
+    await request(`${url}/private/products/${productId}`, {
+      method: 'patch',
+      token,
+      data
+    });
+
+    mutateAll(/@"\/private\/products"@/);
+  };
+
+  const deleteProduct = async (productId: string): Promise<void> => {
+    await request(`${url}/private/products/${productId}`, {
+      method: 'delete',
+      token,
+    });
+
+    mutateAll(/@"\/private\/products"@/);
+  };
+
+  const lockProduct = async (productId: string, data: 
MerchantBackend.Products.LockRequest): Promise<void> => {
+    await request(`${url}/private/products/${productId}/lock`, {
+      method: 'post',
+      token,
+      data
+    });
+
+    mutateAll(/@"\/private\/products"@/);
+  };
+
+  return { createProduct, updateProduct, deleteProduct, lockProduct };
+}
+
+
+export function useInstanceProducts(): 
HttpResponse<(MerchantBackend.Products.ProductDetail & { id: string })[]> {
+  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 list = useSWR<MerchantBackend.Products.InventorySummaryResponse, 
SwrError>([`/private/products`, token, url], fetcher);
+
+  const getKey = (pageIndex: number) => {
+    if (!list.data || !list.data.products.length) return null
+    return [`/private/products/${list.data.products[pageIndex].product_id}`, 
token, url]
+  }
+
+  const res = useSWRInfinite<MerchantBackend.Products.ProductDetail, 
SwrError>(getKey, fetcher)
+  const { data, error, setSize, isValidating }  = res
+
+  useEffect(() => {
+    if (list.data && list.data?.products?.length > 0) {
+      setSize(list.data?.products?.length)
+    }
+  }, [list.data])
+
+  if (list.data && list.data.products.length === 0) {
+    return { data: [], unauthorized: false, notfound: false }
+  }
+
+
+  const dataWithId = !error ? data?.map((d, i) => ({ ...d, id: 
list.data?.products?.[i]?.product_id || '' })) : undefined
+  
+  return { data: dataWithId, unauthorized: error?.status === 401, notfound: 
error?.status === 404, error };
+}
+
diff --git a/packages/frontend/src/hooks/tips.ts 
b/packages/frontend/src/hooks/tips.ts
new file mode 100644
index 0000000..3500c00
--- /dev/null
+++ b/packages/frontend/src/hooks/tips.ts
@@ -0,0 +1,86 @@
+import { MerchantBackend } from '../declaration';
+import { useBackendContext, useInstanceContext } from '../context/backend';
+import { request, mutateAll, HttpResponse, SwrError, fetcher } from 
'./backend';
+import useSWR from 'swr';
+
+
+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
+    });
+
+    mutateAll(/@"\/private\/reserves"@/);
+    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
+    });
+
+    mutateAll(/@"\/private\/reserves"@/);
+    return res;
+  };
+
+  const authorizeTip = async (data: MerchantBackend.Tips.TipCreateRequest): 
Promise<MerchantBackend.Tips.TipCreateConfirmation> => {
+    const res = await request(`${url}/private/tips`, {
+      method: 'post',
+      token,
+      data
+    });
+
+    mutateAll(/@"\/private\/reserves"@/);
+    return res;
+  };
+
+  const deleteReserve = async (pub: string): Promise<void> => {
+    await request(`${url}/private/reserves/${pub}`, {
+      method: 'delete',
+      token,
+    });
+
+    mutateAll(/@"\/private\/reserves"@/);
+  };
+
+
+  return { createReserve, authorizeTip, authorizeTipReserve, deleteReserve };
+}
+
+export 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 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 }
+}
+
diff --git a/packages/frontend/src/hooks/transfer.ts 
b/packages/frontend/src/hooks/transfer.ts
new file mode 100644
index 0000000..4984cc6
--- /dev/null
+++ b/packages/frontend/src/hooks/transfer.ts
@@ -0,0 +1,54 @@
+import { MerchantBackend } from '../declaration';
+import { useBackendContext, useInstanceContext } from '../context/backend';
+import { request, mutateAll, HttpResponse, SwrError } from './backend';
+import useSWR from 'swr';
+
+function transferFetcher(url: string, token: string, backend: string) {
+  return request(`${backend}${url}`, { token, params: { payto_uri: '' } })
+}
+
+
+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
+    });
+
+    mutateAll(/@"\/private\/transfers"@/);
+    return res;
+  };
+
+  return { informTransfer };
+}
+
+export interface TransferMutateAPI {
+  informTransfer: (data: MerchantBackend.Transfers.TransferInformation) => 
Promise<MerchantBackend.Transfers.MerchantTrackTransferResponse>;
+}
+
+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 }
+}
+
+
diff --git a/packages/frontend/src/messages/en.po 
b/packages/frontend/src/messages/en.po
index 514b4b4..245df6c 100644
--- a/packages/frontend/src/messages/en.po
+++ b/packages/frontend/src/messages/en.po
@@ -352,3 +352,24 @@ msgstr "Max Wire Fee"
 
 msgid "fields.instance.fee.label"
 msgstr "Fee"
+
+msgid "fields.product.image.label"
+msgstr "Image"
+
+msgid "fields.product.description.label"
+msgstr "Description"
+
+msgid "fields.product.sell.label"
+msgstr "Sell"
+
+msgid "fields.product.taxes.label"
+msgstr "Taxes"
+
+msgid "fields.product.profit.label"
+msgstr "Profit"
+
+msgid "fields.product.stock.label"
+msgstr "Stock"
+
+msgid "fields.product.sold.label"
+msgstr "Sold"
\ No newline at end of file
diff --git a/packages/frontend/src/paths/admin/create/index.tsx 
b/packages/frontend/src/paths/admin/create/index.tsx
index e832f3a..c8aa992 100644
--- a/packages/frontend/src/paths/admin/create/index.tsx
+++ b/packages/frontend/src/paths/admin/create/index.tsx
@@ -21,7 +21,7 @@ import { Fragment, h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { NotificationCard } from "../../../components/menu";
 import { MerchantBackend } from "../../../declaration";
-import { useAdminMutateAPI } from "../../../hooks/backend";
+import { useAdminAPI } from "../../../hooks/admin";
 import { Notification } from "../../../utils/types";
 import { CreatePage } from "./CreatePage";
 import { InstanceCreatedSuccessfully } from "./InstanceCreatedSuccessfully";
@@ -35,7 +35,7 @@ interface Props {
 export type Entity = MerchantBackend.Instances.InstanceConfigurationMessage;
 
 export default function Create({ onBack, onConfirm, onError, forceId }: 
Props): VNode {
-  const { createInstance } = useAdminMutateAPI();
+  const { createInstance } = useAdminAPI();
   const [notif, setNotif] = useState<Notification | undefined>(undefined)
   const [createdOk, setCreatedOk] = useState<Entity | undefined>(undefined);
 
diff --git a/packages/frontend/src/paths/admin/list/index.tsx 
b/packages/frontend/src/paths/admin/list/index.tsx
index a96c5b7..bbc094f 100644
--- a/packages/frontend/src/paths/admin/list/index.tsx
+++ b/packages/frontend/src/paths/admin/list/index.tsx
@@ -21,12 +21,14 @@
 
 import { Fragment, h, VNode } from 'preact';
 import { View } from './View';
-import { SwrError, useAdminMutateAPI, useBackendInstances } from 
'../../../hooks/backend';
+import { SwrError } from '../../../hooks/backend';
+import { useAdminAPI } from "../../../hooks/admin";
 import { useState } from 'preact/hooks';
 import { MerchantBackend } from '../../../declaration';
 import { Notification } from '../../../utils/types';
 import { DeleteModal } from '../../../components/modal';
 import { Loading } from '../../../components/exception/loading';
+import { useBackendInstances } from '../../../hooks/instance';
 
 interface Props {
   // pushNotification: (n: Notification) => void;
@@ -40,7 +42,7 @@ interface Props {
 export default function Instances({ onUnauthorized, onLoadError, onCreate, 
onUpdate }: Props): VNode {
   const result = useBackendInstances()
   const [deleting, setDeleting] = useState<MerchantBackend.Instances.Instance 
| null>(null)
-  const { deleteInstance } = useAdminMutateAPI()
+  const { deleteInstance } = useAdminAPI()
 
   if (result.unauthorized) return onUnauthorized()
   if (!result.data) {
diff --git a/packages/frontend/src/paths/instance/details/index.tsx 
b/packages/frontend/src/paths/instance/details/index.tsx
index 5771cac..b212018 100644
--- a/packages/frontend/src/paths/instance/details/index.tsx
+++ b/packages/frontend/src/paths/instance/details/index.tsx
@@ -16,11 +16,11 @@
 import { Fragment, h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { useInstanceContext } from "../../../context/backend";
-import { Notification } from "../../../utils/types";
-import { useInstanceDetails, useInstanceMutateAPI, SwrError } from 
"../../../hooks/backend";
+import { SwrError } from "../../../hooks/backend";
 import { DetailPage } from "./DetailPage";
 import { DeleteModal } from "../../../components/modal";
 import { Loading } from "../../../components/exception/loading";
+import { useInstanceAPI, useInstanceDetails } from "../../../hooks/instance";
 
 interface Props {
   onUnauthorized: () => VNode;
@@ -35,7 +35,7 @@ export default function Detail({ onUpdate, onLoadError, 
onUnauthorized, onDelete
   const result = useInstanceDetails()
   const [deleting, setDeleting] = useState<boolean>(false)
 
-  const { deleteInstance } = useInstanceMutateAPI()
+  const { deleteInstance } = useInstanceAPI()
 
   if (result.unauthorized) return onUnauthorized()
   if (result.notfound) return onNotFound();
diff --git 
a/packages/frontend/src/paths/instance/orders/create/OrderCreatedSuccessfully.tsx
 
b/packages/frontend/src/paths/instance/orders/create/OrderCreatedSuccessfully.tsx
index a477e82..2dbf916 100644
--- 
a/packages/frontend/src/paths/instance/orders/create/OrderCreatedSuccessfully.tsx
+++ 
b/packages/frontend/src/paths/instance/orders/create/OrderCreatedSuccessfully.tsx
@@ -16,7 +16,7 @@
  import { h } from "preact";
 import { useEffect, useState } from "preact/hooks";
 import { CreatedSuccessfully } from 
"../../../../components/notifications/CreatedSuccessfully";
-import { useOrderMutateAPI } from "../../../../hooks/backend";
+import { useOrderAPI } from "../../../../hooks/order";
 import { Entity } from "./index";
 
 interface Props {
@@ -26,7 +26,7 @@ interface Props {
 }
 
 export function OrderCreatedSuccessfully({ entity, onConfirm, onCreateAnother 
}: Props) {
-  const { getPaymentURL } = useOrderMutateAPI()
+  const { getPaymentURL } = useOrderAPI()
   const [url, setURL] = useState<string | undefined>(undefined)
   
   useEffect(() => {
diff --git a/packages/frontend/src/paths/instance/orders/create/index.tsx 
b/packages/frontend/src/paths/instance/orders/create/index.tsx
index 3d1dce0..9f323df 100644
--- a/packages/frontend/src/paths/instance/orders/create/index.tsx
+++ b/packages/frontend/src/paths/instance/orders/create/index.tsx
@@ -23,7 +23,7 @@ import { Fragment, h, VNode } from 'preact';
 import { useState } from 'preact/hooks';
 import { NotificationCard } from '../../../../components/menu';
 import { MerchantBackend } from '../../../../declaration';
-import { useOrderMutateAPI } from '../../../../hooks/backend';
+import { useOrderAPI } from '../../../../hooks/order';
 import { Notification } from '../../../../utils/types';
 import { CreatePage } from './CreatePage';
 import { OrderCreatedSuccessfully } from './OrderCreatedSuccessfully';
@@ -37,7 +37,7 @@ interface Props {
   onConfirm: () => void;
 }
 export default function ({ onConfirm, onBack }: Props): VNode {
-  const { createOrder } = useOrderMutateAPI()
+  const { createOrder } = useOrderAPI()
   const [notif, setNotif] = useState<Notification | undefined>(undefined)
   const [createdOk, setCreatedOk] = useState<Entity | undefined>(undefined);
 
@@ -46,6 +46,12 @@ export default function ({ onConfirm, onBack }: Props): 
VNode {
   }
 
   return <Fragment>
+    <NotificationCard notification={{
+      message: 'DEMO',
+      type: 'WARN',
+      description: 'this can be created as a popup or be expanded with more 
options'
+    }} />
+    
     <NotificationCard notification={notif} />
 
     <CreatePage
diff --git a/packages/frontend/src/paths/instance/orders/details/index.tsx 
b/packages/frontend/src/paths/instance/orders/details/index.tsx
index 209d1ce..bad41d2 100644
--- a/packages/frontend/src/paths/instance/orders/details/index.tsx
+++ b/packages/frontend/src/paths/instance/orders/details/index.tsx
@@ -16,7 +16,8 @@
 import { Fragment, h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { Loading } from "../../../../components/exception/loading";
-import { SwrError, useOrderDetails, useOrderMutateAPI } from 
"../../../../hooks/backend";
+import { SwrError } from "../../../../hooks/backend";
+import { useOrderDetails, useOrderAPI } from "../../../../hooks/order";
 import { Notification } from "../../../../utils/types";
 import { DetailPage } from "./DetailPage";
 
@@ -30,7 +31,7 @@ export interface Props {
 }
 
 export default function Update({ oid, onBack, onLoadError, onNotFound, 
onUnauthorized }: Props): VNode {
-  const { refundOrder } = useOrderMutateAPI();
+  const { refundOrder } = useOrderAPI();
   const details = useOrderDetails(oid)
   const [notif, setNotif] = useState<Notification | undefined>(undefined)
 
diff --git a/packages/frontend/src/paths/instance/orders/list/index.tsx 
b/packages/frontend/src/paths/instance/orders/list/index.tsx
index f31100b..c4c8b25 100644
--- a/packages/frontend/src/paths/instance/orders/list/index.tsx
+++ b/packages/frontend/src/paths/instance/orders/list/index.tsx
@@ -21,15 +21,15 @@
 
 import { h, VNode } from 'preact';
 import { useState } from 'preact/hooks';
-import { useConfigContext } from '../../../../context/backend';
 import { MerchantBackend } from '../../../../declaration';
-import { InstanceOrderFilter, SwrError, useInstanceOrders, useOrderMutateAPI, 
useProductMutateAPI } from '../../../../hooks/backend';
+import { SwrError } from '../../../../hooks/backend';
 import { CardTable } from './Table';
 import { format } from 'date-fns';
 import { DatePicker } from '../../../../components/form/DatePicker';
 import { NotificationCard } from '../../../../components/menu';
 import { Notification } from '../../../../utils/types';
 import { copyToClipboard } from '../../../../utils/functions';
+import { InstanceOrderFilter, useInstanceOrders, useOrderAPI } from 
'../../../../hooks/order';
 
 interface Props {
   onUnauthorized: () => VNode;
@@ -47,7 +47,7 @@ export default function ({ onUnauthorized, onLoadError, 
onCreate, onSelect, onNo
   const setNewDate = (date: Date) => setFilter(prev => ({ ...prev, date }))
 
   const result = useInstanceOrders(filter, setNewDate)
-  const { createOrder, refundOrder, getPaymentURL } = useOrderMutateAPI()
+  const { createOrder, refundOrder, getPaymentURL } = useOrderAPI()
   // const { currency } = useConfigContext()
 
   let instances: (MerchantBackend.Orders.OrderHistoryEntry & { id: string })[];
diff --git a/packages/frontend/src/paths/instance/products/list/Table.tsx 
b/packages/frontend/src/paths/instance/products/list/Table.tsx
index c7c59c2..55fb572 100644
--- a/packages/frontend/src/paths/instance/products/list/Table.tsx
+++ b/packages/frontend/src/paths/instance/products/list/Table.tsx
@@ -23,9 +23,10 @@ import { h, VNode } from "preact"
 import { Message } from "preact-messages"
 import { StateUpdater, useEffect, useState } from "preact/hooks"
 import { MerchantBackend } from "../../../../declaration"
+import { useProductAPI } from "../../../../hooks/product"
 import { Actions, buildActions } from "../../../../utils/table"
 
-type Entity = MerchantBackend.Products.InventoryEntry & { id: string }
+type Entity = MerchantBackend.Products.ProductDetail & { id: string }
 
 interface Props {
   instances: Entity[];
@@ -97,43 +98,56 @@ function toggleSelected<T>(id: T): (prev: T[]) => T[] {
 }
 
 function Table({ rowSelection, rowSelectionHandler, instances, onUpdate, 
onDelete }: TableProps): VNode {
+  const { } = useProductAPI()
   return (
     <div class="table-container">
-    <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
-      <thead>
-        <tr>
-          <th class="is-checkbox-cell">
-            <label class="b-checkbox checkbox">
-              <input type="checkbox" checked={rowSelection.length === 
instances.length} onClick={(): void => rowSelectionHandler(rowSelection.length 
=== instances.length ? [] : instances.map(i => i.id))} />
-              <span class="check" />
-            </label>
-          </th>
-          <th><Message id="fields.product.id.label" /></th>
-          <th />
-        </tr>
-      </thead>
-      <tbody>
-        {instances.map(i => {
-          return <tr>
-            <td class="is-checkbox-cell">
+      <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
+        <thead>
+          <tr>
+            <th class="is-checkbox-cell">
               <label class="b-checkbox checkbox">
-                <input type="checkbox" checked={rowSelection.indexOf(i.id) != 
-1} onClick={(): void => rowSelectionHandler(toggleSelected(i.id))} />
+                <input type="checkbox" checked={rowSelection.length === 
instances.length} onClick={(): void => rowSelectionHandler(rowSelection.length 
=== instances.length ? [] : instances.map(i => i.id))} />
                 <span class="check" />
               </label>
-            </td>
-            <td onClick={(): void => onUpdate(i.id)} style={{ cursor: 
'pointer' }} >{i.id}</td>
-            <td class="is-actions-cell right-sticky">
-              <div class="buttons is-right">
-                <button class="button is-small is-danger jb-modal" 
type="button" onClick={(): void => onDelete(i)}>
-                  Delete
-                </button>
-              </div>
-            </td>
+            </th>
+            <th><Message id="fields.product.image.label" /></th>
+            <th><Message id="fields.product.description.label" /></th>
+            <th><Message id="fields.product.sell.label" /></th>
+            <th><Message id="fields.product.taxes.label" /></th>
+            <th><Message id="fields.product.profit.label" /></th>
+            <th><Message id="fields.product.stock.label" /></th>
+            <th><Message id="fields.product.sold.label" /></th>
+            <th />
           </tr>
-        })}
+        </thead>
+        <tbody>
+          {instances.map(i => {
+            return <tr>
+              <td class="is-checkbox-cell">
+                <label class="b-checkbox checkbox">
+                  <input type="checkbox" checked={rowSelection.indexOf(i.id) 
!= -1} onClick={(): void => rowSelectionHandler(toggleSelected(i.id))} />
+                  <span class="check" />
+                </label>
+              </td>
+              <td onClick={(): void => onUpdate(i.id)} style={{ cursor: 
'pointer' }} >{JSON.stringify(i.image)}</td>
+              <td onClick={(): void => onUpdate(i.id)} style={{ cursor: 
'pointer' }} >{i.description}</td>
+              <td onClick={(): void => onUpdate(i.id)} style={{ cursor: 
'pointer' }} >{i.price} / {i.unit}</td>
+              <td onClick={(): void => onUpdate(i.id)} style={{ cursor: 
'pointer' }} >{sum(i.taxes)}</td>
+              <td onClick={(): void => onUpdate(i.id)} style={{ cursor: 
'pointer' }} >{difference(i.price, sum(i.taxes))}</td>
+              <td onClick={(): void => onUpdate(i.id)} style={{ cursor: 
'pointer' }} >{i.total_stock} {i.unit} ({i.next_restock?.t_ms})</td>
+              <td onClick={(): void => onUpdate(i.id)} style={{ cursor: 
'pointer' }} >{i.total_sold} {i.unit}</td>
+              <td class="is-actions-cell right-sticky">
+                <div class="buttons is-right">
+                  <button class="button is-small is-danger jb-modal" 
type="button" onClick={(): void => onDelete(i)}>
+                    Delete
+                </button>
+                </div>
+              </td>
+            </tr>
+          })}
 
-      </tbody>
-    </table>
+        </tbody>
+      </table>
     </div>)
 }
 
@@ -147,3 +161,13 @@ function EmptyTable(): VNode {
 }
 
 
+function difference(price: string, tax: number) {
+  if (!tax) return price;
+  const ps = price.split(':')
+  const p = parseInt(ps[1])
+  ps[1] = `${p - tax}`
+  return ps.join(':')
+}
+function sum(taxes: MerchantBackend.Tax[]) {
+  return taxes.reduce((p, c) => p + parseInt(c.tax.split(':')[1]), 0)
+}
\ No newline at end of file
diff --git a/packages/frontend/src/paths/instance/products/list/index.tsx 
b/packages/frontend/src/paths/instance/products/list/index.tsx
index 3772be3..5578670 100644
--- a/packages/frontend/src/paths/instance/products/list/index.tsx
+++ b/packages/frontend/src/paths/instance/products/list/index.tsx
@@ -21,12 +21,15 @@
 
 import { h, VNode } from 'preact';
 import { create } from 'yup/lib/Reference';
-import { SwrError, useInstanceProducts, useProductMutateAPI } from 
'../../../../hooks/backend';
+import { SwrError } from '../../../../hooks/backend';
+import { useProductAPI } from "../../../../hooks/product";
 import { CardTable } from './Table';
 import logo from '../../../../assets/logo.jpeg';
 import { useConfigContext } from '../../../../context/backend';
 import { MerchantBackend } from '../../../../declaration';
 import { Loading } from '../../../../components/exception/loading';
+import { useInstanceProducts } from '../../../../hooks/product';
+import { NotificationCard } from '../../../../components/menu';
 
 interface Props {
   onUnauthorized: () => VNode;
@@ -35,7 +38,7 @@ interface Props {
 }
 export default function ({ onUnauthorized, onLoadError, onNotFound }: Props): 
VNode {
   const result = useInstanceProducts()
-  const { createProduct, deleteProduct } = useProductMutateAPI()
+  const { createProduct, deleteProduct } = useProductAPI()
   const { currency } = useConfigContext()
 
   if (result.unauthorized) return onUnauthorized()
@@ -46,7 +49,15 @@ export default function ({ onUnauthorized, onLoadError, 
onNotFound }: Props): VN
     return <Loading />
   }
   return <section class="section is-main-section">
-    <CardTable instances={result.data.products.map(o => ({ ...o, id: 
o.product_id }))}
+    <NotificationCard notification={{
+      message: 'DEMO',
+      type:'WARN',
+      description:<ul>
+        <li>image return object when api says string</li>
+      </ul>
+    }} />
+
+    <CardTable instances={result.data}
       onCreate={() => createProduct({
         product_id: `${Math.floor(Math.random() * 999999 + 1)}`,
         address: {},
@@ -61,7 +72,7 @@ export default function ({ onUnauthorized, onLoadError, 
onNotFound }: Props): VN
         unit: 'units',
         next_restock: { t_ms: 'never' }, //WTF? should not be required
       })}
-      onDelete={(prod: MerchantBackend.Products.InventoryEntry) => 
deleteProduct(prod.product_id)}
+      onDelete={(prod: (MerchantBackend.Products.ProductDetail & {id:string})) 
=> deleteProduct(prod.id)}
       onUpdate={() => null}
     />
   </section>
diff --git a/packages/frontend/src/paths/instance/tips/list/index.tsx 
b/packages/frontend/src/paths/instance/tips/list/index.tsx
index b4a11e1..50d02e4 100644
--- a/packages/frontend/src/paths/instance/tips/list/index.tsx
+++ b/packages/frontend/src/paths/instance/tips/list/index.tsx
@@ -23,7 +23,8 @@ import { h, VNode } from 'preact';
 import { Loading } from '../../../../components/exception/loading';
 import { useConfigContext } from '../../../../context/backend';
 import { MerchantBackend } from '../../../../declaration';
-import { SwrError, useInstanceMutateAPI, useInstanceTips, useTipsMutateAPI } 
from '../../../../hooks/backend';
+import { SwrError } from '../../../../hooks/backend';
+import { useInstanceTips, useTipsMutateAPI } from "../../../../hooks/tips";
 import { CardTable } from './Table';
 
 interface Props {
diff --git a/packages/frontend/src/paths/instance/transfers/list/index.tsx 
b/packages/frontend/src/paths/instance/transfers/list/index.tsx
index b7f1b45..e8e20f4 100644
--- a/packages/frontend/src/paths/instance/transfers/list/index.tsx
+++ b/packages/frontend/src/paths/instance/transfers/list/index.tsx
@@ -22,7 +22,8 @@
 import { h, VNode } from 'preact';
 import { Loading } from '../../../../components/exception/loading';
 import { useConfigContext } from '../../../../context/backend';
-import { SwrError, useInstanceTransfers, useTransferMutateAPI } from 
'../../../../hooks/backend';
+import { SwrError } from '../../../../hooks/backend';
+import { useInstanceTransfers, useTransferMutateAPI } from 
"../../../../hooks/transfer";
 import { CardTable } from './Table';
 
 interface Props {
diff --git a/packages/frontend/src/paths/instance/update/index.tsx 
b/packages/frontend/src/paths/instance/update/index.tsx
index 3e76172..41c450e 100644
--- a/packages/frontend/src/paths/instance/update/index.tsx
+++ b/packages/frontend/src/paths/instance/update/index.tsx
@@ -19,7 +19,8 @@ import { Loading } from 
"../../../components/exception/loading";
 import { UpdateTokenModal } from "../../../components/modal";
 import { useInstanceContext } from "../../../context/backend";
 import { MerchantBackend } from "../../../declaration";
-import { SwrError, useInstanceDetails, useInstanceMutateAPI } from 
"../../../hooks/backend";
+import { SwrError } from "../../../hooks/backend";
+import { useInstanceAPI, useInstanceDetails } from "../../../hooks/instance";
 import { UpdatePage } from "./UpdatePage";
 
 export interface Props {
@@ -34,7 +35,7 @@ export interface Props {
 }
 
 export default function Update({ onBack, onConfirm, onLoadError, onNotFound, 
onUpdateError, onUnauthorized }: Props): VNode {
-  const { updateInstance } = useInstanceMutateAPI();
+  const { updateInstance } = useInstanceAPI();
   const details = useInstanceDetails()
 
   if (details.unauthorized) return onUnauthorized()

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