gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant-backoffice] branch master updated: better error handling


From: gnunet
Subject: [taler-merchant-backoffice] branch master updated: better error handling
Date: Fri, 02 Apr 2021 22:23:57 +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 bf8b800  better error handling
bf8b800 is described below

commit bf8b800bf1ee2fdb206a1ecfaba59a1f0031b55e
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Fri Apr 2 17:23:10 2021 -0300

    better error handling
---
 CHANGELOG.md                                       |   2 +
 packages/frontend/src/ApplicationReadyRoutes.tsx   |  80 +++-----
 packages/frontend/src/InstanceRoutes.tsx           |  47 ++---
 packages/frontend/src/declaration.d.ts             |  39 ++++
 packages/frontend/src/hooks/backend.ts             | 224 +++++++++++++++------
 packages/frontend/src/hooks/instance.ts            |  19 +-
 packages/frontend/src/hooks/order.ts               | 137 +++++++------
 packages/frontend/src/hooks/product.ts             |  39 ++--
 packages/frontend/src/hooks/tips.ts                |  49 +++--
 packages/frontend/src/hooks/transfer.ts            |  25 +--
 packages/frontend/src/index.tsx                    |  61 +++---
 packages/frontend/src/messages/en.po               |   9 +-
 .../frontend/src/paths/admin/create/CreatePage.tsx |   6 +-
 packages/frontend/src/paths/admin/create/index.tsx |   4 +-
 packages/frontend/src/paths/admin/list/index.tsx   |  17 +-
 .../frontend/src/paths/instance/details/index.tsx  |  15 +-
 .../orders/create/OrderCreatedSuccessfully.tsx     |   4 +-
 .../src/paths/instance/orders/create/index.tsx     |   2 +-
 .../src/paths/instance/orders/details/index.tsx    |  24 ++-
 .../src/paths/instance/orders/list/index.tsx       |  34 +---
 .../src/paths/instance/products/list/index.tsx     |  22 +-
 .../src/paths/instance/tips/list/index.tsx         |  15 +-
 .../src/paths/instance/transfers/list/index.tsx    |  16 +-
 .../frontend/src/paths/instance/update/index.tsx   |  19 +-
 packages/frontend/src/utils/constants.ts           |   2 +-
 25 files changed, 521 insertions(+), 390 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 082c849..ee6c160 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,6 +28,8 @@ and this project adheres to [Semantic 
Versioning](https://semver.org/spec/v2.0.0
  - order id field to go
 
 frontend, too many redirects
+BUGS TEST CASES:
+https://git.taler.net/anastasis.git/tree/src/cli/test_anastasis_reducer_enter_secret.sh
 
  - navigation to another instance should not do full refresh
  - cleanup instance and token management, because code is a mess and can be 
refactored 
diff --git a/packages/frontend/src/ApplicationReadyRoutes.tsx 
b/packages/frontend/src/ApplicationReadyRoutes.tsx
index a4f3f4f..6644118 100644
--- a/packages/frontend/src/ApplicationReadyRoutes.tsx
+++ b/packages/frontend/src/ApplicationReadyRoutes.tsx
@@ -36,67 +36,52 @@ export function ApplicationReadyRoutes(): VNode {
     changeBackend(url);
     if (token) updateToken(token);
   };
-  const list = useBackendInstancesTestForAdmin()
+  const result = useBackendInstancesTestForAdmin()
 
   const clearTokenAndGoToRoot = () => {
     clearAllTokens();
     route('/')
   }
 
-  if (!list.data) {
-    if (list.unauthorized) {
-      return <Fragment>
-        <NotYetReadyAppMenu title="Login"
-          onLogout={clearTokenAndGoToRoot}
-        />
-        <NotificationCard notification={{
-          message: i18n`Access denied`,
-          description: i18n`Check your token is valid`,
-          type: 'ERROR'
-        }}
-        />
-        <LoginPage onConfirm={updateLoginStatus} />
-      </Fragment>
-    }
-    if (list.notfound) {
-      const path = new URL(backendURL).pathname
-      const match = INSTANCE_ID_LOOKUP.exec(path)
-      if (!match || !match[1]) {
-        // this should be rare because
-        // query to /config is ok but the URL
-        // doest not match with our pattern
-        return <Fragment>
-          <NotYetReadyAppMenu title="Error" onLogout={clearTokenAndGoToRoot} />
-          <NotificationCard notification={{
-            message: i18n`Couldn't access the server`,
-            description: i18n`Could not infer instance id from url 
${backendURL}`,
-            type: 'ERROR',
-          }}
-          />
-          <LoginPage onConfirm={updateLoginStatus} />
-        </Fragment>
-      }
+  if (result.clientError && result.isUnauthorized) {
+    return <Fragment>
+      <NotYetReadyAppMenu title="Login" onLogout={clearTokenAndGoToRoot} />
+      <NotificationCard notification={{
+        message: i18n`Access denied`,
+        description: i18n`Check your token is valid`,
+        type: 'ERROR'
+      }}
+      />
+      <LoginPage onConfirm={updateLoginStatus} />
+    </Fragment>
+  }
 
-      return <Fragment>
-        <Menu instance={match[1]} onLogout={clearTokenAndGoToRoot} />
-        <InstanceRoutes id={match[1]} />
-      </Fragment>
-    }
 
-    if (list.error) {
+  if (result.loading) return <NotYetReadyAppMenu title="Loading..." />
+
+  if (!result.ok) {
+    const path = new URL(backendURL).pathname
+    const match = INSTANCE_ID_LOOKUP.exec(path)
+    if (!match || !match[1]) {
+      // this should be rare because
+      // query to /config is ok but the URL
+      // doest not match with our pattern
       return <Fragment>
-        <NotYetReadyAppMenu title="Error" />
+        <NotYetReadyAppMenu title="Error" onLogout={clearTokenAndGoToRoot} />
         <NotificationCard notification={{
-          message: i18n`Couldn't access the server`,
-          description: list.error.message,
-          type: 'ERROR'
-        }} />
+          message: i18n`Couldn't access the server.`,
+          description: i18n`Could not infer instance id from url 
${backendURL}`,
+          type: 'ERROR',
+        }}
+        />
         <LoginPage onConfirm={updateLoginStatus} />
       </Fragment>
     }
 
-    // is loading
-    return <NotYetReadyAppMenu title="Loading..." />
+    return <Fragment>
+      <Menu instance={match[1]} onLogout={clearTokenAndGoToRoot} />
+      <InstanceRoutes id={match[1]} />
+    </Fragment>
   }
 
   let instance
@@ -106,6 +91,7 @@ export function ApplicationReadyRoutes(): VNode {
   } finally {
     if (!instance) instance = 'default'
   }
+
   return <Fragment>
     <Menu instance={instance} admin onLogout={clearTokenAndGoToRoot} />
     <InstanceRoutes admin id={instance} />
diff --git a/packages/frontend/src/InstanceRoutes.tsx 
b/packages/frontend/src/InstanceRoutes.tsx
index fb8e0ac..afffd51 100644
--- a/packages/frontend/src/InstanceRoutes.tsx
+++ b/packages/frontend/src/InstanceRoutes.tsx
@@ -26,7 +26,7 @@ import { useMessageTemplate } from 'preact-messages';
 import { createHashHistory } from 'history';
 import { useBackendDefaultToken, useBackendInstanceToken } from './hooks';
 import { InstanceContextProvider, useBackendContext } from './context/backend';
-import { SwrError } from "./hooks/backend";
+import { HttpError, HttpResponseServerError, RequestInfo, SwrError } from 
"./hooks/backend";
 // import { Notification } from './utils/types';
 
 import LoginPage from './paths/login';
@@ -53,6 +53,7 @@ import InstanceListPage from './paths/admin/list';
 import InstanceCreatePage from "./paths/admin/create";
 import { NotificationCard } from './components/menu';
 import { Loading } from './components/exception/loading';
+import { MerchantBackend } from './declaration';
 
 export enum InstancePaths {
   // details = '/',
@@ -109,8 +110,8 @@ export function InstanceRoutes({ id, admin }: Props): VNode 
{
 
   const value = useMemo(() => ({ id, token, admin }), [id, token])
 
-  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' }} />
+  const LoginPageServerError = (error: HttpError) => <Fragment>
+    <NotificationCard notification={{ message: `Server reported a problem: 
HTTP status #${error.status}`, description: `Got message: ${error.message} 
from: ${error.info?.url}`, type: 'ERROR' }} />
     <LoginPage onConfirm={updateLoginStatus} />
   </Fragment>
 
@@ -120,15 +121,15 @@ export function InstanceRoutes({ id, admin }: Props): 
VNode {
   </Fragment>
 
   function IfAdminCreateDefaultOr<T>(Next: FunctionComponent<any>) {
-    return (props?:T)=> {
-      if (admin) {
+    return (props?: T) => {
+      if (admin && id === 'default') {
         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={() => {
+          <InstanceCreatePage forceId="default" onConfirm={() => {
             route(AdminPaths.list_instances)
           }} />
         </Fragment>
@@ -161,7 +162,6 @@ export function InstanceRoutes({ id, admin }: Props): VNode 
{
         <Route path={AdminPaths.new_instance} component={InstanceCreatePage}
           onBack={() => route(AdminPaths.list_instances)}
           onConfirm={() => { route(AdminPaths.list_instances); }}
-          onError={LoginPageServerError}
         />
       }
 
@@ -169,7 +169,8 @@ export function InstanceRoutes({ id, admin }: Props): VNode 
{
         <Route path={AdminPaths.update_instance} 
component={AdminInstanceUpdatePage}
           onBack={() => route(AdminPaths.list_instances)}
           onConfirm={() => { route(AdminPaths.list_instances); }}
-          onLoadError={LoginPageServerError}
+          onUpdateError={(e: Error) => { }}
+          onNotFound={NotFoundPage}
         />
       }
 
@@ -177,12 +178,12 @@ export function InstanceRoutes({ id, admin }: Props): 
VNode {
        * Update instance page
        */}
       <Route path={InstancePaths.update} component={InstanceUpdatePage}
-        onUnauthorized={LoginPageAccessDenied}
-        onLoadError={IfAdminCreateDefaultOr(LoginPageServerError)}
+        onBack={() => { route(`/`); }}
+        onConfirm={() => { route(`/`); }}
+        onUpdateError={(e: Error) => { }}
         onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
-        onBack={() => {route(`/`);}}
-        onConfirm={() => {route(`/`);}}
-        onUpdateError={(e: Error) => {}}
+        onUnauthorized={LoginPageAccessDenied}
+        onLoadError={LoginPageServerError}
       />
 
       {/**
@@ -190,7 +191,7 @@ export function InstanceRoutes({ id, admin }: Props): VNode 
{
        */}
       <Route path={InstancePaths.product_list} component={ProductListPage}
         onUnauthorized={LoginPageAccessDenied}
-        onLoadError={IfAdminCreateDefaultOr(LoginPageServerError)}
+        onLoadError={LoginPageServerError}
         onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
       />
       <Route path={InstancePaths.product_update} component={ProductUpdatePage}
@@ -202,21 +203,21 @@ export function InstanceRoutes({ id, admin }: Props): 
VNode {
        * Order pages
        */}
       <Route path={InstancePaths.order_list} component={OrderListPage}
-        onCreate={() => {route(InstancePaths.order_new)}}
+        onCreate={() => { route(InstancePaths.order_new) }}
         onSelect={(id: string) => { 
route(InstancePaths.order_details.replace(':oid', id)) }}
         onUnauthorized={LoginPageAccessDenied}
-        onLoadError={IfAdminCreateDefaultOr(LoginPageServerError)}
+        onLoadError={LoginPageServerError}
         onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
       />
       <Route path={InstancePaths.order_details} component={OrderDetailsPage}
         onUnauthorized={LoginPageAccessDenied}
-        onLoadError={IfAdminCreateDefaultOr(LoginPageServerError)}
+        onLoadError={LoginPageServerError}
         onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
-        onBack={() => {route(InstancePaths.order_list)}}
+        onBack={() => { route(InstancePaths.order_list) }}
       />
       <Route path={InstancePaths.order_new} component={OrderCreatePage}
-        onConfirm={() => {route(InstancePaths.order_list)}}
-        onBack={() => {route(InstancePaths.order_list)}}
+        onConfirm={() => { route(InstancePaths.order_list) }}
+        onBack={() => { route(InstancePaths.order_list) }}
       />
 
       {/**
@@ -224,7 +225,7 @@ export function InstanceRoutes({ id, admin }: Props): VNode 
{
        */}
       <Route path={InstancePaths.transfers_list} component={TransferListPage}
         onUnauthorized={LoginPageAccessDenied}
-        onLoadError={IfAdminCreateDefaultOr(LoginPageServerError)}
+        onLoadError={LoginPageServerError}
         onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
       />
 
@@ -257,9 +258,9 @@ function AdminInstanceUpdatePage({ id, ...rest }: { id: 
string } & InstanceUpdat
   const i18n = useMessageTemplate('');
   return <InstanceContextProvider value={value}>
     <InstanceUpdatePage {...rest}
-      onLoadError={(error: SwrError) => {
+      onLoadError={(error: HttpError) => {
         return <Fragment>
-          <NotificationCard notification={{ message: i18n`Problem reaching the 
server`, description: i18n`Got message: ${error.message} from: ${error.backend} 
(hasToken: ${error.hasToken})`, type: 'ERROR' }} />
+          <NotificationCard notification={{ message: `Server reported a 
problem: HTTP status #${error.status}`, description: `Got message: 
${error.message} from: ${error.info?.url}`, type: 'ERROR' }} />
           <LoginPage onConfirm={updateLoginStatus} />
         </Fragment>
       }}
diff --git a/packages/frontend/src/declaration.d.ts 
b/packages/frontend/src/declaration.d.ts
index cf639b1..6f258c4 100644
--- a/packages/frontend/src/declaration.d.ts
+++ b/packages/frontend/src/declaration.d.ts
@@ -48,6 +48,45 @@ type UUID = string;
 type Integer = number;
 
 export namespace MerchantBackend {
+    interface ErrorDetail {
+
+        // Numeric error code unique to the condition.
+        // The other arguments are specific to the error value reported here.
+        code: number;
+      
+        // Human-readable description of the error, i.e. "missing parameter", 
"commitment violation", ...
+        // Should give a human-readable hint about the error's nature. 
Optional, may change without notice!
+        hint?: string;
+      
+        // Optional detail about the specific input value that failed. May 
change without notice!
+        detail?: string;
+      
+        // Name of the parameter that was bogus (if applicable).
+        parameter?: string;
+      
+        // Path to the argument that was bogus (if applicable).
+        path?: string;
+      
+        // Offset of the argument that was bogus (if applicable).
+        offset?: string;
+      
+        // Index of the argument that was bogus (if applicable).
+        index?: string;
+      
+        // Name of the object that was bogus (if applicable).
+        object?: string;
+      
+        // Name of the currency than was problematic (if applicable).
+        currency?: string;
+      
+        // Expected type (if applicable).
+        type_expected?: string;
+      
+        // Type that was provided instead (if applicable).
+        type_actual?: string;
+      }
+      
+
     // Delivery location, loosely modeled as a subset of
     // ISO20022's PostalAddress25.
     interface Tax {
diff --git a/packages/frontend/src/hooks/backend.ts 
b/packages/frontend/src/hooks/backend.ts
index 943d826..f9466a0 100644
--- a/packages/frontend/src/hooks/backend.ts
+++ b/packages/frontend/src/hooks/backend.ts
@@ -19,14 +19,11 @@
 * @author Sebastian Javier Marchano (sebasjm)
 */
 
-import useSWR, { mutate, cache, useSWRInfinite } from 'swr';
-import axios from 'axios'
+import { mutate, cache } from 'swr';
+import axios, { AxiosError } from 'axios'
 import { MerchantBackend } from '../declaration';
-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';
+import { useBackendContext } from '../context/backend';
+import { useEffect, useState } from 'preact/hooks';
 
 export function mutateAll(re: RegExp) {
   cache.keys().filter(key => {
@@ -35,38 +32,86 @@ export function mutateAll(re: RegExp) {
   }).forEach(key => mutate(key, null))
 }
 
-export type HttpResponse<T> = HttpResponseOk<T> | HttpResponseError;
+export type HttpResponse<T> = HttpResponseOk<T> | HttpResponseLoading<T> | 
HttpError;
+export type HttpResponsePaginated<T> = HttpResponseOkPaginated<T> | 
HttpResponseLoading<T> | HttpError;
+
+export interface RequestInfo {
+  url: string;
+  hasToken: boolean;
+  params: any;
+  data: any;
+}
+
+interface HttpResponseLoading<T> {
+  ok?: false;
+  loading: true;
+  clientError?: false;
+  serverError?: false;
+
+  data?: T;
+}
+export interface HttpResponseOk<T> {
+  ok: true;
+  loading?: false;
+  clientError?: false;
+  serverError?: false;
 
-interface HttpResponseOk<T> {
   data: T;
-  unauthorized: boolean;
-  notfound: boolean;
-  isLoadingMore?: boolean;
-  loadMore?: () => void;
-  loadMorePrev?: () => void;
+  info?: RequestInfo;
+}
+
+export type HttpResponseOkPaginated<T> = HttpResponseOk<T> & WithPagination
+
+export interface WithPagination {
+  loadMore: () => void;
+  loadMorePrev: () => void;
   isReachingEnd?: boolean;
   isReachingStart?: boolean;
 }
 
+export type HttpError = HttpResponseClientError | HttpResponseServerError | 
HttpResponseUnexpectedError;
 export interface SwrError {
   info: any,
   status: number,
   message: string,
-  backend: string,
-  hasToken: boolean,
-}
-interface HttpResponseError {
-  data: undefined;
-  unauthorized: boolean;
-  notfound: boolean;
-  error?: SwrError;
-  isLoadingMore?: boolean;
-  loadMore?: () => void;
-  loadMorePrev?: () => void;
-  isReachingEnd?: boolean;
-  isReachingStart?: boolean;
+}
+export interface HttpResponseServerError {
+  ok?: false;
+  loading?: false;
+  clientError?: false;
+  serverError: true;
+
+  error?: MerchantBackend.ErrorDetail;
+  status: number;
+  message: string;
+  info?: RequestInfo;
+}
+interface HttpResponseClientError {
+  ok?: false;
+  loading?: false;
+  clientError: true;
+  serverError?: false;
+
+  info?: RequestInfo;
+  isUnauthorized: boolean;
+  isNotfound: boolean;
+  status: number;
+  error?: MerchantBackend.ErrorDetail;
+  message: string;
+
 }
 
+interface HttpResponseUnexpectedError {
+  ok?: false;
+  loading?: false;
+  clientError?: false;
+  serverError?: false;
+
+  info?: RequestInfo;
+  status?: number;
+  error: any;
+  message: string;
+}
 
 type Methods = 'get' | 'post' | 'patch' | 'delete' | 'put';
 
@@ -77,17 +122,76 @@ interface RequestOptions {
   params?: any;
 }
 
+function buildRequestOk<T>(res: any, url: string, hasToken: boolean): 
HttpResponseOk<T> {
+  return {
+    ok: true, data: res.data, info: {
+      params: res.config.params,
+      data: res.config.data,
+      url,
+      hasToken,
+    }
+  }
+}
+
+// function buildResponse<T>(data?: T, error?: MerchantBackend.ErrorDetail, 
isValidating?: boolean): HttpResponse<T> {
+//   if (isValidating) return {loading: true}
+//   if (error) return buildRequestFailed()
+// }
 
-export async function request(url: string, options: RequestOptions = {}): 
Promise<any> {
+function buildRequestFailed(ex: AxiosError<MerchantBackend.ErrorDetail>, url: 
string, hasToken: boolean): HttpResponseClientError | HttpResponseServerError | 
HttpResponseUnexpectedError {
+  const status = ex.response?.status
+  
+  const info = {
+    data: ex.request?.data,
+    params: ex.request?.params,
+    url,
+    hasToken,
+  };
+
+  if (status && status >= 400 && status < 500) {
+    const error: HttpResponseClientError = {
+      clientError: true,
+      isNotfound: status === 404,
+      isUnauthorized: status === 401,
+      status,
+      info,
+      message: ex.response?.data?.hint || ex.message,
+      error: ex.response?.data
+    }
+    return error
+  }
+  if (status && status >= 500 && status < 600) {
+    const error: HttpResponseServerError = {
+      serverError: true,
+      status,
+      info,
+      message: ex.response?.data?.hint || ex.message,
+      error: ex.response?.data
+    }
+    return error;
+  }
+
+  const error: HttpResponseUnexpectedError = {
+    info,
+    status,
+    error: ex,
+    message: ex.message
+  }
+
+  return error
+}
+
+export async function request<T>(url: string, options: RequestOptions = {}): 
Promise<HttpResponseOk<T>> {
   const headers = options.token ? { Authorization: `Bearer ${options.token}` } 
: undefined
 
+
   try {
-    // http://localhost:9966/instances/blog/private/instances
-    // Hack, endpoint should respond 404
-    if (/^\/instances\/[^/]*\/private\/instances$/.test(new 
URL(url).pathname)) {
-      console.warn(`HACK: Not going to query ${url}, instead return 404`)
-      throw ({ response: { status: 404 }, message: 'not found' })
-    }
+    // // http://localhost:9966/instances/blog/private/instances
+    // // Hack, endpoint should respond 404
+    // if (/^\/instances\/[^/]*\/private\/instances$/.test(new 
URL(url).pathname)) {
+    //   console.warn(`HACK: Not going to query ${url}, instead return 404`)
+    //   throw ({ response: { status: 404 }, message: 'not found' })
+    // }
 
 
     const res = await axios({
@@ -98,59 +202,49 @@ export async function request(url: string, options: 
RequestOptions = {}): Promis
       data: options.data,
       params: options.params
     })
-    return res.data
+
+    return buildRequestOk<T>(res, url, !!options.token)
   } catch (e) {
-    console.error(e)
-    const info = e.response?.data
-    const status = e.response?.status
-    const hint = info?.hint
-    throw { info, status, message: hint || e.message, backend: url, hasToken: 
!!options.token }
+    const error = buildRequestFailed(e, url, !!options.token)
+    throw error
   }
 
 }
 
-export function fetcher(url: string, token: string, backend: string) {
-  return request(`${backend}${url}`, { token })
+export function fetcher<T>(url: string, token: string, backend: string): 
Promise<HttpResponseOk<T>> {
+  return request<T>(`${backend}${url}`, { token })
 }
 
 export function useBackendInstancesTestForAdmin(): 
HttpResponse<MerchantBackend.Instances.InstancesResponse> {
   const { url, token } = useBackendContext()
-  interface Result {
-    data?: MerchantBackend.Instances.InstancesResponse;
-    error?: SwrError;
-  }
-  const [result, setResult] = useState<Result | undefined>(undefined)
+
+  type Type = MerchantBackend.Instances.InstancesResponse;
+
+  const [result, setResult] = useState<HttpResponse<Type>>({ loading: true })
 
   useEffect(() => {
-    request(`${url}/private/instances`, { token })
-      .then(data => setResult({ data }))
-      .catch(error => setResult({ error }))
+    request<Type>(`${url}/private/instances`, { token })
+      .then(data => setResult(data))
+      .catch(error => setResult(error))
   }, [url, token])
 
-  const data = result?.data
-  const error = result?.error
 
-  return { data, unauthorized: error?.status === 401, notfound: error?.status 
=== 404, error }
+  return result
 }
 
 
 export function useBackendConfig(): 
HttpResponse<MerchantBackend.VersionResponse> {
   const { url, token } = useBackendContext()
 
-  interface Result {
-    data?: MerchantBackend.VersionResponse;
-    error?: SwrError;
-  }
-  const [result, setResult] = useState<Result | undefined>(undefined)
+  type Type = MerchantBackend.VersionResponse;
+
+  const [result, setResult] = useState<HttpResponse<Type>>({ loading: true })
 
   useEffect(() => {
-    request(`${url}/config`, { token })
-      .then(data => setResult({ data }))
-      .catch(error => setResult({ error }))
+    request<Type>(`${url}/config`, { token })
+      .then(data => setResult(data))
+      .catch(error => setResult(error))
   }, [url, token])
 
-  const data = result?.data
-  const error = result?.error
-  
-  return { data, unauthorized: error?.status === 401, notfound: error?.status 
=== 404, error }
+  return result
 }
diff --git a/packages/frontend/src/hooks/instance.ts 
b/packages/frontend/src/hooks/instance.ts
index 31709bc..c96c1f6 100644
--- a/packages/frontend/src/hooks/instance.ts
+++ b/packages/frontend/src/hooks/instance.ts
@@ -1,6 +1,6 @@
 import { MerchantBackend } from '../declaration';
 import { useBackendContext, useInstanceContext } from '../context/backend';
-import { fetcher, HttpResponse, request, SwrError } from './backend';
+import { fetcher, HttpError, HttpResponse, HttpResponseOk, request, SwrError } 
from './backend';
 import useSWR, { mutate } from 'swr';
 
 
@@ -69,9 +69,6 @@ export function useInstanceAPI(): InstanceAPI {
 
 
 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();
 
@@ -81,17 +78,23 @@ export function useInstanceDetails(): 
HttpResponse<MerchantBackend.Instances.Que
     url: `${baseUrl}/instances/${id}`, token: instanceToken
   }
 
-  const { data, error } = 
useSWR<MerchantBackend.Instances.QueryInstancesResponse, 
SwrError>([`/private/`, token, url], fetcher)
+  const { data, error, isValidating } = 
useSWR<HttpResponseOk<MerchantBackend.Instances.QueryInstancesResponse>, 
HttpError>([`/private/`, token, url], fetcher)
 
-  return { data, unauthorized: error?.status === 401, notfound: error?.status 
=== 404, error }
+  if (isValidating) return {loading:true, data: data?.data}
+  if (data) return data
+  if (error) return error
+  return {loading: true}
 }
 
 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)
+  const { data, error, isValidating } = 
useSWR<HttpResponseOk<MerchantBackend.Instances.InstancesResponse>, 
HttpError>(['/private/instances', token, url], fetcher)
 
-  return { data, unauthorized: error?.status === 401, notfound: error?.status 
=== 404, error }
+  if (isValidating) return {loading:true, data: data?.data}
+  if (data) return data
+  if (error) return error
+  return {loading: true}
 }
 
diff --git a/packages/frontend/src/hooks/order.ts 
b/packages/frontend/src/hooks/order.ts
index 2b51366..da056dd 100644
--- a/packages/frontend/src/hooks/order.ts
+++ b/packages/frontend/src/hooks/order.ts
@@ -4,26 +4,23 @@ 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';
+import { fetcher, HttpError, HttpResponse, HttpResponseOk, 
HttpResponsePaginated, mutateAll, request, SwrError, WithPagination } 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>;
+  createOrder: (data: MerchantBackend.Orders.PostOrderRequest) => 
Promise<HttpResponseOk<MerchantBackend.Orders.PostOrderResponse>>;
+  forgetOrder: (id: string, data: MerchantBackend.Orders.ForgetRequest) => 
Promise<HttpResponseOk<void>>;
+  refundOrder: (id: string, data: MerchantBackend.Orders.RefundRequest) => 
Promise<HttpResponseOk<MerchantBackend.Orders.MerchantRefundResponse>>;
+  deleteOrder: (id: string) => Promise<HttpResponseOk<void>>;
+  getPaymentURL: (id: string) => Promise<HttpResponseOk<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 orderFetcher<T>(url: string, token: string, backend: string, 
paid?: YesOrNo, refunded?: YesOrNo, wired?: YesOrNo, searchDate?: Date, delta?: 
number): Promise<HttpResponseOk<T>> {
+  const date_ms = delta && delta < 0 && searchDate ? searchDate.getTime() + 1 
: searchDate?.getTime()
+  return request<T>(`${backend}${url}`, { token, params: { paid, refunded, 
wired, delta, date_ms } })
 }
 
 
@@ -37,65 +34,70 @@ export function useOrderAPI(): OrderAPI {
     url: `${baseUrl}/instances/${id}`, token: instanceToken
   }
 
-  const createOrder = async (data: MerchantBackend.Orders.PostOrderRequest): 
Promise<MerchantBackend.Orders.PostOrderResponse> => {
-    const res = await request(`${url}/private/orders`, {
+  const createOrder = async (data: MerchantBackend.Orders.PostOrderRequest): 
Promise<HttpResponseOk<MerchantBackend.Orders.PostOrderResponse>> => {
+    mutateAll(/@"\/private\/orders"@/)
+    return 
request<MerchantBackend.Orders.PostOrderResponse>(`${url}/private/orders`, {
       method: 'post',
       token,
       data
     })
-
-    mutateAll(/@"\/private\/orders"@/)
-    return res
+    // return res
   }
-  const refundOrder = async (orderId: string, data: 
MerchantBackend.Orders.RefundRequest): 
Promise<MerchantBackend.Orders.MerchantRefundResponse> => {
-    const res = await request(`${url}/private/orders/${orderId}/refund`, {
+  const refundOrder = async (orderId: string, data: 
MerchantBackend.Orders.RefundRequest): 
Promise<HttpResponseOk<MerchantBackend.Orders.MerchantRefundResponse>> => {
+    mutateAll(/@"\/private\/orders"@/)
+    return 
request<MerchantBackend.Orders.MerchantRefundResponse>(`${url}/private/orders/${orderId}/refund`,
 {
       method: 'post',
       token,
       data
     })
 
-    mutateAll(/@"\/private\/orders"@/)
-    return res
+    // return res
   }
 
-  const forgetOrder = async (orderId: string, data: 
MerchantBackend.Orders.ForgetRequest): Promise<void> => {
-    await request(`${url}/private/orders/${orderId}/forget`, {
+  const forgetOrder = async (orderId: string, data: 
MerchantBackend.Orders.ForgetRequest): Promise<HttpResponseOk<void>> => {
+    mutateAll(/@"\/private\/orders"@/)
+    return 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}`, {
+  const deleteOrder = async (orderId: string): Promise<HttpResponseOk<void>> 
=> {
+    mutateAll(/@"\/private\/orders"@/)
+    return 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}`, {
+  const getPaymentURL = async (orderId: string): 
Promise<HttpResponseOk<string>> => {
+    return 
request<MerchantBackend.Orders.MerchantOrderStatusResponse>(`${url}/private/orders/${orderId}`,
 {
       method: 'get',
       token
+    }).then((res) => {
+      const url = res.data.order_status === "unpaid" ? res.data.taler_pay_uri 
: res.data.contract_terms.fulfillment_url
+      const response: HttpResponseOk<string> = res as any
+      response.data = url || ''
+      return response
     })
-    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> {
+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)
+  const { data, error, isValidating } = 
useSWR<HttpResponseOk<MerchantBackend.Orders.MerchantOrderStatusResponse>, 
HttpError>([`/private/orders/${oderId}`, token, url], fetcher)
 
-  return { data, unauthorized: error?.status === 401, notfound: error?.status 
=== 404, error }
+  if (isValidating) return { loading: true, data: data?.data }
+  if (data) return data
+  if (error) return error
+  return { loading: true }
 }
 
 export interface InstanceOrderFilter {
@@ -105,7 +107,7 @@ export interface InstanceOrderFilter {
   date?: Date;
 }
 
-export function useInstanceOrders(args: InstanceOrderFilter, updateFilter: 
(d:Date)=>void): HttpResponse<MerchantBackend.Orders.OrderHistory> {
+export function useInstanceOrders(args: InstanceOrderFilter, updateFilter: (d: 
Date) => void): HttpResponsePaginated<MerchantBackend.Orders.OrderHistory> {
   const { url: baseUrl, token: baseToken } = useBackendContext();
   const { token: instanceToken, id, admin } = useInstanceContext();
 
@@ -127,51 +129,58 @@ export function useInstanceOrders(args: 
InstanceOrderFilter, updateFilter: (d:Da
    * 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>(
+  const { data: beforeData, error: beforeError, isValidating: loadingBefore } 
= useSWR<HttpResponseOk<MerchantBackend.Orders.OrderHistory>, HttpError>(
     [`/private/orders`, token, url, args?.paid, args?.refunded, args?.wired, 
args?.date, totalBefore],
     orderFetcher,
   )
-  const { data:afterData, error:afterError } = 
useSWR<MerchantBackend.Orders.OrderHistory, SwrError>(
+  const { data: afterData, error: afterError, isValidating: loadingAfter } = 
useSWR<HttpResponseOk<MerchantBackend.Orders.OrderHistory>, HttpError>(
     [`/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)
+  const [lastBefore, setLastBefore] = 
useState<HttpResponse<MerchantBackend.Orders.OrderHistory>>({ loading: true })
+  const [lastAfter, setLastAfter] = 
useState<HttpResponse<MerchantBackend.Orders.OrderHistory>>({ loading: true })
   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))
-    }
+  if (beforeError) return beforeError
+  if (afterError) return afterError
+  
+  
+  const pagination = {
+    isReachingEnd: afterData && afterData.data.orders.length < totalAfter,
+    isReachingStart: (!args?.date) || (beforeData && 
beforeData.data.orders.length < totalBefore),
+    loadMore: () => {
+      if (!afterData) return
+      if (afterData.data.orders.length < MAX_RESULT_SIZE) {
+        setPageAfter(pageAfter + 1)
+      } else {
+        const from = afterData.data.orders[afterData.data.orders.length - 
1].timestamp.t_ms
+        if (from) updateFilter(new Date(from))
+      }
+    },
+    loadMorePrev: () => {
+      if (!beforeData) return
+      if (beforeData.data.orders.length < MAX_RESULT_SIZE) {
+        setPageBefore(pageBefore + 1)
+      } else if (beforeData) {
+        const from = beforeData.data.orders[beforeData.data.orders.length - 
1].timestamp.t_ms
+        if (from) updateFilter(new Date(from))
+      }
+    },
+  }
+  
+  const orders = !beforeData || !afterData ? [] : (beforeData || 
lastBefore).data.orders.slice().reverse().concat((afterData || 
lastAfter).data.orders)
+  if (loadingAfter || loadingBefore) return { loading: true, data: { orders: 
orders } }
+  if (beforeData && afterData) {
+    return { ok: true, data: { orders: orders }, ...pagination }
   }
+  return { loading: true }
 
-  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
index 1909a18..6568a05 100644
--- a/packages/frontend/src/hooks/product.ts
+++ b/packages/frontend/src/hooks/product.ts
@@ -2,7 +2,7 @@ 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';
+import { fetcher, HttpError, HttpResponse, HttpResponseOk, mutateAll, request, 
SwrError } from './backend';
 
 
 export interface ProductAPI {
@@ -77,29 +77,34 @@ export function useInstanceProducts(): 
HttpResponse<(MerchantBackend.Products.Pr
     url: `${baseUrl}/instances/${id}`, token: instanceToken
   };
 
-  const list = useSWR<MerchantBackend.Products.InventorySummaryResponse, 
SwrError>([`/private/products`, token, url], fetcher);
+  const { data: list, error: listError, isValidating: listLoading } = 
useSWR<HttpResponseOk<MerchantBackend.Products.InventorySummaryResponse>, 
HttpError>([`/private/products`, token, url], fetcher);
 
-  const getKey = (pageIndex: number) => {
-    if (!list.data || !list.data.products.length) return null
+  const { data: products, error: productError, setSize } = 
useSWRInfinite<HttpResponseOk<MerchantBackend.Products.ProductDetail>, 
HttpError>((pageIndex: number) => {
+    if (!list?.data || !list.data.products.length || listError) 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
+  }, fetcher)
 
   useEffect(() => {
-    if (list.data && list.data?.products?.length > 0) {
-      setSize(list.data?.products?.length)
+    if (list?.data && list.data.products.length > 0) {
+      setSize(list.data.products.length)
     }
-  }, [list.data])
+  }, [list?.data])
 
-  if (list.data && list.data.products.length === 0) {
-    return { data: [], unauthorized: false, notfound: false }
-  }
 
+  if (listLoading) return { loading: true, data: [] }
+  if (listError) return listError
+  if (productError) return productError
+  if (list?.data && list.data.products.length === 0) {
+    return { ok: true, data: [] }
+  }
+  if (products) {
+    const dataWithId = products.map((d, i) => {
+      //take the id from the queried url
+      return ({ ...d.data, id: d.info?.url.replace(/.*\/private\/products\//, 
'') || '' })
+    })
+    return { ok: true, data: dataWithId }
+  }
 
-  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 };
+  return { loading: true }
 }
 
diff --git a/packages/frontend/src/hooks/tips.ts 
b/packages/frontend/src/hooks/tips.ts
index 3500c00..3809276 100644
--- a/packages/frontend/src/hooks/tips.ts
+++ b/packages/frontend/src/hooks/tips.ts
@@ -1,6 +1,6 @@
 import { MerchantBackend } from '../declaration';
 import { useBackendContext, useInstanceContext } from '../context/backend';
-import { request, mutateAll, HttpResponse, SwrError, fetcher } from 
'./backend';
+import { request, mutateAll, HttpResponse, SwrError, fetcher, HttpError, 
HttpResponseOk } from './backend';
 import useSWR from 'swr';
 
 
@@ -14,47 +14,42 @@ export function useTipsMutateAPI(): TipsMutateAPI {
     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`, {
+  const createReserve = async (data: 
MerchantBackend.Tips.ReserveCreateRequest): 
Promise<HttpResponseOk<MerchantBackend.Tips.ReserveCreateConfirmation>> => {
+    mutateAll(/@"\/private\/reserves"@/);
+
+    return 
request<MerchantBackend.Tips.ReserveCreateConfirmation>(`${url}/private/reserves`,
 {
       method: 'post',
       token,
       data
     });
+  };
 
+  const authorizeTipReserve = (pub: string, data: 
MerchantBackend.Tips.TipCreateRequest): 
Promise<HttpResponseOk<MerchantBackend.Tips.TipCreateConfirmation>> => {
     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`, {
+    return 
request<MerchantBackend.Tips.TipCreateConfirmation>(`${url}/private/reserves/${pub}/authorize-tip`,
 {
       method: 'post',
       token,
       data
     });
+  };
 
+  const authorizeTip = (data: MerchantBackend.Tips.TipCreateRequest): 
Promise<HttpResponseOk<MerchantBackend.Tips.TipCreateConfirmation>> => {
     mutateAll(/@"\/private\/reserves"@/);
-    return res;
-  };
 
-  const authorizeTip = async (data: MerchantBackend.Tips.TipCreateRequest): 
Promise<MerchantBackend.Tips.TipCreateConfirmation> => {
-    const res = await request(`${url}/private/tips`, {
+    return 
request<MerchantBackend.Tips.TipCreateConfirmation>(`${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}`, {
+  const deleteReserve = (pub: string): Promise<HttpResponse<void>> => {
+    mutateAll(/@"\/private\/reserves"@/);
+    return request(`${url}/private/reserves/${pub}`, {
       method: 'delete',
       token,
     });
-
-    mutateAll(/@"\/private\/reserves"@/);
   };
 
 
@@ -62,13 +57,12 @@ export function useTipsMutateAPI(): TipsMutateAPI {
 }
 
 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>;
+  createReserve: (data: MerchantBackend.Tips.ReserveCreateRequest) => 
Promise<HttpResponseOk<MerchantBackend.Tips.ReserveCreateConfirmation>>;
+  authorizeTipReserve: (id: string, data: 
MerchantBackend.Tips.TipCreateRequest) => 
Promise<HttpResponseOk<MerchantBackend.Tips.TipCreateConfirmation>>;
+  authorizeTip: (data: MerchantBackend.Tips.TipCreateRequest) => 
Promise<HttpResponseOk<MerchantBackend.Tips.TipCreateConfirmation>>;
+  deleteReserve: (id: string) => Promise<HttpResponse<void>>;
 }
 
-
 export function useInstanceTips(): 
HttpResponse<MerchantBackend.Tips.TippingReserveStatus> {
   const { url: baseUrl, token: baseToken } = useBackendContext();
   const { token: instanceToken, id, admin } = useInstanceContext();
@@ -79,8 +73,11 @@ export function useInstanceTips(): 
HttpResponse<MerchantBackend.Tips.TippingRese
     url: `${baseUrl}/instances/${id}`, token: instanceToken
   }
 
-  const { data, error } = useSWR<MerchantBackend.Tips.TippingReserveStatus, 
SwrError>([`/private/reserves`, token, url], fetcher)
+  const { data, error, isValidating } = 
useSWR<HttpResponseOk<MerchantBackend.Tips.TippingReserveStatus>, 
HttpError>([`/private/reserves`, token, url], fetcher)
 
-  return { data, unauthorized: error?.status === 401, notfound: error?.status 
=== 404, error }
+  if (isValidating) return {loading:true, data: data?.data}
+  if (data) return data
+  if (error) return error
+  return {loading: true}
 }
 
diff --git a/packages/frontend/src/hooks/transfer.ts 
b/packages/frontend/src/hooks/transfer.ts
index 4984cc6..0c9e7b8 100644
--- a/packages/frontend/src/hooks/transfer.ts
+++ b/packages/frontend/src/hooks/transfer.ts
@@ -1,13 +1,12 @@
 import { MerchantBackend } from '../declaration';
 import { useBackendContext, useInstanceContext } from '../context/backend';
-import { request, mutateAll, HttpResponse, SwrError } from './backend';
+import { request, mutateAll, HttpResponse, HttpError, HttpResponseOk } from 
'./backend';
 import useSWR from 'swr';
 
-function transferFetcher(url: string, token: string, backend: string) {
-  return request(`${backend}${url}`, { token, params: { payto_uri: '' } })
+async function transferFetcher<T>(url: string, token: string, backend: 
string): Promise<HttpResponseOk<T>> {
+  return request<T>(`${backend}${url}`, { token, params: { payto_uri: '' } })
 }
 
-
 export function useTransferMutateAPI(): TransferMutateAPI {
   const { url: baseUrl, token: adminToken } = useBackendContext();
   const { token: instanceToken, id, admin } = useInstanceContext();
@@ -18,22 +17,21 @@ export function useTransferMutateAPI(): TransferMutateAPI {
     url: `${baseUrl}/instances/${id}`, token: instanceToken
   };
 
-  const informTransfer = async (data: 
MerchantBackend.Transfers.TransferInformation): 
Promise<MerchantBackend.Transfers.MerchantTrackTransferResponse> => {
-    const res = await request(`${url}/private/transfers`, {
+  const informTransfer = async (data: 
MerchantBackend.Transfers.TransferInformation): 
Promise<HttpResponseOk<MerchantBackend.Transfers.MerchantTrackTransferResponse>>
 => {
+    mutateAll(/@"\/private\/transfers"@/);
+    
+    return 
request<MerchantBackend.Transfers.MerchantTrackTransferResponse>(`${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>;
+  informTransfer: (data: MerchantBackend.Transfers.TransferInformation) => 
Promise<HttpResponseOk<MerchantBackend.Transfers.MerchantTrackTransferResponse>>;
 }
 
 export function useInstanceTransfers(): 
HttpResponse<MerchantBackend.Transfers.TransferList> {
@@ -46,9 +44,12 @@ export function useInstanceTransfers(): 
HttpResponse<MerchantBackend.Transfers.T
     url: `${baseUrl}/instances/${id}`, token: instanceToken
   }
 
-  const { data, error } = useSWR<MerchantBackend.Transfers.TransferList, 
SwrError>([`/private/transfers`, token, url], transferFetcher)
+  const { data, error, isValidating } = 
useSWR<HttpResponseOk<MerchantBackend.Transfers.TransferList>, 
HttpError>([`/private/transfers`, token, url], transferFetcher)
 
-  return { data, unauthorized: error?.status === 401, notfound: error?.status 
=== 404, error }
+  if (isValidating) return {loading:true, data: data?.data}
+  if (data) return data
+  if (error) return error
+  return {loading: true}
 }
 
 
diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx
index a2145c8..7f7884f 100644
--- a/packages/frontend/src/index.tsx
+++ b/packages/frontend/src/index.tsx
@@ -22,7 +22,7 @@
 import "./scss/main.scss"
 
 import { h, VNode } from 'preact';
-import { useEffect, useMemo } from "preact/hooks";
+import { useMemo } from "preact/hooks";
 import { route } from 'preact-router';
 import { MessageProvider, useMessageTemplate } from 'preact-messages';
 
@@ -35,6 +35,7 @@ import { hasKey, onTranslationError } from 
"./utils/functions";
 import LoginPage from './paths/login';
 import { ApplicationReadyRoutes } from "./ApplicationReadyRoutes";
 import { NotificationCard, NotYetReadyAppMenu } from "./components/menu";
+import { Loading } from "./components/exception/loading";
 
 export default function Application(): VNode {
   const state = useBackendContextState()
@@ -50,12 +51,9 @@ export default function Application(): VNode {
 
 function ApplicationStatusRoutes(): VNode {
   const { changeBackend, triedToLog, updateToken, resetBackend } = 
useBackendContext()
-  const backendConfig = useBackendConfig();
+  const result = useBackendConfig();
   const i18n = useMessageTemplate()
 
-  const v = `${backendConfig.data?.currency} ${backendConfig.data?.version}`
-  const ctx = useMemo(() => ({ currency: backendConfig.data?.currency || '', 
version: backendConfig.data?.version || '' }), [v])
-
   const updateLoginInfoAndGoToRoot = (url: string, token?: string) => {
     changeBackend(url)
     if (token) updateToken(token)
@@ -69,27 +67,44 @@ function ApplicationStatusRoutes(): VNode {
     </div>
   }
 
-  if (!backendConfig.data) {
+  if (result.clientError && result.isUnauthorized) return <div id="app">
+    <NotYetReadyAppMenu title="Login" />
+    <LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
+  </div>
 
-    if (!backendConfig.error) return <div class="is-loading" />
+  if (result.clientError && result.isNotfound) return <div id="app">
+    <NotYetReadyAppMenu title="Error" />
+    <NotificationCard notification={{
+      message: i18n`Server not found`,
+      type: 'ERROR',
+      description: `Check your url`,
+    }} />
+    <LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
+  </div>
 
-    if (backendConfig.unauthorized) {
-      return <div id="app">
-        <NotYetReadyAppMenu title="Login" />
-        <LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
-      </div>
-    }
+  if (result.serverError) return <div id="app">
+    <NotYetReadyAppMenu title="Error" />
+    <NotificationCard notification={{
+      message: i18n`Couldn't access the server`,
+      type: 'ERROR',
+      description: i18n`Got message ${result.message} from 
${result.info?.url}`,
+    }} />
+    <LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
+  </div>
 
-    return <div id="app">
-      <NotYetReadyAppMenu title="Error" />
-      <NotificationCard notification={{
-        message: i18n`Couldnt access the server`,
-        type: 'ERROR',
-        description: i18n`Got message: ${backendConfig.error.message} from: 
${backendConfig.error.backend} (hasToken: ${backendConfig.error.hasToken})`,
-      }} />
-      <LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
-    </div>
-  }
+  if (result.loading) return <Loading />
+
+  if (!result.ok) return <div id="app">
+    <NotYetReadyAppMenu title="Error" />
+    <NotificationCard notification={{
+      message: i18n`Unexpected Error`,
+      type: 'ERROR',
+      description: i18n`Got message ${result.error.message} from 
${result.info?.url}`,
+    }} />
+    <LoginPage onConfirm={updateLoginInfoAndGoToRoot} />
+  </div>
+
+  const ctx = useMemo(() => ({ currency: result.data.currency, version: 
result.data.version }), [result.data.currency, result.data.version])
 
   return <div id="app" class="has-navbar-fixed-top">
     <ConfigContextProvider value={ctx}>
diff --git a/packages/frontend/src/messages/en.po 
b/packages/frontend/src/messages/en.po
index 245df6c..a21f4cd 100644
--- a/packages/frontend/src/messages/en.po
+++ b/packages/frontend/src/messages/en.po
@@ -145,10 +145,13 @@ msgstr "Pay delay"
 msgid "fields.instance.default_wire_transfer_delay.label"
 msgstr "Wire transfer delay"
 
-msgid "Couldnt access the server"
-msgstr "Couldnt access the server"
+msgid "Couldn't access the server"
+msgstr "Couldn't access the server"
 
-msgid "Got message: %s from: %s (hasToken: %s)"
+msgid "Unexpected Error"
+msgstr "Unexpected Error"
+
+msgid "Got message %s from %s"
 msgstr "Got message \"%s\" from %s"
 
 msgid "Merchant"
diff --git a/packages/frontend/src/paths/admin/create/CreatePage.tsx 
b/packages/frontend/src/paths/admin/create/CreatePage.tsx
index 84a6241..21cc219 100644
--- a/packages/frontend/src/paths/admin/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/admin/create/CreatePage.tsx
@@ -51,9 +51,9 @@ interface KeyValue {
 function with_defaults(id?: string): Partial<Entity> {
   return {
     id,
-    default_pay_delay: { d_ms: 1000 },
-    default_wire_fee_amortization: 10,
-    default_wire_transfer_delay: { d_ms: 2000 },
+    default_pay_delay: { d_ms: 1000*60*5 },
+    default_wire_fee_amortization: 1,
+    default_wire_transfer_delay: { d_ms: 2000*60*5 },
   };
 }
 
diff --git a/packages/frontend/src/paths/admin/create/index.tsx 
b/packages/frontend/src/paths/admin/create/index.tsx
index c8aa992..e277d15 100644
--- a/packages/frontend/src/paths/admin/create/index.tsx
+++ b/packages/frontend/src/paths/admin/create/index.tsx
@@ -22,6 +22,7 @@ import { useState } from "preact/hooks";
 import { NotificationCard } from "../../../components/menu";
 import { MerchantBackend } from "../../../declaration";
 import { useAdminAPI } from "../../../hooks/admin";
+import { RequestInfo } from "../../../hooks/backend";
 import { Notification } from "../../../utils/types";
 import { CreatePage } from "./CreatePage";
 import { InstanceCreatedSuccessfully } from "./InstanceCreatedSuccessfully";
@@ -29,12 +30,11 @@ import { InstanceCreatedSuccessfully } from 
"./InstanceCreatedSuccessfully";
 interface Props {
   onBack?: () => void;
   onConfirm: () => void;
-  onError: (error: any) => void;
   forceId?: string;
 }
 export type Entity = MerchantBackend.Instances.InstanceConfigurationMessage;
 
-export default function Create({ onBack, onConfirm, onError, forceId }: 
Props): VNode {
+export default function Create({ onBack, onConfirm, forceId }: Props): VNode {
   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 bbc094f..3c6a44c 100644
--- a/packages/frontend/src/paths/admin/list/index.tsx
+++ b/packages/frontend/src/paths/admin/list/index.tsx
@@ -21,7 +21,7 @@
 
 import { Fragment, h, VNode } from 'preact';
 import { View } from './View';
-import { SwrError } from '../../../hooks/backend';
+import { HttpError, HttpResponseServerError, RequestInfo, SwrError } from 
'../../../hooks/backend';
 import { useAdminAPI } from "../../../hooks/admin";
 import { useState } from 'preact/hooks';
 import { MerchantBackend } from '../../../declaration';
@@ -31,24 +31,23 @@ import { Loading } from 
'../../../components/exception/loading';
 import { useBackendInstances } from '../../../hooks/instance';
 
 interface Props {
-  // pushNotification: (n: Notification) => void;
   onCreate: () => void;
   onUpdate: (id: string) => void;
   instances: MerchantBackend.Instances.Instance[];
   onUnauthorized: () => VNode;
-  onLoadError: (e: SwrError) => VNode;
+  onNotFound: () => VNode;
+  onLoadError: (error: HttpError) => VNode;
 }
 
-export default function Instances({ onUnauthorized, onLoadError, onCreate, 
onUpdate }: Props): VNode {
+export default function Instances({ onUnauthorized, onLoadError, onNotFound, 
onCreate, onUpdate }: Props): VNode {
   const result = useBackendInstances()
   const [deleting, setDeleting] = useState<MerchantBackend.Instances.Instance 
| null>(null)
   const { deleteInstance } = useAdminAPI()
 
-  if (result.unauthorized) return onUnauthorized()
-  if (!result.data) {
-    if (result.error) return onLoadError(result.error)
-    return <Loading />
-  }
+  if (result.clientError && result.isUnauthorized) return onUnauthorized()
+  if (result.clientError && result.isNotfound) return onNotFound()
+  if (result.loading) return <Loading />
+  if (!result.ok) return onLoadError(result)
 
   return <Fragment>
     <View instances={result.data.instances}
diff --git a/packages/frontend/src/paths/instance/details/index.tsx 
b/packages/frontend/src/paths/instance/details/index.tsx
index b212018..b8b63a0 100644
--- a/packages/frontend/src/paths/instance/details/index.tsx
+++ b/packages/frontend/src/paths/instance/details/index.tsx
@@ -16,15 +16,16 @@
 import { Fragment, h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { useInstanceContext } from "../../../context/backend";
-import { SwrError } from "../../../hooks/backend";
+import { HttpError, HttpResponseServerError, RequestInfo, 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";
+import { MerchantBackend } from "../../../declaration";
 
 interface Props {
   onUnauthorized: () => VNode;
-  onLoadError: (e: SwrError) => VNode;
+  onLoadError: (error: HttpError) => VNode;
   onUpdate: () => void;
   onNotFound: () => VNode;
   onDelete: () => void;
@@ -37,12 +38,10 @@ export default function Detail({ onUpdate, onLoadError, 
onUnauthorized, onDelete
 
   const { deleteInstance } = useInstanceAPI()
 
-  if (result.unauthorized) return onUnauthorized()
-  if (result.notfound) return onNotFound();
-  if (!result.data) {
-    if (result.error) return onLoadError(result.error)
-    return <Loading />
-  }
+  if (result.clientError && result.isUnauthorized) return onUnauthorized()
+  if (result.clientError && result.isNotfound) return onNotFound()
+  if (result.loading) return <Loading />
+  if (!result.ok) return onLoadError(result)
 
   return <Fragment>
     <DetailPage
diff --git 
a/packages/frontend/src/paths/instance/orders/create/OrderCreatedSuccessfully.tsx
 
b/packages/frontend/src/paths/instance/orders/create/OrderCreatedSuccessfully.tsx
index 2dbf916..091c4d0 100644
--- 
a/packages/frontend/src/paths/instance/orders/create/OrderCreatedSuccessfully.tsx
+++ 
b/packages/frontend/src/paths/instance/orders/create/OrderCreatedSuccessfully.tsx
@@ -30,8 +30,8 @@ export function OrderCreatedSuccessfully({ entity, onConfirm, 
onCreateAnother }:
   const [url, setURL] = useState<string | undefined>(undefined)
   
   useEffect(() => {
-    getPaymentURL(entity.response.order_id).then(url => {
-      setURL(url)
+    getPaymentURL(entity.response.order_id).then(response => {
+      setURL(response.data)
     })
   },[entity.response.order_id])
 
diff --git a/packages/frontend/src/paths/instance/orders/create/index.tsx 
b/packages/frontend/src/paths/instance/orders/create/index.tsx
index 9f323df..9d978c2 100644
--- a/packages/frontend/src/paths/instance/orders/create/index.tsx
+++ b/packages/frontend/src/paths/instance/orders/create/index.tsx
@@ -58,7 +58,7 @@ export default function ({ onConfirm, onBack }: Props): VNode 
{
       onBack={onBack}
       onCreate={(request: MerchantBackend.Orders.PostOrderRequest) => {
         createOrder(request).then((response) => {
-          setCreatedOk({ request, response })
+          setCreatedOk({ request, response: response.data })
         }).catch((error) => {
           setNotif({
             message: 'could not create order',
diff --git a/packages/frontend/src/paths/instance/orders/details/index.tsx 
b/packages/frontend/src/paths/instance/orders/details/index.tsx
index bad41d2..dbd23d6 100644
--- a/packages/frontend/src/paths/instance/orders/details/index.tsx
+++ b/packages/frontend/src/paths/instance/orders/details/index.tsx
@@ -16,7 +16,9 @@
 import { Fragment, h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { Loading } from "../../../../components/exception/loading";
-import { SwrError } from "../../../../hooks/backend";
+import { NotificationCard } from "../../../../components/menu";
+import { MerchantBackend } from "../../../../declaration";
+import { HttpError, HttpResponseServerError, RequestInfo } from 
"../../../../hooks/backend";
 import { useOrderDetails, useOrderAPI } from "../../../../hooks/order";
 import { Notification } from "../../../../utils/types";
 import { DetailPage } from "./DetailPage";
@@ -27,23 +29,23 @@ export interface Props {
   onBack: () => void;
   onUnauthorized: () => VNode;
   onNotFound: () => VNode;
-  onLoadError: (e: SwrError) => VNode;
+  onLoadError: (error: HttpError) => VNode;
 }
 
 export default function Update({ oid, onBack, onLoadError, onNotFound, 
onUnauthorized }: Props): VNode {
   const { refundOrder } = useOrderAPI();
-  const details = useOrderDetails(oid)
+  const result = useOrderDetails(oid)
   const [notif, setNotif] = useState<Notification | undefined>(undefined)
 
-  if (details.unauthorized) return onUnauthorized()
-  if (details.notfound) return onNotFound();
-
-  if (!details.data) {
-    if (details.error) return onLoadError(details.error)
-    return <Loading />
-  }
+  if (result.clientError && result.isUnauthorized) return onUnauthorized()
+  if (result.clientError && result.isNotfound) return onNotFound()
+  if (result.loading) return <Loading />
+  if (!result.ok) return onLoadError(result)
 
   return <Fragment>
+
+    <NotificationCard notification={notif} />
+
     <DetailPage
       onBack={onBack}
       id={oid}
@@ -57,7 +59,7 @@ export default function Update({ oid, onBack, onLoadError, 
onNotFound, onUnautho
           description: error.message
         }))
       }
-      selected={details.data}
+      selected={result.data}
     />
   </Fragment>
 }
\ No newline at end of file
diff --git a/packages/frontend/src/paths/instance/orders/list/index.tsx 
b/packages/frontend/src/paths/instance/orders/list/index.tsx
index c4c8b25..d846c27 100644
--- a/packages/frontend/src/paths/instance/orders/list/index.tsx
+++ b/packages/frontend/src/paths/instance/orders/list/index.tsx
@@ -22,7 +22,7 @@
 import { h, VNode } from 'preact';
 import { useState } from 'preact/hooks';
 import { MerchantBackend } from '../../../../declaration';
-import { SwrError } from '../../../../hooks/backend';
+import { HttpError, HttpResponseServerError, RequestInfo, SwrError } from 
'../../../../hooks/backend';
 import { CardTable } from './Table';
 import { format } from 'date-fns';
 import { DatePicker } from '../../../../components/form/DatePicker';
@@ -30,10 +30,11 @@ import { NotificationCard } from 
'../../../../components/menu';
 import { Notification } from '../../../../utils/types';
 import { copyToClipboard } from '../../../../utils/functions';
 import { InstanceOrderFilter, useInstanceOrders, useOrderAPI } from 
'../../../../hooks/order';
+import { Loading } from '../../../../components/exception/loading';
 
 interface Props {
   onUnauthorized: () => VNode;
-  onLoadError: (e: SwrError) => VNode;
+  onLoadError: (error: HttpError) => VNode;
   onNotFound: () => VNode;
   onSelect: (id: string) => void;
   onCreate: () => void;
@@ -47,24 +48,14 @@ 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 } = useOrderAPI()
-  // const { currency } = useConfigContext()
-
-  let instances: (MerchantBackend.Orders.OrderHistoryEntry & { id: string })[];
+  const { refundOrder, getPaymentURL } = useOrderAPI()
 
   const [notif, setNotif] = useState<Notification | undefined>(undefined)
 
-  if (result.unauthorized) return onUnauthorized()
-  if (result.notfound) return onNotFound()
-  if (!result.data) {
-    if (result.error) return onLoadError(result.error)
-    instances = []
-  } else {
-    instances = result.data.orders.map(o => ({ ...o, id: o.order_id }))
-  }
-  interface Values {
-    filter: []
-  }
+  if (result.clientError && result.isUnauthorized) return onUnauthorized()
+  if (result.clientError && result.isNotfound) return onNotFound()
+  if (result.loading) return <Loading />
+  if (!result.ok) return onLoadError(result)
 
   const isPaidActive = filter.paid === 'yes' ? "is-active" : ''
   const isRefundedActive = filter.refunded === 'yes' ? "is-active" : ''
@@ -72,11 +63,6 @@ export default function ({ onUnauthorized, onLoadError, 
onCreate, onSelect, onNo
   const isAllActive = filter.paid === undefined && filter.refunded === 
undefined && filter.wired === undefined ? 'is-active' : ''
 
   return <section class="section is-main-section">
-    <NotificationCard notification={{
-      message: 'DEMO WARNING',
-      type: 'WARN',
-      description: 'refund button is being forced in the first row, other 
depends on the refundable property'
-    }} />
     <NotificationCard notification={notif} />
 
     <div class="columns">
@@ -117,10 +103,10 @@ export default function ({ onUnauthorized, onLoadError, 
onCreate, onSelect, onNo
       dateReceiver={setNewDate}
     />
 
-    <CardTable instances={instances}
+    <CardTable instances={result.data.orders.map(o => ({ ...o, id: o.order_id 
}))}
       onCreate={onCreate}
       onSelect={(order) => onSelect(order.id)}
-      onCopyURL={(id) => getPaymentURL(id).then(copyToClipboard)}
+      onCopyURL={(id) => getPaymentURL(id).then((resp) => 
copyToClipboard(resp.data))}
       onRefund={(id, value) => refundOrder(id, value)
         .then(() => setNotif({
           message: 'refund created successfully',
diff --git a/packages/frontend/src/paths/instance/products/list/index.tsx 
b/packages/frontend/src/paths/instance/products/list/index.tsx
index 5578670..9eff8f4 100644
--- a/packages/frontend/src/paths/instance/products/list/index.tsx
+++ b/packages/frontend/src/paths/instance/products/list/index.tsx
@@ -21,7 +21,7 @@
 
 import { h, VNode } from 'preact';
 import { create } from 'yup/lib/Reference';
-import { SwrError } from '../../../../hooks/backend';
+import { HttpError, HttpResponseServerError, RequestInfo } from 
'../../../../hooks/backend';
 import { useProductAPI } from "../../../../hooks/product";
 import { CardTable } from './Table';
 import logo from '../../../../assets/logo.jpeg';
@@ -34,25 +34,23 @@ import { NotificationCard } from 
'../../../../components/menu';
 interface Props {
   onUnauthorized: () => VNode;
   onNotFound: () => VNode;
-  onLoadError: (e: SwrError) => VNode;
+  onLoadError: (e: HttpError) => VNode;
 }
 export default function ({ onUnauthorized, onLoadError, onNotFound }: Props): 
VNode {
   const result = useInstanceProducts()
   const { createProduct, deleteProduct } = useProductAPI()
   const { currency } = useConfigContext()
 
-  if (result.unauthorized) return onUnauthorized()
-  if (result.notfound) return onNotFound()
-  
-  if (!result.data) {
-    if (result.error) return onLoadError(result.error)
-    return <Loading />
-  }
+  if (result.clientError && result.isUnauthorized) return onUnauthorized()
+  if (result.clientError && result.isNotfound) return onNotFound()
+  if (result.loading) return <Loading />
+  if (!result.ok) return onLoadError(result)
+
   return <section class="section is-main-section">
     <NotificationCard notification={{
       message: 'DEMO',
-      type:'WARN',
-      description:<ul>
+      type: 'WARN',
+      description: <ul>
         <li>image return object when api says string</li>
       </ul>
     }} />
@@ -72,7 +70,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.ProductDetail & {id:string})) 
=> deleteProduct(prod.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 50d02e4..5d4cbd1 100644
--- a/packages/frontend/src/paths/instance/tips/list/index.tsx
+++ b/packages/frontend/src/paths/instance/tips/list/index.tsx
@@ -23,13 +23,13 @@ import { h, VNode } from 'preact';
 import { Loading } from '../../../../components/exception/loading';
 import { useConfigContext } from '../../../../context/backend';
 import { MerchantBackend } from '../../../../declaration';
-import { SwrError } from '../../../../hooks/backend';
+import { HttpError, HttpResponseServerError, RequestInfo } from 
'../../../../hooks/backend';
 import { useInstanceTips, useTipsMutateAPI } from "../../../../hooks/tips";
 import { CardTable } from './Table';
 
 interface Props {
   onUnauthorized: () => VNode;
-  onLoadError: (e: SwrError) => VNode;
+  onLoadError: (e: HttpError) => VNode;
   onNotFound: () => VNode;
 }
 export default function ({ onUnauthorized, onLoadError, onNotFound }: Props): 
VNode {
@@ -37,14 +37,11 @@ export default function ({ onUnauthorized, onLoadError, 
onNotFound }: Props): VN
   const { createReserve, deleteReserve } = useTipsMutateAPI()
   const { currency } = useConfigContext()
 
-  if (result.unauthorized) return onUnauthorized()
-  if (result.notfound) return onNotFound()
+  if (result.clientError && result.isUnauthorized) return onUnauthorized()
+  if (result.clientError && result.isNotfound) return onNotFound()
+  if (result.loading) return <Loading />
+  if (!result.ok) return onLoadError(result)
 
-  if (!result.data) {
-    if (result.error) return onLoadError(result.error)
-    return <Loading />
-  }
-  
   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({
diff --git a/packages/frontend/src/paths/instance/transfers/list/index.tsx 
b/packages/frontend/src/paths/instance/transfers/list/index.tsx
index e8e20f4..eaa3c06 100644
--- a/packages/frontend/src/paths/instance/transfers/list/index.tsx
+++ b/packages/frontend/src/paths/instance/transfers/list/index.tsx
@@ -22,27 +22,25 @@
 import { h, VNode } from 'preact';
 import { Loading } from '../../../../components/exception/loading';
 import { useConfigContext } from '../../../../context/backend';
-import { SwrError } from '../../../../hooks/backend';
+import { MerchantBackend } from '../../../../declaration';
+import { HttpError, HttpResponseServerError, RequestInfo, SwrError } from 
'../../../../hooks/backend';
 import { useInstanceTransfers, useTransferMutateAPI } from 
"../../../../hooks/transfer";
 import { CardTable } from './Table';
 
 interface Props {
   onUnauthorized: () => VNode;
-  onLoadError: (e: SwrError) => VNode;
+  onLoadError: (error: HttpError) => VNode;
   onNotFound: () => VNode;
 }
 export default function ({ onUnauthorized, onLoadError, onNotFound }: Props): 
VNode {
   const result = useInstanceTransfers()
   const { informTransfer } = useTransferMutateAPI()
-  const { currency } = useConfigContext()
   
-  if (result.unauthorized) return onUnauthorized()
-  if (result.notfound) return onNotFound();
+  if (result.clientError && result.isUnauthorized) return onUnauthorized()
+  if (result.clientError && result.isNotfound) return onNotFound()
+  if (result.loading) return <Loading />
+  if (!result.ok) return onLoadError(result)
 
-  if (!result.data) {
-    if (result.error) return onLoadError(result.error)
-    return <Loading />
-  }
   return <section class="section is-main-section">
     <CardTable instances={result.data.transfers.map(o => ({ ...o, id: 
String(o.transfer_serial_id) }))}
       onCreate={() => informTransfer({
diff --git a/packages/frontend/src/paths/instance/update/index.tsx 
b/packages/frontend/src/paths/instance/update/index.tsx
index 41c450e..f26a5d2 100644
--- a/packages/frontend/src/paths/instance/update/index.tsx
+++ b/packages/frontend/src/paths/instance/update/index.tsx
@@ -19,7 +19,7 @@ import { Loading } from 
"../../../components/exception/loading";
 import { UpdateTokenModal } from "../../../components/modal";
 import { useInstanceContext } from "../../../context/backend";
 import { MerchantBackend } from "../../../declaration";
-import { SwrError } from "../../../hooks/backend";
+import { HttpError, HttpResponseServerError, RequestInfo, SwrError } from 
"../../../hooks/backend";
 import { useInstanceAPI, useInstanceDetails } from "../../../hooks/instance";
 import { UpdatePage } from "./UpdatePage";
 
@@ -29,28 +29,25 @@ export interface Props {
 
   onUnauthorized: () => VNode;
   onNotFound: () => VNode;
-  onLoadError: (e: SwrError) => VNode;
+  onLoadError: (e: HttpError) => VNode;
   onUpdateError: (e: Error) => void;
 
 }
 
 export default function Update({ onBack, onConfirm, onLoadError, onNotFound, 
onUpdateError, onUnauthorized }: Props): VNode {
   const { updateInstance } = useInstanceAPI();
-  const details = useInstanceDetails()
+  const result = useInstanceDetails()
 
-  if (details.unauthorized) return onUnauthorized()
-  if (details.notfound) return onNotFound();
-  
-  if (!details.data) {
-    if (details.error) return onLoadError(details.error)
-    return <Loading />
-  }
+  if (result.clientError && result.isUnauthorized) return onUnauthorized()
+  if (result.clientError && result.isNotfound) return onNotFound()
+  if (result.loading) return <Loading />
+  if (!result.ok) return onLoadError(result)
 
   return <Fragment>
     <UpdatePage
       onBack={onBack}
       isLoading={false}
-      selected={details.data}
+      selected={result.data}
       onUpdate={(d: MerchantBackend.Instances.InstanceReconfigurationMessage, 
t?: MerchantBackend.Instances.InstanceAuthConfigurationMessage): Promise<void> 
=> {
         return updateInstance(d, t).then(onConfirm).catch(onUpdateError)
       }} />
diff --git a/packages/frontend/src/utils/constants.ts 
b/packages/frontend/src/utils/constants.ts
index a65f58b..8d642d7 100644
--- a/packages/frontend/src/utils/constants.ts
+++ b/packages/frontend/src/utils/constants.ts
@@ -30,4 +30,4 @@ export const INSTANCE_ID_LOOKUP = /^\/instances\/([^/]*)\/?$/
 export const PAGE_SIZE = 20
 // how bigger can be the result set
 // after this threshold, load more with move the cursor
-export const MAX_RESULT_SIZE = 39;
\ No newline at end of file
+export const MAX_RESULT_SIZE = PAGE_SIZE*2-1;
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]