summaryrefslogtreecommitdiff
path: root/packages/merchant-backoffice/src/InstanceRoutes.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/merchant-backoffice/src/InstanceRoutes.tsx')
-rw-r--r--packages/merchant-backoffice/src/InstanceRoutes.tsx528
1 files changed, 0 insertions, 528 deletions
diff --git a/packages/merchant-backoffice/src/InstanceRoutes.tsx b/packages/merchant-backoffice/src/InstanceRoutes.tsx
deleted file mode 100644
index 06f1db1..0000000
--- a/packages/merchant-backoffice/src/InstanceRoutes.tsx
+++ /dev/null
@@ -1,528 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021 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 { Fragment, FunctionComponent, h, VNode } from "preact";
-import { Route, route, Router } from "preact-router";
-import { useCallback, useEffect, useMemo, useState } from "preact/hooks";
-import { Loading } from "./components/exception/loading";
-import { Menu, NotificationCard } from "./components/menu";
-import { useBackendContext } from "./context/backend";
-import { InstanceContextProvider } from "./context/instance";
-import {
- useBackendDefaultToken,
- useBackendInstanceToken,
- useLocalStorage,
-} from "./hooks";
-import { HttpError } from "./hooks/backend";
-import { Translate, useTranslator } from "./i18n";
-import InstanceCreatePage from "./paths/admin/create";
-import InstanceListPage from "./paths/admin/list";
-import OrderCreatePage from "./paths/instance/orders/create";
-import OrderDetailsPage from "./paths/instance/orders/details";
-import OrderListPage from "./paths/instance/orders/list";
-import ProductCreatePage from "./paths/instance/products/create";
-import ProductListPage from "./paths/instance/products/list";
-import ProductUpdatePage from "./paths/instance/products/update";
-import TransferListPage from "./paths/instance/transfers/list";
-import TransferCreatePage from "./paths/instance/transfers/create";
-import ReservesCreatePage from "./paths/instance/reserves/create";
-import ReservesDetailsPage from "./paths/instance/reserves/details";
-import ReservesListPage from "./paths/instance/reserves/list";
-import ListKYCPage from "./paths/instance/kyc/list";
-import InstanceUpdatePage, {
- Props as InstanceUpdatePageProps,
- AdminUpdate as InstanceAdminUpdatePage,
-} from "./paths/instance/update";
-import LoginPage from "./paths/login";
-import NotFoundPage from "./paths/notfound";
-import { Notification } from "./utils/types";
-import { useInstanceKYCDetails } from "./hooks/instance";
-import { format } from "date-fns";
-
-export enum InstancePaths {
- // details = '/',
- error = "/error",
- update = "/update",
-
- product_list = "/products",
- product_update = "/product/:pid/update",
- product_new = "/product/new",
-
- order_list = "/orders",
- order_new = "/order/new",
- order_details = "/order/:oid/details",
-
- reserves_list = "/reserves",
- reserves_details = "/reserves/:rid/details",
- reserves_new = "/reserves/new",
-
- kyc = "/kyc",
-
- transfers_list = "/transfers",
- transfers_new = "/transfer/new",
-}
-
-// eslint-disable-next-line @typescript-eslint/no-empty-function
-const noop = () => {};
-
-export enum AdminPaths {
- list_instances = "/instances",
- new_instance = "/instance/new",
- update_instance = "/instance/:id/update",
-}
-
-export interface Props {
- id: string;
- admin?: boolean;
- setInstanceName: (s: string) => void;
-}
-
-export function InstanceRoutes({ id, admin, setInstanceName }: Props): VNode {
- const [_, updateDefaultToken] = useBackendDefaultToken();
- const [token, updateToken] = useBackendInstanceToken(id);
- const {
- updateLoginStatus: changeBackend,
- addTokenCleaner,
- clearAllTokens,
- } = useBackendContext();
- const cleaner = useCallback(() => {
- updateToken(undefined);
- }, [id]);
- const i18n = useTranslator();
-
- type GlobalNotifState = (Notification & { to: string }) | undefined;
- const [globalNotification, setGlobalNotification] =
- useState<GlobalNotifState>(undefined);
-
- useEffect(() => {
- addTokenCleaner(cleaner);
- }, [addTokenCleaner, cleaner]);
-
- const changeToken = (token?: string) => {
- if (admin) {
- updateToken(token);
- } else {
- updateDefaultToken(token);
- }
- };
- const updateLoginStatus = (url: string, token?: string) => {
- changeBackend(url);
- if (!token) return;
- changeToken(token);
- };
-
- const value = useMemo(
- () => ({ id, token, admin, changeToken }),
- [id, token, admin]
- );
-
- function ServerErrorRedirectTo(to: InstancePaths | AdminPaths) {
- return function ServerErrorRedirectToImpl(error: HttpError) {
- setGlobalNotification({
- message: i18n`The backend reported a problem: HTTP status #${error.status}`,
- description: i18n`Diagnostic from ${error.info?.url} is "${error.message}"`,
- details:
- error.clientError || error.serverError
- ? error.error?.detail
- : undefined,
- type: "ERROR",
- to,
- });
- return <Redirect to={to} />;
- };
- }
-
- const LoginPageAccessDenied = () => (
- <Fragment>
- <NotificationCard
- notification={{
- message: i18n`Access denied`,
- description: i18n`The access token provided is invalid.`,
- type: "ERROR",
- }}
- />
- <LoginPage onConfirm={updateLoginStatus} />
- </Fragment>
- );
-
- function IfAdminCreateDefaultOr<T>(Next: FunctionComponent<any>) {
- return function IfAdminCreateDefaultOrImpl(props?: T) {
- if (admin && id === "default") {
- return (
- <Fragment>
- <NotificationCard
- notification={{
- message: i18n`No 'default' instance configured yet.`,
- description: i18n`Create a 'default' instance to begin using the merchant backoffice.`,
- type: "INFO",
- }}
- />
- <InstanceCreatePage
- forceId="default"
- onConfirm={() => {
- route(AdminPaths.list_instances);
- }}
- />
- </Fragment>
- );
- }
- if (props) {
- return <Next {...props} />;
- }
- return <Next />;
- };
- }
-
- const clearTokenAndGoToRoot = () => {
- clearAllTokens();
- route("/");
- };
-
- return (
- <InstanceContextProvider value={value}>
- <Menu
- instance={id}
- admin={admin}
- onLogout={clearTokenAndGoToRoot}
- setInstanceName={setInstanceName}
- />
- <KycBanner />
- <NotificationCard notification={globalNotification} />
-
- <Router
- onChange={(e) => {
- const movingOutFromNotification =
- globalNotification && e.url !== globalNotification.to;
- if (movingOutFromNotification) {
- setGlobalNotification(undefined);
- }
- }}
- >
- <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`);
- }}
- setInstanceName={setInstanceName}
- onUnauthorized={LoginPageAccessDenied}
- onLoadError={ServerErrorRedirectTo(InstancePaths.error)}
- />
- )}
-
- {admin && (
- <Route
- path={AdminPaths.new_instance}
- component={InstanceCreatePage}
- onBack={() => route(AdminPaths.list_instances)}
- onConfirm={() => {
- route(AdminPaths.list_instances);
- }}
- />
- )}
-
- {admin && (
- <Route
- path={AdminPaths.update_instance}
- component={AdminInstanceUpdatePage}
- onBack={() => route(AdminPaths.list_instances)}
- onConfirm={() => {
- route(AdminPaths.list_instances);
- }}
- onUpdateError={ServerErrorRedirectTo(AdminPaths.list_instances)}
- onLoadError={ServerErrorRedirectTo(AdminPaths.list_instances)}
- onNotFound={NotFoundPage}
- />
- )}
-
- {/**
- * Update instance page
- */}
- <Route
- path={InstancePaths.update}
- component={InstanceUpdatePage}
- onBack={() => {
- route(`/`);
- }}
- onConfirm={() => {
- route(`/`);
- }}
- onUpdateError={noop}
- onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
- onUnauthorized={LoginPageAccessDenied}
- onLoadError={ServerErrorRedirectTo(InstancePaths.error)}
- />
-
- {/**
- * Product pages
- */}
- <Route
- path={InstancePaths.product_list}
- component={ProductListPage}
- onUnauthorized={LoginPageAccessDenied}
- onLoadError={ServerErrorRedirectTo(InstancePaths.update)}
- onCreate={() => {
- route(InstancePaths.product_new);
- }}
- onSelect={(id: string) => {
- route(InstancePaths.product_update.replace(":pid", id));
- }}
- onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
- />
- <Route
- path={InstancePaths.product_update}
- component={ProductUpdatePage}
- onUnauthorized={LoginPageAccessDenied}
- onLoadError={ServerErrorRedirectTo(InstancePaths.product_list)}
- onConfirm={() => {
- route(InstancePaths.product_list);
- }}
- onBack={() => {
- route(InstancePaths.product_list);
- }}
- onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
- />
- <Route
- path={InstancePaths.product_new}
- component={ProductCreatePage}
- onConfirm={() => {
- route(InstancePaths.product_list);
- }}
- onBack={() => {
- route(InstancePaths.product_list);
- }}
- />
-
- {/**
- * 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={ServerErrorRedirectTo(InstancePaths.update)}
- onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
- />
- <Route
- path={InstancePaths.order_details}
- component={OrderDetailsPage}
- onUnauthorized={LoginPageAccessDenied}
- onLoadError={ServerErrorRedirectTo(InstancePaths.order_list)}
- onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
- onBack={() => {
- route(InstancePaths.order_list);
- }}
- />
- <Route
- path={InstancePaths.order_new}
- component={OrderCreatePage}
- onConfirm={() => {
- route(InstancePaths.order_list);
- }}
- onBack={() => {
- route(InstancePaths.order_list);
- }}
- />
-
- {/**
- * Transfer pages
- */}
- <Route
- path={InstancePaths.transfers_list}
- component={TransferListPage}
- onUnauthorized={LoginPageAccessDenied}
- onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
- onLoadError={ServerErrorRedirectTo(InstancePaths.update)}
- onCreate={() => {
- route(InstancePaths.transfers_new);
- }}
- />
-
- <Route
- path={InstancePaths.transfers_new}
- component={TransferCreatePage}
- onConfirm={() => {
- route(InstancePaths.transfers_list);
- }}
- onBack={() => {
- route(InstancePaths.transfers_list);
- }}
- />
-
- {/**
- * reserves pages
- */}
- <Route
- path={InstancePaths.reserves_list}
- component={ReservesListPage}
- onUnauthorized={LoginPageAccessDenied}
- onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
- onLoadError={ServerErrorRedirectTo(InstancePaths.update)}
- onSelect={(id: string) => {
- route(InstancePaths.reserves_details.replace(":rid", id));
- }}
- onCreate={() => {
- route(InstancePaths.reserves_new);
- }}
- />
-
- <Route
- path={InstancePaths.reserves_details}
- component={ReservesDetailsPage}
- onUnauthorized={LoginPageAccessDenied}
- onLoadError={ServerErrorRedirectTo(InstancePaths.reserves_list)}
- onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
- onBack={() => {
- route(InstancePaths.reserves_list);
- }}
- />
-
- <Route
- path={InstancePaths.reserves_new}
- component={ReservesCreatePage}
- onConfirm={() => {
- route(InstancePaths.reserves_list);
- }}
- onBack={() => {
- route(InstancePaths.reserves_list);
- }}
- />
-
- <Route path={InstancePaths.kyc} component={ListKYCPage} />
- {/**
- * Example pages
- */}
- <Route path="/loading" component={Loading} />
- <Route default component={NotFoundPage} />
- </Router>
- </InstanceContextProvider>
- );
-}
-
-export function Redirect({ to }: { to: string }): null {
- useEffect(() => {
- route(to, true);
- });
- return null;
-}
-
-function AdminInstanceUpdatePage({
- id,
- ...rest
-}: { id: string } & InstanceUpdatePageProps) {
- const [token, changeToken] = useBackendInstanceToken(id);
- const { updateLoginStatus: changeBackend } = useBackendContext();
- const updateLoginStatus = (url: string, token?: string) => {
- changeBackend(url);
- if (token) changeToken(token);
- };
- const value = useMemo(
- () => ({ id, token, admin: true, changeToken }),
- [id, token]
- );
- const i18n = useTranslator();
-
- return (
- <InstanceContextProvider value={value}>
- <InstanceAdminUpdatePage
- {...rest}
- instanceId={id}
- onLoadError={(error: HttpError) => {
- return (
- <Fragment>
- <NotificationCard
- notification={{
- message: i18n`The backend reported a problem: HTTP status #${error.status}`,
- description: i18n`Diagnostic from ${error.info?.url} is "${error.message}"`,
- details:
- error.clientError || error.serverError
- ? error.error?.detail
- : undefined,
- type: "ERROR",
- }}
- />
- <LoginPage onConfirm={updateLoginStatus} />
- </Fragment>
- );
- }}
- onUnauthorized={() => {
- return (
- <Fragment>
- <NotificationCard
- notification={{
- message: i18n`Access denied`,
- description: i18n`The access token provided is invalid`,
- type: "ERROR",
- }}
- />
- <LoginPage onConfirm={updateLoginStatus} />
- </Fragment>
- );
- }}
- />
- </InstanceContextProvider>
- );
-}
-
-function KycBanner(): VNode {
- const kycStatus = useInstanceKYCDetails();
- const today = format(new Date(), "yyyy-MM-dd");
- const [lastHide, setLastHide] = useLocalStorage("kyc-last-hide");
- const hasBeenHidden = today === lastHide;
- const needsToBeShown = kycStatus.ok && kycStatus.data.type === "redirect";
- if (hasBeenHidden || !needsToBeShown) return <Fragment />;
- return (
- <NotificationCard
- notification={{
- type: "WARN",
- message: "KYC verification needed",
- description: (
- <div>
- <p>
- Some transfer are on hold until a KYC process is completed. Go to
- the KYC section in the left panel for more information
- </p>
- <div class="buttons is-right">
- <button class="button" onClick={() => setLastHide(today)}>
- <Translate>Hide for today</Translate>
- </button>
- </div>
- </div>
- ),
- }}
- />
- );
-}