merchant-backoffice

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

commit 9128d984cefe1a337a63f5f02493d53ededc6ede
parent a868b2376d1fd6fc83118a1cc2cb76ff6671c8b8
Author: Sebastian <sebasjm@gmail.com>
Date:   Wed,  3 Mar 2021 14:34:54 -0300

improve app routing

Diffstat:
MCHANGELOG.md | 19+++++++++++++++----
Mpackages/frontend/.storybook/preview.js | 4++--
Apackages/frontend/src/AdminRoutes.tsx | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/frontend/src/ApplicationReadyRoutes.tsx | 167++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Mpackages/frontend/src/InstanceRoutes.tsx | 34+++++++++++++++++++++++-----------
5 files changed, 215 insertions(+), 78 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -13,20 +13,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - validate everything using onChange - feature: input as date format - bug: there is missing a mutate call when updating to remove the instance from cache - - submit form on key press == enter - - (WIP) remove checkbox from auth token, use button (manage auth) - - (WIP) auth token config as popup with 3 actions (clear (sure?), cancel, set token) - add order section - add product section - add tips section - - implement better error handling + - implement better error handling (improve creation of duplicated instances) - replace Yup and type definition with a taler-library for the purpose (first wait Florian to refactor wallet core) - add more doc style comments - configure eslint - configure prettier - prune scss styles to reduce size - create a loading page to be use when the data is not ready + +## [Unreleased] + - submit form on key press == enter + - version of backoffice in sidebar + - fixed login dialog on mobile + - LangSelector ascomponent + - refactored Navigation and Sidebar + - do not display Logout when there is no token + - fix: Login Page should show on unauthorized + - fix: row clicking on card table was overriding checkbox onClick + - remove headers of the page + - clear all tokens now remove backend-url + - (WIP) remove checkbox from auth token, use button (manage auth) + - (WIP) auth token config as popup with 3 actions (clear (sure?), cancel, set token) ## [0.0.2] - 2021-02-25 - REFACTOR: remove react-i18n and implement messageformat diff --git a/packages/frontend/.storybook/preview.js b/packages/frontend/.storybook/preview.js @@ -1,6 +1,6 @@ import "../src/scss/main.scss" import { MessageProvider } from "preact-messages"; -import { ConfigContext } from '../src/context/backend' +import { ConfigContextProvider } from '../src/context/backend' import * as messages from '../src/messages' import { h } from 'preact'; @@ -35,5 +35,5 @@ export const decorators = [ <Story /> </MessageProvider> }, - (Story) => <ConfigContext.Provider value={mockConfig}> <Story /> </ConfigContext.Provider> + (Story) => <ConfigContextProvider value={mockConfig}> <Story /> </ConfigContextProvider> ]; diff --git a/packages/frontend/src/AdminRoutes.tsx b/packages/frontend/src/AdminRoutes.tsx @@ -0,0 +1,68 @@ +import { h, VNode } from "preact"; +import Router, { route, Route } from "preact-router"; +import { RootPaths, Redirect } from "./index"; +import NotFoundPage from './routes/notfound'; +import InstanceListPage from './routes/instances/list'; +import InstanceCreatePage from "./routes/instances/create"; +import { MerchantBackend } from "./declaration"; +import { useMessageTemplate } from "preact-messages"; +import { Notification } from "./utils/types"; +import { InstanceRoutes } from "./InstanceRoutes"; + +interface Props { + pushNotification: (n: Notification) => void; + instances: MerchantBackend.Instances.Instance[] + addTokenCleaner: any; +} +export function AdminRoutes({ instances, pushNotification, addTokenCleaner }: Props): VNode { + const i18n = useMessageTemplate(); + + return <Router> + <Route path={RootPaths.root} component={Redirect} to={RootPaths.list_instances} /> + + <Route path={RootPaths.list_instances} component={InstanceListPage} + + onCreate={() => { + route(RootPaths.new_instance); + }} + + instances={instances} + + onUpdate={(id: string): void => { + route(`/instance/${id}/update`); + }} + + // onUnauthorized={() => <LoginPage + // withMessage={{ message: i18n`Access denied`, description: i18n`Check your token is valid`, type: 'ERROR', }} + // onConfirm={updateLoginStatus} />} + + // onQueryError={(error: SwrError) => <LoginPage + // withMessage={{ message: i18n`Problem reaching the server`, description: i18n`Got message: ${error.message} from: ${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR', }} + // onConfirm={updateLoginStatus} />} + + /> + + <Route path={RootPaths.new_instance} component={InstanceCreatePage} + + onBack={() => route(RootPaths.list_instances)} + + onConfirm={() => { + pushNotification({ message: i18n`create_success`, type: 'SUCCESS' }); + route(RootPaths.list_instances); + }} + + onError={(error: any) => { + pushNotification({ message: i18n`create_error`, type: 'ERROR' }); + }} + /> + + <Route path={RootPaths.instance_id_route} component={InstanceRoutes} + pushNotification={pushNotification} + addTokenCleaner={addTokenCleaner} + parent="/instance/:id" + /> + + <Route default component={NotFoundPage} /> + + </Router> +} +\ No newline at end of file diff --git a/packages/frontend/src/ApplicationReadyRoutes.tsx b/packages/frontend/src/ApplicationReadyRoutes.tsx @@ -18,73 +18,118 @@ * * @author Sebastian Javier Marchano (sebasjm) */ -import { h, VNode } from 'preact'; -import { useContext } from "preact/hooks"; -import { Route, Router, route } from 'preact-router'; -import { useMessageTemplate } from 'preact-messages'; +import { Fragment, h, VNode } from 'preact'; +import { route } from 'preact-router'; import { Notification } from "./utils/types"; -import { BackendContext } from './context/backend'; -import { SwrError } from "./hooks/backend"; +import { useBackendContext } from './context/backend'; +import { useBackendInstances } from "./hooks/backend"; import { InstanceRoutes } from "./InstanceRoutes"; -import { RootPaths, Redirect } from "./index"; -import NotFoundPage from './routes/notfound'; import LoginPage from './routes/login'; -import InstanceListPage from './routes/instances/list'; -import InstanceCreatePage from "./routes/instances/create"; -import { Notifications } from './components/notifications'; - -export function ApplicationReadyRoutes({ pushNotification, addTokenCleaner }: { pushNotification: (n: Notification) => void; addTokenCleaner: any; }): VNode { - const { changeBackend, updateToken } = useContext(BackendContext); +import { INSTANCE_ID_LOOKUP } from './utils/constants'; +import { Menu } from './components/menu'; +import { AdminRoutes } from './AdminRoutes'; +import { useMessageTemplate } from 'preact-messages'; +interface Props { + pushNotification: (n: Notification) => void; + addTokenCleaner: any; + clearAllTokens: () => void; +} +export function ApplicationReadyRoutes({ pushNotification, addTokenCleaner, clearAllTokens }: Props): VNode { + const i18n = useMessageTemplate(); + const { url: currentBaseUrl, changeBackend, updateToken } = useBackendContext(); const updateLoginStatus = (url: string, token?: string) => { changeBackend(url); - if (token) - updateToken(token); + if (token) updateToken(token); }; - const i18n = useMessageTemplate(); - - return <Router> - <Route path={RootPaths.root} component={Redirect} - to={RootPaths.list_instances} /> - - <Route path={RootPaths.list_instances} component={InstanceListPage} - - onCreate={() => { - route(RootPaths.new_instance); - }} - - onUpdate={(id: string): void => { - route(`/instance/${id}/update`); - }} - - onUnauthorized={() => <LoginPage - withMessage={{ message: i18n`Access denied`, description: i18n`Check your token is valid`, type: 'ERROR', }} - onConfirm={updateLoginStatus} />} - - onQueryError={(error: SwrError) => <LoginPage - withMessage={{ message: i18n`Problem reaching the server`, description: i18n`Got message: ${error.message} from: ${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR', }} - onConfirm={updateLoginStatus} />} - - /> - - <Route path={RootPaths.new_instance} component={InstanceCreatePage} - - onBack={() => route(RootPaths.list_instances)} - - onConfirm={() => { - pushNotification({ message: i18n`create_success`, type: 'SUCCESS' }); - route(RootPaths.list_instances); - }} - - onError={(error: any) => { - pushNotification({ message: i18n`create_error`, type: 'ERROR' }); - }} /> - - <Route path={RootPaths.instance_id_route} component={InstanceRoutes} + const list = useBackendInstances() + + if (!list.data) { + if (list.unauthorized) { + return <Fragment> + <Menu onLogout={() => { + clearAllTokens(); + route('/') + }} /> + <LoginPage + withMessage={{ message: i18n`Access denied`, description: i18n`Check your token is valid`, type: 'ERROR', }} + onConfirm={updateLoginStatus} + /> + </Fragment> + } + if (list.notfound) { + try { + const path = new URL(currentBaseUrl).pathname + const match = INSTANCE_ID_LOOKUP.exec(path) + if (!match || !match[1]) throw new Error(i18n`Could not infer instance id from url ${currentBaseUrl}`) + return <Fragment> + <Menu instance={match[1]} onLogout={() => { + clearAllTokens(); + route('/') + }} /> + <InstanceRoutes + id={match[1]} + addTokenCleaner={addTokenCleaner} + pushNotification={pushNotification} + /> + </Fragment> + } catch (e) { + // this should be rare becuase + // query to /config is ok but the URL + // doest not match with our pattern + return <Fragment> + <Menu onLogout={() => { + clearAllTokens(); + route('/') + }} /> + <LoginPage + withMessage={{ message: i18n`Couldnt access the server`, description: e.message, type: 'ERROR', }} + onConfirm={updateLoginStatus} + /> + </Fragment> + } + } + if (!list.error) { + return <div id="app"> + <section class="section is-title-bar"> + + <div class="level"> + <div class="level-left"> + <div class="level-item"> + <ul> + <li>loading s</li> + </ul> + </div> + </div> + </div> + </section> + </div> + } + return <div id="app"> + <section class="section is-title-bar"> + + <div class="level"> + <div class="level-left"> + <div class="level-item"> + <ul> + <li>There was an unexpected error</li> + <li>{JSON.stringify(list.error)}</li> + </ul> + </div> + </div> + </div> + </section> + </div> + } + + return <Fragment> + <Menu onLogout={() => { + clearAllTokens(); + route('/') + }} /> + <AdminRoutes instances={list.data.instances} + addTokenCleaner={addTokenCleaner} pushNotification={pushNotification} - addTokenCleaner={addTokenCleaner} /> - - <Route default component={NotFoundPage} /> - - </Router>; + /> + </Fragment> } diff --git a/packages/frontend/src/InstanceRoutes.tsx b/packages/frontend/src/InstanceRoutes.tsx @@ -20,28 +20,29 @@ */ import { h, VNode } from 'preact'; -import { useCallback, useContext, useEffect, useMemo } from "preact/hooks"; +import { useCallback, useEffect, useMemo } from "preact/hooks"; import { Route, Router, route } from 'preact-router'; import { useMessageTemplate } from 'preact-messages'; import { useBackendInstanceToken } from './hooks'; -import { BackendContext, InstanceContext } from './context/backend'; +import { InstanceContextProvider, useBackendContext } from './context/backend'; import { SwrError } from "./hooks/backend"; import { InstancePaths } from "./index"; import { Notification } from './utils/types'; import NotFoundPage from './routes/notfound'; import LoginPage from './routes/login'; -import InstanceDetailsPage from "./routes/instances/details"; import InstanceUpdatePage from "./routes/instances/update"; +import DetailPage from './routes/instances/details'; export interface Props { id: string; pushNotification: (n: Notification) => void; addTokenCleaner: any; + parent?: string; } -export function InstanceRoutes({ id, pushNotification, addTokenCleaner }: Props): VNode { +export function InstanceRoutes({ id, pushNotification, addTokenCleaner, parent }: Props): VNode { const [token, updateToken] = useBackendInstanceToken(id); - const { changeBackend } = useContext(BackendContext); + const { changeBackend } = useBackendContext(); const cleaner = useCallback(() => { updateToken(undefined); }, [id]); const i18n = useMessageTemplate(''); @@ -55,12 +56,23 @@ export function InstanceRoutes({ id, pushNotification, addTokenCleaner }: Props) updateToken(token); }; - const value = useMemo(() => ({ id, token }), [id, token]) + const value = useMemo(() => ({ id, token, admin: !!parent }), [id, token]) - return <InstanceContext.Provider value={value}> + return <InstanceContextProvider value={value}> <Router> + <Route path={(!parent? "" : parent) + InstancePaths.details} + component={DetailPage} - <Route path={InstancePaths.update} + onUnauthorized={() => <LoginPage + withMessage={{ message: i18n`Access denied`, description: i18n`Check your token is valid`, type: 'ERROR', }} + onConfirm={updateLoginStatus} />} + + onLoadError={(error: SwrError) => <LoginPage + withMessage={{ message: i18n`Problem reaching the server`, description: i18n`Got message: ${error.message} from: ${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR', }} + onConfirm={updateLoginStatus} />} + /> + + <Route path={(!parent? "" : parent) + InstancePaths.update} component={InstanceUpdatePage} onUnauthorized={() => <LoginPage @@ -72,12 +84,12 @@ export function InstanceRoutes({ id, pushNotification, addTokenCleaner }: Props) onConfirm={updateLoginStatus} />} onBack={() => { - route(`/instances`); + route(`/`); }} onConfirm={() => { pushNotification({ message: i18n`create_success`, type: 'SUCCESS' }); - route(`/instances`); + route(`/`); }} onUpdateError={(e: Error) => { @@ -87,6 +99,6 @@ export function InstanceRoutes({ id, pushNotification, addTokenCleaner }: Props) <Route default component={NotFoundPage} /> </Router> - </InstanceContext.Provider>; + </InstanceContextProvider>; }