merchant-backoffice

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

commit 40bcbdfa0543f3cb3aa4eb84df098b9b48af94c7
parent 5365512386a26e4f48ac50d3e99ae855ce0c75fa
Author: Sebastian <sebasjm@gmail.com>
Date:   Thu, 16 Dec 2021 16:18:52 -0300

-formatted with prettier

Diffstat:
Mpackages/merchant-backend/src/hooks/product.ts | 207+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mpackages/merchant-backoffice/src/ApplicationReadyRoutes.tsx | 142+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mpackages/merchant-backoffice/src/components/menu/SideBar.tsx | 177+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mpackages/merchant-backoffice/src/hooks/instance.ts | 15++++++++-------
4 files changed, 351 insertions(+), 190 deletions(-)

diff --git a/packages/merchant-backend/src/hooks/product.ts b/packages/merchant-backend/src/hooks/product.ts @@ -13,48 +13,70 @@ You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { useEffect } from 'preact/hooks'; -import useSWR, { trigger, useSWRInfinite, cache, mutate } from 'swr'; -import { useBackendContext } from '../context/backend'; +import { useEffect } from "preact/hooks"; +import useSWR, { trigger, useSWRInfinite, cache, mutate } from "swr"; +import { useBackendContext } from "../context/backend"; // import { useFetchContext } from '../context/fetch'; -import { useInstanceContext } from '../context/instance'; -import { MerchantBackend, WithId } from '../declaration'; -import { fetcher, HttpError, HttpResponse, HttpResponseOk, mutateAll, request } from './backend'; +import { useInstanceContext } from "../context/instance"; +import { MerchantBackend, WithId } from "../declaration"; +import { + fetcher, + HttpError, + HttpResponse, + HttpResponseOk, + mutateAll, + request, +} from "./backend"; export interface ProductAPI { - createProduct: (data: MerchantBackend.Products.ProductAddDetail) => Promise<void>; - updateProduct: (id: string, data: MerchantBackend.Products.ProductPatchDetail) => Promise<void>; + 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>; + 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> => { + 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', + method: "post", token, - data + data, }); await mutateAll(/@"\/private\/products"@/, null); }; - const updateProduct = async (productId: string, data: MerchantBackend.Products.ProductPatchDetail): Promise<void> => { + const updateProduct = async ( + productId: string, + data: MerchantBackend.Products.ProductPatchDetail + ): Promise<void> => { const r = await request(`${url}/private/products/${productId}`, { - method: 'patch', + method: "patch", token, - data + data, }); /** @@ -62,7 +84,7 @@ export function useProductAPI(): ProductAPI { * I'm keeping this for later inspection */ - // -- Clear all cache + // -- Clear all cache // -- This seems to work always but is bad // const keys = [...cache.keys()] @@ -71,7 +93,7 @@ export function useProductAPI(): ProductAPI { // await Promise.all(keys.map(k => trigger(k))) // -- From the keys to the cache trigger - // -- An intermediate step + // -- An intermediate step // const keys = [ // [`/private/products`, token, url], @@ -86,7 +108,7 @@ export function useProductAPI(): ProductAPI { // await Promise.all(keys.map(k => mutate(k))) - // -- This is how is supposed to be use + // -- This is how is supposed to be use // await mutate([`/private/products`, token, url]) // await mutate([`/private/products/${productId}`, token, url]) @@ -97,23 +119,26 @@ export function useProductAPI(): ProductAPI { // return r // -- FIXME: why this un-break the tests? - return Promise.resolve() + return Promise.resolve(); }; const deleteProduct = async (productId: string): Promise<void> => { await request(`${url}/private/products/${productId}`, { - method: 'delete', + method: "delete", token, }); await mutateAll(/@"\/private\/products"@/); }; - const lockProduct = async (productId: string, data: MerchantBackend.Products.LockRequest): Promise<void> => { + const lockProduct = async ( + productId: string, + data: MerchantBackend.Products.LockRequest + ): Promise<void> => { await request(`${url}/private/products/${productId}/lock`, { - method: 'post', + method: "post", token, - data + data, }); await mutateAll(/@"\/private\/products"@/); @@ -122,19 +147,31 @@ export function useProductAPI(): ProductAPI { return { createProduct, updateProduct, deleteProduct, lockProduct }; } - -export function useInstanceProducts(): HttpResponse<(MerchantBackend.Products.ProductDetail & WithId)[]> { +export function useInstanceProducts(): HttpResponse< + (MerchantBackend.Products.ProductDetail & WithId)[] +> { const { url: baseUrl, token: baseToken } = useBackendContext(); const { token: instanceToken, id, admin } = useInstanceContext(); // const { useSWR, useSWRInfinite } = useFetchContext(); - const { url, token } = !admin ? { - url: baseUrl, token: baseToken - } : { - url: `${baseUrl}/instances/${id}`, token: instanceToken - }; - - const { data: list, error: listError, isValidating: listLoading } = useSWR<HttpResponseOk<MerchantBackend.Products.InventorySummaryResponse>, HttpError>([`/private/products`, token, url], fetcher, { + const { url, token } = !admin + ? { + url: baseUrl, + token: baseToken, + } + : { + url: `${baseUrl}/instances/${id}`, + token: instanceToken, + }; + + const { + data: list, + error: listError, + isValidating: listLoading, + } = useSWR< + HttpResponseOk<MerchantBackend.Products.InventorySummaryResponse>, + HttpError + >([`/private/products`, token, url], fetcher, { refreshInterval: 0, refreshWhenHidden: false, revalidateOnFocus: false, @@ -142,58 +179,84 @@ export function useInstanceProducts(): HttpResponse<(MerchantBackend.Products.Pr refreshWhenOffline: false, }); - const { data: products, error: productError, setSize, size } = useSWRInfinite<HttpResponseOk<MerchantBackend.Products.ProductDetail>, HttpError>((pageIndex: number) => { - if (!list?.data || !list.data.products.length || listError || listLoading) return null - return [`/private/products/${list.data.products[pageIndex].product_id}`, token, url] - }, fetcher, { - revalidateAll: true, - }) + const { + data: products, + error: productError, + setSize, + size, + } = useSWRInfinite< + HttpResponseOk<MerchantBackend.Products.ProductDetail>, + HttpError + >( + (pageIndex: number) => { + if (!list?.data || !list.data.products.length || listError || listLoading) + return null; + return [ + `/private/products/${list.data.products[pageIndex].product_id}`, + token, + url, + ]; + }, + fetcher, + { + revalidateAll: true, + } + ); useEffect(() => { if (list?.data && list.data.products.length > 0) { - setSize(list.data.products.length) + setSize(list.data.products.length); } - }, [list?.data.products.length, listLoading]) - + }, [list?.data.products.length, listLoading]); - if (listLoading) return { loading: true, data: [] } - if (listError) return listError - if (productError) return productError + if (listLoading) return { loading: true, data: [] }; + if (listError) return listError; + if (productError) return productError; if (list?.data && list.data.products.length === 0) { - return { ok: true, data: [] } + return { ok: true, data: [] }; } if (products) { const dataWithId = products.map((d) => { //take the id from the queried url - return ({ ...d.data, id: d.info?.url.replace(/.*\/private\/products\//, '') || '' }) - }) - return { ok: true, data: dataWithId } + return { + ...d.data, + id: d.info?.url.replace(/.*\/private\/products\//, "") || "", + }; + }); + return { ok: true, data: dataWithId }; } - return { loading: true } + return { loading: true }; } -export function useProductDetails(productId: string): HttpResponse<MerchantBackend.Products.ProductDetail> { +export function useProductDetails( + productId: string +): HttpResponse<MerchantBackend.Products.ProductDetail> { 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, isValidating } = useSWR<HttpResponseOk<MerchantBackend.Products.ProductDetail>, HttpError>( - [`/private/products/${productId}`, token, url], fetcher, { + const { url, token } = !admin + ? { + url: baseUrl, + token: baseToken, + } + : { + url: `${baseUrl}/instances/${id}`, + token: instanceToken, + }; + + const { data, error, isValidating } = useSWR< + HttpResponseOk<MerchantBackend.Products.ProductDetail>, + HttpError + >([`/private/products/${productId}`, token, url], fetcher, { refreshInterval: 0, refreshWhenHidden: false, revalidateOnFocus: false, revalidateOnReconnect: false, refreshWhenOffline: false, - } - ) + }); - if (isValidating) return { loading: true, data: data?.data } - if (data) return data - if (error) return error - return { loading: true } + if (isValidating) return { loading: true, data: data?.data }; + if (data) return data; + if (error) return error; + return { loading: true }; } diff --git a/packages/merchant-backoffice/src/ApplicationReadyRoutes.tsx b/packages/merchant-backoffice/src/ApplicationReadyRoutes.tsx @@ -15,86 +15,120 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ -import { Fragment, h, VNode } from 'preact'; -import Router, { Route, route } from 'preact-router'; -import { useBackendContext } from './context/backend'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { Fragment, h, VNode } from "preact"; +import Router, { Route, route } from "preact-router"; +import { useBackendContext } from "./context/backend"; import { useBackendInstancesTestForAdmin } from "./hooks/backend"; import { InstanceRoutes } from "./InstanceRoutes"; -import LoginPage from './paths/login'; -import { INSTANCE_ID_LOOKUP } from './utils/constants'; -import { NotYetReadyAppMenu, Menu, NotificationCard } from './components/menu'; -import { useTranslator } from './i18n'; -import { createHashHistory } from 'history'; -import { useState } from 'preact/hooks'; +import LoginPage from "./paths/login"; +import { INSTANCE_ID_LOOKUP } from "./utils/constants"; +import { NotYetReadyAppMenu, Menu, NotificationCard } from "./components/menu"; +import { useTranslator } from "./i18n"; +import { createHashHistory } from "history"; +import { useState } from "preact/hooks"; export function ApplicationReadyRoutes(): VNode { const i18n = useTranslator(); - const { url: backendURL, updateLoginStatus, clearAllTokens } = useBackendContext(); + const { + url: backendURL, + updateLoginStatus, + clearAllTokens, + } = useBackendContext(); - const result = useBackendInstancesTestForAdmin() + const result = useBackendInstancesTestForAdmin(); const clearTokenAndGoToRoot = () => { clearAllTokens(); - route('/') - } + route("/"); + }; if (result.clientError && result.isUnauthorized) { - return <Fragment> - <NotYetReadyAppMenu title="Login" onLogout={clearTokenAndGoToRoot} /> - <NotificationCard notification={{ - message: i18n`Access denied`, - description: i18n`Check your token is valid`, - type: 'ERROR' - }} - /> - <LoginPage onConfirm={updateLoginStatus} /> - </Fragment> + return ( + <Fragment> + <NotYetReadyAppMenu title="Login" onLogout={clearTokenAndGoToRoot} /> + <NotificationCard + notification={{ + message: i18n`Access denied`, + description: i18n`Check your token is valid`, + type: "ERROR", + }} + /> + <LoginPage onConfirm={updateLoginStatus} /> + </Fragment> + ); } - - if (result.loading) return <NotYetReadyAppMenu title="Loading..." /> + if (result.loading) return <NotYetReadyAppMenu title="Loading..." />; let admin = true; let instanceNameByBackendURL; if (!result.ok) { - const path = new URL(backendURL).pathname - const match = INSTANCE_ID_LOOKUP.exec(path) + const path = new URL(backendURL).pathname; + const match = INSTANCE_ID_LOOKUP.exec(path); if (!match || !match[1]) { // this should be rare because // query to /config is ok but the URL // does not match our pattern - return <Fragment> - <NotYetReadyAppMenu title="Error" onLogout={clearTokenAndGoToRoot} /> - <NotificationCard notification={{ - message: i18n`Couldn't access the server.`, - description: i18n`Could not infer instance id from url ${backendURL}`, - type: 'ERROR', - }} - /> - <LoginPage onConfirm={updateLoginStatus} /> - </Fragment> + return ( + <Fragment> + <NotYetReadyAppMenu title="Error" onLogout={clearTokenAndGoToRoot} /> + <NotificationCard + notification={{ + message: i18n`Couldn't access the server.`, + description: i18n`Could not infer instance id from url ${backendURL}`, + type: "ERROR", + }} + /> + <LoginPage onConfirm={updateLoginStatus} /> + </Fragment> + ); } - admin = false - instanceNameByBackendURL = match[1] + admin = false; + instanceNameByBackendURL = match[1]; } - const history = createHashHistory() - return <Router history={history}> - <Route default component={DefaultMainRoute} clearTokenAndGoToRoot={clearTokenAndGoToRoot} admin={admin} instanceNameByBackendURL={instanceNameByBackendURL} /> - </Router> + const history = createHashHistory(); + return ( + <Router history={history}> + <Route + default + component={DefaultMainRoute} + clearTokenAndGoToRoot={clearTokenAndGoToRoot} + admin={admin} + instanceNameByBackendURL={instanceNameByBackendURL} + /> + </Router> + ); } -function DefaultMainRoute({ clearTokenAndGoToRoot, instance, admin, instanceNameByBackendURL }: any) { - const [instanceName, setInstanceName] = useState(instanceNameByBackendURL || instance || 'default') - - return <Fragment> - <Menu instance={instanceName} admin={admin} onLogout={clearTokenAndGoToRoot} setInstanceName={setInstanceName} /> - <InstanceRoutes admin={admin} id={instanceName} setInstanceName={setInstanceName} /> - </Fragment> - +function DefaultMainRoute({ + clearTokenAndGoToRoot, + instance, + admin, + instanceNameByBackendURL, +}: any) { + const [instanceName, setInstanceName] = useState( + instanceNameByBackendURL || instance || "default" + ); + + return ( + <Fragment> + <Menu + instance={instanceName} + admin={admin} + onLogout={clearTokenAndGoToRoot} + setInstanceName={setInstanceName} + /> + <InstanceRoutes + admin={admin} + id={instanceName} + setInstanceName={setInstanceName} + /> + </Fragment> + ); } diff --git a/packages/merchant-backoffice/src/components/menu/SideBar.tsx b/packages/merchant-backoffice/src/components/menu/SideBar.tsx @@ -15,18 +15,17 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { Fragment, h, VNode } from 'preact'; -import { useCallback } from 'preact/hooks'; -import { useBackendContext } from '../../context/backend'; -import { useConfigContext } from '../../context/config'; -import { useInstanceContext } from '../../context/instance'; -import { Translate } from '../../i18n'; -import { LangSelector } from './LangSelector'; +import { Fragment, h, VNode } from "preact"; +import { useCallback } from "preact/hooks"; +import { useBackendContext } from "../../context/backend"; +import { useConfigContext } from "../../context/config"; +import { useInstanceContext } from "../../context/instance"; +import { Translate } from "../../i18n"; +import { LangSelector } from "./LangSelector"; interface Props { onLogout: () => void; @@ -36,7 +35,13 @@ interface Props { mimic?: boolean; } -export function Sidebar({ mobile, instance, onLogout, admin, mimic }: Props): VNode { +export function Sidebar({ + mobile, + instance, + onLogout, + admin, + mimic, +}: Props): VNode { const config = useConfigContext(); const backend = useBackendContext(); @@ -46,16 +51,30 @@ export function Sidebar({ mobile, instance, onLogout, admin, mimic }: Props): VN // } // return path // },[instance]) - + return ( <aside class="aside is-placed-left is-expanded"> - { mobile && <div class="footer" onClick={(e) => { return e.stopImmediatePropagation() }}> - <LangSelector /> - </div>} + {mobile && ( + <div + class="footer" + onClick={(e) => { + return e.stopImmediatePropagation(); + }} + > + <LangSelector /> + </div> + )} <div class="aside-tools"> <div class="aside-tools-label"> - <div><b>Taler</b> Backoffice</div> - <div class="is-size-7 has-text-right" style={{ lineHeight: 0, marginTop: -10 }}>{process.env.__VERSION__} ({config.version})</div> + <div> + <b>Taler</b> Backoffice + </div> + <div + class="is-size-7 has-text-right" + style={{ lineHeight: 0, marginTop: -10 }} + > + {process.env.__VERSION__} ({config.version}) + </div> </div> </div> <div class="menu is-menu-main"> @@ -64,79 +83,124 @@ export function Sidebar({ mobile, instance, onLogout, admin, mimic }: Props): VN </p> <ul class="menu-list"> <li> - <a href={("/update")} class="has-icon"> - <span class="icon"><i class="mdi mdi-square-edit-outline" /></span> - <span class="menu-item-label"><Translate>Settings</Translate></span> + <a href={"/update"} class="has-icon"> + <span class="icon"> + <i class="mdi mdi-square-edit-outline" /> + </span> + <span class="menu-item-label"> + <Translate>Settings</Translate> + </span> </a> </li> <li> - <a href={("/orders")} class="has-icon"> - <span class="icon"><i class="mdi mdi-cash-register" /></span> - <span class="menu-item-label"><Translate>Orders</Translate></span> + <a href={"/orders"} class="has-icon"> + <span class="icon"> + <i class="mdi mdi-cash-register" /> + </span> + <span class="menu-item-label"> + <Translate>Orders</Translate> + </span> </a> </li> <li> - <a href={("/products")} class="has-icon"> - <span class="icon"><i class="mdi mdi-shopping" /></span> - <span class="menu-item-label"><Translate>Products</Translate></span> + <a href={"/products"} class="has-icon"> + <span class="icon"> + <i class="mdi mdi-shopping" /> + </span> + <span class="menu-item-label"> + <Translate>Products</Translate> + </span> </a> </li> <li> - <a href={("/transfers")} class="has-icon"> - <span class="icon"><i class="mdi mdi-bank" /></span> - <span class="menu-item-label"><Translate>Transfers</Translate></span> + <a href={"/transfers"} class="has-icon"> + <span class="icon"> + <i class="mdi mdi-bank" /> + </span> + <span class="menu-item-label"> + <Translate>Transfers</Translate> + </span> </a> </li> <li> - <a href={("/reserves")} class="has-icon"> - <span class="icon"><i class="mdi mdi-cash" /></span> + <a href={"/reserves"} class="has-icon"> + <span class="icon"> + <i class="mdi mdi-cash" /> + </span> <span class="menu-item-label">Reserves</span> </a> </li> </ul> - <p class="menu-label"><Translate>Connection</Translate></p> + <p class="menu-label"> + <Translate>Connection</Translate> + </p> <ul class="menu-list"> <li> <div> - <span style={{ width: '3rem' }} class="icon"><i class="mdi mdi-currency-eur" /></span> + <span style={{ width: "3rem" }} class="icon"> + <i class="mdi mdi-currency-eur" /> + </span> <span class="menu-item-label">{config.currency}</span> </div> </li> <li> - <div > - <span style={{ width: '3rem' }} class="icon"><i class="mdi mdi-web" /></span> + <div> + <span style={{ width: "3rem" }} class="icon"> + <i class="mdi mdi-web" /> + </span> <span class="menu-item-label"> {new URL(backend.url).hostname} </span> </div> </li> <li> - <div > - <span style={{ width: '3rem' }} class="icon">ID</span> + <div> + <span style={{ width: "3rem" }} class="icon"> + ID + </span> <span class="menu-item-label"> {!instance ? "default" : instance} </span> </div> </li> - {admin && !mimic && <Fragment> - <p class="menu-label"><Translate>Instances</Translate></p> - <li> - <a href={("/instance/new")} class="has-icon"> - <span class="icon"><i class="mdi mdi-plus" /></span> - <span class="menu-item-label"><Translate>New</Translate></span> - </a> - </li> - <li> - <a href={("/instances")} class="has-icon"> - <span class="icon"><i class="mdi mdi-format-list-bulleted" /></span> - <span class="menu-item-label"><Translate>List</Translate></span> - </a> - </li> - </Fragment>} + {admin && !mimic && ( + <Fragment> + <p class="menu-label"> + <Translate>Instances</Translate> + </p> + <li> + <a href={"/instance/new"} class="has-icon"> + <span class="icon"> + <i class="mdi mdi-plus" /> + </span> + <span class="menu-item-label"> + <Translate>New</Translate> + </span> + </a> + </li> + <li> + <a href={"/instances"} class="has-icon"> + <span class="icon"> + <i class="mdi mdi-format-list-bulleted" /> + </span> + <span class="menu-item-label"> + <Translate>List</Translate> + </span> + </a> + </li> + </Fragment> + )} <li> - <a class="has-icon is-state-info is-hoverable" onClick={(): void => onLogout()}> - <span class="icon"><i class="mdi mdi-logout default" /></span> - <span class="menu-item-label"><Translate>Log out</Translate></span> + <a + class="has-icon is-state-info is-hoverable" + onClick={(): void => onLogout()} + > + <span class="icon"> + <i class="mdi mdi-logout default" /> + </span> + <span class="menu-item-label"> + <Translate>Log out</Translate> + </span> </a> </li> </ul> @@ -144,4 +208,3 @@ export function Sidebar({ mobile, instance, onLogout, admin, mimic }: Props): VN </aside> ); } - diff --git a/packages/merchant-backoffice/src/hooks/instance.ts b/packages/merchant-backoffice/src/hooks/instance.ts @@ -22,7 +22,8 @@ import { HttpError, HttpResponse, HttpResponseOk, - request, useMatchMutate + request, + useMatchMutate, } from "./backend"; interface InstanceAPI { @@ -64,8 +65,8 @@ export function useAdminAPI(): AdminAPI { method: "delete", token, params: { - purge: 'YES' - } + purge: "YES", + }, }); mutateAll(/\/management\/instances/); @@ -136,8 +137,8 @@ export function useInstanceAPI(): InstanceAPI { const { token: instanceToken, id, admin } = useInstanceContext(); const { url, token } = !admin - ? { url: baseUrl, token: adminToken, } - : { url: `${baseUrl}/instances/${id}`, token: instanceToken, }; + ? { url: baseUrl, token: adminToken } + : { url: `${baseUrl}/instances/${id}`, token: instanceToken }; const updateInstance = async ( instance: MerchantBackend.Instances.InstanceReconfigurationMessage @@ -190,8 +191,8 @@ export function useInstanceDetails(): HttpResponse<MerchantBackend.Instances.Que const { token: instanceToken, id, admin } = useInstanceContext(); const { url, token } = !admin - ? { url: baseUrl, token: baseToken, } - : { url: `${baseUrl}/instances/${id}`, token: instanceToken, }; + ? { url: baseUrl, token: baseToken } + : { url: `${baseUrl}/instances/${id}`, token: instanceToken }; const { data, error, isValidating } = useSWR< HttpResponseOk<MerchantBackend.Instances.QueryInstancesResponse>,