merchant-backoffice

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

commit 26d5d2ccbe443184aaa68757cd33dc3e9a6d7ca6
parent 86d7aea467be60343e9026d25266bb541786707b
Author: Sebastian <sebasjm@gmail.com>
Date:   Fri, 26 Mar 2021 14:11:11 -0300

taking instance name from the request parameter

Diffstat:
MCHANGELOG.md | 19+++++++++++++++++--
Mpackages/frontend/preact.config.js | 19-------------------
Mpackages/frontend/src/ApplicationReadyRoutes.tsx | 14++++++++++----
Mpackages/frontend/src/InstanceRoutes.tsx | 9+++++++--
Mpackages/frontend/src/components/exception/login.tsx | 10++++++----
Mpackages/frontend/src/components/menu/index.tsx | 24+++++++++++++++---------
Mpackages/frontend/src/hooks/backend.ts | 24+++++++++++++++++++-----
Mpackages/frontend/src/hooks/index.ts | 49++++++++++++++-----------------------------------
Mpackages/frontend/src/paths/admin/create/index.tsx | 14+++++++-------
Mpackages/frontend/src/paths/admin/list/Table.tsx | 3++-
10 files changed, 97 insertions(+), 88 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -24,14 +24,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - edit button to go to instance settings - check if there is a way to remove auto async for /routes /components/{async,routes} so it can be turned on when building non-single-bundle - - product detail: we could have some button that brings us to thedetailed screen for the product + - product detail: we could have some button that brings us to the detailed screen for the product + - order id field to go - - https://bugs.gnunet.org/view.php?id=6815 +created +wired (if wired === true) +refund + +error details +fatal error: exchange error +warning: exchange repotred problem + - navigation to another instance should not do full refresh + - cleanup instance and token management, because code is a mess and can be refactored ## [Unreleased] + - fixed bug when updating token and not admin + - showing a yellow bar on non-default instance navigation (admin) + - +## [0.0.6] - 2021-03-25 - complete order list information (#6793) - complete product list information (#6792) - missing fields in the instance update + - https://bugs.gnunet.org/view.php?id=6815 + ## [0.0.5] - 2021-03-18 - change the admin title to "instances" if we are listing the instances and "settings: $ID" on updating instances (#6790) diff --git a/packages/frontend/preact.config.js b/packages/frontend/preact.config.js @@ -61,21 +61,3 @@ export default { // config.plugins.push(new I18n()) } } - -class I18n { - constructor(options) { - this.options = options - } - - apply(compiler) { - compiler.hooks.normalModuleFactory.tap('I18n', (factory) => { - // console.log('factory', factory) - factory.hooks.parser.for('javascript/auto').tap('I18n', (parser, options) => { - // console.log('parser', parser) - parser.hooks.callAnyMember.for('console').tap('I18n', expression => { - console.log("expression", expression) - }); - }); - }) - } -} -\ No newline at end of file diff --git a/packages/frontend/src/ApplicationReadyRoutes.tsx b/packages/frontend/src/ApplicationReadyRoutes.tsx @@ -99,10 +99,16 @@ export function ApplicationReadyRoutes(): VNode { return <NotYetReadyAppMenu title="Loading..." /> } + let instance + try { + const url = new URL(window.location.href) + instance = url.searchParams.get('instance') + } finally { + if (!instance) instance = 'default' + } return <Fragment> - <Menu instance="default" admin - onLogout={clearTokenAndGoToRoot} - /> - <InstanceRoutes admin id="default" /> + <Menu instance={instance} admin onLogout={clearTokenAndGoToRoot} /> + <InstanceRoutes admin id={instance} /> </Fragment> + } diff --git a/packages/frontend/src/InstanceRoutes.tsx b/packages/frontend/src/InstanceRoutes.tsx @@ -24,7 +24,7 @@ import { useCallback, useEffect, useMemo } from "preact/hooks"; import { Route, Router, route } from 'preact-router'; import { useMessageTemplate } from 'preact-messages'; import { createHashHistory } from 'history'; -import { useBackendInstanceToken } from './hooks'; +import { useBackendDefaultToken, useBackendInstanceToken } from './hooks'; import { InstanceContextProvider, useBackendContext } from './context/backend'; import { SwrError, useInstanceDetails } from "./hooks/backend"; // import { Notification } from './utils/types'; @@ -116,6 +116,7 @@ function AdminInstanceUpdatePage({ id, ...rest }: { id: string } & InstanceUpdat } export function InstanceRoutes({ id, admin }: Props): VNode { + const [_, updateDefaultToken] = useBackendDefaultToken() const [token, updateToken] = useBackendInstanceToken(id); const { changeBackend, addTokenCleaner } = useBackendContext(); const cleaner = useCallback(() => { updateToken(undefined); }, [id]); @@ -127,8 +128,12 @@ export function InstanceRoutes({ id, admin }: Props): VNode { const updateLoginStatus = (url: string, token?: string) => { changeBackend(url); - if (token) + if (!token) return + if (admin) { updateToken(token); + } else { + updateDefaultToken(token) + } }; const value = useMemo(() => ({ id, token, admin }), [id, token]) diff --git a/packages/frontend/src/components/exception/login.tsx b/packages/frontend/src/components/exception/login.tsx @@ -22,7 +22,7 @@ import { h, VNode } from "preact"; import { useMessageTemplate } from "preact-messages"; import { useState } from "preact/hooks"; -import { useBackendContext } from "../../context/backend"; +import { useBackendContext, useInstanceContext } from "../../context/backend"; import { Notification } from "../../utils/types"; interface Props { @@ -31,9 +31,11 @@ interface Props { } export function LoginModal({ onConfirm, withMessage }: Props): VNode { - const backend = useBackendContext() - const [token, setToken] = useState(backend.token || '') - const [url, setURL] = useState(backend.url) + const { url: backendUrl, token: baseToken } = useBackendContext() + const { admin, token: instanceToken } = useInstanceContext() + const [token, setToken] = useState(!admin ? baseToken : instanceToken || '') + + const [url, setURL] = useState(backendUrl) const i18n = useMessageTemplate() return <div class="columns is-centered"> diff --git a/packages/frontend/src/components/menu/index.tsx b/packages/frontend/src/components/menu/index.tsx @@ -22,6 +22,7 @@ import { NavigationBar } from "./NavigationBar"; import { Sidebar } from "./SideBar"; import Match from 'preact-router/match'; import { Notification } from "../../utils/types"; +import { calculateRootPath } from "../../hooks"; function getInstanceTitle(path: string, id: string): string { @@ -45,12 +46,10 @@ function getInstanceTitle(path: string, id: string): string { } const INSTANCE_ID_LOOKUP = /^\/instance\/([^/]*)\// -function getAdminTitle(path: string) { +function getAdminTitle(path: string, instance: string) { if (path === AdminPaths.new_instance) return `New instance` if (path === AdminPaths.list_instances) return `Instances` - const match = INSTANCE_ID_LOOKUP.exec(path) - if (match && match[1]) return getInstanceTitle(path.replace(INSTANCE_ID_LOOKUP, '/'), match[1]); - return getInstanceTitle(path, 'default') + return getInstanceTitle(path, instance) } interface MenuProps { @@ -71,12 +70,19 @@ export function Menu({ onLogout, title, instance, admin }: MenuProps): VNode { const [mobileOpen, setMobileOpen] = useState(false) return <Match>{({ path }: any) => { - const titleWithSubtitle = title ? title : (!admin ? getInstanceTitle(path, instance) : getAdminTitle(path)) - + const titleWithSubtitle = title ? title : (!admin ? getInstanceTitle(path, instance) : getAdminTitle(path, instance)) + const adminInstance = instance === "default" return (<WithTitle title={titleWithSubtitle}> <div class={mobileOpen ? "has-aside-mobile-expanded" : ""} onClick={() => setMobileOpen(false)}> <NavigationBar onMobileMenu={() => setMobileOpen(!mobileOpen)} title={titleWithSubtitle} /> - {onLogout && <Sidebar onLogout={onLogout} admin={admin} instance={instance} mobile={mobileOpen} />} + + {onLogout && <Sidebar onLogout={onLogout} admin={admin && adminInstance} instance={instance} mobile={mobileOpen} />} + + { admin && !adminInstance && <nav class="level"> + <div class="level-item has-text-centered has-background-warning"> + <p class="is-size-5">You are viewing the instance <b>"{instance}"</b>. <a href={calculateRootPath()} >go back</a></p> + </div> + </nav> } </div> </WithTitle> ) @@ -92,7 +98,7 @@ interface NotYetReadyAppMenuProps { interface NotifProps { notification?: Notification; } -export function NotificationCard({ notification:n }: NotifProps) { +export function NotificationCard({ notification: n }: NotifProps) { if (!n) return null return <div class="notification"> <div class="columns is-vcentered"> @@ -102,7 +108,7 @@ export function NotificationCard({ notification:n }: NotifProps) { <p>{n.message}</p> </div> <div class="message-body"> - {n.description} + {n.description} </div> </article> </div> diff --git a/packages/frontend/src/hooks/backend.ts b/packages/frontend/src/hooks/backend.ts @@ -441,17 +441,26 @@ export function useInstanceMutateAPI(): InstaceMutateAPI { } export function useBackendInstances(): HttpResponse<MerchantBackend.Instances.InstancesResponse> { - const { url, token } = useBackendContext() + 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: 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 = !admin ? baseUrl : `${baseUrl}/instances/${id}` + 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) @@ -496,6 +505,12 @@ export function useInstanceOrders(args: InstanceOrderFilter, updateFilter: (d:Da 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, @@ -527,7 +542,6 @@ export function useInstanceOrders(args: InstanceOrderFilter, updateFilter: (d:Da setPageAfter(pageAfter + 1) } else { const from = afterData?.orders?.[afterData?.orders?.length-1]?.timestamp?.t_ms - // console.log(afterData?.orders?.map(d => d.row_id), PAGE_SIZE, from && format(new Date(from), 'yyyy/MM/dd HH:mm:ss')) if (from) updateFilter(new Date(from)) } } diff --git a/packages/frontend/src/hooks/index.ts b/packages/frontend/src/hooks/index.ts @@ -32,16 +32,26 @@ export function useBackendContextState() { const [cleaners, setCleaners] = useState([tokenCleaner]) const addTokenCleaner = (c: () => void) => setCleaners(cs => [...cs, c]) const addTokenCleanerMemo = useCallback((c: () => void) => { addTokenCleaner(c) }, [tokenCleaner]) + const clearAllTokens = () => { cleaners.forEach(c => c()) + for (let i = 0; i < localStorage.length; i++) { + const k = localStorage.key(i) + if (k && /^backend-token/.test(k)) localStorage.removeItem(k) + } resetBackend() } return { url, token, triedToLog, changeBackend, updateToken, lang, setLang, resetBackend, clearAllTokens, addTokenCleaner: addTokenCleanerMemo } } +export const calculateRootPath = () => { + const rootPath = typeof window !== undefined ? window.location.origin + window.location.pathname : '/' + return rootPath +} + export function useBackendURL(): [string, boolean, StateUpdater<string>, () => void] { - const [value, setter] = useNotNullLocalStorage('backend-url', typeof window !== 'undefined' ? window.location.origin : '') + const [value, setter] = useNotNullLocalStorage('backend-url', calculateRootPath()) const [triedToLog, setTriedToLog] = useLocalStorage('tried-login') const checkedSetter = (v: ValueOrFunction<string>) => { @@ -59,49 +69,18 @@ export function useBackendDefaultToken(): [string | undefined, StateUpdater<stri return useLocalStorage('backend-token') } -export function useBackendInstanceToken(id: string): [string | undefined, StateUpdater<string | undefined>, VoidFunction] { +export function useBackendInstanceToken(id: string): [string | undefined, StateUpdater<string | undefined>] { const [token, setToken] = useLocalStorage(`backend-token-${id}`) - const [ids, setIds] = useLocalStorage(`backend-token-ids`) - - function clearAllTokens() { - // TODO: refactor this - ids?.split(',').map(i => localStorage.removeItem(`backend-token-${i}`)) - } - - useEffect(() => { - setIds((ids: string | undefined): string | undefined => { - if (!ids) return ids - const all = ids.split(',') - if (all.includes(id)) return ids - return all.concat(id).filter(Boolean).join(',') - }) - }, [id, setIds]) - const [defaultToken, defaultSetToken] = useBackendDefaultToken() // instance named 'default' use the default token if (id === 'default') { - return [defaultToken, defaultSetToken, clearAllTokens] + return [defaultToken, defaultSetToken] } - return [token, setToken, clearAllTokens] + return [token, setToken] } -// export function useBackend(): [State, StateUpdater<State>] { -// const [url, setUrl] = useLocalStorage('backend-url', window.location.origin) -// const [token, setToken] = useLocalStorage('backend-token') - -// const updater: StateUpdater<State> = (value: State | ((value: State) => State)) => { -// const valueToStore = value instanceof Function ? value({ token, url: url || window.location.origin }) : value; -// setUrl(valueToStore.url) -// setToken(valueToStore.token) - -// mutate('/private/instances', null) -// } - -// return [{ token, url: url || window.location.origin }, updater] -// } - export function useLang(): [string, StateUpdater<string>] { const browserLang = typeof window !== "undefined" ? navigator.language || (navigator as any).userLanguage : undefined; const defaultLang = (browserLang || 'en').substring(0,2) diff --git a/packages/frontend/src/paths/admin/create/index.tsx b/packages/frontend/src/paths/admin/create/index.tsx @@ -15,9 +15,6 @@ */ import { ComponentChildren, Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; -import { Loading } from "../../../components/exception/loading"; -import { FormProvider } from "../../../components/form/Field"; -import { Input } from "../../../components/form/Input"; import { NotificationCard } from "../../../components/menu"; import { MerchantBackend } from "../../../declaration"; import { useAdminMutateAPI } from "../../../hooks/backend"; @@ -46,7 +43,7 @@ export default function Create({ onBack, onConfirm, onError, forceId }: Props): <div class="field-body is-flex-grow-3"> <div class="field"> <p class="control"> - <input class="input" readonly value={createdOk.id} readOnly /> + <input class="input" readonly value={createdOk.id} /> </p> </div> </div> @@ -58,7 +55,7 @@ export default function Create({ onBack, onConfirm, onError, forceId }: Props): <div class="field-body is-flex-grow-3"> <div class="field"> <p class="control"> - <input class="input" readonly value={createdOk.name} disabled /> + <input class="input" readonly value={createdOk.name} /> </p> </div> </div> @@ -70,7 +67,10 @@ export default function Create({ onBack, onConfirm, onError, forceId }: Props): <div class="field-body is-flex-grow-3"> <div class="field"> <p class="control"> - <input class="input" readonly value={createdOk.auth.token} disabled /> + {createdOk.auth.method === 'external' && 'external'} + {createdOk.auth.method === 'token' && + <input class="input" readonly value={createdOk.auth.token} /> + } </p> </div> </div> @@ -107,7 +107,7 @@ interface CreatedSuccessfullyProps { function CreatedSuccessfully({ children, onConfirm }: CreatedSuccessfullyProps) { return <div class="columns is-fullwidth is-vcentered content-full-size"> <div class="column" /> - <div class="column is-half"> + <div class="column is-three-quarters"> <div class="card"> <header class="card-header has-background-success"> <p class="card-header-title has-text-white-ter"> diff --git a/packages/frontend/src/paths/admin/list/Table.tsx b/packages/frontend/src/paths/admin/list/Table.tsx @@ -24,6 +24,7 @@ import { Message } from "preact-messages" import { StateUpdater, useEffect, useState } from "preact/hooks" import { useBackendContext } from "../../../context/backend"; import { MerchantBackend } from "../../../declaration" +import { calculateRootPath } from "../../../hooks"; interface Props { instances: MerchantBackend.Instances.Instance[]; @@ -121,7 +122,7 @@ function Table({ rowSelection, rowSelectionHandler, instances, onUpdate, onDelet <span class="check" /> </label> </td> - <td><a href={`/instances/${i.id}`} {...{ native: true }} onClick={() => changeBackend(`${url}/instances/${i.id}`)} >{i.id}</a></td> + <td><a href={`${calculateRootPath()}?instance=${i.id}`} {...{ native: true }} >{i.id}</a></td> <td >{i.name}</td> <td class="is-actions-cell right-sticky"> <div class="buttons is-right">