merchant-backoffice

ZZZ: Inactive/Deprecated
Log | Files | Refs | Submodules | README

commit b15cf80c3448501d042f34b542ed7c34314261c3
parent 9685d16d8c88b78ae1e992d3e9e676497b730ab1
Author: Sebastian <sebasjm@gmail.com>
Date:   Thu,  1 Apr 2021 12:36:15 -0300

started to work on the product page

Diffstat:
Mpackages/frontend/src/InstanceRoutes.tsx | 445++++++++++++++++++++++---------------------------------------------------------
Apackages/frontend/src/hooks/admin.ts | 34++++++++++++++++++++++++++++++++++
Mpackages/frontend/src/hooks/backend.ts | 506++-----------------------------------------------------------------------------
Apackages/frontend/src/hooks/instance.ts | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apackages/frontend/src/hooks/order.ts | 177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apackages/frontend/src/hooks/product.ts | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apackages/frontend/src/hooks/tips.ts | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apackages/frontend/src/hooks/transfer.ts | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/frontend/src/messages/en.po | 22++++++++++++++++++++++
Mpackages/frontend/src/paths/admin/create/index.tsx | 4++--
Mpackages/frontend/src/paths/admin/list/index.tsx | 6++++--
Mpackages/frontend/src/paths/instance/details/index.tsx | 6+++---
Mpackages/frontend/src/paths/instance/orders/create/OrderCreatedSuccessfully.tsx | 4++--
Mpackages/frontend/src/paths/instance/orders/create/index.tsx | 10++++++++--
Mpackages/frontend/src/paths/instance/orders/details/index.tsx | 5+++--
Mpackages/frontend/src/paths/instance/orders/list/index.tsx | 6+++---
Mpackages/frontend/src/paths/instance/products/list/Table.tsx | 87+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mpackages/frontend/src/paths/instance/products/list/index.tsx | 19+++++++++++++++----
Mpackages/frontend/src/paths/instance/tips/list/index.tsx | 3++-
Mpackages/frontend/src/paths/instance/transfers/list/index.tsx | 3++-
Mpackages/frontend/src/paths/instance/update/index.tsx | 5+++--
21 files changed, 812 insertions(+), 872 deletions(-)

diff --git 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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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()