diff options
5 files changed, 23 insertions, 172 deletions
diff --git a/packages/merchant-backoffice-ui/src/AdminRoutes.tsx b/packages/merchant-backoffice-ui/src/AdminRoutes.tsx index a35c4160e..b186f1408 100644 --- a/packages/merchant-backoffice-ui/src/AdminRoutes.tsx +++ b/packages/merchant-backoffice-ui/src/AdminRoutes.tsx @@ -46,8 +46,6 @@ export function AdminRoutes(): VNode { route(InstancePaths.bank_list); }} - // onError={(error: any) => { - // }} /> </Router> ); diff --git a/packages/merchant-backoffice-ui/src/Routing.tsx b/packages/merchant-backoffice-ui/src/Routing.tsx index 172214d0b..50a91c060 100644 --- a/packages/merchant-backoffice-ui/src/Routing.tsx +++ b/packages/merchant-backoffice-ui/src/Routing.tsx @@ -39,7 +39,7 @@ import { MerchantBackend } from "./declaration.js"; import { useInstanceBankAccounts } from "./hooks/bank.js"; import { useInstanceKYCDetails } from "./hooks/instance.js"; import { usePreference } from "./hooks/preference.js"; -import { useSessionState } from "./hooks/session.js"; +import { DEFAULT_ADMIN_USERNAME, useSessionState } from "./hooks/session.js"; import InstanceCreatePage from "./paths/admin/create/index.js"; import InstanceListPage from "./paths/admin/list/index.js"; import BankAccountCreatePage from "./paths/instance/accounts/create/index.js"; @@ -200,7 +200,7 @@ export function Routing(_p: Props): VNode { function IfAdminCreateDefaultOr<T>(Next: FunctionComponent<unknown>) { return function IfAdminCreateDefaultOrImpl(props?: T) { - if (state.isAdmin && state.instance === "default") { + if (state.isAdmin && state.instance === DEFAULT_ADMIN_USERNAME) { return ( <Fragment> <NotificationCard @@ -211,7 +211,7 @@ export function Routing(_p: Props): VNode { }} /> <InstanceCreatePage - forceId="default" + forceId={DEFAULT_ADMIN_USERNAME} onConfirm={() => { route(InstancePaths.bank_list); }} diff --git a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx index 0a15f122a..a9b9618bb 100644 --- a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx +++ b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx @@ -19,32 +19,32 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { useMerchantApiContext, useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, h, VNode } from "preact"; import { useConfigContext } from "../../context/config.js"; import { useInstanceKYCDetails } from "../../hooks/instance.js"; import { LangSelector } from "./LangSelector.js"; import { useSessionState } from "../../hooks/session.js"; -const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined; +// const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined; const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined; interface Props { mobile?: boolean; - mimic?: boolean; } export function Sidebar({ mobile, - mimic, }: Props): VNode { const config = useConfigContext(); // const { url: backendURL } = useBackendContext() const { i18n } = useTranslationContext(); const kycStatus = useInstanceKYCDetails(); const needKYC = kycStatus.ok && kycStatus.data.type === "redirect"; - const { state } = useSessionState(); - + const { state, logOut } = useSessionState(); + const { url } = useMerchantApiContext(); + const isLoggedIn = state.status === "loggedIn" || state.status === "impersonate" + const hasToken = isLoggedIn && state.token !== undefined return ( <aside class="aside is-placed-left is-expanded" style={{ overflowY: "scroll" }}> {mobile && ( @@ -71,7 +71,7 @@ export function Sidebar({ </div> </div> <div class="menu is-menu-main"> - {instance ? ( + {isLoggedIn ? ( <Fragment> <ul class="menu-list"> <li> @@ -206,7 +206,7 @@ export function Sidebar({ <i class="mdi mdi-web" /> </span> <span class="menu-item-label"> - {new URL(backendURL).hostname} + {url.hostname} </span> </div> </li> @@ -216,11 +216,11 @@ export function Sidebar({ ID </span> <span class="menu-item-label"> - {!instance ? "default" : instance} + {state.instance} </span> </div> </li> - {admin && !mimic && ( + {state.isAdmin && state.status !== "impersonate" && ( <Fragment> <p class="menu-label"> <i18n.Translate>Instances</i18n.Translate> @@ -247,11 +247,14 @@ export function Sidebar({ </li> </Fragment> )} - {isPasswordOk ? + {hasToken ? <li> <a class="has-icon is-state-info is-hoverable" - onClick={(): void => onLogout()} + onClick={(e): void => { + logOut() + e.preventDefault(); + }} > <span class="icon"> <i class="mdi mdi-logout default" /> diff --git a/packages/merchant-backoffice-ui/src/hooks/index.ts b/packages/merchant-backoffice-ui/src/hooks/index.ts deleted file mode 100644 index cb58cf066..000000000 --- a/packages/merchant-backoffice-ui/src/hooks/index.ts +++ /dev/null @@ -1,150 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2024 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - 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/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { buildCodecForObject, codecForMap, codecForString, codecForTimestamp } from "@gnu-taler/taler-util"; -import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser"; -import { StateUpdater, useState } from "preact/hooks"; -import { LoginToken } from "../declaration.js"; -import { ValueOrFunction } from "../utils/types.js"; - -const calculateRootPath = () => { - const rootPath = - typeof window !== undefined - ? window.location.origin + window.location.pathname - : "/"; - - /** - * By default, merchant backend serves the html content - * from the /webui root. This should cover most of the - * cases and the rootPath will be the merchant backend - * URL where the instances are - */ - return rootPath.replace("/webui/", ""); -}; - -const loginTokenCodec = buildCodecForObject<LoginToken>() - .property("token", codecForString()) - .property("expiration", codecForTimestamp) - .build("loginToken") -const TOKENS_KEY = buildStorageKey("merchant-token", codecForMap(loginTokenCodec)); - - -function useBackendURL( - url?: string, -): [string, StateUpdater<string>] { - const [value, setter] = useSimpleLocalStorage( - "merchant-base-url", - url || calculateRootPath(), - ); - - const checkedSetter = (v: ValueOrFunction<string>) => { - return setter((p) => (v instanceof Function ? v(p ?? "") : v).replace(/\/$/, "")); - }; - - return [value!, checkedSetter]; -} - -function useBackendDefaultToken( -): [LoginToken | undefined, ((d: LoginToken | undefined) => void)] { - const { update: setToken, value: tokenMap, reset } = useLocalStorage(TOKENS_KEY, {}) - - // const tokenOfDefaultInstance = tokenMap["default"] - // const clearCache = useMatchMutate() - // useEffect(() => { - // clearCache() - // }, [tokenOfDefaultInstance]) - - function updateToken( - value: (LoginToken | undefined) - ): void { - if (value === undefined) { - reset() - } else { - const res = { ...tokenMap, "default": value } - setToken(res) - } - } - return [tokenMap["default"], updateToken]; -} - -function useBackendInstanceToken( - id: string, -): [LoginToken | undefined, ((d: LoginToken | undefined) => void)] { - const { update: setToken, value: tokenMap, reset } = useLocalStorage(TOKENS_KEY, {}) - const [defaultToken, defaultSetToken] = useBackendDefaultToken(); - - // instance named 'default' use the default token - if (id === "default") { - return [defaultToken, defaultSetToken]; - } - function updateToken( - value: (LoginToken | undefined) - ): void { - if (value === undefined) { - reset() - } else { - const res = { ...tokenMap, [id]: value } - setToken(res) - } - } - - return [tokenMap[id], updateToken]; -} - -function useLang(initial?: string): [string, StateUpdater<string>] { - const browserLang = - typeof window !== "undefined" - ? navigator.language || (navigator as any).userLanguage - : undefined; - const defaultLang = (browserLang || initial || "en").substring(0, 2); - return useSimpleLocalStorage("lang-preference", defaultLang) as [string, StateUpdater<string>]; -} - -function useSimpleLocalStorage( - key: string, - initialValue?: string, -): [string | undefined, StateUpdater<string | undefined>] { - const [storedValue, setStoredValue] = useState<string | undefined>( - (): string | undefined => { - return typeof window !== "undefined" - ? window.localStorage.getItem(key) || initialValue - : initialValue; - }, - ); - - const setValue = ( - value?: string | ((val?: string) => string | undefined), - ) => { - setStoredValue((p) => { - const toStore = value instanceof Function ? value(p) : value; - if (typeof window !== "undefined") { - if (!toStore) { - window.localStorage.removeItem(key); - } else { - window.localStorage.setItem(key, toStore); - } - } - return toStore; - }); - }; - - return [storedValue, setValue]; -} diff --git a/packages/merchant-backoffice-ui/src/paths/login/index.tsx b/packages/merchant-backoffice-ui/src/paths/login/index.tsx index d155dd255..d94b7e506 100644 --- a/packages/merchant-backoffice-ui/src/paths/login/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/login/index.tsx @@ -19,6 +19,7 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { HttpStatusCode } from "@gnu-taler/taler-util"; import { useMerchantApiContext, useTranslationContext, @@ -27,9 +28,8 @@ import { ComponentChildren, Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { NotificationCard } from "../../components/menu/index.js"; import { AccessToken } from "../../declaration.js"; -import { useSessionState } from "../../hooks/session.js"; +import { DEFAULT_ADMIN_USERNAME, useSessionState } from "../../hooks/session.js"; import { Notification } from "../../utils/types.js"; -import { HttpStatusCode } from "@gnu-taler/taler-util"; interface Props { } @@ -41,7 +41,7 @@ function normalizeToken(r: string): AccessToken { export function LoginPage(_p: Props): VNode { const [token, setToken] = useState(""); const [notif, setNotif] = useState<Notification | undefined>(undefined); - const { logIn } = useSessionState(); + const { state, logIn } = useSessionState(); const { lib } = useMerchantApiContext(); const { i18n } = useTranslationContext(); @@ -57,7 +57,7 @@ export function LoginPage(_p: Props): VNode { }); if (result.type === "ok") { const { access_token } = result.body; - logIn({ token: access_token }); + logIn({ instance: state.instance, token: access_token }); return; } else { switch(result.case) { @@ -79,7 +79,7 @@ export function LoginPage(_p: Props): VNode { } } - if (admin && id !== "default") { + if (state.isAdmin && state.instance !== DEFAULT_ADMIN_USERNAME) { //admin trying to access another instance return ( <div class="columns is-centered" style={{ margin: "auto" }}> |