[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.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [taler-merchant-backoffice] branch master updated: started to work on the product page,
gnunet <=