2022-10-24 10:46:14 +02:00
|
|
|
/*
|
|
|
|
This file is part of GNU Taler
|
2022-12-16 20:59:37 +01:00
|
|
|
(C) 2021-2023 Taler Systems S.A.
|
2022-10-24 10:46:14 +02:00
|
|
|
|
|
|
|
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)
|
|
|
|
*/
|
|
|
|
|
2023-02-08 21:39:39 +01:00
|
|
|
import {
|
|
|
|
useTranslationContext,
|
|
|
|
HttpError,
|
2023-02-25 23:43:45 +01:00
|
|
|
ErrorType,
|
2023-02-08 21:39:39 +01:00
|
|
|
} from "@gnu-taler/web-util/lib/index.browser";
|
2022-12-20 21:45:24 +01:00
|
|
|
import { format } from "date-fns";
|
2022-10-24 10:46:14 +02:00
|
|
|
import { Fragment, FunctionComponent, h, VNode } from "preact";
|
|
|
|
import { Route, route, Router } from "preact-router";
|
|
|
|
import { useCallback, useEffect, useMemo, useState } from "preact/hooks";
|
2022-11-04 14:24:29 +01:00
|
|
|
import { Loading } from "./components/exception/loading.js";
|
|
|
|
import { Menu, NotificationCard } from "./components/menu/index.js";
|
|
|
|
import { useBackendContext } from "./context/backend.js";
|
|
|
|
import { InstanceContextProvider } from "./context/instance.js";
|
2022-10-24 10:46:14 +02:00
|
|
|
import {
|
|
|
|
useBackendDefaultToken,
|
|
|
|
useBackendInstanceToken,
|
|
|
|
useLocalStorage,
|
2022-11-04 14:24:29 +01:00
|
|
|
} from "./hooks/index.js";
|
2022-12-20 21:45:24 +01:00
|
|
|
import { useInstanceKYCDetails } from "./hooks/instance.js";
|
2022-11-04 14:24:29 +01:00
|
|
|
import InstanceCreatePage from "./paths/admin/create/index.js";
|
|
|
|
import InstanceListPage from "./paths/admin/list/index.js";
|
2022-12-20 21:45:24 +01:00
|
|
|
import ListKYCPage from "./paths/instance/kyc/list/index.js";
|
2022-11-04 14:24:29 +01:00
|
|
|
import OrderCreatePage from "./paths/instance/orders/create/index.js";
|
|
|
|
import OrderDetailsPage from "./paths/instance/orders/details/index.js";
|
|
|
|
import OrderListPage from "./paths/instance/orders/list/index.js";
|
|
|
|
import ProductCreatePage from "./paths/instance/products/create/index.js";
|
|
|
|
import ProductListPage from "./paths/instance/products/list/index.js";
|
|
|
|
import ProductUpdatePage from "./paths/instance/products/update/index.js";
|
|
|
|
import ReservesCreatePage from "./paths/instance/reserves/create/index.js";
|
|
|
|
import ReservesDetailsPage from "./paths/instance/reserves/details/index.js";
|
|
|
|
import ReservesListPage from "./paths/instance/reserves/list/index.js";
|
2022-12-20 21:45:24 +01:00
|
|
|
import TemplateCreatePage from "./paths/instance/templates/create/index.js";
|
2023-01-27 16:35:10 +01:00
|
|
|
import TemplateUsePage from "./paths/instance/templates/use/index.js";
|
2022-12-20 21:45:24 +01:00
|
|
|
import TemplateListPage from "./paths/instance/templates/list/index.js";
|
|
|
|
import TemplateUpdatePage from "./paths/instance/templates/update/index.js";
|
2023-01-27 19:08:03 +01:00
|
|
|
import WebhookCreatePage from "./paths/instance/webhooks/create/index.js";
|
|
|
|
import WebhookListPage from "./paths/instance/webhooks/list/index.js";
|
|
|
|
import WebhookUpdatePage from "./paths/instance/webhooks/update/index.js";
|
2022-12-20 21:45:24 +01:00
|
|
|
import TransferCreatePage from "./paths/instance/transfers/create/index.js";
|
|
|
|
import TransferListPage from "./paths/instance/transfers/list/index.js";
|
2022-10-24 10:46:14 +02:00
|
|
|
import InstanceUpdatePage, {
|
|
|
|
AdminUpdate as InstanceAdminUpdatePage,
|
2022-12-20 21:45:24 +01:00
|
|
|
Props as InstanceUpdatePageProps,
|
2022-11-04 14:24:29 +01:00
|
|
|
} from "./paths/instance/update/index.js";
|
|
|
|
import LoginPage from "./paths/login/index.js";
|
|
|
|
import NotFoundPage from "./paths/notfound/index.js";
|
|
|
|
import { Notification } from "./utils/types.js";
|
2023-02-08 21:39:39 +01:00
|
|
|
import { MerchantBackend } from "./declaration.js";
|
2022-10-24 10:46:14 +02:00
|
|
|
|
|
|
|
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",
|
2022-12-19 20:25:09 +01:00
|
|
|
|
|
|
|
templates_list = "/templates",
|
|
|
|
templates_update = "/templates/:tid/update",
|
|
|
|
templates_new = "/templates/new",
|
2023-01-27 16:35:10 +01:00
|
|
|
templates_use = "/templates/:tid/use",
|
2023-01-27 19:08:03 +01:00
|
|
|
|
|
|
|
webhooks_list = "/webhooks",
|
|
|
|
webhooks_update = "/webhooks/:tid/update",
|
|
|
|
webhooks_new = "/webhooks/new",
|
2022-10-24 10:46:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
2022-12-20 21:45:24 +01:00
|
|
|
path: string;
|
2022-10-24 10:46:14 +02:00
|
|
|
setInstanceName: (s: string) => void;
|
|
|
|
}
|
|
|
|
|
2022-12-20 21:45:24 +01:00
|
|
|
export function InstanceRoutes({
|
|
|
|
id,
|
|
|
|
admin,
|
|
|
|
path,
|
|
|
|
setInstanceName,
|
|
|
|
}: Props): VNode {
|
2022-10-24 10:46:14 +02:00
|
|
|
const [_, updateDefaultToken] = useBackendDefaultToken();
|
|
|
|
const [token, updateToken] = useBackendInstanceToken(id);
|
|
|
|
const {
|
|
|
|
updateLoginStatus: changeBackend,
|
|
|
|
addTokenCleaner,
|
|
|
|
clearAllTokens,
|
|
|
|
} = useBackendContext();
|
|
|
|
const cleaner = useCallback(() => {
|
|
|
|
updateToken(undefined);
|
|
|
|
}, [id]);
|
2022-12-20 21:45:24 +01:00
|
|
|
const { i18n } = useTranslationContext();
|
2022-10-24 10:46:14 +02:00
|
|
|
|
|
|
|
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 }),
|
2022-12-19 16:23:39 +01:00
|
|
|
[id, token, admin],
|
2022-10-24 10:46:14 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
function ServerErrorRedirectTo(to: InstancePaths | AdminPaths) {
|
2023-02-08 21:39:39 +01:00
|
|
|
return function ServerErrorRedirectToImpl(
|
|
|
|
error: HttpError<MerchantBackend.ErrorDetail>,
|
|
|
|
) {
|
2023-02-25 23:43:45 +01:00
|
|
|
if (error.type === ErrorType.TIMEOUT) {
|
|
|
|
setGlobalNotification({
|
|
|
|
message: i18n.str`The request to the backend take too long and was cancelled`,
|
|
|
|
description: i18n.str`Diagnostic from ${error.info?.url} is "${error.message}"`,
|
|
|
|
type: "ERROR",
|
|
|
|
to,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
setGlobalNotification({
|
|
|
|
message: i18n.str`The backend reported a problem: HTTP status #${error.status}`,
|
|
|
|
description: i18n.str`Diagnostic from ${error.info?.url} is "${error.message}"`,
|
|
|
|
details:
|
|
|
|
error.clientError || error.serverError
|
|
|
|
? error.error?.detail
|
|
|
|
: undefined,
|
|
|
|
type: "ERROR",
|
|
|
|
to,
|
|
|
|
});
|
|
|
|
}
|
2022-10-24 10:46:14 +02:00
|
|
|
return <Redirect to={to} />;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const LoginPageAccessDenied = () => (
|
|
|
|
<Fragment>
|
|
|
|
<NotificationCard
|
|
|
|
notification={{
|
2022-12-20 21:45:24 +01:00
|
|
|
message: i18n.str`Access denied`,
|
|
|
|
description: i18n.str`The access token provided is invalid.`,
|
2022-10-24 10:46:14 +02:00
|
|
|
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={{
|
2022-12-20 21:45:24 +01:00
|
|
|
message: i18n.str`No 'default' instance configured yet.`,
|
|
|
|
description: i18n.str`Create a 'default' instance to begin using the merchant backoffice.`,
|
2022-10-24 10:46:14 +02:00
|
|
|
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}
|
2022-12-20 21:45:24 +01:00
|
|
|
path={path}
|
2022-10-24 10:46:14 +02:00
|
|
|
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);
|
|
|
|
}}
|
|
|
|
/>
|
2023-01-27 19:08:03 +01:00
|
|
|
{/**
|
|
|
|
* Webhooks pages
|
|
|
|
*/}
|
|
|
|
<Route
|
|
|
|
path={InstancePaths.webhooks_list}
|
|
|
|
component={WebhookListPage}
|
|
|
|
onUnauthorized={LoginPageAccessDenied}
|
|
|
|
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
|
|
|
|
onLoadError={ServerErrorRedirectTo(InstancePaths.update)}
|
|
|
|
onCreate={() => {
|
|
|
|
route(InstancePaths.webhooks_new);
|
|
|
|
}}
|
|
|
|
onSelect={(id: string) => {
|
|
|
|
route(InstancePaths.webhooks_update.replace(":tid", id));
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
<Route
|
|
|
|
path={InstancePaths.webhooks_update}
|
|
|
|
component={WebhookUpdatePage}
|
|
|
|
onConfirm={() => {
|
|
|
|
route(InstancePaths.webhooks_list);
|
|
|
|
}}
|
|
|
|
onUnauthorized={LoginPageAccessDenied}
|
|
|
|
onLoadError={ServerErrorRedirectTo(InstancePaths.webhooks_list)}
|
|
|
|
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
|
|
|
|
onBack={() => {
|
|
|
|
route(InstancePaths.webhooks_list);
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
<Route
|
|
|
|
path={InstancePaths.webhooks_new}
|
|
|
|
component={WebhookCreatePage}
|
|
|
|
onConfirm={() => {
|
|
|
|
route(InstancePaths.webhooks_list);
|
|
|
|
}}
|
|
|
|
onBack={() => {
|
|
|
|
route(InstancePaths.webhooks_list);
|
|
|
|
}}
|
|
|
|
/>
|
2022-12-19 20:25:09 +01:00
|
|
|
{/**
|
|
|
|
* Templates pages
|
|
|
|
*/}
|
|
|
|
<Route
|
|
|
|
path={InstancePaths.templates_list}
|
|
|
|
component={TemplateListPage}
|
|
|
|
onUnauthorized={LoginPageAccessDenied}
|
|
|
|
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
|
|
|
|
onLoadError={ServerErrorRedirectTo(InstancePaths.update)}
|
|
|
|
onCreate={() => {
|
|
|
|
route(InstancePaths.templates_new);
|
|
|
|
}}
|
2023-01-27 16:35:10 +01:00
|
|
|
onNewOrder={(id: string) => {
|
|
|
|
route(InstancePaths.templates_use.replace(":tid", id));
|
|
|
|
}}
|
2022-12-19 20:25:09 +01:00
|
|
|
onSelect={(id: string) => {
|
|
|
|
route(InstancePaths.templates_update.replace(":tid", id));
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
<Route
|
|
|
|
path={InstancePaths.templates_update}
|
|
|
|
component={TemplateUpdatePage}
|
|
|
|
onConfirm={() => {
|
|
|
|
route(InstancePaths.templates_list);
|
|
|
|
}}
|
|
|
|
onUnauthorized={LoginPageAccessDenied}
|
|
|
|
onLoadError={ServerErrorRedirectTo(InstancePaths.templates_list)}
|
|
|
|
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
|
|
|
|
onBack={() => {
|
|
|
|
route(InstancePaths.templates_list);
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
<Route
|
|
|
|
path={InstancePaths.templates_new}
|
|
|
|
component={TemplateCreatePage}
|
|
|
|
onConfirm={() => {
|
|
|
|
route(InstancePaths.templates_list);
|
|
|
|
}}
|
|
|
|
onBack={() => {
|
|
|
|
route(InstancePaths.templates_list);
|
|
|
|
}}
|
|
|
|
/>
|
2023-01-27 16:35:10 +01:00
|
|
|
<Route
|
|
|
|
path={InstancePaths.templates_use}
|
|
|
|
component={TemplateUsePage}
|
|
|
|
onOrderCreated={(id: string) => {
|
|
|
|
route(InstancePaths.order_details.replace(":oid", id));
|
|
|
|
}}
|
|
|
|
onUnauthorized={LoginPageAccessDenied}
|
|
|
|
onLoadError={ServerErrorRedirectTo(InstancePaths.templates_list)}
|
|
|
|
onNotFound={IfAdminCreateDefaultOr(NotFoundPage)}
|
|
|
|
onBack={() => {
|
|
|
|
route(InstancePaths.templates_list);
|
|
|
|
}}
|
|
|
|
/>
|
2022-10-24 10:46:14 +02:00
|
|
|
|
|
|
|
{/**
|
|
|
|
* 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
|
2023-01-03 05:57:39 +01:00
|
|
|
}: { id: string } & InstanceUpdatePageProps): VNode {
|
2022-10-24 10:46:14 +02:00
|
|
|
const [token, changeToken] = useBackendInstanceToken(id);
|
|
|
|
const { updateLoginStatus: changeBackend } = useBackendContext();
|
2023-02-08 21:39:39 +01:00
|
|
|
const updateLoginStatus = (url: string, token?: string): void => {
|
2022-10-24 10:46:14 +02:00
|
|
|
changeBackend(url);
|
|
|
|
if (token) changeToken(token);
|
|
|
|
};
|
|
|
|
const value = useMemo(
|
|
|
|
() => ({ id, token, admin: true, changeToken }),
|
2022-12-19 16:23:39 +01:00
|
|
|
[id, token],
|
2022-10-24 10:46:14 +02:00
|
|
|
);
|
2022-12-20 21:45:24 +01:00
|
|
|
const { i18n } = useTranslationContext();
|
2022-10-24 10:46:14 +02:00
|
|
|
|
|
|
|
return (
|
|
|
|
<InstanceContextProvider value={value}>
|
|
|
|
<InstanceAdminUpdatePage
|
|
|
|
{...rest}
|
|
|
|
instanceId={id}
|
2023-02-08 21:39:39 +01:00
|
|
|
onLoadError={(error: HttpError<MerchantBackend.ErrorDetail>) => {
|
2023-02-25 23:43:45 +01:00
|
|
|
const notif =
|
|
|
|
error.type === ErrorType.TIMEOUT
|
|
|
|
? {
|
|
|
|
message: i18n.str`The request to the backend take too long and was cancelled`,
|
|
|
|
description: i18n.str`Diagnostic from ${error.info?.url} is "${error.message}"`,
|
|
|
|
type: "ERROR" as const,
|
|
|
|
}
|
|
|
|
: {
|
2022-12-20 21:45:24 +01:00
|
|
|
message: i18n.str`The backend reported a problem: HTTP status #${error.status}`,
|
|
|
|
description: i18n.str`Diagnostic from ${error.info?.url} is "${error.message}"`,
|
2022-10-24 10:46:14 +02:00
|
|
|
details:
|
|
|
|
error.clientError || error.serverError
|
|
|
|
? error.error?.detail
|
|
|
|
: undefined,
|
2023-02-25 23:43:45 +01:00
|
|
|
type: "ERROR" as const,
|
|
|
|
};
|
|
|
|
return (
|
|
|
|
<Fragment>
|
|
|
|
<NotificationCard notification={notif} />
|
2022-10-24 10:46:14 +02:00
|
|
|
<LoginPage onConfirm={updateLoginStatus} />
|
|
|
|
</Fragment>
|
|
|
|
);
|
|
|
|
}}
|
|
|
|
onUnauthorized={() => {
|
|
|
|
return (
|
|
|
|
<Fragment>
|
|
|
|
<NotificationCard
|
|
|
|
notification={{
|
2022-12-20 21:45:24 +01:00
|
|
|
message: i18n.str`Access denied`,
|
|
|
|
description: i18n.str`The access token provided is invalid`,
|
2022-10-24 10:46:14 +02:00
|
|
|
type: "ERROR",
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
<LoginPage onConfirm={updateLoginStatus} />
|
|
|
|
</Fragment>
|
|
|
|
);
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</InstanceContextProvider>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function KycBanner(): VNode {
|
|
|
|
const kycStatus = useInstanceKYCDetails();
|
2022-12-20 21:45:24 +01:00
|
|
|
const { i18n } = useTranslationContext();
|
2022-10-24 10:46:14 +02:00
|
|
|
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)}>
|
2022-12-20 21:45:24 +01:00
|
|
|
<i18n.Translate>Hide for today</i18n.Translate>
|
2022-10-24 10:46:14 +02:00
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
),
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|