summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/auditor-backoffice-ui/src/Application.tsx372
-rw-r--r--packages/auditor-backoffice-ui/src/ApplicationReadyRoutes.tsx164
-rw-r--r--packages/auditor-backoffice-ui/src/InstanceRoutes.tsx332
-rw-r--r--packages/auditor-backoffice-ui/src/components/exception/loading.tsx48
-rw-r--r--packages/auditor-backoffice-ui/src/components/forms/FormProvider.tsx109
-rw-r--r--packages/auditor-backoffice-ui/src/components/forms/Input.tsx116
-rw-r--r--packages/auditor-backoffice-ui/src/components/forms/InputCurrency.tsx67
-rw-r--r--packages/auditor-backoffice-ui/src/components/forms/InputNumber.tsx60
-rw-r--r--packages/auditor-backoffice-ui/src/components/forms/InputSelector.tsx94
-rw-r--r--packages/auditor-backoffice-ui/src/components/forms/InputToggle.tsx91
-rw-r--r--packages/auditor-backoffice-ui/src/components/forms/InputWithAddon.tsx116
-rw-r--r--packages/auditor-backoffice-ui/src/components/forms/useField.tsx92
-rw-r--r--packages/auditor-backoffice-ui/src/components/menu/LangSelector.tsx92
-rw-r--r--packages/auditor-backoffice-ui/src/components/menu/NavigationBar.tsx73
-rw-r--r--packages/auditor-backoffice-ui/src/components/menu/SideBar.tsx286
-rw-r--r--packages/auditor-backoffice-ui/src/components/menu/index.tsx259
-rw-r--r--packages/auditor-backoffice-ui/src/components/modal/index.tsx496
-rw-r--r--packages/auditor-backoffice-ui/src/context/backend.ts71
-rw-r--r--packages/auditor-backoffice-ui/src/context/config.ts29
-rw-r--r--packages/auditor-backoffice-ui/src/context/entity.ts47
-rw-r--r--packages/auditor-backoffice-ui/src/custom.d.ts42
-rw-r--r--packages/auditor-backoffice-ui/src/declaration.d.ts345
-rw-r--r--packages/auditor-backoffice-ui/src/hooks/backend.ts240
-rw-r--r--packages/auditor-backoffice-ui/src/hooks/balance.ts61
-rw-r--r--packages/auditor-backoffice-ui/src/hooks/config.ts24
-rw-r--r--packages/auditor-backoffice-ui/src/hooks/deposit-confirmation.ts78
-rw-r--r--packages/auditor-backoffice-ui/src/hooks/entity.ts90
-rw-r--r--packages/auditor-backoffice-ui/src/hooks/index.ts83
-rw-r--r--packages/auditor-backoffice-ui/src/hooks/useSettings.ts73
-rw-r--r--packages/auditor-backoffice-ui/src/index.tsx6
-rw-r--r--packages/auditor-backoffice-ui/src/paths/balances/Table.tsx170
-rw-r--r--packages/auditor-backoffice-ui/src/paths/balances/index.tsx75
-rw-r--r--packages/auditor-backoffice-ui/src/paths/default/Table.tsx166
-rw-r--r--packages/auditor-backoffice-ui/src/paths/default/index.tsx117
-rw-r--r--packages/auditor-backoffice-ui/src/paths/deposit_confirmations/list/List.stories.tsx109
-rw-r--r--packages/auditor-backoffice-ui/src/paths/deposit_confirmations/list/ListPage.tsx228
-rw-r--r--packages/auditor-backoffice-ui/src/paths/deposit_confirmations/list/Table.tsx0
-rw-r--r--packages/auditor-backoffice-ui/src/paths/deposit_confirmations/list/index.tsx73
-rw-r--r--packages/auditor-backoffice-ui/src/paths/notfound/index.tsx34
-rw-r--r--packages/auditor-backoffice-ui/src/paths/settings/index.tsx107
-rw-r--r--packages/merchant-backoffice-ui/src/Application.tsx2
-rw-r--r--packages/web-util/src/utils/request.ts19
42 files changed, 4811 insertions, 345 deletions
diff --git a/packages/auditor-backoffice-ui/src/Application.tsx b/packages/auditor-backoffice-ui/src/Application.tsx
index 36059fe1a..3b6aa8dd3 100644
--- a/packages/auditor-backoffice-ui/src/Application.tsx
+++ b/packages/auditor-backoffice-ui/src/Application.tsx
@@ -1,6 +1,6 @@
/*
This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
+ (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
@@ -14,261 +14,153 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import {Fragment, VNode, h, render} from "preact";
-import "./scss/main.scss";
-import { AuditorBackend } from "./declaration.js"
-
/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
* @author Nic Eigel
*/
+import {HttpStatusCode, LibtoolVersion} from "@gnu-taler/taler-util";
+import {
+ ErrorType,
+ TranslationProvider,
+ useTranslationContext
+} from "@gnu-taler/web-util/browser";
+import {Fragment, VNode, h, render} from "preact";
+import {useMemo} from "preact/hooks";
+import {ApplicationReadyRoutes} from "./ApplicationReadyRoutes.js";
+import {Loading} from "./components/exception/loading.js";
+import {
+ NotConnectedAppMenu,
+ NotificationCard
+} from "./components/menu/index.js";
+import {
+ BackendContextProvider
+} from "./context/backend.js";
+import {ConfigContextProvider} from "./context/config.js";
+import {useBackendConfig} from "./hooks/backend.js";
+import { strings } from "./i18n/strings.js";
export function Application(): VNode {
return (
- <MyComponent/>
+ <BackendContextProvider>
+ <TranslationProvider source={strings}>
+ <ApplicationStatusRoutes/>
+ </TranslationProvider>
+ </BackendContextProvider>
);
}
-
-export class Configer {
- name?: string
- version?: string
- implementation?: string
- currency?: string
- auditor_public_key?: string
- exchange_master_public_key?: string
-
- public constructor(init?: Partial<Configer>) {
- Object.assign(this, init);
- }
-}
-
-interface ApiResponse {
- Data: Array<Configer>
-}
-
-function tryConfig(): Promise<Configer> {
- console.log("getting here");
- const request: RequestInfo = new Request('http://localhost:8083/config', {
- method: 'GET'
- })
- return fetch(request)
- // the JSON body is taken from the response
- //.then(res => res.json())
- //.then(res => {
- // console.log(res);
- // return res as AuditorBackend.Config;
- .then(response => {
- if (!response.ok) {
- throw new Error(response.statusText)
- }
- return response.json() as Promise<Configer>
- })
-
-}
-
-let testers: Array<Configer> = []
-
-// Implementation code where T is the returned data shape
-function api<T>(url: string): Promise<T> {
- return fetch(url)
- .then(response => {
- if (!response.ok) {
- throw new Error(response.statusText)
- }
- return response.json()
- })
-
-}
-
-// Consumer
-api<Configer>('http://localhost:8083/config')
- .then((r: Configer) => {
- let e = structuredClone(r)
- testers.push(r)
- e.name = "er"
- testers.push(e)
- teste()
- })
- .catch(error => {
- /* show error message */
- })
-
-
-function MyComponent() {
-
- return <section class="section is-main-section">
- <div>
- <div class="card has-table">
- <header class="card-header">
- <p class="card-header-title">
- <span class="icon">
- <i class="mdi mdi-cash-register"/>
- </span>
- Deposit Confirmations
- </p>
- </header>
- <div class="card-content">
- <div class="b-table has-pagination">
- <div class="table-wrapper has-mobile-cards">
- <div class="table-container">
- <table class="table is-striped is-hoverable is-fullwidth">
- <thead>
- <th>Name</th>
- <th>Currency</th>
- <th>Version</th>
- <th>Auditor Pubkey</th>
- <th>Exchange Master Pub Key</th>
- <th>Implementation</th>
- </thead>
- <Glossary/>
- </table>
- </div>
- </div>
- </div>
- </div>
- </div>
-
- <Sidebar/>
- <NavigationBar title={"Deposit confirmations"}/>
-
- </div>
- </section>;
-}
-
-function Glossary() {
- console.log(testers)
- return (
- <tbody>
- {
- testers.map(item => (
- <Fragment key={item.name}>
- <tr>
- <td>{item.name}</td>
- <td>{item.currency}</td>
- <td>{item.version}</td>
- <td>test</td>
- <td>test</td>
- <td>{item.implementation}</td>
- <td class="is-actions-cell right-sticky">
- <div class="buttons is-right">
- <button
- class="button is-small is-info jb-modal"
- type="button"
- >
- Update
- </button>
- <button
- class="button is-small is-danger jb-modal"
- type="button"
- >
- Delete
- </button>
- </div>
- </td>
- </tr>
+/**
+ * Check connection testing against /config
+ *
+ * @returns
+ */
+function ApplicationStatusRoutes(): VNode {
+ const result = useBackendConfig();
+ const {i18n} = useTranslationContext();
+
+ const configData = result.ok && result.data
+ ? result.data
+ : undefined;
+ const ctx = useMemo(() => (configData), [configData]);
+
+ if (!result.ok) {
+ if (result.loading) return <Loading/>;
+ if (
+ result.type === ErrorType.CLIENT &&
+ result.status === HttpStatusCode.Unauthorized
+ ) {
+ return (
+ <Fragment>
+ <NotConnectedAppMenu title="Login"/>
+ <NotificationCard
+ notification={{
+ message: i18n.str`Checking the /config endpoint got authorization error`,
+ type: "ERROR",
+ description: `The /config endpoint of the backend server should be accessible`,
+ }}
+ />
</Fragment>
- ))
+ );
}
- </tbody>
- );
-}
-
-const App = (
- <ul>
- <MyComponent/>
- </ul>
-);
-
-
-function teste() {
- render(App, document.body);
-}
-
-
-export function Sidebar(props: any): VNode {
- //const config = useConfigContext();
- //const { url: backendURL } = useBackendContext()
- //const {i18n} = useTranslationContext();
-
- return (
- <aside class="aside is-placed-left is-expanded" style={{overflowY: "scroll"}}>
- <div class="aside-tools">
- <div class="aside-tools-label">
- <div>
- <b>Taler</b> Backoffice
- </div>
- <div
- class="is-size-7 has-text-right"
- style={{lineHeight: 0, marginTop: -10}}
- >
- Version (0.1)
- </div>
- </div>
- </div>
- <div class="menu is-menu-main">
+ if (
+ result.type === ErrorType.CLIENT &&
+ result.status === HttpStatusCode.NotFound
+ ) {
+ return (
<Fragment>
- <ul class="menu-list">
- <li>
- <a href={"/deposit-confirmations"} class="has-icon">
- <span class="icon">
- <i class="mdi mdi-cash-register"/>
- </span>
- <span class="menu-item-label">
- Deposit confirmations
- </span>
- </a>
- </li>
- </ul>
+ <NotConnectedAppMenu title="Error"/>
+ <NotificationCard
+ notification={{
+ message: i18n.str`Could not find /config endpoint on this URL`,
+ type: "ERROR",
+ description: `Check the URL or contact the system administrator.`,
+ }}
+ />
</Fragment>
- </div>
- </aside>
- );
-}
+ );
+ }
+ if (result.type === ErrorType.SERVER) {
+ <Fragment>
+ <NotConnectedAppMenu title="Error"/>
+ <NotificationCard
+ notification={{
+ message: i18n.str`Server response with an error code`,
+ type: "ERROR",
+ description: i18n.str`Got message "${result.message}" from ${result.info?.url}`,
+ }}
+ />
+ </Fragment>;
+ }
+ if (result.type === ErrorType.UNREADABLE) {
+ <Fragment>
+ <NotConnectedAppMenu title="Error"/>
+ <NotificationCard
+ notification={{
+ message: i18n.str`Response from server is unreadable, http status: ${result.status}`,
+ type: "ERROR",
+ description: i18n.str`Got message "${result.message}" from ${result.info?.url}`,
+ }}
+ />
+ </Fragment>;
+ }
+ return (
+ <Fragment>
+ <NotConnectedAppMenu title="Error"/>
+ <NotificationCard
+ notification={{
+ message: i18n.str`Unexpected Error`,
+ type: "ERROR",
+ description: i18n.str`Got message "${result.message}" from ${result.info?.url}`,
+ }}
+ />
+ </Fragment>
+ );
+ }
-interface Props {
- title: string;
-}
+ const SUPPORTED_VERSION = "1:0:1"
+ if (result.data && !LibtoolVersion.compare(
+ SUPPORTED_VERSION,
+ result.data.version,
+ )?.compatible) {
+ return <Fragment>
+ <NotConnectedAppMenu title="Error"/>
+ <NotificationCard
+ notification={{
+ message: i18n.str`Incompatible version`,
+ type: "ERROR",
+ description: i18n.str`Auditor backend server version ${result.data.version} is not compatible with the supported version ${SUPPORTED_VERSION}`,
+ }}
+ />
+ </Fragment>
+ }
-export function NavigationBar({title}: Props): VNode {
return (
- <nav
- class="navbar is-fixed-top"
- role="navigation"
- aria-label="main navigation"
- >
- <div class="navbar-brand">
- <span class="navbar-item" style={{fontSize: 24, fontWeight: 900}}>
- {title}
- </span>
-
- <a
- role="button"
- class="navbar-burger"
- aria-label="menu"
- aria-expanded="false"
- onClick={(e) => {
- e.stopPropagation();
- }}
- >
- <span aria-hidden="true"/>
- <span aria-hidden="true"/>
- <span aria-hidden="true"/>
- </a>
- </div>
-
- <div class="navbar-menu ">
- <a
- class="navbar-start is-justify-content-center is-flex-grow-1"
- href="https://taler.net"
- >
- <img style={{height: 35, margin: 10}}/>
- </a>
- <div class="navbar-end">
- <div class="navbar-item" style={{paddingTop: 4, paddingBottom: 4}}>
- </div>
- </div>
- </div>
- </nav>
+ <div class="has-navbar-fixed-top">
+ <ConfigContextProvider value={ctx!}>
+ <ApplicationReadyRoutes/>
+ </ConfigContextProvider>
+ </div>
);
} \ No newline at end of file
diff --git a/packages/auditor-backoffice-ui/src/ApplicationReadyRoutes.tsx b/packages/auditor-backoffice-ui/src/ApplicationReadyRoutes.tsx
new file mode 100644
index 000000000..9645f2bac
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/ApplicationReadyRoutes.tsx
@@ -0,0 +1,164 @@
+/*
+ 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)
+ * @author Nic Eigel
+ */
+import {HttpStatusCode, TranslatedString} from "@gnu-taler/taler-util";
+import {ErrorType, useTranslationContext} from "@gnu-taler/web-util/browser";
+import {createHashHistory} from "history";
+import {Fragment, VNode, h} from "preact";
+import {Route, Router, route} from "preact-router";
+import {useEffect, useErrorBoundary, useState} from "preact/hooks";
+import {InstanceRoutes} from "./InstanceRoutes.js";
+import {
+ NotConnectedAppMenu,
+ NotYetReadyAppMenu,
+ NotificationCard,
+} from "./components/menu/index.js";
+import {useBackendContext} from "./context/backend.js";
+import {Settings} from "./paths/settings/index.js";
+import {INSTANCE_ID_LOOKUP} from "./utils/constants.js";
+
+/**
+ * Check if admin against /management/instances
+ * @returns
+ */
+export function ApplicationReadyRoutes(): VNode {
+ const {i18n} = useTranslationContext();
+ const [unauthorized, setUnauthorized] = useState(false)
+ const {
+ url: backendURL,
+ /*updateToken,
+ alreadyTriedLogin,*/
+ } = useBackendContext();
+
+ /*function updateLoginStatus(token: LoginToken | undefined) {
+ updateToken(token)
+ setUnauthorized(false)
+ }
+ const result = useBackendInstancesTestForAdmin();
+
+ const clearTokenAndGoToRoot = () => {
+ route("/");
+ };*/
+ const [showSettings, setShowSettings] = useState(false)
+ /* const unauthorizedAdmin = !result.loading
+ && !result.ok
+ && result.type === ErrorType.CLIENT
+ && result.status === HttpStatusCode.Unauthorized;
+
+ if (!alreadyTriedLogin && !result.ok) {
+ return (
+ <Fragment>
+ <NotConnectedAppMenu title="Welcome!" />
+ <LoginPage onConfirm={updateToken} />
+ </Fragment>
+ );
+ }*/
+
+ if (showSettings) {
+ return <Fragment>
+ <NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} title="UI Settings"/>
+ <Settings onClose={() => setShowSettings(false)}/>
+ </Fragment>
+ }
+ /*
+ if (result.loading) {
+ return <NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} title="Loading..." isPasswordOk={false} />;
+ }
+
+ let admin = result.ok || unauthorizedAdmin;*/
+ /*let instanceNameByBackendURL: string | undefined;
+
+ {
+ // * the testing against admin endpoint failed and it's not
+ // an authorization problem
+ // * merchant backend will return this SPA under the main
+ // endpoint or /instance/<id> endpoint
+ // => trying to infer the instance id
+ const path = new URL(backendURL).pathname;
+ const match = INSTANCE_ID_LOOKUP.exec(path);
+ if (!match || !match[1]) {
+ // this should be rare because
+ // query to /config is ok but the URL
+ // does not match our pattern
+ return (
+ <Fragment>
+ <NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} title="Error"/>
+ <NotificationCard
+ notification={{
+ message: i18n.str`Couldn't access the server.`,
+ description: i18n.str`Could not infer instance id from url ${backendURL}`,
+ type: "ERROR",
+ }}
+ />
+ {/* <ConnectionPage onConfirm={changeBackend} /> *//* }
+ </Fragment>
+ );
+ }
+
+ instanceNameByBackendURL = match[1];
+ }*/
+
+ /*if (unauthorized || unauthorizedAdmin) {
+ return <Fragment>
+ <NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} title="Login"
+ onLogout={clearTokenAndGoToRoot} isPasswordOk={false}/>
+ <NotificationCard
+ notification={{
+ message: i18n.str`Access denied`,
+ description: i18n.str`Check your token is valid`,
+ type: "ERROR",
+ }}
+ />
+ <LoginPage onConfirm={updateLoginStatus}/>
+ </Fragment>
+ }*/
+
+ const history = createHashHistory();
+ return (
+ <Router history={history}>
+ <Route
+ default
+ component={DefaultMainRoute}
+ // instanceNameByBackendURL={instanceNameByBackendURL}
+ />
+ </Router>
+ );
+}
+
+function DefaultMainRoute({
+ //instance,
+ // instanceNameByBackendURL,
+ url, //from preact-router
+ }: any): VNode {
+ const [instanceName, setInstanceName] = useState(
+ "default",
+ );
+
+ url = "app/#" + url;
+
+ return (
+ <InstanceRoutes
+ path={url}
+ // id={instanceName}
+ // setInstanceName={setInstanceName}
+ />
+ );
+}
diff --git a/packages/auditor-backoffice-ui/src/InstanceRoutes.tsx b/packages/auditor-backoffice-ui/src/InstanceRoutes.tsx
new file mode 100644
index 000000000..0ce54e810
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/InstanceRoutes.tsx
@@ -0,0 +1,332 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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)
+ * @author Nicola Eigel
+ */
+
+import { TranslatedString } from "@gnu-taler/taler-util";
+import {
+ ErrorType,
+ HttpError,
+ useTranslationContext,
+} from "@gnu-taler/web-util/browser";
+import { format } from "date-fns";
+import { Fragment, FunctionComponent, VNode, h } from "preact";
+import { Route, Router, route } from "preact-router";
+import { useEffect, useErrorBoundary, useMemo, useState } from "preact/hooks";
+import { Loading } from "./components/exception/loading.js";
+import { Menu, NotificationCard } from "./components/menu/index.js";
+import { EntityContextProvider } from "./context/entity.js";
+import {
+ useSimpleLocalStorage,
+} from "./hooks/index.js";
+import { dateFormatForSettings, useSettings } from "./hooks/useSettings.js";
+/*import AmountArithmeticInconsistencyList from "./paths/amount_arithmetic_inconsistencies/list/index.js";
+import BadSigLossList from "./paths/bad_sig_losses/list/index.js";
+import BalanceList from "./paths/balances/list/index.js";
+import ClosureLagList from "./paths/closure_lags/list/index.js";
+import CoinInconsistencyList from "./paths/coin_inconsistencies/list/index.js";
+import DenominationKeyValidityWithdrawInconsistencyList from "./paths/denomination_key_validity_withdraw_inconsistencies/list/index.js";
+import DenominationPendingList from "./paths/denominations_pending/list/index.js";
+import DenominationWithoutSigList from "./paths/denominations_without_sig/list/index.js";*/
+import DepositConfirmationList from "./paths/deposit_confirmations/list/index.js";
+/*import EmergencyList from "./paths/emergencies/list/index.js";
+import EmergencyByCountList from "./paths/emergencies_by_count/list/index.js";
+import ExchangeSignkeyList from "./paths/exchange_signkeys/list/index.js";
+import FeeTimeInconsistencyList from "./paths/fee_time_inconsistencies/list/index.js";
+import HistoricDenominationRevenueList from "./paths/historic_denominations_revenue/list/index.js";
+import MisattributionInInconsistencyList from "./paths/misattribution_in_inconsistencies/list/index.js";
+import ProgressList from "./paths/progress/list/index.js";
+import PurseNotClosedInconsistencyList from "./paths/purses_not_closed/list/index.js";
+import PurseList from "./paths/purses/list/index.js";
+import RefreshHangingList from "./paths/refreshes_hanging/list/index.js";
+import ReserveBalanceInsufficientInconsistencyList from "./paths/reserve_balance_insufficient_inconsistencies/list/index.js";
+import ReserveBalanceSummaryWrongInconsistencyList from "./paths/reserve_balance_summary_wrong_inconsistencies/list/index.js";
+import ReserveInInconsistencyList from "./paths/reserve_in_inconsistencies/list/index.js";
+import ReserveNotClosedInconsistencyList from "./paths/reserve_not_closed_inconsistencies/list/index.js";
+import RowInconsistencyList from "./paths/row_inconsistencies/list/index.js";
+import RowMinorInconsistencyList from "./paths/row_minor_inconsistencies/list/index.js";
+import WireFormatInconsistencyList from "./paths/wire_format_inconsistencies/list/index.js";
+import WireOutInconsistencyList from "./paths/wire_out_inconsistencies/list/index.js";
+*/
+import { Notification } from "./utils/types.js";
+import NotFoundPage from "./paths/notfound/index.js";
+import { Settings } from "./paths/settings/index.js";
+import BalanceList from "./paths/balances/index.js";
+import DefaultList from "./paths/default/index.js";
+import { AuditorBackend, ClassBalance, ClassDepositConfirmation, DepositConfirmationDetail } from "./declaration.js";
+
+export enum Paths {
+ error = "/error",
+ settings = "/settings",
+
+ amount_arithmethic_inconsistency_list = "/amount-arithmetic-inconsistencies",
+
+ bad_sig_losses_list = "/bad-sig-losses",
+
+ balance_list = "/balance",
+
+ closure_lag_list = "/closure-lags",
+
+ coin_inconsistency_list = "/coin-inconsistencies",
+
+ denomination_key_validity_withdraw_inconsistency_list = "/denomination-key-validity-withdraw-inconsistencies",
+
+ denomination_pending_list = "/denominations-pending",
+
+ denomination_without_sig_list = "/denominations-without-sig",
+
+ deposit_confirmation_list = "/deposit-confirmations",
+ deposit_confirmation_update = "/deposit-confirmation/:rowid/update",
+
+ emergency_list = "/emergencies",
+
+ emergency_by_count_list = "/emergencies-by-count",
+
+ exchange_signkey_list = "/exchange-sign-keys",
+
+ fee_time_inconsistency_list = "/fee-time-inconsistencies",
+
+ historic_denomination_revenue_list = "/historic-denomination-revenues",
+
+ misattribution_in_inconsistency_list = "/misattribution-in-inconsistencies",
+
+ progress_list = "/progress",
+
+ purse_not_closed_inconsistency_list = "/purse-not-closed-inconsistencies",
+
+ purse_list = "/purses",
+
+ refresh_hanging_list = "/refreshes-hanging",
+
+ reserve_balance_insufficient_inconsistency_list = "/reserve-balance-insufficient-inconsistencies",
+
+ reserve_balance_summary_wrong_inconsistency_list = "/reserve-balance-summary-wrong-inconsistencies",
+
+ reserve_in_inconsistency_list = "/reserve-in-inconsistencies",
+
+ reserve_not_closed_inconsistency_list = "/reserve-not-closed-inconsistencies",
+
+ row_inconsistency_list = "/row-inconsistencies",
+
+ row_minor_inconsistency_list = "/row-minor-inconsistencies",
+
+ wire_format_inconsistency_list = "/wire-format-inconsistencies",
+
+ wire_out_inconsistency_list = "/wire-out-inconsistencies"
+}
+
+interface TestProps {
+ title: string;
+ endpoint: string;
+ entity: any;
+}
+
+function getInstanceTitle(path: string): TestProps {
+ switch (path) {
+ /*case Paths.amount_arithmethic_inconsistency_list:
+ return [`Amount arithmetic inconsistencies`, ""];
+ case Paths.bad_sig_losses_list:
+ return `Bad sig losses`;*/
+ case Paths.balance_list:
+ const balance: ClassBalance = {} as ClassBalance;
+ return { title: "Balances", endpoint: "balances", entity: balance };
+ /*case Paths.closure_lag_list:
+ return `Closure Lags`;
+ case Paths.coin_inconsistency_list:
+ return `Coin inconsistencies`;
+ case Paths.denomination_key_validity_withdraw_inconsistency_list:
+ return `Denomination key validity withdraw inconsistency`;
+ case Paths.denomination_pending_list:
+ return `Denominations pending`;
+ case Paths.denomination_without_sig_list:
+ return `Denominations without sigs`;*/
+ case Paths.deposit_confirmation_list:
+ let depositConfirmation: ClassDepositConfirmation = {} as ClassDepositConfirmation;
+ return { title: "Deposit Confirmations", endpoint: "deposit-confirmation", entity: depositConfirmation };
+ /*case Paths.deposit_confirmation_update:
+ return `Update deposit confirmation`;
+ case Paths.emergency_list:
+ return `Emergencies`;
+ case Paths.emergency_by_count_list:
+ return `Emergencies by count`;
+ case Paths.exchange_signkey_list:
+ return `Exchange signkeys`;
+ case Paths.fee_time_inconsistency_list:
+ return `Fee time inconsistencies`;
+ case Paths.historic_denomination_revenue_list:
+ return `Historic denomination revenue`;
+ case Paths.misattribution_in_inconsistency_list:
+ return `Misattribution in inconsistencies`;
+ case Paths.progress_list:
+ return `Progress`;
+ case Paths.purse_not_closed_inconsistency_list:
+ return `Purse not closed inconsistencies`;
+ case Paths.purse_list:
+ return `Purses`;
+ case Paths.refresh_hanging_list:
+ return `Refreshes hanging`;
+ case Paths.reserve_balance_insufficient_inconsistency_list:
+ return `Reserve balance insufficient inconsistencies`;
+ case Paths.reserve_balance_summary_wrong_inconsistency_list:
+ return `Reserve balance summary wrong inconsistencies`;
+ case Paths.reserve_in_inconsistency_list:
+ return `Reserves in inconsistencies`;
+ case Paths.reserve_not_closed_inconsistency_list:
+ return `Reserves not closed inconsistencies`;
+ case Paths.row_inconsistency_list:
+ return `Row inconsistencies`;
+ case Paths.row_minor_inconsistency_list:
+ return `Row minor inconsistencies`;
+ case Paths.wire_format_inconsistency_list:
+ return `Wire format inconsistencies`;
+ case Paths.wire_out_inconsistency_list:
+ return `Wire out inconsistencies`;*/
+ case Paths.settings:
+ return { title: `Settings`, endpoint: "settings", entity: null };
+ default:
+ return { title: "", endpoint: "", entity: null };
+ }
+}
+
+// eslint-disable-next-line @typescript-eslint/no-empty-function
+const noop = () => {
+};
+
+export interface Props {
+ // id: string;
+ path: string;
+ //setInstanceName: (s: string) => void;
+}
+
+export function InstanceRoutes({
+ // id,
+ path,
+ // setInstanceName
+ }: Props): VNode {
+ const { i18n } = useTranslationContext();
+
+ type GlobalNotifState = (Notification & { to: string | undefined }) | undefined;
+ const [globalNotification, setGlobalNotification] =
+ useState<GlobalNotifState>(undefined);
+
+ const [error] = useErrorBoundary();
+ const { title, endpoint, entity } = getInstanceTitle(path.replace("app/#", ""));
+
+ const value = useMemo(
+ () => ({ title, path, endpoint, entity }),
+ [title, path, endpoint, entity],
+ );
+
+ /*function ServerErrorRedirectTo(to: Paths) {
+ return function ServerErrorRedirectToImpl(
+ error: HttpError<AuditorBackend.ErrorDetail>,
+ ) {
+ if (error.type === ErrorType.TIMEOUT) {
+ setGlobalNotification({
+ message: `The request to the backend take too long and was cancelled`,
+ description: `Diagnostic from ${error.info.url} is "${error.message}"`,
+ type: "ERROR",
+ to,
+ });
+ } else {
+ setGlobalNotification({
+ message: `The backend reported a problem: HTTP status #${error.status}`,
+ description: `Diagnostic from ${error.info.url} is '${error.message}'`,
+ details:
+ error.type === ErrorType.CLIENT || error.type === ErrorType.SERVER
+ ? error.payload.detail
+ : undefined,
+ type: "ERROR",
+ to,
+ });
+ }
+ return <Redirect to={to} />;
+ };
+ }*/
+
+
+ return (
+ <EntityContextProvider value={value}>
+ <Menu
+ // instance={id}
+ path={path}
+ title={"depsits"}
+ onShowSettings={() => {
+ route(Paths.settings);
+ }} />
+ <NotificationCard notification={globalNotification} />
+ {error &&
+ <NotificationCard notification={{
+ message: "Internal error, please report",
+ type: "ERROR",
+ description: <pre>
+ {(error instanceof Error ? error.stack : String(error)) as TranslatedString}
+ </pre>,
+ }} />
+ }
+
+ <Router
+ onChange={(e) => {
+ const movingOutFromNotification =
+ globalNotification && e.url !== globalNotification.to;
+ if (movingOutFromNotification) {
+ setGlobalNotification(undefined);
+ }
+ }}
+ >
+
+ <Route path="/" component={Redirect} to={Paths.deposit_confirmation_list} />
+ {/* //TODO: add rest
+ <Route path="/" component={Redirect} to={InstancePaths.deposit_confirmation_list}/>*/}
+ {/**
+ * Deposit confirmation pages
+ */}
+ <Route
+ path={Paths.balance_list}
+ component={DefaultList}
+ onNotFound={NotFoundPage}
+ //onLoadError={ServerErrorRedirectTo(Paths.balance_list)}
+ />
+ <Route
+ path={Paths.deposit_confirmation_list}
+ component={DefaultList}
+ onNotFound={NotFoundPage}
+ //onLoadError={ServerErrorRedirectTo(Paths.deposit_confirmation_list)}
+ />
+ {<Route
+ path={Paths.settings}
+ component={Settings}
+ />}
+ {/**
+ * Example pages
+ */}
+ {/* <Route path="/loading" component={Loading}/>
+ <Route default component={NotFoundPage}/>*/}
+ </Router>
+ </EntityContextProvider>
+ );
+}
+
+export function Redirect({ to }: { to: string }): null {
+ useEffect(() => {
+ route(to, true);
+ });
+ return null;
+} \ No newline at end of file
diff --git a/packages/auditor-backoffice-ui/src/components/exception/loading.tsx b/packages/auditor-backoffice-ui/src/components/exception/loading.tsx
new file mode 100644
index 000000000..11b62c124
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/components/exception/loading.tsx
@@ -0,0 +1,48 @@
+/*
+ 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 { h, VNode } from "preact";
+
+export function Loading(): VNode {
+ return (
+ <div
+ class="columns is-centered is-vcentered"
+ style={{
+ height: "calc(100% - 3rem)",
+ position: "absolute",
+ width: "100%",
+ }}
+ >
+ <Spinner />
+ </div>
+ );
+}
+
+export function Spinner(): VNode {
+ return (
+ <div class="lds-ring">
+ <div />
+ <div />
+ <div />
+ <div />
+ </div>
+ );
+}
diff --git a/packages/auditor-backoffice-ui/src/components/forms/FormProvider.tsx b/packages/auditor-backoffice-ui/src/components/forms/FormProvider.tsx
new file mode 100644
index 000000000..0d53c4d08
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/components/forms/FormProvider.tsx
@@ -0,0 +1,109 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 { ComponentChildren, createContext, h, VNode } from "preact";
+import { useContext, useMemo } from "preact/hooks";
+
+type Updater<S> = (value: (prevState: S) => S) => void;
+
+export interface Props<T> {
+ object?: Partial<T>;
+ errors?: FormErrors<T>;
+ name?: string;
+ valueHandler: Updater<Partial<T>> | null;
+ children: ComponentChildren;
+}
+
+const noUpdater: Updater<Partial<unknown>> = () => (s: unknown) => s;
+
+export function FormProvider<T>({
+ object = {},
+ errors = {},
+ name = "",
+ valueHandler,
+ children,
+}: Props<T>): VNode {
+ const initialObject = useMemo(() => object, []);
+ const value = useMemo<FormType<T>>(
+ () => ({
+ errors,
+ object,
+ initialObject,
+ valueHandler: valueHandler ? valueHandler : noUpdater,
+ name,
+ toStr: {},
+ fromStr: {},
+ }),
+ [errors, object, valueHandler],
+ );
+
+ return (
+ <FormContext.Provider value={value}>
+ <form
+ class="field"
+ onSubmit={(e) => {
+ e.preventDefault();
+ // if (valueHandler) valueHandler(object);
+ }}
+ >
+ {children}
+ </form>
+ </FormContext.Provider>
+ );
+}
+
+export interface FormType<T> {
+ object: Partial<T>;
+ initialObject: Partial<T>;
+ errors: FormErrors<T>;
+ toStr: FormtoStr<T>;
+ name: string;
+ fromStr: FormfromStr<T>;
+ valueHandler: Updater<Partial<T>>;
+}
+
+const FormContext = createContext<FormType<unknown>>(null!);
+
+/**
+ * FIXME:
+ * USE MEMORY EVENTS INSTEAD OF CONTEXT
+ * @deprecated
+ */
+
+export function useFormContext<T>() {
+ return useContext<FormType<T>>(FormContext);
+}
+
+export type FormErrors<T> = {
+ [P in keyof T]?: string | FormErrors<T[P]>;
+};
+
+export type FormtoStr<T> = {
+ [P in keyof T]?: (f?: T[P]) => string;
+};
+
+export type FormfromStr<T> = {
+ [P in keyof T]?: (f: string) => T[P];
+};
+
+export type FormUpdater<T> = {
+ [P in keyof T]?: (f: keyof T) => (v: T[P]) => void;
+};
diff --git a/packages/auditor-backoffice-ui/src/components/forms/Input.tsx b/packages/auditor-backoffice-ui/src/components/forms/Input.tsx
new file mode 100644
index 000000000..c1ddcb064
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/components/forms/Input.tsx
@@ -0,0 +1,116 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 { ComponentChildren, h, VNode } from "preact";
+import { useField, InputProps } from "./useField.js";
+
+interface Props<T> extends InputProps<T> {
+ inputType?: "text" | "number" | "multiline" | "password";
+ expand?: boolean;
+ toStr?: (v?: any) => string;
+ fromStr?: (s: string) => any;
+ inputExtra?: any;
+ side?: ComponentChildren;
+ children?: ComponentChildren;
+}
+
+const defaultToString = (f?: any): string => f || "";
+const defaultFromString = (v: string): any => v as any;
+
+const TextInput = ({ inputType, error, ...rest }: any) =>
+ inputType === "multiline" ? (
+ <textarea
+ {...rest}
+ class={error ? "textarea is-danger" : "textarea"}
+ rows="3"
+ />
+ ) : (
+ <input
+ {...rest}
+ class={error ? "input is-danger" : "input"}
+ type={inputType}
+ />
+ );
+
+export function Input<T>({
+ name,
+ readonly,
+ placeholder,
+ tooltip,
+ label,
+ expand,
+ help,
+ children,
+ inputType,
+ inputExtra,
+ side,
+ fromStr = defaultFromString,
+ toStr = defaultToString,
+}: Props<keyof T>): VNode {
+ const { error, value, onChange, required } = useField<T>(name);
+ return (
+ <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label">
+ {label}
+ {tooltip && (
+ <span class="icon has-tooltip-right" data-tooltip={tooltip}>
+ <i class="mdi mdi-information" />
+ </span>
+ )}
+ </label>
+ </div>
+ <div class="field-body is-flex-grow-3">
+ <div class="field">
+ <p
+ class={
+ expand
+ ? "control is-expanded has-icons-right"
+ : "control has-icons-right"
+ }
+ >
+ <TextInput
+ error={error}
+ {...inputExtra}
+ inputType={inputType}
+ placeholder={placeholder}
+ readonly={readonly}
+ disabled={readonly}
+ name={String(name)}
+ value={toStr(value)}
+ onChange={(e: h.JSX.TargetedEvent<HTMLInputElement>): void =>
+ onChange(fromStr(e.currentTarget.value))
+ }
+ />
+ {help}
+ {children}
+ {required && (
+ <span class="icon has-text-danger is-right">
+ <i class="mdi mdi-alert" />
+ </span>
+ )}
+ </p>
+ {error && <p class="help is-danger">{error}</p>}
+ </div>
+ {side}
+ </div>
+ </div>
+ );
+}
diff --git a/packages/auditor-backoffice-ui/src/components/forms/InputCurrency.tsx b/packages/auditor-backoffice-ui/src/components/forms/InputCurrency.tsx
new file mode 100644
index 000000000..b02354d7c
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/components/forms/InputCurrency.tsx
@@ -0,0 +1,67 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 { ComponentChildren, h, VNode } from "preact";
+import { useConfigContext } from "../../context/config.js";
+import { Amount } from "../../declaration.js";
+import { InputWithAddon } from "./InputWithAddon.js";
+import { InputProps } from "./useField.js";
+
+export interface Props<T> extends InputProps<T> {
+ expand?: boolean;
+ addonAfter?: ComponentChildren;
+ children?: ComponentChildren;
+ side?: ComponentChildren;
+}
+
+export function InputCurrency<T>({
+ name,
+ readonly,
+ label,
+ placeholder,
+ help,
+ tooltip,
+ expand,
+ addonAfter,
+ children,
+ side,
+}: Props<keyof T>): VNode {
+ const config = useConfigContext();
+ return (
+ <InputWithAddon<T>
+ name={name}
+ readonly={readonly}
+ addonBefore={config.currency}
+ side={side}
+ label={label}
+ placeholder={placeholder}
+ help={help}
+ tooltip={tooltip}
+ addonAfter={addonAfter}
+ inputType="number"
+ expand={expand}
+ toStr={(v?: Amount) => v?.split(":")[1] || ""}
+ fromStr={(v: string) => (!v ? undefined : `${config.currency}:${v}`)}
+ inputExtra={{ min: 0 }}
+ >
+ {children}
+ </InputWithAddon>
+ );
+}
diff --git a/packages/auditor-backoffice-ui/src/components/forms/InputNumber.tsx b/packages/auditor-backoffice-ui/src/components/forms/InputNumber.tsx
new file mode 100644
index 000000000..3b5df1474
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/components/forms/InputNumber.tsx
@@ -0,0 +1,60 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 { ComponentChildren, h } from "preact";
+import { InputWithAddon } from "./InputWithAddon.js";
+import { InputProps } from "./useField.js";
+
+export interface Props<T> extends InputProps<T> {
+ readonly?: boolean;
+ expand?: boolean;
+ side?: ComponentChildren;
+ children?: ComponentChildren;
+}
+
+export function InputNumber<T>({
+ name,
+ readonly,
+ placeholder,
+ tooltip,
+ label,
+ help,
+ expand,
+ children,
+ side,
+}: Props<keyof T>) {
+ return (
+ <InputWithAddon<T>
+ name={name}
+ readonly={readonly}
+ fromStr={(v) => (!v ? undefined : parseInt(v, 10))}
+ toStr={(v) => `${v}`}
+ inputType="number"
+ expand={expand}
+ label={label}
+ placeholder={placeholder}
+ help={help}
+ tooltip={tooltip}
+ inputExtra={{ min: 0 }}
+ children={children}
+ side={side}
+ />
+ );
+}
diff --git a/packages/auditor-backoffice-ui/src/components/forms/InputSelector.tsx b/packages/auditor-backoffice-ui/src/components/forms/InputSelector.tsx
new file mode 100644
index 000000000..a8dad5d89
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/components/forms/InputSelector.tsx
@@ -0,0 +1,94 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 { h, VNode } from "preact";
+import { InputProps, useField } from "./useField.js";
+
+interface Props<T> extends InputProps<T> {
+ readonly?: boolean;
+ expand?: boolean;
+ values: any[];
+ toStr?: (v?: any) => string;
+ fromStr?: (s: string) => any;
+}
+
+const defaultToString = (f?: any): string => f || "";
+const defaultFromString = (v: string): any => v as any;
+
+export function InputSelector<T>({
+ name,
+ readonly,
+ expand,
+ placeholder,
+ tooltip,
+ label,
+ help,
+ values,
+ fromStr = defaultFromString,
+ toStr = defaultToString,
+}: Props<keyof T>): VNode {
+ const { error, value, onChange, required } = useField<T>(name);
+ return (
+ <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label">
+ {label}
+ {tooltip && (
+ <span class="icon has-tooltip-right" data-tooltip={tooltip}>
+ <i class="mdi mdi-information" />
+ </span>
+ )}
+ </label>
+ </div>
+ <div class="field-body is-flex-grow-3">
+ <div class="field has-icons-right">
+ <p class={expand ? "control is-expanded select" : "control select "}>
+ <select
+ class={error ? "select is-danger" : "select"}
+ name={String(name)}
+ disabled={readonly}
+ readonly={readonly}
+ onChange={(e) => {
+ onChange(fromStr(e.currentTarget.value));
+ }}
+ >
+ {placeholder && <option>{placeholder}</option>}
+ {values.map((v, i) => {
+ return (
+ <option key={i} value={v} selected={value === v}>
+ {toStr(v)}
+ </option>
+ );
+ })}
+ </select>
+
+ {help}
+ </p>
+ {required && (
+ <span class="icon has-text-danger is-right" style={{height: "2.5em"}}>
+ <i class="mdi mdi-alert" />
+ </span>
+ )}
+ {error && <p class="help is-danger">{error}</p>}
+ </div>
+ </div>
+ </div>
+ );
+}
diff --git a/packages/auditor-backoffice-ui/src/components/forms/InputToggle.tsx b/packages/auditor-backoffice-ui/src/components/forms/InputToggle.tsx
new file mode 100644
index 000000000..f95dfcd05
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/components/forms/InputToggle.tsx
@@ -0,0 +1,91 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 { h, VNode } from "preact";
+import { InputProps, useField } from "./useField.js";
+
+interface Props<T> extends InputProps<T> {
+ name: T;
+ readonly?: boolean;
+ expand?: boolean;
+ threeState?: boolean;
+ toBoolean?: (v?: any) => boolean | undefined;
+ fromBoolean?: (s: boolean | undefined) => any;
+}
+
+const defaultToBoolean = (f?: any): boolean | undefined => f || "";
+const defaultFromBoolean = (v: boolean | undefined): any => v as any;
+
+export function InputToggle<T>({
+ name,
+ readonly,
+ placeholder,
+ tooltip,
+ label,
+ help,
+ threeState,
+ expand,
+ fromBoolean = defaultFromBoolean,
+ toBoolean = defaultToBoolean,
+}: Props<keyof T>): VNode {
+ const { error, value, onChange } = useField<T>(name);
+
+ const onCheckboxClick = (): void => {
+ const c = toBoolean(value);
+ if (c === false && threeState) return onChange(undefined as any);
+ return onChange(fromBoolean(!c));
+ };
+
+ return (
+ <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label" >
+ {label}
+ {tooltip && (
+ <span class="icon has-tooltip-right" data-tooltip={tooltip}>
+ <i class="mdi mdi-information" />
+ </span>
+ )}
+ </label>
+ </div>
+ <div class="field-body is-flex-grow-3">
+ <div class="field">
+ <p class={expand ? "control is-expanded" : "control"}>
+ <label class="toggle" style={{ marginLeft: 4, marginTop: 0 }}>
+ <input
+ type="checkbox"
+ class={toBoolean(value) === undefined ? "is-indeterminate" : "toggle-checkbox"}
+ checked={toBoolean(value)}
+ placeholder={placeholder}
+ readonly={readonly}
+ name={String(name)}
+ disabled={readonly}
+ onChange={onCheckboxClick}
+ />
+ <div class="toggle-switch"></div>
+ </label>
+ {help}
+ </p>
+ {error && <p class="help is-danger">{error}</p>}
+ </div>
+ </div>
+ </div>
+ );
+}
diff --git a/packages/auditor-backoffice-ui/src/components/forms/InputWithAddon.tsx b/packages/auditor-backoffice-ui/src/components/forms/InputWithAddon.tsx
new file mode 100644
index 000000000..e9fd88770
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/components/forms/InputWithAddon.tsx
@@ -0,0 +1,116 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 { ComponentChildren, h, VNode } from "preact";
+import { InputProps, useField } from "./useField.js";
+
+export interface Props<T> extends InputProps<T> {
+ expand?: boolean;
+ inputType?: "text" | "number" | "password";
+ addonBefore?: ComponentChildren;
+ addonAfter?: ComponentChildren;
+ addonAfterAction?: () => void;
+ toStr?: (v?: any) => string;
+ fromStr?: (s: string) => any;
+ inputExtra?: any;
+ children?: ComponentChildren;
+ side?: ComponentChildren;
+}
+
+const defaultToString = (f?: any): string => f || "";
+const defaultFromString = (v: string): any => v as any;
+
+export function InputWithAddon<T>({
+ name,
+ readonly,
+ addonBefore,
+ children,
+ expand,
+ label,
+ placeholder,
+ help,
+ tooltip,
+ inputType,
+ inputExtra,
+ side,
+ addonAfter,
+ addonAfterAction,
+ toStr = defaultToString,
+ fromStr = defaultFromString,
+}: Props<keyof T>): VNode {
+ const { error, value, onChange, required } = useField<T>(name);
+
+ return (
+ <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label">
+ {label}
+ {tooltip && (
+ <span class="icon has-tooltip-right" data-tooltip={tooltip}>
+ <i class="mdi mdi-information" />
+ </span>
+ )}
+ </label>
+ </div>
+ <div class="field-body is-flex-grow-3">
+ <div class="field">
+ <div class="field has-addons">
+ {addonBefore && (
+ <div class="control">
+ <a class="button is-static">{addonBefore}</a>
+ </div>
+ )}
+ <p
+ class={`control${expand ? " is-expanded" : ""}${required ? " has-icons-right" : ""
+ }`}
+ >
+ <input
+ {...(inputExtra || {})}
+ class={error ? "input is-danger" : "input"}
+ type={inputType}
+ placeholder={placeholder}
+ readonly={readonly}
+ disabled={readonly}
+ name={String(name)}
+ value={toStr(value)}
+ onChange={(e): void => onChange(fromStr(e.currentTarget.value))}
+ />
+ {required && (
+ <span class="icon has-text-danger is-right">
+ <i class="mdi mdi-alert" />
+ </span>
+ )}
+ {children}
+ </p>
+ {addonAfter && (
+ <div class="control" onClick={addonAfterAction} style={{ cursor: addonAfterAction ? "pointer" : undefined }}>
+ <a class="button is-static">{addonAfter}</a>
+ </div>
+ )}
+ </div>
+ {error && <p class="help is-danger">{error}</p>}
+ <span class="has-text-grey">{help}</span>
+ </div>
+ {expand ? <div>{side}</div> : side}
+ </div>
+
+ </div>
+ );
+}
diff --git a/packages/auditor-backoffice-ui/src/components/forms/useField.tsx b/packages/auditor-backoffice-ui/src/components/forms/useField.tsx
new file mode 100644
index 000000000..c7559faae
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/components/forms/useField.tsx
@@ -0,0 +1,92 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 { ComponentChildren, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { useFormContext } from "./FormProvider.js";
+
+interface Use<V> {
+ error?: string;
+ required: boolean;
+ value: any;
+ initial: any;
+ onChange: (v: V) => void;
+ toStr: (f: V | undefined) => string;
+ fromStr: (v: string) => V;
+}
+
+export function useField<T>(name: keyof T): Use<T[typeof name]> {
+ const { errors, object, initialObject, toStr, fromStr, valueHandler } =
+ useFormContext<T>();
+ type P = typeof name;
+ type V = T[P];
+ const [isDirty, setDirty] = useState(false);
+ const updateField =
+ (field: P) =>
+ (value: V): void => {
+ setDirty(true);
+ return valueHandler((prev) => {
+ return setValueDeeper(prev, String(field).split("."), value);
+ });
+ };
+
+ const defaultToString = (f?: V): string => String(!f ? "" : f);
+ const defaultFromString = (v: string): V => v as any;
+ const value = readField(object, String(name));
+ const initial = readField(initialObject, String(name));
+ const hasError = readField(errors, String(name));
+ return {
+ error: isDirty ? hasError : undefined,
+ required: !isDirty && hasError,
+ value,
+ initial,
+ onChange: updateField(name) as any,
+ toStr: toStr[name] ? toStr[name]! : defaultToString,
+ fromStr: fromStr[name] ? fromStr[name]! : defaultFromString,
+ };
+}
+/**
+ * read the field of an object an support accessing it using '.'
+ *
+ * @param object
+ * @param name
+ * @returns
+ */
+const readField = (object: any, name: string) => {
+ return name
+ .split(".")
+ .reduce((prev, current) => prev && prev[current], object);
+};
+
+const setValueDeeper = (object: any, names: string[], value: any): any => {
+ if (names.length === 0) return value;
+ const [head, ...rest] = names;
+ return { ...object, [head]: setValueDeeper(object[head] || {}, rest, value) };
+};
+
+export interface InputProps<T> {
+ name: T;
+ label: ComponentChildren;
+ placeholder?: string;
+ tooltip?: ComponentChildren;
+ readonly?: boolean;
+ help?: ComponentChildren;
+}
diff --git a/packages/auditor-backoffice-ui/src/components/menu/LangSelector.tsx b/packages/auditor-backoffice-ui/src/components/menu/LangSelector.tsx
new file mode 100644
index 000000000..41fe1374a
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/components/menu/LangSelector.tsx
@@ -0,0 +1,92 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import langIcon from "../../assets/icons/languageicon.svg";
+import { strings as messages } from "../../i18n/strings.js";
+
+type LangsNames = {
+ [P in keyof typeof messages]: string;
+};
+
+const names: LangsNames = {
+ es: "Español [es]",
+ en: "English [en]",
+ fr: "Français [fr]",
+ de: "Deutsch [de]",
+ sv: "Svenska [sv]",
+ it: "Italiano [it]",
+};
+
+function getLangName(s: keyof LangsNames | string) {
+ if (names[s]) return names[s];
+ return s;
+}
+
+export function LangSelector(): VNode {
+ const [updatingLang, setUpdatingLang] = useState(false);
+ const { lang, changeLanguage } = useTranslationContext();
+
+ return (
+ <div class="dropdown is-active ">
+ <div class="dropdown-trigger">
+ <button
+ class="button has-tooltip-left"
+ data-tooltip="change language selection"
+ aria-haspopup="true"
+ aria-controls="dropdown-menu"
+ onClick={() => setUpdatingLang(!updatingLang)}
+ >
+ <div class="icon is-small is-left">
+ <img src={langIcon} />
+ </div>
+ <span>{getLangName(lang)}</span>
+ <div class="icon is-right">
+ <i class="mdi mdi-chevron-down" />
+ </div>
+ </button>
+ </div>
+ {updatingLang && (
+ <div class="dropdown-menu" id="dropdown-menu" role="menu">
+ <div class="dropdown-content">
+ {Object.keys(messages)
+ .filter((l) => l !== lang)
+ .map((l) => (
+ <a
+ key={l}
+ class="dropdown-item"
+ value={l}
+ onClick={() => {
+ changeLanguage(l);
+ setUpdatingLang(false);
+ }}
+ >
+ {getLangName(l)}
+ </a>
+ ))}
+ </div>
+ </div>
+ )}
+ </div>
+ );
+}
diff --git a/packages/auditor-backoffice-ui/src/components/menu/NavigationBar.tsx b/packages/auditor-backoffice-ui/src/components/menu/NavigationBar.tsx
new file mode 100644
index 000000000..21561087e
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/components/menu/NavigationBar.tsx
@@ -0,0 +1,73 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 { h, VNode } from "preact";
+// @ts-ignore
+import logo from "../../assets/logo-2021.svg";
+
+interface Props {
+ onMobileMenu: () => void;
+ title: string;
+}
+
+export function NavigationBar({ onMobileMenu, title }: Props): VNode {
+ return (
+ <nav
+ class="navbar is-fixed-top"
+ role="navigation"
+ aria-label="main navigation"
+ >
+ <div class="navbar-brand">
+ <span class="navbar-item" style={{ fontSize: 24, fontWeight: 900 }}>
+ {title}
+ </span>
+
+ <a
+ role="button"
+ class="navbar-burger"
+ aria-label="menu"
+ aria-expanded="false"
+ onClick={(e) => {
+ onMobileMenu();
+ e.stopPropagation();
+ }}
+ >
+ <span aria-hidden="true" />
+ <span aria-hidden="true" />
+ <span aria-hidden="true" />
+ </a>
+ </div>
+
+ <div class="navbar-menu ">
+ <a
+ class="navbar-start is-justify-content-center is-flex-grow-1"
+ href="https://taler.net"
+ >
+ <img src={logo} style={{ height: 35, margin: 10 }} />
+ </a>
+ <div class="navbar-end">
+ <div class="navbar-item" style={{ paddingTop: 4, paddingBottom: 4 }}>
+ </div>
+ </div>
+ </div>
+ </nav>
+ );
+}
diff --git a/packages/auditor-backoffice-ui/src/components/menu/SideBar.tsx b/packages/auditor-backoffice-ui/src/components/menu/SideBar.tsx
index a000cd251..c7d5bc2b8 100644
--- a/packages/auditor-backoffice-ui/src/components/menu/SideBar.tsx
+++ b/packages/auditor-backoffice-ui/src/components/menu/SideBar.tsx
@@ -43,18 +43,288 @@ export function Sidebar(props: any): VNode {
</div>
<div class="menu is-menu-main">
<Fragment>
- <ul class="menu-list">
- <li>
- <a href={"/deposit-confirmations"} class="has-icon">
+ <ul class="menu-list">
+ <li>
+ <a href={"/amount-arithmetic-inconsistencies"} class="has-icon">
<span class="icon">
- <i class="mdi mdi-cash-register"/>
+ <i class="mdi mdi-cash-register" />
</span>
- <span class="menu-item-label">
+ <span class="menu-item-label">
+ <i18n.Translate>Amount arithmetic inconsistencies</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/bad-sig-losses"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-cash-register" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Bad sig losses</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/balance"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-cash-register" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Balance</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/closure-lags"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-cash-register" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Closure Lags</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/coin-inconsistencies"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-cash-register" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Coin inconsistencies</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/denomination-key-validity-withdraw-inconsistencies"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-cash-register" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Denomination key validity withdraw inconsistency</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/denominations-pending"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-cash-register" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Denominations pending</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/denominations-without-sig"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-cash-register" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Denominations without sigs</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/deposit-confirmations"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-cash-register" />
+ </span>
+ <span class="menu-item-label">
<i18n.Translate>Deposit Confirmations</i18n.Translate>
</span>
- </a>
- </li>
- </ul>
+ </a>
+ </li>
+ <li>
+ <a href={"/emergencies"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-square-edit-outline" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Emergencies</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/emergencies-by-count"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-square-edit-outline" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Emergencies by count</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/exchange-sign-keys"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-square-edit-outline" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Exchange signkeys</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/fee-time-inconsistencies"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-square-edit-outline" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Fee time inconsistencies</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/historic-denomination-revenues"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-square-edit-outline" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Historic denomination revenue</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/misattribution-in-inconsistencies"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-square-edit-outline" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Misattribution in inconsistencies</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/progress"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-square-edit-outline" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Progress</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/purse-not-closed-inconsistencies"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-square-edit-outline" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Purse not closed inconsistencies</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/purses"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-square-edit-outline" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Purses</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/refreshes-hanging"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-square-edit-outline" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Refreshes hanging</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/reserve-balance-insufficient-inconsistencies"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-square-edit-outline" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Reserve balance insufficient inconsistencies</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/reserve-balance-summary-wrong-inconsistencies"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-square-edit-outline" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Reserve balance summary wrong inconsistencies</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/reserve-in-inconsistencies"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-square-edit-outline" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Reserves in inconsistencies</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/reserve-not-closed-inconsistencies"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-square-edit-outline" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Reserves not closed inconsistencies</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/row-inconsistencies"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-square-edit-outline" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Row inconsistencies</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/row-minor-inconsistencies"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-square-edit-outline" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Row minor inconsistencies</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/wire-format-inconsistencies"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-square-edit-outline" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Wire format inconsistencies</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/wire-out-inconsistencies"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-square-edit-outline" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Wire out inconsistencies</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a href={"/settings"} class="has-icon">
+ <span class="icon">
+ <i class="mdi mdi-square-edit-outline" />
+ </span>
+ <span class="menu-item-label">
+ <i18n.Translate>Settings</i18n.Translate>
+ </span>
+ </a>
+ </li>
+ </ul>
</Fragment>
</div>
</aside>
diff --git a/packages/auditor-backoffice-ui/src/components/menu/index.tsx b/packages/auditor-backoffice-ui/src/components/menu/index.tsx
index 4c02797ab..e811f977f 100644
--- a/packages/auditor-backoffice-ui/src/components/menu/index.tsx
+++ b/packages/auditor-backoffice-ui/src/components/menu/index.tsx
@@ -17,12 +17,95 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
- * @author Nic Eigel
*
+ * @author Sebastian Javier Marchano (sebasjm)
+ * @author Nic Eigel
*/
-/*
+
import {ComponentChildren, Fragment, h, VNode} from "preact";
-import {useEffect} from "preact/hooks";
+import {useEffect, useState} from "preact/hooks";
+import {Paths} from "../../InstanceRoutes.js";
+import {Notification} from "../../utils/types.js";
+import {NavigationBar} from "./NavigationBar.js";
+import {Sidebar} from "./SideBar.js";
+
+/**
+ * @author Nic Eigel
+ *
+ */
+
+function getInstanceTitle(path: string): string {
+ switch (path) {
+ case Paths.amount_arithmethic_inconsistency_list:
+ return `Amount arithmetic inconsistencies`;
+ case Paths.bad_sig_losses_list:
+ return `Bad sig losses`;
+ case Paths.balance_list:
+ return `Balances`;
+ case Paths.closure_lag_list:
+ return `Closure Lags`;
+ case Paths.coin_inconsistency_list:
+ return `Coin inconsistencies`;
+ case Paths.denomination_key_validity_withdraw_inconsistency_list:
+ return `Denomination key validity withdraw inconsistency`;
+ case Paths.denomination_pending_list:
+ return `Denominations pending`;
+ case Paths.denomination_without_sig_list:
+ return `Denominations without sigs`;
+ case Paths.deposit_confirmation_list:
+ return `Deposit confirmations`;
+ case Paths.deposit_confirmation_update:
+ return `Update deposit confirmation`;
+ case Paths.emergency_list:
+ return `Emergencies`;
+ case Paths.emergency_by_count_list:
+ return `Emergencies by count`;
+ case Paths.exchange_signkey_list:
+ return `Exchange signkeys`;
+ case Paths.fee_time_inconsistency_list:
+ return `Fee time inconsistencies`;
+ case Paths.historic_denomination_revenue_list:
+ return `Historic denomination revenue`;
+ case Paths.misattribution_in_inconsistency_list:
+ return `Misattribution in inconsistencies`;
+ case Paths.progress_list:
+ return `Progress`;
+ case Paths.purse_not_closed_inconsistency_list:
+ return `Purse not closed inconsistencies`;
+ case Paths.purse_list:
+ return `Purses`;
+ case Paths.refresh_hanging_list:
+ return `Refreshes hanging`;
+ case Paths.reserve_balance_insufficient_inconsistency_list:
+ return `Reserve balance insufficient inconsistencies`;
+ case Paths.reserve_balance_summary_wrong_inconsistency_list:
+ return `Reserve balance summary wrong inconsistencies`;
+ case Paths.reserve_in_inconsistency_list:
+ return `Reserves in inconsistencies`;
+ case Paths.reserve_not_closed_inconsistency_list:
+ return `Reserves not closed inconsistencies`;
+ case Paths.row_inconsistency_list:
+ return `Row inconsistencies`;
+ case Paths.row_minor_inconsistency_list:
+ return `Row minor inconsistencies`;
+ case Paths.wire_format_inconsistency_list:
+ return `Wire format inconsistencies`;
+ case Paths.wire_out_inconsistency_list:
+ return `Wire out inconsistencies`;
+ case Paths.settings:
+ return `Settings`;
+ default:
+ return "";
+ }
+}
+
+interface MenuProps {
+ title?: string;
+ path: string;
+ // instance: string;
+ onShowSettings: () => void;
+// setInstanceName: (s: string) => void;
+}
function WithTitle({
title,
@@ -38,24 +121,15 @@ function WithTitle({
}
export function Menu({
- onLogout,
onShowSettings,
title,
- instance,
+ // instance,
path,
- admin,
- setInstanceName,
- isPasswordOk
+ // setInstanceName
}: MenuProps): VNode {
const [mobileOpen, setMobileOpen] = useState(false);
-
- const titleWithSubtitle = title
- ? title
- : !admin
- ? getInstanceTitle(path, instance)
- : getAdminTitle(path, instance);
- const adminInstance = instance === "default";
- const mimic = admin && !adminInstance;
+ let t = title;
+ const titleWithSubtitle = getInstanceTitle("/"+path);
return (
<WithTitle title={titleWithSubtitle}>
<div
@@ -67,41 +141,124 @@ export function Menu({
title={titleWithSubtitle}
/>
- {onLogout && (
- <Sidebar
- onShowSettings={onShowSettings}
- onLogout={onLogout}
- admin={admin}
- mimic={mimic}
- instance={instance}
- mobile={mobileOpen}
- isPasswordOk={isPasswordOk}
- />
- )}
-
- {mimic && (
- <nav class="level" style={{
- zIndex: 100,
- position: "fixed",
- width: "50%",
- marginLeft: "20%"
- }}>
- <div class="level-item has-text-centered has-background-warning">
- <p class="is-size-5">
- You are viewing the instance <b>&quot;{instance}&quot;</b>.{" "}
- <a
- href="#/instances"
- onClick={(e) => {
- setInstanceName("default");
- }}
- >
- go back
- </a>
- </p>
- </div>
- </nav>
- )}
+ <Sidebar
+ onShowSettings={onShowSettings}
+ // instance={instance}
+ mobile={mobileOpen}
+ />
+ {/*<nav class="level" style={{
+ zIndex: 100,
+ position: "fixed",
+ width: "50%",
+ marginLeft: "20%"
+ }}>
+ <div class="level-item has-text-centered has-background-warning">
+ <p class="is-size-5">
+ You are viewing the instance <b>something</b>.{" "}
+ <a
+ href="#/instances"
+ onClick={(e) => {
+ }}
+ >
+ go back
+ </a>
+ </p>
+ </div>
+ </nav>*/}
</div>
</WithTitle>
);
-}*/ \ No newline at end of file
+}
+
+interface NotYetReadyAppMenuProps {
+ title: string;
+ onShowSettings: () => void;
+}
+
+interface NotifProps {
+ notification?: Notification;
+}
+
+export function NotificationCard({notification: n}: NotifProps): VNode | null {
+ if (!n) return null;
+ return (
+ <div class="notification">
+ <div class="columns is-vcentered">
+ <div class="column is-12">
+ <article
+ class={
+ n.type === "ERROR"
+ ? "message is-danger"
+ : n.type === "WARN"
+ ? "message is-warning"
+ : "message is-info"
+ }
+ >
+ <div class="message-header">
+ <p>{n.message}</p>
+ </div>
+ {n.description && (
+ <div class="message-body">
+ <div>{n.description}</div>
+ {n.details && <pre>{n.details}</pre>}
+ </div>
+ )}
+ </article>
+ </div>
+ </div>
+ </div>
+ );
+}
+
+interface NotConnectedAppMenuProps {
+ title: string;
+}
+
+export function NotConnectedAppMenu({
+ title,
+ }: NotConnectedAppMenuProps): VNode {
+ const [mobileOpen, setMobileOpen] = useState(false);
+
+ useEffect(() => {
+ document.title = `Taler Backoffice: ${title}`;
+ }, [title]);
+
+ return (
+ <div
+ class={mobileOpen ? "has-aside-mobile-expanded" : ""}
+ onClick={() => setMobileOpen(false)}
+ >
+ <NavigationBar
+ onMobileMenu={() => setMobileOpen(!mobileOpen)}
+ title={title}
+ />
+ </div>
+ );
+}
+
+
+export function NotYetReadyAppMenu({
+ onShowSettings,
+ title
+ }: NotYetReadyAppMenuProps): VNode {
+ const [mobileOpen, setMobileOpen] = useState(false);
+
+ useEffect(() => {
+ document.title = `Taler Backoffice: ${title}`;
+ }, [title]);
+
+ return (
+ <div
+ class={mobileOpen ? "has-aside-mobile-expanded" : ""}
+ onClick={() => setMobileOpen(false)}
+ >
+ <NavigationBar
+ onMobileMenu={() => setMobileOpen(!mobileOpen)}
+ title={title}
+ />
+ (
+ <Sidebar onShowSettings={onShowSettings} instance="" mobile={mobileOpen}/>
+ )
+ </div>
+ );
+} \ No newline at end of file
diff --git a/packages/auditor-backoffice-ui/src/components/modal/index.tsx b/packages/auditor-backoffice-ui/src/components/modal/index.tsx
new file mode 100644
index 000000000..efda6b944
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/components/modal/index.tsx
@@ -0,0 +1,496 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { ComponentChildren, Fragment, h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { useEntityContext } from "../../context/entity.js";
+import { DEFAULT_REQUEST_TIMEOUT } from "../../utils/constants.js";
+import { Spinner } from "../exception/loading.js";
+import { FormProvider } from "../forms/FormProvider.js";
+import { Input } from "../forms/Input.js";
+
+interface Props {
+ active?: boolean;
+ description?: string;
+ onCancel?: () => void;
+ onConfirm?: () => void;
+ label?: string;
+ children?: ComponentChildren;
+ danger?: boolean;
+ disabled?: boolean;
+}
+
+export function ConfirmModal({
+ active,
+ description,
+ onCancel,
+ onConfirm,
+ children,
+ danger,
+ disabled,
+ label = "Confirm",
+}: Props): VNode {
+ const { i18n } = useTranslationContext();
+ return (
+ <div class={active ? "modal is-active" : "modal"}>
+ <div class="modal-background " onClick={onCancel} />
+ <div class="modal-card" style={{ maxWidth: 700 }}>
+ <header class="modal-card-head">
+ {!description ? null : (
+ <p class="modal-card-title">
+ <b>{description}</b>
+ </p>
+ )}
+ <button class="delete " aria-label="close" onClick={onCancel} />
+ </header>
+ <section class="modal-card-body">{children}</section>
+ <footer class="modal-card-foot">
+ <div class="buttons is-right" style={{ width: "100%" }}>
+ {onConfirm ? (
+ <Fragment>
+ <button class="button " onClick={onCancel}>
+ <i18n.Translate>Cancel</i18n.Translate>
+ </button>
+
+ <button
+ class={danger ? "button is-danger " : "button is-info "}
+ disabled={disabled}
+ onClick={onConfirm}
+ >
+ <i18n.Translate>{label}</i18n.Translate>
+ </button>
+ </Fragment>
+ ) : (
+ <button class="button " onClick={onCancel}>
+ <i18n.Translate>Close</i18n.Translate>
+ </button>
+ )}
+ </div>
+ </footer>
+ </div>
+ <button
+ class="modal-close is-large "
+ aria-label="close"
+ onClick={onCancel}
+ />
+ </div>
+ );
+}
+
+export function ContinueModal({
+ active,
+ description,
+ onCancel,
+ onConfirm,
+ children,
+ disabled,
+}: Props): VNode {
+ const { i18n } = useTranslationContext();
+ return (
+ <div class={active ? "modal is-active" : "modal"}>
+ <div class="modal-background " onClick={onCancel} />
+ <div class="modal-card">
+ <header class="modal-card-head has-background-success">
+ {!description ? null : <p class="modal-card-title">{description}</p>}
+ <button class="delete " aria-label="close" onClick={onCancel} />
+ </header>
+ <section class="modal-card-body">{children}</section>
+ <footer class="modal-card-foot">
+ <div class="buttons is-right" style={{ width: "100%" }}>
+ <button
+ class="button is-success "
+ disabled={disabled}
+ onClick={onConfirm}
+ >
+ <i18n.Translate>Continue</i18n.Translate>
+ </button>
+ </div>
+ </footer>
+ </div>
+ <button
+ class="modal-close is-large "
+ aria-label="close"
+ onClick={onCancel}
+ />
+ </div>
+ );
+}
+
+export function SimpleModal({ onCancel, children }: any): VNode {
+ return (
+ <div class="modal is-active">
+ <div class="modal-background " onClick={onCancel} />
+ <div class="modal-card">
+ <section class="modal-card-body is-main-section">{children}</section>
+ </div>
+ <button
+ class="modal-close is-large "
+ aria-label="close"
+ onClick={onCancel}
+ />
+ </div>
+ );
+}
+
+export function ClearConfirmModal({
+ description,
+ onCancel,
+ onClear,
+ onConfirm,
+ children,
+}: Props & { onClear?: () => void }): VNode {
+ const { i18n } = useTranslationContext();
+ return (
+ <div class="modal is-active">
+ <div class="modal-background " onClick={onCancel} />
+ <div class="modal-card">
+ <header class="modal-card-head">
+ {!description ? null : <p class="modal-card-title">{description}</p>}
+ <button class="delete " aria-label="close" onClick={onCancel} />
+ </header>
+ <section class="modal-card-body is-main-section">{children}</section>
+ <footer class="modal-card-foot">
+ {onClear && (
+ <button
+ class="button is-danger"
+ onClick={onClear}
+ disabled={onClear === undefined}
+ >
+ <i18n.Translate>Clear</i18n.Translate>
+ </button>
+ )}
+ <div class="buttons is-right" style={{ width: "100%" }}>
+ <button class="button " onClick={onCancel}>
+ <i18n.Translate>Cancel</i18n.Translate>
+ </button>
+ <button
+ class="button is-info"
+ onClick={onConfirm}
+ disabled={onConfirm === undefined}
+ >
+ <i18n.Translate>Confirm</i18n.Translate>
+ </button>
+ </div>
+ </footer>
+ </div>
+ <button
+ class="modal-close is-large "
+ aria-label="close"
+ onClick={onCancel}
+ />
+ </div>
+ );
+}
+
+interface DeleteModalProps {
+ element: { id: string; name: string };
+ onCancel: () => void;
+ onConfirm: (id: string) => void;
+}
+
+export function DeleteModal({
+ element,
+ onCancel,
+ onConfirm,
+}: DeleteModalProps): VNode {
+ return (
+ <ConfirmModal
+ label={`Delete instance`}
+ description={`Delete the instance "${element.name}"`}
+ danger
+ active
+ onCancel={onCancel}
+ onConfirm={() => onConfirm(element.id)}
+ >
+ <p>
+ If you delete the instance named <b>&quot;{element.name}&quot;</b> (ID:{" "}
+ <b>{element.id}</b>), the merchant will no longer be able to process
+ orders or refunds
+ </p>
+ <p>
+ This action deletes the instance private key, but preserves all
+ transaction data. You can still access that data after deleting the
+ instance.
+ </p>
+ <p class="warning">
+ Deleting an instance <b>cannot be undone</b>.
+ </p>
+ </ConfirmModal>
+ );
+}
+
+export function PurgeModal({
+ element,
+ onCancel,
+ onConfirm,
+}: DeleteModalProps): VNode {
+ return (
+ <ConfirmModal
+ label={`Purge the instance`}
+ description={`Purge the instance "${element.name}"`}
+ danger
+ active
+ onCancel={onCancel}
+ onConfirm={() => onConfirm(element.id)}
+ >
+ <p>
+ If you purge the instance named <b>&quot;{element.name}&quot;</b> (ID:{" "}
+ <b>{element.id}</b>), you will also delete all it&apos;s transaction
+ data.
+ </p>
+ <p>
+ The instance will disappear from your list, and you will no longer be
+ able to access it&apos;s data.
+ </p>
+ <p class="warning">
+ Purging an instance <b>cannot be undone</b>.
+ </p>
+ </ConfirmModal>
+ );
+}
+
+interface UpdateTokenModalProps {
+ oldToken?: string;
+ onCancel: () => void;
+ onConfirm: (value: string) => void;
+ onClear: () => void;
+}
+
+//FIXME: merge UpdateTokenModal with SetTokenNewInstanceModal
+export function UpdateTokenModal({
+ onCancel,
+ onClear,
+ onConfirm,
+ oldToken,
+}: UpdateTokenModalProps): VNode {
+ type State = { old_token: string; new_token: string; repeat_token: string };
+ const [form, setValue] = useState<Partial<State>>({
+ old_token: "",
+ new_token: "",
+ repeat_token: "",
+ });
+ const { i18n } = useTranslationContext();
+
+ const hasInputTheCorrectOldToken = oldToken && oldToken !== form.old_token;
+ const errors = {
+ old_token: hasInputTheCorrectOldToken
+ ? i18n.str`is not the same as the current access token`
+ : undefined,
+ new_token: !form.new_token
+ ? i18n.str`cannot be empty`
+ : form.new_token === form.old_token
+ ? i18n.str`cannot be the same as the old token`
+ : undefined,
+ repeat_token:
+ form.new_token !== form.repeat_token
+ ? i18n.str`is not the same`
+ : undefined,
+ };
+
+ const hasErrors = Object.keys(errors).some(
+ (k) => (errors as any)[k] !== undefined,
+ );
+
+ const instance = useEntityContext();
+
+ const text = i18n.str`You are updating the access token from instance with id `;
+
+ return (
+ <ClearConfirmModal
+ description={text}
+ onCancel={onCancel}
+ onConfirm={!hasErrors ? () => onConfirm(form.new_token!) : undefined}
+ onClear={!hasInputTheCorrectOldToken && oldToken ? onClear : undefined}
+ >
+ <div class="columns">
+ <div class="column" />
+ <div class="column is-four-fifths">
+ <FormProvider errors={errors} object={form} valueHandler={setValue}>
+ {oldToken && (
+ <Input<State>
+ name="old_token"
+ label={i18n.str`Old access token`}
+ tooltip={i18n.str`access token currently in use`}
+ inputType="password"
+ />
+ )}
+ <Input<State>
+ name="new_token"
+ label={i18n.str`New access token`}
+ tooltip={i18n.str`next access token to be used`}
+ inputType="password"
+ />
+ <Input<State>
+ name="repeat_token"
+ label={i18n.str`Repeat access token`}
+ tooltip={i18n.str`confirm the same access token`}
+ inputType="password"
+ />
+ </FormProvider>
+ <p>
+ <i18n.Translate>
+ Clearing the access token will mean public access to the instance
+ </i18n.Translate>
+ </p>
+ </div>
+ <div class="column" />
+ </div>
+ </ClearConfirmModal>
+ );
+}
+
+export function SetTokenNewInstanceModal({
+ onCancel,
+ onClear,
+ onConfirm,
+}: UpdateTokenModalProps): VNode {
+ type State = { old_token: string; new_token: string; repeat_token: string };
+ const [form, setValue] = useState<Partial<State>>({
+ new_token: "",
+ repeat_token: "",
+ });
+ const { i18n } = useTranslationContext();
+
+ const errors = {
+ new_token: !form.new_token
+ ? i18n.str`cannot be empty`
+ : form.new_token === form.old_token
+ ? i18n.str`cannot be the same as the old access token`
+ : undefined,
+ repeat_token:
+ form.new_token !== form.repeat_token
+ ? i18n.str`is not the same`
+ : undefined,
+ };
+
+ const hasErrors = Object.keys(errors).some(
+ (k) => (errors as any)[k] !== undefined,
+ );
+
+ return (
+ <div class="modal is-active">
+ <div class="modal-background " onClick={onCancel} />
+ <div class="modal-card">
+ <header class="modal-card-head">
+ <p class="modal-card-title">{i18n.str`You are setting the access token for the new instance`}</p>
+ <button class="delete " aria-label="close" onClick={onCancel} />
+ </header>
+ <section class="modal-card-body is-main-section">
+ <div class="columns">
+ <div class="column" />
+ <div class="column is-four-fifths">
+ <FormProvider
+ errors={errors}
+ object={form}
+ valueHandler={setValue}
+ >
+ <Input<State>
+ name="new_token"
+ label={i18n.str`New access token`}
+ tooltip={i18n.str`next access token to be used`}
+ inputType="password"
+ />
+ <Input<State>
+ name="repeat_token"
+ label={i18n.str`Repeat access token`}
+ tooltip={i18n.str`confirm the same access token`}
+ inputType="password"
+ />
+ </FormProvider>
+ <p>
+ <i18n.Translate>
+ With external authorization method no check will be done by
+ the merchant backend
+ </i18n.Translate>
+ </p>
+ </div>
+ <div class="column" />
+ </div>
+ </section>
+ <footer class="modal-card-foot">
+ {onClear && (
+ <button
+ class="button is-danger"
+ onClick={onClear}
+ disabled={onClear === undefined}
+ >
+ <i18n.Translate>Set external authorization</i18n.Translate>
+ </button>
+ )}
+ <div class="buttons is-right" style={{ width: "100%" }}>
+ <button class="button " onClick={onCancel}>
+ <i18n.Translate>Cancel</i18n.Translate>
+ </button>
+ <button
+ class="button is-info"
+ onClick={() => onConfirm(form.new_token!)}
+ disabled={hasErrors}
+ >
+ <i18n.Translate>Set access token</i18n.Translate>
+ </button>
+ </div>
+ </footer>
+ </div>
+ <button
+ class="modal-close is-large "
+ aria-label="close"
+ onClick={onCancel}
+ />
+ </div>
+ );
+}
+
+export function LoadingModal({ onCancel }: { onCancel: () => void }): VNode {
+ const { i18n } = useTranslationContext();
+ return (
+ <div class="modal is-active">
+ <div class="modal-background " onClick={onCancel} />
+ <div class="modal-card">
+ <header class="modal-card-head">
+ <p class="modal-card-title">
+ <i18n.Translate>Operation in progress...</i18n.Translate>
+ </p>
+ </header>
+ <section class="modal-card-body">
+ <div class="columns">
+ <div class="column" />
+ <Spinner />
+ <div class="column" />
+ </div>
+ <p>{i18n.str`The operation will be automatically canceled after ${DEFAULT_REQUEST_TIMEOUT} seconds`}</p>
+ </section>
+ <footer class="modal-card-foot">
+ <div class="buttons is-right" style={{ width: "100%" }}>
+ <button class="button " onClick={onCancel}>
+ <i18n.Translate>Cancel</i18n.Translate>
+ </button>
+ </div>
+ </footer>
+ </div>
+ <button
+ class="modal-close is-large "
+ aria-label="close"
+ onClick={onCancel}
+ />
+ </div>
+ );
+}
diff --git a/packages/auditor-backoffice-ui/src/context/backend.ts b/packages/auditor-backoffice-ui/src/context/backend.ts
new file mode 100644
index 000000000..e53057398
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/context/backend.ts
@@ -0,0 +1,71 @@
+/*
+ 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)
+ * @author Nic Eigel
+ */
+
+import { createContext, h, VNode } from "preact";
+import { useContext } from "preact/hooks";
+import { useBackendURL } from "../hooks/index.js";
+
+//TODO: maybe add token
+interface BackendContextType {
+ url: string,
+ /*alreadyTriedLogin: boolean;
+ token?: LoginToken;
+ updateToken: (token: LoginToken | undefined) => void;*/
+}
+
+const BackendContext = createContext<BackendContextType>({
+ url: "",
+ /*alreadyTriedLogin: false,
+ token: undefined,
+ updateToken: () => null,*/
+});
+
+function useBackendContextState(
+ defaultUrl?: string,
+): BackendContextType {
+ const [url] = useBackendURL(defaultUrl);
+ //const [token, updateToken] = useBackendDefaultToken();
+
+ return {
+ url,
+ /* token,
+ alreadyTriedLogin: token !== undefined,
+ updateToken,*/
+ };
+}
+
+export const BackendContextProvider = ({
+ children,
+ defaultUrl,
+ }: {
+ children: any;
+ defaultUrl?: string;
+}): VNode => {
+ const value = useBackendContextState(defaultUrl);
+
+ return h(BackendContext.Provider, { value, children });
+};
+
+
+
+export const useBackendContext = (): BackendContextType =>
+ useContext(BackendContext); \ No newline at end of file
diff --git a/packages/auditor-backoffice-ui/src/context/config.ts b/packages/auditor-backoffice-ui/src/context/config.ts
new file mode 100644
index 000000000..58ee5a594
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/context/config.ts
@@ -0,0 +1,29 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 { createContext } from "preact";
+import { useContext } from "preact/hooks";
+import { AuditorBackend } from "../declaration.js";
+
+const Context = createContext<AuditorBackend.VersionResponse>(null!);
+
+export const ConfigContextProvider = Context.Provider;
+export const useConfigContext = (): AuditorBackend.VersionResponse => useContext(Context);
diff --git a/packages/auditor-backoffice-ui/src/context/entity.ts b/packages/auditor-backoffice-ui/src/context/entity.ts
new file mode 100644
index 000000000..8181931c4
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/context/entity.ts
@@ -0,0 +1,47 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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)
+ * @author Nic Eigel
+ */
+
+import { createContext } from "preact";
+import { useContext } from "preact/hooks";
+
+interface EntityType {
+ title: string;
+ path: string;
+ endpoint: string;
+ entity: any;
+}
+
+const EntityContext = createContext<EntityType>({} as any);
+
+export const EntityContextProvider = EntityContext.Provider;
+
+export const useEntityContext = (): EntityType => useContext(EntityContext);
+
+interface EntityDataType {
+ data: any;
+}
+
+const EntityDataContext = createContext<EntityDataType>({} as any);
+
+export const EntityDataContextProvider = EntityDataContext.Provider;
+
+export const useEntityDataContext = (): EntityDataType => useContext(EntityDataContext);
diff --git a/packages/auditor-backoffice-ui/src/custom.d.ts b/packages/auditor-backoffice-ui/src/custom.d.ts
new file mode 100644
index 000000000..e693c2951
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/custom.d.ts
@@ -0,0 +1,42 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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/>
+ */
+declare module "*.po" {
+ const content: any;
+ export default content;
+}
+declare module "jed" {
+ const x: any;
+ export = x;
+}
+declare module "*.jpeg" {
+ const content: any;
+ export default content;
+}
+declare module "*.png" {
+ const content: any;
+ export default content;
+}
+declare module "*.svg" {
+ const content: any;
+ export default content;
+}
+
+declare module "*.scss" {
+ const content: Record<string, string>;
+ export default content;
+}
+declare const __VERSION__: string;
+declare const __GIT_HASH__: string;
diff --git a/packages/auditor-backoffice-ui/src/declaration.d.ts b/packages/auditor-backoffice-ui/src/declaration.d.ts
index d5301c115..a016ea85a 100644
--- a/packages/auditor-backoffice-ui/src/declaration.d.ts
+++ b/packages/auditor-backoffice-ui/src/declaration.d.ts
@@ -16,30 +16,341 @@
/**
*
+ * @author Sebastian Javier Marchano (sebasjm)
* @author Nic Eigel
*/
+type HashCode = string;
+type EddsaPublicKey = string;
+type EddsaSignature = string;
+type WireTransferIdentifierRawP = string;
+type RelativeTime = TalerProtocolDuration;
+type ImageDataUrl = string;
+type AuditorUserType = "business" | "individual";
+
+
+export interface WithId {
+ id: string;
+}
+
+interface Timestamp {
+ // Milliseconds since epoch, or the special
+ // value "forever" to represent an event that will
+ // never happen.
+ t_s: number | "never";
+}
+
+interface TalerProtocolDuration {
+ d_us: number | "forever";
+}
+
+interface Duration {
+ d_ms: number | "forever";
+}
+
+interface WithId {
+ id: string;
+}
+
+type Amount = string;
+type UUID = string;
+type Integer = number;
+
export namespace AuditorBackend {
- interface DepositConfirmation {
- // identifier
- serial_id: number;
+ interface DepositConfirmation {
+ // identifier
+ deposit_confirmation_serial_id: number;
+
+ //TODO Comment
+ h_contract_terms: string;
+
+ //TODO Comment
+ h_policy: string;
+
+ //TODO Comment
+ h_wire: string;
+
+ //TODO Comment
+ exchange_timestamp: string;
+
+ //TODO Comment
+ refund_deadline: string;
+
+ //TODO Comment
+ wire_deadline: string;
+
+ //TODO Comment
+ total_without_fee: string;
+
+ //TODO Comment
+ coin_pubs: string;
+
+ //TODO Comment
+ coin_sigs: string;
+
+ //TODO Comment
+ merchant_pub: string;
+
+ //TODO Comment
+ merchant_sig: string;
+
+ //TODO Comment
+ exchange_pub: string;
+
+ //TODO Comment
+ exchange_sig: string;
+
+ //TODO Comment
+ suppressed: string;
+
+ //TODO Comment
+ ancient: string;
+ }
- // amount of deposit confirmation
- amount: string;
+ //TODO rename
+ interface Config {
+ name: string;
+ version: string;
+ implementation: string;
+ currency: string;
+ auditor_public_key: string;
+ exchange_master_public_key: string;
+ }
- // timestamp of deposit confirmation
- timestamp: string;
+ interface ErrorDetail {
+ // Numeric error code unique to the condition.
+ // The other arguments are specific to the error value reported here.
+ code: number;
- // account
- account: string;
+ // Human-readable description of the error, i.e. "missing parameter", "commitment violation", ...
+ // Should give a human-readable hint about the error's nature. Optional, may change without notice!
+ hint?: string;
+
+ // Optional detail about the specific input value that failed. May change without notice!
+ detail?: string;
+
+ // Name of the parameter that was bogus (if applicable).
+ parameter?: string;
+
+ // Path to the argument that was bogus (if applicable).
+ path?: string;
+
+ // Offset of the argument that was bogus (if applicable).
+ offset?: string;
+
+ // Index of the argument that was bogus (if applicable).
+ index?: string;
+
+ // Name of the object that was bogus (if applicable).
+ object?: string;
+
+ // Name of the currency than was problematic (if applicable).
+ currency?: string;
+
+ // Expected type (if applicable).
+ type_expected?: string;
+
+ // Type that was provided instead (if applicable).
+ type_actual?: string;
+ }
+
+ interface VersionResponse {
+ // libtool-style representation of the Merchant protocol version, see
+ // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
+ // The format is "current:revision:age".
+ // Name of the protocol.
+
+ name: "taler-auditor";
+ version: string;
+
+ // Default (!) currency supported by this backend.
+ // This is the currency that the backend should
+ // suggest by default to the user when entering
+ // amounts. See currencies for a list of
+ // supported currencies and how to render them.
+ implementation: string;
+ currency: string;
+ auditor_public_key: string;
+ exchange_master_public_key: string;
+
+ // How services should render currencies supported
+ // by this backend. Maps
+ // currency codes (e.g. "EUR" or "KUDOS") to
+ // the respective currency specification.
+ // All currencies in this map are supported by
+ // the backend. Note that the actual currency
+ // specifications are a *hint* for applications
+ // that would like *advice* on how to render amounts.
+ // Applications *may* ignore the currency specification
+ // if they know how to render currencies that they are
+ // used with.
+ //currencies: { currency: CurrencySpecification };
+
+ // Array of exchanges trusted by the merchant.
+ // Since protocol v6.
+ // exchanges: ExchangeConfigInfo[];
+ }
+
+ namespace Default {
+ interface ObjectResponse {
+ object: AnyEntry[];
}
+ }
+
+ namespace DepositConfirmation {
- interface Config {
- name: string;
- version: string;
- implementation: string;
- currency: string;
- auditor_public_key: string;
- exchange_master_public_key: string;
+ interface InventorySummaryResponse {
+ // List of products that are present in the inventory
+ deposit_confirmations: InventoryEntry[];
}
-} \ No newline at end of file
+
+ interface DepositConfirmationDetail {
+ // identifier
+ deposit_confirmation_serial_id: number;
+
+ //TODO Comment
+ h_contract_terms: string;
+
+ //TODO Comment
+ h_policy: string;
+
+ //TODO Comment
+ h_wire: string;
+
+ //TODO Comment
+ exchange_timestamp: string;
+
+ //TODO Comment
+ refund_deadline: string;
+
+ //TODO Comment
+ wire_deadline: string;
+
+ //TODO Comment
+ total_without_fee: string;
+
+ //TODO Comment
+ coin_pubs: string;
+
+ //TODO Comment
+ coin_sigs: string;
+
+ //TODO Comment
+ merchant_pub: string;
+
+ //TODO Comment
+ merchant_sig: string;
+
+ //TODO Comment
+ exchange_pub: string;
+
+ //TODO Comment
+ exchange_sig: string;
+
+ //TODO Comment
+ suppressed: string;
+
+ //TODO Comment
+ ancient: string;
+ }
+ }
+
+ namespace Balance {
+
+ interface InventorySummaryResponse {
+ // List of products that are present in the inventory
+ balances: InventoryEntry[];
+ }
+
+ interface BalanceDetail {
+ // identifier
+ row_id: number;
+
+ //TODO Comment
+ balance_key: string;
+
+ //TODO Comment
+ balance_value: string;
+
+ //TODO Comment
+ suppressed: string;
+ }
+ }
+}
+
+export class ClassDepositConfirmation {
+ // List of products that are present in the inventory
+ deposit_confirmations: DepositConfirmationDetail[];
+}
+
+export class DepositConfirmationDetail implements AuditorBackend.DepositConfirmation.DepositConfirmationDetail {
+ deposit_confirmation_serial_id: number;
+
+ //TODO Comment
+ h_contract_terms: string;
+
+ //TODO Comment
+ h_policy: string;
+
+ //TODO Comment
+ h_wire: string;
+
+ //TODO Comment
+ exchange_timestamp: string;
+
+ //TODO Comment
+ refund_deadline: string;
+
+ //TODO Comment
+ wire_deadline: string;
+
+ //TODO Comment
+ total_without_fee: string;
+
+ //TODO Comment
+ coin_pubs: string;
+
+ //TODO Comment
+ coin_sigs: string;
+
+ //TODO Comment
+ merchant_pub: string;
+
+ //TODO Comment
+ merchant_sig: string;
+
+ //TODO Comment
+ exchange_pub: string;
+
+ //TODO Comment
+ exchange_sig: string;
+
+ //TODO Comment
+ suppressed: string;
+
+ //TODO Comment
+ ancient: string;
+}
+
+
+
+export class ClassBalance {
+ // List of products that are present in the inventory
+ balances: BalanceDetail[];
+}
+
+class BalanceDetail implements AuditorBackend.Balance.BalanceDetail {
+ // identifier
+ row_id: number;
+
+ //TODO Comment
+ balance_key: string;
+
+ //TODO Comment
+ balance_value: string;
+
+ //TODO Comment
+ suppressed: string;
+}
+
+//} \ No newline at end of file
diff --git a/packages/auditor-backoffice-ui/src/hooks/backend.ts b/packages/auditor-backoffice-ui/src/hooks/backend.ts
index 7f293162f..4b3b3f88d 100644
--- a/packages/auditor-backoffice-ui/src/hooks/backend.ts
+++ b/packages/auditor-backoffice-ui/src/hooks/backend.ts
@@ -1,21 +1,225 @@
-//import { HttpResponse, RequestError } from "@gnu-taler/web-util/lib/utils/request.js";
-import { AuditorBackend } from "../declaration.js";
-import Config = AuditorBackend.Config;
-
-export function tryConfig(): Promise<Config> {
- // const request: RequestInfo = new Request('./Config.json', {
- // method: 'GET',
- // headers: headers
- // })
-
- return fetch("/config.json")
- // the JSON body is taken from the response
- .then(res => res.json())
- .then(res => {
- // The response has an `any` type, so we need to cast
- // it to the `User` type, and return it from the promise
- return res as Config;
- });
+/*
+ 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)
+ * @author Nic Eigel
+ */
+
+import {AbsoluteTime, HttpStatusCode} from "@gnu-taler/taler-util";
+import {
+ ErrorType,
+ HttpError,
+ HttpResponse,
+ HttpResponseOk,
+ RequestError,
+ RequestOptions,
+ useApiContext,
+} from "@gnu-taler/web-util/browser";
+import {useCallback, useEffect, useState} from "preact/hooks";
+import {useSWRConfig} from "swr";
+import {useBackendContext} from "../context/backend.js";
+import {useEntityContext} from "../context/entity.js";
+import {AuditorBackend, Timestamp} from "../declaration.js";
+
+export function useMatchMutate(): (
+ re?: RegExp,
+ value?: unknown,
+) => Promise<any> {
+ const {cache, mutate} = useSWRConfig();
+
+ if (!(cache instanceof Map)) {
+ throw new Error(
+ "matchMutate requires the cache provider to be a Map instance",
+ );
+ }
+
+ return function matchRegexMutate(re?: RegExp) {
+ return mutate((key) => {
+ // evict if no key or regex === all
+ if (!key || !re) return true
+ // match string
+ if (typeof key === 'string' && re.test(key)) return true
+ // record or object have the path at [0]
+ if (typeof key === 'object' && re.test(key[0])) return true
+ //key didn't match regex
+ return false
+ }, undefined, {
+ revalidate: true,
+ });
+ };
}
+const CHECK_CONFIG_INTERVAL_OK = 5 * 60 * 1000;
+const CHECK_CONFIG_INTERVAL_FAIL = 2 * 1000;
+
+export function useBackendConfig(): HttpResponse<
+ AuditorBackend.VersionResponse | undefined,
+ RequestError<AuditorBackend.ErrorDetail>
+> {
+ const {request} = useBackendBaseRequest();
+
+ type Type = AuditorBackend.VersionResponse;
+ type State = { data: HttpResponse<Type, RequestError<AuditorBackend.ErrorDetail>>, timer: number }
+ const [result, setResult] = useState<State>({data: {loading: true}, timer: 0});
+
+ useEffect(() => {
+ if (result.timer) {
+ clearTimeout(result.timer);
+ }
+
+ function tryConfig(): void {
+ request<Type>(`/config`)
+ .then((data) => {
+ const timer: any = setTimeout(() => {
+ tryConfig();
+ }, CHECK_CONFIG_INTERVAL_OK);
+ setResult({data, timer});
+ })
+ .catch((error) => {
+ const timer: any = setTimeout(() => {
+ tryConfig();
+ }, CHECK_CONFIG_INTERVAL_FAIL);
+ const data = error.cause;
+ setResult({data, timer});
+ });
+ }
+
+ tryConfig();
+ }, [request]);
+
+ return result.data;
+}
+
+interface useBackendInstanceRequestType {
+
+ request: <T>(
+ endpoint: string,
+ options?: RequestOptions,
+ ) => Promise<HttpResponseOk<T>>;
+ fetcher: <T>(endpoint: string) => Promise<HttpResponseOk<T>>;
+ multiFetcher: <T>(params: [url: string[]]) => Promise<HttpResponseOk<T>[]>;
+ depositConfirmationFetcher: <T>(
+ params: [
+ endpoint: string,
+ ],
+ ) => Promise<HttpResponseOk<T>>;
+}
+
+interface useBackendBaseRequestType {
+
+ request: <T>(
+ endpoint: string,
+ options?: RequestOptions,
+ ) => Promise<HttpResponseOk<T>>;
+}
+
+type YesOrNo = "yes" | "no";
+
+/**
+ *
+ * @param root the request is intended to the base URL and no the instance URL
+ * @returns request handler to
+ */
+//TODO: Add token
+export function useBackendBaseRequest(): useBackendBaseRequestType {
+ const {url: backend} = useBackendContext();
+ const {request: requestHandler} = useApiContext();
+ const token = "secret-token:D4CST1Z6AHN3RT03M0T9NSTF2QGHTB5ZD2D3RYZB4HAWG8SX0JEFWBXCKXZHMB7Y3Z7KVFW0B3XPXD5BHCFP8EB0R6CNH2KAWDWVET0";
+
+ const request = useCallback(
+ function requestImpl<T>(
+ endpoint: string,
+ //todo: remove
+ options: RequestOptions = {},
+ ): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(backend, endpoint, {...options, token}).then(res => {
+ return res;
+ }).catch(err => {
+ throw err;
+ });
+ },
+ [backend],
+ );
+
+ return {request};
+}
+
+
+export function useBackendInstanceRequest(): useBackendInstanceRequestType {
+ const {url: rootBackendUrl} = useBackendContext();
+ // const {id} = useInstanceContext();
+ const {request: requestHandler} = useApiContext();
+
+ //TODO: check
+ const baseUrl = "http://localhost:8083/";
+ const token = "secret-token:D4CST1Z6AHN3RT03M0T9NSTF2QGHTB5ZD2D3RYZB4HAWG8SX0JEFWBXCKXZHMB7Y3Z7KVFW0B3XPXD5BHCFP8EB0R6CNH2KAWDWVET0";
+
+
+
+
+ const request = useCallback(
+ function requestImpl<T>(
+ endpoint: string,
+ options: RequestOptions = {},
+ ): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(baseUrl, endpoint, {...options, token});
+ },
+ [baseUrl],
+ );
+
+ const multiFetcher = useCallback(
+ function multiFetcherImpl<T>(
+ args: [endpoints: string[]],
+ ): Promise<HttpResponseOk<T>[]> {
+ const [endpoints] = args;
+ return Promise.all(
+ endpoints.map((endpoint) =>
+ requestHandler<T>(baseUrl, endpoint),
+ ),
+ );
+ },
+ [baseUrl],
+ );
+
+ const fetcher = useCallback(
+ function fetcherImpl<T>(endpoint: string): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(baseUrl, endpoint, {token});
+ },
+ [baseUrl],
+ );
+
+ const depositConfirmationFetcher = useCallback(
+ function orderFetcherImpl<T>(
+ args: [endpoint: string,
+ ],
+ ): Promise<HttpResponseOk<T>> {
+ const [endpoint] = args;
+ const params: any = {"token": "secret-token:D4CST1Z6AHN3RT03M0T9NSTF2QGHTB5ZD2D3RYZB4HAWG8SX0JEFWBXCKXZHMB7Y3Z7KVFW0B3XPXD5BHCFP8EB0R6CNH2KAWDWVET0"};
+ return requestHandler<T>(baseUrl, endpoint, {params, token});
+ },
+ [baseUrl],
+ );
+
+
+ return {
+ request,
+ fetcher,
+ depositConfirmationFetcher,
+ multiFetcher
+ };
+} \ No newline at end of file
diff --git a/packages/auditor-backoffice-ui/src/hooks/balance.ts b/packages/auditor-backoffice-ui/src/hooks/balance.ts
new file mode 100644
index 000000000..74cc943ff
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/hooks/balance.ts
@@ -0,0 +1,61 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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/>
+ */
+import {
+ HttpResponse,
+ HttpResponseOk,
+ HttpResponsePaginated,
+ RequestError,
+} from "@gnu-taler/web-util/browser";
+import { useEffect, useState } from "preact/hooks";
+import { AuditorBackend, WithId } from "../declaration.js";
+import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils/constants.js";
+import { useBackendInstanceRequest, useMatchMutate } from "./backend.js";
+
+// FIX default import https://github.com/microsoft/TypeScript/issues/49189
+import _useSWR, { SWRHook } from "swr";
+const useSWR = _useSWR as unknown as SWRHook;
+
+type YesOrNo = "yes" | "no";
+
+export interface InstanceOrderFilter {
+ suppressed?: YesOrNo;
+ suppressDate?: Date;
+}
+
+export function useBalances(): HttpResponse<
+ (AuditorBackend.Balance.InventorySummaryResponse)[],
+ AuditorBackend.ErrorDetail
+> {
+ const { fetcher, multiFetcher } = useBackendInstanceRequest();
+
+ const { data: list, error: listError } = useSWR<
+ HttpResponseOk<AuditorBackend.Balance.InventorySummaryResponse>,
+ RequestError<AuditorBackend.ErrorDetail>
+ >([`monitoring/balances`], fetcher, {
+ refreshInterval: 0,
+ refreshWhenHidden: false,
+ revalidateOnFocus: false,
+ revalidateOnReconnect: false,
+ refreshWhenOffline: false,
+ });
+
+ if (listError) return listError.cause;
+
+ if (list?.data) {
+ return { ok: true, data: [list.data] };
+ }
+ return { loading: true };
+} \ No newline at end of file
diff --git a/packages/auditor-backoffice-ui/src/hooks/config.ts b/packages/auditor-backoffice-ui/src/hooks/config.ts
new file mode 100644
index 000000000..a57fa15d5
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/hooks/config.ts
@@ -0,0 +1,24 @@
+/*
+ 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/>
+ */
+
+import { createContext } from "preact";
+import { useContext } from "preact/hooks";
+import { AuditorBackend } from "../declaration.js";
+
+const Context = createContext<AuditorBackend.VersionResponse>(null!);
+
+export const ConfigContextProvider = Context.Provider;
+export const useConfigContext = (): AuditorBackend.VersionResponse => useContext(Context);
diff --git a/packages/auditor-backoffice-ui/src/hooks/deposit-confirmation.ts b/packages/auditor-backoffice-ui/src/hooks/deposit-confirmation.ts
new file mode 100644
index 000000000..c6a21059e
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/hooks/deposit-confirmation.ts
@@ -0,0 +1,78 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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/>
+ */
+import {
+ HttpResponse,
+ HttpResponseOk,
+ HttpResponsePaginated,
+ RequestError,
+} from "@gnu-taler/web-util/browser";
+import { useEffect, useState } from "preact/hooks";
+import { AuditorBackend, WithId } from "../declaration.js";
+import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils/constants.js";
+import { useBackendInstanceRequest, useMatchMutate } from "./backend.js";
+
+// FIX default import https://github.com/microsoft/TypeScript/issues/49189
+import _useSWR, { SWRHook } from "swr";
+const useSWR = _useSWR as unknown as SWRHook;
+
+type YesOrNo = "yes" | "no";
+
+export function useDepositConfirmations(): HttpResponse<
+ (AuditorBackend.DepositConfirmation.DepositConfirmationDetail & WithId)[],
+ AuditorBackend.ErrorDetail
+> {
+ const { fetcher, multiFetcher } = useBackendInstanceRequest();
+
+ const { data: list, error: listError } = useSWR<
+ HttpResponseOk<AuditorBackend.DepositConfirmation.InventorySummaryResponse>,
+ RequestError<AuditorBackend.ErrorDetail>
+ >([`monitoring/deposit-confirmation`], fetcher, {
+ refreshInterval: 0,
+ refreshWhenHidden: false,
+ revalidateOnFocus: false,
+ revalidateOnReconnect: false,
+ refreshWhenOffline: false,
+ });
+
+ const paths = (list?.data.deposit_confirmations || []).map(
+ (dc) => `/deposit-confirmation/${dc.deposit_confirmation_serial_id}`,
+ );
+ const { data: depositConfirmations, error: depositConfirmationError } = useSWR<
+ HttpResponseOk<AuditorBackend.DepositConfirmation.DepositConfirmationDetail>[],
+ RequestError<AuditorBackend.ErrorDetail>
+ >([paths], multiFetcher, {
+ refreshInterval: 0,
+ refreshWhenHidden: false,
+ revalidateOnFocus: false,
+ revalidateOnReconnect: false,
+ refreshWhenOffline: false,
+ });
+
+ if (listError) return listError.cause;
+ if (depositConfirmationError) return depositConfirmationError.cause;
+
+ if (depositConfirmations) {
+ const dataWithId = depositConfirmations.map((d) => {
+ //take the id from the queried url
+ return {
+ ...d.data,
+ id: d.info?.url.replace(/.*\/deposit-confirmation\//, "") || "",
+ };
+ });
+ return { ok: true, data: dataWithId };
+ }
+ return { loading: true };
+} \ No newline at end of file
diff --git a/packages/auditor-backoffice-ui/src/hooks/entity.ts b/packages/auditor-backoffice-ui/src/hooks/entity.ts
new file mode 100644
index 000000000..13953b5f3
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/hooks/entity.ts
@@ -0,0 +1,90 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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/>
+ */
+import {
+ HttpResponse,
+ HttpResponseOk,
+ HttpResponsePaginated,
+ RequestError,
+} from "@gnu-taler/web-util/browser";
+import { useEffect, useState } from "preact/hooks";
+import { AuditorBackend, WithId } from "../declaration.js";
+import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils/constants.js";
+import { useBackendInstanceRequest, useMatchMutate } from "./backend.js";
+
+// FIX default import https://github.com/microsoft/TypeScript/issues/49189
+import _useSWR, { SWRHook, useSWRConfig } from "swr";
+import { useEntityContext } from "../context/entity.js";
+
+const useSWR = _useSWR as unknown as SWRHook;
+
+type YesOrNo = "yes" | "no";
+
+export interface InstanceOrderFilter {
+ suppressed?: YesOrNo;
+ suppressDate?: Date;
+}
+
+interface Props {
+ endpoint: string;
+ entity: any;
+}
+
+export function getEntityList({ endpoint, entity }: Props): HttpResponse<any, AuditorBackend.ErrorDetail> {
+ const { fetcher } = useBackendInstanceRequest();
+
+ const { data: list, error: listError } = useSWR<
+ HttpResponseOk<typeof entity>,
+ RequestError<AuditorBackend.ErrorDetail>
+ >([`monitoring/` + endpoint], fetcher, {
+ refreshInterval: 0,
+ refreshWhenHidden: false,
+ revalidateOnFocus: false,
+ revalidateOnReconnect: false,
+ refreshWhenOffline: false,
+ });
+
+ if (listError) return listError.cause;
+
+ if (list?.data != null) {
+ return { ok: true, data: [list?.data] };
+ }
+ return { loading: true };
+}
+export interface EntityAPI {
+ updateEntity: (
+ id: string
+ ) => Promise<void>;
+}
+
+export function useEntityAPI(): EntityAPI {
+ const mutateAll = useMatchMutate();
+ const { request } = useBackendInstanceRequest();
+ const { endpoint } = useEntityContext();
+ const data = {"suppressed": true};
+
+ const updateEntity = async (
+ id: string,
+ ): Promise<void> => {
+ const r = await request(`monitoring/${endpoint}/${id}`, {
+ method: "PATCH",
+ data,
+ });
+
+ return await mutateAll(/.*\/monitoring.*/);
+ };
+
+ return { updateEntity };
+} \ No newline at end of file
diff --git a/packages/auditor-backoffice-ui/src/hooks/index.ts b/packages/auditor-backoffice-ui/src/hooks/index.ts
new file mode 100644
index 000000000..6e97b0f83
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/hooks/index.ts
@@ -0,0 +1,83 @@
+/*
+ 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/>
+ */
+
+import {StateUpdater, useState} from "preact/hooks";
+import { ValueOrFunction } from "../utils/types.js";
+
+export function useBackendURL(
+ url?: string,
+): [string, StateUpdater<string>] {
+ //TODO: remove
+ url = "http://localhost:8083"
+ const [value, setter] = useSimpleLocalStorage(
+ "auditor-base-url",
+ url || calculateRootPath(),
+ );
+
+ const checkedSetter = (v: ValueOrFunction<string>) => {
+ return setter((p) => (v instanceof Function ? v(p ?? "") : v).replace(/\/$/, ""));
+ };
+
+ //todo: remove override
+
+ return [value!, checkedSetter];
+}
+
+const calculateRootPath = () => {
+ const rootPath =
+ typeof window !== undefined
+ ? window.location.origin + window.location.pathname
+ : "/";
+
+ /**
+ * By default, auditor backend serves the html content
+ * from the /webui root. This should cover most of the
+ * cases and the rootPath will be the auditor backend
+ * URL where the instances are
+ */
+ return rootPath.replace("/webui/", "");
+};
+
+export 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];
+} \ No newline at end of file
diff --git a/packages/auditor-backoffice-ui/src/hooks/useSettings.ts b/packages/auditor-backoffice-ui/src/hooks/useSettings.ts
new file mode 100644
index 000000000..8c1ebd9f6
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/hooks/useSettings.ts
@@ -0,0 +1,73 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 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/>
+ */
+
+import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser";
+import {
+ Codec,
+ buildCodecForObject,
+ codecForBoolean,
+ codecForConstString,
+ codecForEither,
+ codecForString,
+} from "@gnu-taler/taler-util";
+
+export interface Settings {
+ advanceOrderMode: boolean;
+ dateFormat: "ymd" | "dmy" | "mdy";
+}
+
+const defaultSettings: Settings = {
+ advanceOrderMode: false,
+ dateFormat: "ymd",
+}
+
+export const codecForSettings = (): Codec<Settings> =>
+ buildCodecForObject<Settings>()
+ .property("advanceOrderMode", codecForBoolean())
+ .property("dateFormat", codecForEither(
+ codecForConstString("ymd"),
+ codecForConstString("dmy"),
+ codecForConstString("mdy"),
+ ))
+ .build("Settings");
+
+const SETTINGS_KEY = buildStorageKey("merchant-settings", codecForSettings());
+
+export function useSettings(): [
+ Readonly<Settings>,
+ (s: Settings) => void,
+] {
+ const { value, update } = useLocalStorage(SETTINGS_KEY, defaultSettings);
+
+ // const parsed: Settings = value ?? defaultSettings;
+ // function updateField<T extends keyof Settings>(k: T, v: Settings[T]) {
+ // const next = { ...parsed, [k]: v }
+ // update(next);
+ // }
+ return [value, update];
+}
+
+export function dateFormatForSettings(s: Settings): string {
+ switch (s.dateFormat) {
+ case "ymd": return "yyyy/MM/dd"
+ case "dmy": return "dd/MM/yyyy"
+ case "mdy": return "MM/dd/yyyy"
+ }
+}
+
+export function datetimeFormatForSettings(s: Settings): string {
+ return dateFormatForSettings(s) + " HH:mm:ss"
+} \ No newline at end of file
diff --git a/packages/auditor-backoffice-ui/src/index.tsx b/packages/auditor-backoffice-ui/src/index.tsx
index 79c0edce4..fc956e8aa 100644
--- a/packages/auditor-backoffice-ui/src/index.tsx
+++ b/packages/auditor-backoffice-ui/src/index.tsx
@@ -1,6 +1,6 @@
/*
This file is part of GNU Taler
- (C) 2021-2023 Taler Systems S.A.
+ (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
@@ -14,10 +14,6 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-/**
- * @author Nic Eigel
- */
-
import { Application } from "./Application.js";
import { h, render } from "preact";
diff --git a/packages/auditor-backoffice-ui/src/paths/balances/Table.tsx b/packages/auditor-backoffice-ui/src/paths/balances/Table.tsx
new file mode 100644
index 000000000..c93db44bc
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/paths/balances/Table.tsx
@@ -0,0 +1,170 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 { Amounts } from "@gnu-taler/taler-util";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { format } from "date-fns";
+import { ComponentChildren, Fragment, h, VNode } from "preact";
+import { StateUpdater, useState } from "preact/hooks";
+import emptyImage from "../../../../assets/empty.png";
+import {
+ FormErrors,
+ FormProvider,
+} from "../../components/forms/FormProvider.js";
+import { AuditorBackend, WithId } from "../../declaration.js";
+import { dateFormatForSettings, useSettings } from "../../hooks/useSettings.js";
+
+
+interface Props {
+ instances: AuditorBackend.Balance.InventorySummaryResponse[];
+ selected?: boolean;
+}
+
+export function CardTable({
+ instances,
+ }: Props): VNode {
+ const [rowSelection, rowSelectionHandler] = useState<string | undefined>(
+ undefined,
+ );
+ const { i18n } = useTranslationContext();
+ return (
+ <div class="card has-table">
+ <header class="card-header">
+ <p class="card-header-title">
+ <span class="icon">
+ <i class="mdi mdi-shopping" />
+ </span>
+ <i18n.Translate>Inventory</i18n.Translate>
+ </p>
+ <div class="card-header-icon" aria-label="more options">
+ <span
+ class="has-tooltip-left"
+ data-tooltip={i18n.str`add product to inventory`}
+ >
+ <button class="button is-info" type="button">
+ <span class="icon is-small">
+ <i class="mdi mdi-plus mdi-36px" />
+ </span>
+ </button>
+ </span>
+ </div>
+ </header>
+ <div class="card-content">
+ <div class="b-table has-pagination">
+ <div class="table-wrapper has-mobile-cards">
+//TODO add
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+}
+
+
+interface TableProps {
+ rowSelection: string | undefined;
+ instances: AuditorBackend.Balance.BalanceDetail[];
+ onUpdate: (
+ id: string,
+ data: AuditorBackend.Balance.BalanceDetail,
+ ) => Promise<void>;
+ rowSelectionHandler: StateUpdater<string | undefined>;
+}
+
+function Table({
+ rowSelection,
+ rowSelectionHandler,
+ instances,
+ onUpdate,
+ }: TableProps): VNode {
+ const { i18n } = useTranslationContext();
+ const [settings] = useSettings();
+ return (
+ <div class="table-container">
+ <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
+ <thead>
+ <tr>
+ {instances.map((i) => {
+ return (
+ <Fragment>
+ <th>
+ <i18n.Translate>Image</i18n.Translate>
+ </th>
+ </Fragment>
+ );
+ })}
+ <th>
+ <i18n.Translate>Image</i18n.Translate>
+ </th>
+ <th>
+ <i18n.Translate>Description</i18n.Translate>
+ </th>
+ <th>
+ <i18n.Translate>Price per unit</i18n.Translate>
+ </th>
+ <th>
+ <i18n.Translate>Taxes</i18n.Translate>
+ </th>
+ <th>
+ <i18n.Translate>Sales</i18n.Translate>
+ </th>
+ <th>
+ <i18n.Translate>Stock</i18n.Translate>
+ </th>
+ <th>
+ <i18n.Translate>Sold</i18n.Translate>
+ </th>
+ <th />
+ </tr>
+ </thead>
+ <tbody>
+ {instances.map((i) => {
+ return (
+ <Fragment>
+ <tr key="info">
+
+ </tr>
+ </Fragment>
+ );
+ })}
+ </tbody>
+ </table>
+ </div>
+ );
+}
+
+function EmptyTable(): VNode {
+ const { i18n } = useTranslationContext();
+ return (
+ <div class="content has-text-grey has-text-centered">
+ <p>
+ <span class="icon is-large">
+ <i class="mdi mdi-emoticon-sad mdi-48px" />
+ </span>
+ </p>
+ <p>
+ <i18n.Translate>
+ There is no products yet, add more pressing the + sign
+ </i18n.Translate>
+ </p>
+ </div>
+ );
+}
diff --git a/packages/auditor-backoffice-ui/src/paths/balances/index.tsx b/packages/auditor-backoffice-ui/src/paths/balances/index.tsx
new file mode 100644
index 000000000..454005c90
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/paths/balances/index.tsx
@@ -0,0 +1,75 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 {
+ ErrorType,
+ HttpError,
+ useTranslationContext,
+} from "@gnu-taler/web-util/browser";
+import { Fragment, h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { Loading } from "../../components/exception/loading.js";
+import { NotificationCard } from "../../components/menu/index.js";
+import { AuditorBackend, WithId } from "../../declaration.js";
+import {
+ useBalances,
+} from "../../hooks/balance.js";
+import { Notification } from "../../utils/types.js";
+import { CardTable } from "./Table.js";
+import { HttpStatusCode } from "@gnu-taler/taler-util";
+//import { ConfirmModal, DeleteModal } from "../../../components/modal/index.js";
+//import { JumpToElementById } from "../../../components/form/JumpToElementById.js";
+
+interface Props {
+ onNotFound: () => VNode;
+ onLoadError: (e: HttpError<AuditorBackend.ErrorDetail>) => VNode;
+}
+
+export default function BalanceList({
+ onLoadError,
+ onNotFound,
+ }: Props): VNode {
+ const result = useBalances();
+ const [notif, setNotif] = useState<Notification | undefined>(undefined);
+
+ const { i18n } = useTranslationContext();
+
+ if (result.loading) return <Loading />;
+ if (!result.ok) {
+ if (
+ result.type === ErrorType.CLIENT &&
+ result.status === HttpStatusCode.Unauthorized
+ )
+ return onNotFound();
+ return onLoadError(result);
+ }
+
+ return (
+ <Fragment>
+ <section class="section is-main-section">
+ <NotificationCard notification={notif} />
+ {<CardTable
+ instances={result.data}
+ />}
+ </section>
+ </Fragment>
+ );
+}
diff --git a/packages/auditor-backoffice-ui/src/paths/default/Table.tsx b/packages/auditor-backoffice-ui/src/paths/default/Table.tsx
new file mode 100644
index 000000000..2f5959b7c
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/paths/default/Table.tsx
@@ -0,0 +1,166 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 { Amounts } from "@gnu-taler/taler-util";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { format } from "date-fns";
+import { ComponentChildren, Fragment, h, VNode } from "preact";
+import { StateUpdater, useState } from "preact/hooks";
+import emptyImage from "../../../../assets/empty.png";
+import {
+ FormErrors,
+ FormProvider,
+} from "../../components/forms/FormProvider.js";
+import { InputCurrency } from "../../components/forms/InputCurrency.js";
+import { InputNumber } from "../../components/forms/InputNumber.js";
+import { AuditorBackend, WithId } from "../../declaration.js";
+import { dateFormatForSettings, useSettings } from "../../hooks/useSettings.js";
+import { useEntityContext, useEntityDataContext } from "../../context/entity.js";
+
+interface Props {
+ onSuppress: (id: any) => void;
+}
+
+export function CardTable({onSuppress}: Props): any {
+
+ const data = useEntityDataContext();
+ const [rowSelection, rowSelectionHandler] = useState<string | undefined>(
+ undefined,
+ );
+ const { i18n } = useTranslationContext();
+ const { title, endpoint, entity } = useEntityContext();
+
+ return (
+ <div class="card has-table">
+ <header class="card-header">
+ <p class="card-header-title">
+ <span class="icon">
+ <i class="mdi mdi-shopping" />
+ </span>
+ <i18n.Translate>{title}</i18n.Translate>
+ </p>
+ <div class="card-header-icon" aria-label="more options">
+ </div>
+ </header>
+ <div class="card-content">
+ <div class="b-table has-pagination">
+ <div class="table-wrapper has-mobile-cards">
+ {data.data[0][endpoint] != null ? (
+ <Table
+ data={data.data[0][endpoint]}
+ onSuppress={onSuppress}
+ />
+ ) : (
+ <EmptyTable />
+ )}
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+}
+
+interface TableProps {
+ data: any;
+ onSuppress: (id: any) => void;
+}
+
+function Table({
+ data,
+ onSuppress,
+ }: TableProps): VNode {
+ const { i18n } = useTranslationContext();
+ const { entity } = useEntityContext();
+ type Entity = typeof entity;
+ let count = 0;
+
+ return (
+ <div class="table-container">
+ <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
+ <thead>
+ <tr>
+ {Object.keys(data[0]).map((i: Entity) => {
+ const paramName = i[0].toUpperCase() + i.replace("_", " ").slice(1, i.count);
+ return (
+ <Fragment key={count.toString() + i}>
+ <th>
+ <i18n.Translate>{paramName}</i18n.Translate>
+ </th>
+ </Fragment>);
+ })}
+ </tr>
+ </thead>
+ <tbody>
+ {data.map((key: Entity, value: string) => {
+ return (
+ <tr>
+ {Object.keys(data[0]).map((i: Entity) => {
+ return (
+ <Fragment>
+ <td>
+ {(key[i] == false) ? "false" : key[i]}
+ </td>
+ </Fragment>
+ );
+ })}
+ <td class="is-actions-cell right-sticky">
+ <div class="buttons is-right">
+ <span
+ class="has-tooltip-bottom"
+ data-tooltip={i18n.str`suppress`}
+ >
+ <button
+ class="button is-small is-success "
+ type="button"
+ onClick={(): void => onSuppress(key["row_id"])}
+ >
+ {<i18n.Translate>Suppress</i18n.Translate>}
+ </button>
+ </span>
+ </div>
+ </td>
+ </tr>
+ );
+ })
+ }
+ </tbody>
+ </table>
+ </div>
+ );
+}
+
+function EmptyTable(): VNode {
+ const { i18n } = useTranslationContext();
+ return (
+ <div class="content has-text-grey has-text-centered">
+ <p>
+ <span class="icon is-large">
+ <i class="mdi mdi-emoticon-happy mdi-48px" />
+ </span>
+ </p>
+ <p>
+ <i18n.Translate>
+ There are no entries yet
+ </i18n.Translate>
+ </p>
+ </div>
+ );
+} \ No newline at end of file
diff --git a/packages/auditor-backoffice-ui/src/paths/default/index.tsx b/packages/auditor-backoffice-ui/src/paths/default/index.tsx
new file mode 100644
index 000000000..eb88fc413
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/paths/default/index.tsx
@@ -0,0 +1,117 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 {
+ ErrorType,
+ HttpError,
+ useTranslationContext,
+} from "@gnu-taler/web-util/browser";
+import { h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { Loading } from "../../components/exception/loading.js";
+import { NotificationCard } from "../../components/menu/index.js";
+import { AuditorBackend, WithId } from "../../declaration.js";
+import { Notification } from "../../utils/types.js";
+import { CardTable } from "./Table.js";
+import { HttpStatusCode } from "@gnu-taler/taler-util";
+import { EntityDataContextProvider, useEntityContext } from "../../context/entity.js";
+import { getEntityList, useEntityAPI } from "../../hooks/entity.js";
+import { useMemo } from "preact/hooks";
+import { ConfirmModal, DeleteModal } from "../../components/modal/index.js";
+//import { JumpToElementById } from "../../../components/form/JumpToElementById.js";
+
+interface Props {
+ onNotFound: () => VNode;
+ onLoadError: (e: HttpError<AuditorBackend.ErrorDetail>) => VNode;
+}
+
+export default function DefaultList({
+ onLoadError,
+ onNotFound,
+ }: Props): VNode {
+ const { endpoint, entity } = useEntityContext();
+ const result = getEntityList({ endpoint, entity });
+ const { updateEntity } = useEntityAPI();
+ const [suppressing, setSuppressing] =
+ useState<typeof entity & WithId | null>(null);
+ const [notif, setNotif] = useState<Notification | undefined>(undefined);
+ const { i18n } = useTranslationContext();
+
+ if (result.loading) return <Loading />;
+ if (!result.ok) {
+ if (
+ result.type === ErrorType.CLIENT &&
+ result.status === HttpStatusCode.Unauthorized
+ )
+ return onNotFound();
+ return onLoadError(result);
+ }
+
+ let data = result.data;
+ const value = useMemo(
+ () => ({ data }),
+ [data],
+ );
+
+ return (
+ <section class="section is-main-section">
+ <NotificationCard notification={notif} />
+
+ <EntityDataContextProvider value={value}>
+ <CardTable
+ onSuppress={(e: typeof entity & WithId) =>
+ setSuppressing(e)
+ }
+ />
+ </EntityDataContextProvider>
+
+ {suppressing && (
+ <ConfirmModal
+ label={`Suppress row`}
+ description={`Suppress the row`}
+ danger
+ active
+ onCancel={() => setSuppressing(null)}
+ onConfirm={async (): Promise<void> => {
+ try {
+ await updateEntity(suppressing);
+ setNotif({
+ message: i18n.str`Entity row with id: ${suppressing} has been suppressed`,
+ type: "SUCCESS",
+ });
+ } catch (error) {
+ setNotif({
+ message: i18n.str`Failed to suppress row`,
+ type: "ERROR",
+ description: error instanceof Error ? error.message : undefined,
+ });
+ }
+ setSuppressing(null);
+ }}
+ >
+ <p class="warning">
+ Suppressing a row <b>cannot be undone</b> in this GUI.
+ </p>
+ </ConfirmModal>
+ )}
+ </section>
+ );
+}
diff --git a/packages/auditor-backoffice-ui/src/paths/deposit_confirmations/list/List.stories.tsx b/packages/auditor-backoffice-ui/src/paths/deposit_confirmations/list/List.stories.tsx
new file mode 100644
index 000000000..5c9c44786
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/paths/deposit_confirmations/list/List.stories.tsx
@@ -0,0 +1,109 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 { h, VNode, FunctionalComponent } from "preact";
+/*import { ListPage as TestedComponent } from "./ListPage.js";*/
+
+export default {
+ title: "Pages/Order/List",
+ //component: TestedComponent,
+ argTypes: {
+ onShowAll: { action: "onShowAll" },
+ onShowPaid: { action: "onShowPaid" },
+ onShowRefunded: { action: "onShowRefunded" },
+ onShowNotWired: { action: "onShowNotWired" },
+ onCopyURL: { action: "onCopyURL" },
+ onSelectDate: { action: "onSelectDate" },
+ onLoadMoreBefore: { action: "onLoadMoreBefore" },
+ onLoadMoreAfter: { action: "onLoadMoreAfter" },
+ onSelectOrder: { action: "onSelectOrder" },
+ onRefundOrder: { action: "onRefundOrder" },
+ onSearchOrderById: { action: "onSearchOrderById" },
+ onCreate: { action: "onCreate" },
+ },
+};
+
+function createExample<Props>(
+ Component: FunctionalComponent<Props>,
+ props: Partial<Props>,
+) {
+ const r = (args: any) => <Component {...args} />;
+ r.args = props;
+ return r;
+}
+/*
+
+export const Example = createExample(TestedComponent, {
+ orders: [
+ {
+ id: "123",
+ amount: "TESTKUDOS:10",
+ paid: false,
+ refundable: true,
+ row_id: 1,
+ summary: "summary",
+ timestamp: {
+ t_s: new Date().getTime() / 1000,
+ },
+ order_id: "123",
+ },
+ {
+ id: "234",
+ amount: "TESTKUDOS:12",
+ paid: true,
+ refundable: true,
+ row_id: 2,
+ summary:
+ "summary with long text, very very long text that someone want to add as a description of the order",
+ timestamp: {
+ t_s: new Date().getTime() / 1000,
+ },
+ order_id: "234",
+ },
+ {
+ id: "456",
+ amount: "TESTKUDOS:1",
+ paid: false,
+ refundable: false,
+ row_id: 3,
+ summary:
+ "summary with long text, very very long text that someone want to add as a description of the order",
+ timestamp: {
+ t_s: new Date().getTime() / 1000,
+ },
+ order_id: "456",
+ },
+ {
+ id: "234",
+ amount: "TESTKUDOS:12",
+ paid: false,
+ refundable: false,
+ row_id: 4,
+ summary:
+ "summary with long text, very very long text that someone want to add as a description of the order",
+ timestamp: {
+ t_s: new Date().getTime() / 1000,
+ },
+ order_id: "234",
+ },
+ ],
+});
+*/
diff --git a/packages/auditor-backoffice-ui/src/paths/deposit_confirmations/list/ListPage.tsx b/packages/auditor-backoffice-ui/src/paths/deposit_confirmations/list/ListPage.tsx
new file mode 100644
index 000000000..ba41497ce
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/paths/deposit_confirmations/list/ListPage.tsx
@@ -0,0 +1,228 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { format } from "date-fns";
+import { h, VNode, Fragment } from "preact";
+import { useState } from "preact/hooks";
+/*import { DatePicker } from "../../../../components/picker/DatePicker.js";
+import { MerchantBackend, WithId } from "../../../../declaration.js";
+import { CardTable } from "./Table.js";
+import { dateFormatForSettings, useSettings } from "../../../../hooks/useSettings.js";*/
+/*
+
+export interface ListPageProps {
+ onShowAll: () => void;
+ onShowNotPaid: () => void;
+ onShowPaid: () => void;
+ onShowRefunded: () => void;
+ onShowNotWired: () => void;
+ onShowWired: () => void;
+ onCopyURL: (id: string) => void;
+ isAllActive: string;
+ isPaidActive: string;
+ isNotPaidActive: string;
+ isRefundedActive: string;
+ isNotWiredActive: string;
+ isWiredActive: string;
+
+ jumpToDate?: Date;
+ onSelectDate: (date?: Date) => void;
+
+ orders: (MerchantBackend.Orders.OrderHistoryEntry & WithId)[];
+ onLoadMoreBefore?: () => void;
+ hasMoreBefore?: boolean;
+ hasMoreAfter?: boolean;
+ onLoadMoreAfter?: () => void;
+
+ onSelectOrder: (o: MerchantBackend.Orders.OrderHistoryEntry & WithId) => void;
+ onRefundOrder: (o: MerchantBackend.Orders.OrderHistoryEntry & WithId) => void;
+ onCreate: () => void;
+}
+
+export function ListPage({
+ hasMoreAfter,
+ hasMoreBefore,
+ onLoadMoreAfter,
+ onLoadMoreBefore,
+ orders,
+ isAllActive,
+ onSelectOrder,
+ onRefundOrder,
+ jumpToDate,
+ onCopyURL,
+ onShowAll,
+ onShowPaid,
+ onShowNotPaid,
+ onShowRefunded,
+ onShowNotWired,
+ onShowWired,
+ onSelectDate,
+ isPaidActive,
+ isRefundedActive,
+ isNotWiredActive,
+ onCreate,
+ isNotPaidActive,
+ isWiredActive,
+}: ListPageProps): VNode {
+ const { i18n } = useTranslationContext();
+ const dateTooltip = i18n.str`select date to show nearby orders`;
+ const [pickDate, setPickDate] = useState(false);
+ const [settings] = useSettings();
+
+ return (
+ <Fragment>
+ <div class="columns">
+ <div class="column is-two-thirds">
+ <div class="tabs" style={{ overflow: "inherit" }}>
+ <ul>
+ <li class={isNotPaidActive}>
+ <div
+ class="has-tooltip-right"
+ data-tooltip={i18n.str`only show paid orders`}
+ >
+ <a onClick={onShowNotPaid}>
+ <i18n.Translate>New</i18n.Translate>
+ </a>
+ </div>
+ </li>
+ <li class={isPaidActive}>
+ <div
+ class="has-tooltip-right"
+ data-tooltip={i18n.str`only show paid orders`}
+ >
+ <a onClick={onShowPaid}>
+ <i18n.Translate>Paid</i18n.Translate>
+ </a>
+ </div>
+ </li>
+ <li class={isRefundedActive}>
+ <div
+ class="has-tooltip-right"
+ data-tooltip={i18n.str`only show orders with refunds`}
+ >
+ <a onClick={onShowRefunded}>
+ <i18n.Translate>Refunded</i18n.Translate>
+ </a>
+ </div>
+ </li>
+ <li class={isNotWiredActive}>
+ <div
+ class="has-tooltip-left"
+ data-tooltip={i18n.str`only show orders where customers paid, but wire payments from payment provider are still pending`}
+ >
+ <a onClick={onShowNotWired}>
+ <i18n.Translate>Not wired</i18n.Translate>
+ </a>
+ </div>
+ </li>
+ <li class={isWiredActive}>
+ <div
+ class="has-tooltip-left"
+ data-tooltip={i18n.str`only show orders where customers paid, but wire payments from payment provider are still pending`}
+ >
+ <a onClick={onShowWired}>
+ <i18n.Translate>Completed</i18n.Translate>
+ </a>
+ </div>
+ </li>
+ <li class={isAllActive}>
+ <div
+ class="has-tooltip-right"
+ data-tooltip={i18n.str`remove all filters`}
+ >
+ <a onClick={onShowAll}>
+ <i18n.Translate>All</i18n.Translate>
+ </a>
+ </div>
+ </li>
+ </ul>
+ </div>
+ </div>
+ <div class="column ">
+ <div class="buttons is-right">
+ <div class="field has-addons">
+ {jumpToDate && (
+ <div class="control">
+ <a class="button is-fullwidth" onClick={() => onSelectDate(undefined)}>
+ <span
+ class="icon"
+ data-tooltip={i18n.str`clear date filter`}
+ >
+ <i class="mdi mdi-close" />
+ </span>
+ </a>
+ </div>
+ )}
+ <div class="control">
+ <span class="has-tooltip-top" data-tooltip={dateTooltip}>
+ <input
+ class="input"
+ type="text"
+ readonly
+ value={!jumpToDate ? "" : format(jumpToDate, dateFormatForSettings(settings))}
+ placeholder={i18n.str`date (${dateFormatForSettings(settings)})`}
+ onClick={() => {
+ setPickDate(true);
+ }}
+ />
+ </span>
+ </div>
+ <div class="control">
+ <span class="has-tooltip-left" data-tooltip={dateTooltip}>
+ <a
+ class="button is-fullwidth"
+ onClick={() => {
+ setPickDate(true);
+ }}
+ >
+ <span class="icon">
+ <i class="mdi mdi-calendar" />
+ </span>
+ </a>
+ </span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <DatePicker
+ opened={pickDate}
+ closeFunction={() => setPickDate(false)}
+ dateReceiver={onSelectDate}
+ />
+
+ <CardTable
+ orders={orders}
+ onCreate={onCreate}
+ onCopyURL={onCopyURL}
+ onSelect={onSelectOrder}
+ onRefund={onRefundOrder}
+ hasMoreAfter={hasMoreAfter}
+ hasMoreBefore={hasMoreBefore}
+ onLoadMoreAfter={onLoadMoreAfter}
+ onLoadMoreBefore={onLoadMoreBefore}
+ />
+ </Fragment>
+ );
+}
+*/
diff --git a/packages/auditor-backoffice-ui/src/paths/deposit_confirmations/list/Table.tsx b/packages/auditor-backoffice-ui/src/paths/deposit_confirmations/list/Table.tsx
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/paths/deposit_confirmations/list/Table.tsx
diff --git a/packages/auditor-backoffice-ui/src/paths/deposit_confirmations/list/index.tsx b/packages/auditor-backoffice-ui/src/paths/deposit_confirmations/list/index.tsx
new file mode 100644
index 000000000..5c24646bd
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/paths/deposit_confirmations/list/index.tsx
@@ -0,0 +1,73 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 {
+ ErrorType,
+ HttpError,
+ useTranslationContext,
+} from "@gnu-taler/web-util/browser";
+import { Fragment, h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { Loading } from "../../../components/exception/loading.js";
+import { NotificationCard } from "../../../components/menu/index.js";
+import { AuditorBackend, WithId } from "../../../declaration.js";
+import { useDepositConfirmations } from "../../../hooks/deposit-confirmation.js";
+import { Notification } from "../../../utils/types.js";
+//import { CardTable } from "./Table.js";
+import { HttpStatusCode } from "@gnu-taler/taler-util";
+//import { ConfirmModal, DeleteModal } from "../../../components/modal/index.js";
+//import { JumpToElementById } from "../../../components/form/JumpToElementById.js";
+
+interface Props {
+ onNotFound: () => VNode;
+ onLoadError: (e: HttpError<AuditorBackend.ErrorDetail>) => VNode;
+}
+
+export default function DepositConfirmationList({
+ onLoadError,
+ onNotFound,
+ }: Props): VNode {
+ const result = useDepositConfirmations();
+ const [notif, setNotif] = useState<Notification | undefined>(undefined);
+
+ const { i18n } = useTranslationContext();
+
+ if (result.loading) return <Loading />;
+ if (!result.ok) {
+ if (
+ result.type === ErrorType.CLIENT &&
+ result.status === HttpStatusCode.Unauthorized
+ )
+ return onNotFound();
+ return onLoadError(result);
+ }
+
+ return (
+ <Fragment>
+ <section class="section is-main-section">
+ //<NotificationCard notification={notif} />
+ {/*<CardTable
+ instances={result.data}
+ />*/}
+ </section>
+ </Fragment>
+ );
+}
diff --git a/packages/auditor-backoffice-ui/src/paths/notfound/index.tsx b/packages/auditor-backoffice-ui/src/paths/notfound/index.tsx
new file mode 100644
index 000000000..ff7d0e25b
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/paths/notfound/index.tsx
@@ -0,0 +1,34 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 { h, VNode } from "preact";
+import { Link } from "preact-router";
+
+export default function NotFoundPage(): VNode {
+ return (
+ <div>
+ <p>That page doesn&apos;t exist.</p>
+ <Link href="/">
+ <h4>Back to Home</h4>
+ </Link>
+ </div>
+ );
+}
diff --git a/packages/auditor-backoffice-ui/src/paths/settings/index.tsx b/packages/auditor-backoffice-ui/src/paths/settings/index.tsx
new file mode 100644
index 000000000..77a56a794
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/paths/settings/index.tsx
@@ -0,0 +1,107 @@
+/*
+ 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)
+ * @author Nic Eigel
+ */
+
+import { useLang, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { VNode, h } from "preact";
+import { FormErrors, FormProvider } from "../../components/forms/FormProvider.js";
+import { LangSelector } from "../../components/menu/LangSelector.js";
+import { Settings, useSettings } from "../../hooks/useSettings.js";
+
+function getBrowserLang(): string | undefined {
+ if (typeof window === "undefined") return undefined;
+ if (window.navigator.languages) return window.navigator.languages[0];
+ if (window.navigator.language) return window.navigator.language;
+ return undefined;
+}
+
+export function Settings({ onClose }: { onClose?: () => void }): VNode {
+ const { i18n } = useTranslationContext()
+ const borwserLang = getBrowserLang()
+ const { update } = useLang(undefined, {})
+
+ const [value, updateValue] = useSettings()
+ const errors: FormErrors<Settings> = {
+ }
+
+ function valueHandler(s: (d: Partial<Settings>) => Partial<Settings>): void {
+ const next = s(value)
+ const v: Settings = {
+ advanceOrderMode: next.advanceOrderMode ?? false,
+ dateFormat: next.dateFormat ?? "ymd"
+ }
+ updateValue(v)
+ }
+
+ return <div>
+ <section class="section is-main-section">
+ <div class="columns">
+ <div class="column" />
+ <div class="column is-four-fifths">
+ <div>
+
+ <FormProvider<Settings>
+ name="settings"
+ errors={errors}
+ object={value}
+ valueHandler={valueHandler}
+ >
+ <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label">
+ <i18n.Translate>Language</i18n.Translate>
+ <span class="icon has-tooltip-right" data-tooltip={"Force language setting instance of taking the browser"}>
+ <i class="mdi mdi-information" />
+ </span>
+ </label>
+ </div>
+ <div class="field field-body has-addons is-flex-grow-3">
+ <LangSelector />
+ &nbsp;
+ {borwserLang !== undefined && <button
+ data-tooltip={i18n.str`generate random secret key`}
+ class="button is-info mr-2"
+ onClick={(e) => {
+ update(borwserLang.substring(0, 2))
+ }}
+ >
+ <i18n.Translate>Set default</i18n.Translate>
+ </button>}
+ </div>
+ </div>
+ </FormProvider>
+ </div>
+ </div>
+ <div class="column" />
+ </div>
+ </section >
+ {onClose &&
+ <section class="section is-main-section">
+ <button
+ class="button"
+ onClick={onClose}
+ >
+ <i18n.Translate>Close</i18n.Translate>
+ </button>
+ </section>
+ }
+ </div >
+} \ No newline at end of file
diff --git a/packages/merchant-backoffice-ui/src/Application.tsx b/packages/merchant-backoffice-ui/src/Application.tsx
index cf46a34d5..f1b8526b3 100644
--- a/packages/merchant-backoffice-ui/src/Application.tsx
+++ b/packages/merchant-backoffice-ui/src/Application.tsx
@@ -42,7 +42,7 @@ import { strings } from "./i18n/strings.js";
export function Application(): VNode {
return (
- <BackendContextProvider>
+ <BackendContextProvider>
<TranslationProvider source={strings}>
<ApplicationStatusRoutes />
</TranslationProvider>
diff --git a/packages/web-util/src/utils/request.ts b/packages/web-util/src/utils/request.ts
index f8a892d99..29354d2c0 100644
--- a/packages/web-util/src/utils/request.ts
+++ b/packages/web-util/src/utils/request.ts
@@ -16,6 +16,7 @@
import { HttpStatusCode } from "@gnu-taler/taler-util";
import { base64encode } from "./base64.js";
+import { request } from "express";
export enum ErrorType {
CLIENT,
@@ -26,7 +27,6 @@ export enum ErrorType {
}
-
/**
*
* @param baseUrl URL where the service is located
@@ -47,6 +47,10 @@ export async function defaultRequestHandler<T>(
`${options.basicAuth.username}:${options.basicAuth.password}`,
)}`;
}
+
+ //TODO remove
+ requestHeaders["Access-Control-Allow-Origin"] = "*";
+
requestHeaders["Content-Type"] =
!options.contentType || options.contentType === "json" ? "application/json" : "text/plain";
@@ -78,7 +82,7 @@ export async function defaultRequestHandler<T>(
loading: false,
message: `invalid URL: "${baseUrl}${endpoint}"`,
};
- throw new RequestError(error)
+ throw new RequestError(error);
}
Object.entries(requestParams).forEach(([key, value]) => {
@@ -109,7 +113,7 @@ export async function defaultRequestHandler<T>(
loading: false,
message: `unsupported request body type: "${typeof requestBody}"`,
};
- throw new RequestError(error)
+ throw new RequestError(error);
}
}
@@ -215,6 +219,7 @@ interface HttpResponseLoading<T> {
data?: T;
}
+
export interface HttpResponseOk<T> {
ok: true;
loading?: false;
@@ -250,6 +255,7 @@ export interface HttpResponseServerError<ErrorDetail> {
message: string;
info: RequestInfo;
}
+
interface HttpRequestTimeoutError {
ok?: false;
loading?: false;
@@ -259,6 +265,7 @@ interface HttpRequestTimeoutError {
message: string;
}
+
interface HttpResponseClientError<ErrorDetail> {
ok?: false;
loading?: false;
@@ -292,12 +299,14 @@ interface HttpResponseUnreadableError {
body: string;
message: string;
}
+
export class RequestError<ErrorDetail> extends Error {
/**
* @deprecated use cause
*/
info: HttpError<ErrorDetail>;
cause: HttpError<ErrorDetail>;
+
constructor(d: HttpError<ErrorDetail>) {
super(d.message);
this.info = d;
@@ -426,9 +435,9 @@ export function buildRequestFailed<ErrorDetail>(
function validateURL(baseUrl: string, endpoint: string): URL | undefined {
try {
- return new URL(`${baseUrl}${endpoint}`)
+ return new URL(`${baseUrl}${endpoint}`);
} catch (ex) {
- return undefined
+ return undefined;
}
} \ No newline at end of file