aboutsummaryrefslogtreecommitdiff
path: root/packages/demobank-ui/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/demobank-ui/src')
-rw-r--r--packages/demobank-ui/src/components/Routing.tsx11
-rw-r--r--packages/demobank-ui/src/hooks/circuit.ts99
-rw-r--r--packages/demobank-ui/src/hooks/settings.ts7
-rw-r--r--packages/demobank-ui/src/pages.ts2
-rw-r--r--packages/demobank-ui/src/pages/AccountPage/index.ts7
-rw-r--r--packages/demobank-ui/src/pages/AccountPage/state.ts5
-rw-r--r--packages/demobank-ui/src/pages/AccountPage/views.tsx2
-rw-r--r--packages/demobank-ui/src/pages/LoginForm.tsx2
-rw-r--r--packages/demobank-ui/src/pages/OperationState/state.ts2
-rw-r--r--packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx12
-rw-r--r--packages/demobank-ui/src/pages/ProfileNavigation.tsx2
-rw-r--r--packages/demobank-ui/src/pages/QrCodeSection.tsx2
-rw-r--r--packages/demobank-ui/src/pages/ShowAccountDetails.tsx2
-rw-r--r--packages/demobank-ui/src/pages/UpdateAccountPassword.tsx2
-rw-r--r--packages/demobank-ui/src/pages/WalletWithdrawForm.tsx4
-rw-r--r--packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx2
-rw-r--r--packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx (renamed from packages/demobank-ui/src/pages/HomePage.tsx)43
-rw-r--r--packages/demobank-ui/src/pages/WithdrawalQRCode.tsx2
-rw-r--r--packages/demobank-ui/src/pages/admin/Account.tsx2
-rw-r--r--packages/demobank-ui/src/pages/admin/AccountForm.tsx2
-rw-r--r--packages/demobank-ui/src/pages/admin/AccountList.tsx209
-rw-r--r--packages/demobank-ui/src/pages/admin/AdminHome.tsx176
-rw-r--r--packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx2
-rw-r--r--packages/demobank-ui/src/pages/admin/RemoveAccount.tsx2
-rw-r--r--packages/demobank-ui/src/pages/business/CreateCashout.tsx2
-rw-r--r--packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx2
26 files changed, 406 insertions, 199 deletions
diff --git a/packages/demobank-ui/src/components/Routing.tsx b/packages/demobank-ui/src/components/Routing.tsx
index 1d587fe32..c94e74201 100644
--- a/packages/demobank-ui/src/components/Routing.tsx
+++ b/packages/demobank-ui/src/components/Routing.tsx
@@ -21,7 +21,7 @@ import { Route, Router, route } from "preact-router";
import { useEffect } from "preact/hooks";
import { useBackendState } from "../hooks/backend.js";
import { BankFrame } from "../pages/BankFrame.js";
-import { HomePage, WithdrawalOperationPage } from "../pages/HomePage.js";
+import { WithdrawalOperationPage } from "../pages/WithdrawalOperationPage.js";
import { LoginForm } from "../pages/LoginForm.js";
import { PublicHistoriesPage } from "../pages/PublicHistoriesPage.js";
import { RegistrationPage } from "../pages/RegistrationPage.js";
@@ -35,6 +35,7 @@ import { CreateNewAccount } from "../pages/admin/CreateNewAccount.js";
import { CashoutListForAccount } from "../pages/admin/CashoutListForAccount.js";
import { ShowCashoutDetails } from "../pages/business/ShowCashoutDetails.js";
import { WireTransfer } from "../pages/admin/Account.js";
+import { AccountPage } from "../pages/AccountPage/index.js";
export function Routing(): VNode {
const history = createHashHistory();
@@ -301,17 +302,11 @@ export function Routing(): VNode {
}}
/>;
} else {
- return <HomePage
+ return <AccountPage
account={username}
goToConfirmOperation={(wopid) => {
route(`/operation/${wopid}`);
}}
- goToBusinessAccount={() => {
- route("/business");
- }}
- onRegister={() => {
- route("/register");
- }}
/>
}
}}
diff --git a/packages/demobank-ui/src/hooks/circuit.ts b/packages/demobank-ui/src/hooks/circuit.ts
index 06e068d6d..fc17c0184 100644
--- a/packages/demobank-ui/src/hooks/circuit.ts
+++ b/packages/demobank-ui/src/hooks/circuit.ts
@@ -18,10 +18,11 @@ import { useState } from "preact/hooks";
import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js";
import { useBackendState } from "./backend.js";
-import { AccessToken, AmountJson, Amounts, OperationOk, TalerCoreBankResultByMethod, TalerCorebankApi, TalerError, TalerHttpError } from "@gnu-taler/taler-util";
+import { AccessToken, AmountJson, AmountString, Amounts, OperationOk, TalerCoreBankResultByMethod, TalerCorebankApi, TalerError, TalerHttpError } from "@gnu-taler/taler-util";
import _useSWR, { SWRHook } from "swr";
import { useBankCoreApiContext } from "../context/config.js";
-import { assertUnreachable } from "../pages/HomePage.js";
+import { assertUnreachable } from "../pages/WithdrawalOperationPage.js";
+import { format, getDate, getDay, getHours, getMonth, getYear, set, sub } from "date-fns";
// FIX default import https://github.com/microsoft/TypeScript/issues/49189
const useSWR = _useSWR as unknown as SWRHook;
@@ -121,11 +122,6 @@ export function useRatiosAndFeeConfig() {
return undefined;
}
-interface PaginationFilter {
- account?: string;
- page?: number;
-}
-
export function useBusinessAccounts() {
const { state: credentials } = useBackendState();
const token = credentials.status !== "loggedIn" ? undefined : credentials.token
@@ -250,3 +246,92 @@ export function useCashoutDetails(cashoutId: string) {
if (error) return error;
return undefined;
}
+export type MonitorMetrics = {
+ lastHour: TalerCoreBankResultByMethod<"getMonitor">,
+ lastDay: TalerCoreBankResultByMethod<"getMonitor">,
+ lastMonth: TalerCoreBankResultByMethod<"getMonitor">,
+}
+
+function getTimeframesForDate(time: Date, timeframe: TalerCorebankApi.MonitorTimeframeParam): { current: number, previous: number } {
+ switch (timeframe) {
+ case TalerCorebankApi.MonitorTimeframeParam.hour: return {
+ current: getHours(sub(time, { hours: 1 })),
+ previous: getHours(sub(time, { hours: 2 }))
+ }
+ case TalerCorebankApi.MonitorTimeframeParam.day: return {
+ current: getDate(sub(time, { days: 1 })),
+ previous: getDate(sub(time, { days: 2 }))
+ }
+ case TalerCorebankApi.MonitorTimeframeParam.month: return {
+ current: getMonth(sub(time, { months: 1 })),
+ previous: getMonth(sub(time, { months: 2 }))
+ }
+ case TalerCorebankApi.MonitorTimeframeParam.year: return {
+ current: getYear(sub(time, { years: 1 })),
+ previous: getYear(sub(time, { years: 2 }))
+ }
+ case TalerCorebankApi.MonitorTimeframeParam.decade: return {
+ current: getYear(sub(time, { years: 10 })),
+ previous: getYear(sub(time, { years: 20 }))
+ }
+ default: assertUnreachable(timeframe)
+ }
+}
+
+export type LastMonitor = { current: TalerCoreBankResultByMethod<"getMonitor">, previous: TalerCoreBankResultByMethod<"getMonitor"> }
+export function useLastMonitorInfo(time: Date, timeframe: TalerCorebankApi.MonitorTimeframeParam) {
+ const { api, config } = useBankCoreApiContext();
+
+ async function fetcher() {
+ const params = getTimeframesForDate(time, timeframe)
+ // const resp = await Promise.all([
+ // api.getMonitor({ timeframe, which: params.current }),
+ // api.getMonitor({ timeframe, which: params.previous }),
+ // ])
+ const current: TalerCoreBankResultByMethod<"getMonitor"> = {
+ type: "ok" as const,
+ body: {
+ cashinCount: 1,
+ cashinExternalVolume: "LOCAL:1234" as AmountString,
+ cashoutCount: 2,
+ cashoutExternalVolume: "LOCAL:2345" as AmountString,
+ talerPayoutCount: 3,
+ talerPayoutInternalVolume: "LOCAL:3456" as AmountString,
+ }
+ }
+
+ const previous = {
+ type: "ok" as const,
+ body: {
+ cashinCount: 1,
+ cashinExternalVolume: "LOCAL:2345" as AmountString,
+ cashoutCount: 2,
+ cashoutExternalVolume: "LOCAL:2345" as AmountString,
+ talerPayoutCount: 3,
+ talerPayoutInternalVolume: "LOCAL:3456" as AmountString,
+ }
+
+ }
+ return {
+ current,
+ previous,
+ }
+ }
+
+ const { data, error } = useSWR<LastMonitor, TalerHttpError>(
+ config.have_cashout || true ? ["useLastMonitorInfo"] : false, fetcher, {
+ refreshInterval: 0,
+ refreshWhenHidden: false,
+ revalidateOnFocus: false,
+ revalidateOnReconnect: false,
+ refreshWhenOffline: false,
+ errorRetryCount: 0,
+ errorRetryInterval: 1,
+ shouldRetryOnError: false,
+ keepPreviousData: true,
+ });
+
+ if (data) return data;
+ if (error) return error;
+ return undefined;
+}
diff --git a/packages/demobank-ui/src/hooks/settings.ts b/packages/demobank-ui/src/hooks/settings.ts
index cfc3b6a5b..ca2d131f2 100644
--- a/packages/demobank-ui/src/hooks/settings.ts
+++ b/packages/demobank-ui/src/hooks/settings.ts
@@ -15,18 +15,15 @@
*/
import {
- AmountString,
Codec,
TranslatedString,
buildCodecForObject,
- codecForAmountString,
codecForBoolean,
codecForNumber,
codecForString,
- codecOptional,
+ codecOptional
} from "@gnu-taler/taler-util";
-import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { buildStorageKey, useLocalStorage, useTranslationContext } from "@gnu-taler/web-util/browser";
interface Settings {
currentWithdrawalOperationId: string | undefined;
diff --git a/packages/demobank-ui/src/pages.ts b/packages/demobank-ui/src/pages.ts
index c78240a02..cf003fde5 100644
--- a/packages/demobank-ui/src/pages.ts
+++ b/packages/demobank-ui/src/pages.ts
@@ -1,4 +1,4 @@
-import { WithdrawalOperationPage } from "./pages/HomePage.js";
+import { WithdrawalOperationPage } from "./pages/WithdrawalOperationPage.js";
import { PageEntry, pageDefinition } from "./route.js";
// const operationById: PageEntry<{ operationId: string }> = {
diff --git a/packages/demobank-ui/src/pages/AccountPage/index.ts b/packages/demobank-ui/src/pages/AccountPage/index.ts
index ef6b4fede..87ed878b0 100644
--- a/packages/demobank-ui/src/pages/AccountPage/index.ts
+++ b/packages/demobank-ui/src/pages/AccountPage/index.ts
@@ -14,9 +14,8 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { AbsoluteTime, AmountJson, TalerCorebankApi, TalerError, TalerErrorDetail } from "@gnu-taler/taler-util";
-import { HttpResponsePaginated, utils } from "@gnu-taler/web-util/browser";
-import { VNode } from "preact";
+import { AbsoluteTime, AmountJson, TalerCorebankApi, TalerError } from "@gnu-taler/taler-util";
+import { utils } from "@gnu-taler/web-util/browser";
import { ErrorLoading } from "../../components/ErrorLoading.js";
import { Loading } from "../../components/Loading.js";
import { LoginForm } from "../LoginForm.js";
@@ -25,7 +24,6 @@ import { InvalidIbanView, ReadyView } from "./views.js";
export interface Props {
account: string;
- goToBusinessAccount: () => void;
goToConfirmOperation: (id: string) => void;
}
@@ -51,7 +49,6 @@ export namespace State {
error: undefined;
account: string,
limit: AmountJson,
- goToBusinessAccount: () => void;
goToConfirmOperation: (id: string) => void;
}
diff --git a/packages/demobank-ui/src/pages/AccountPage/state.ts b/packages/demobank-ui/src/pages/AccountPage/state.ts
index 96d45b7bd..793593f0d 100644
--- a/packages/demobank-ui/src/pages/AccountPage/state.ts
+++ b/packages/demobank-ui/src/pages/AccountPage/state.ts
@@ -18,9 +18,9 @@ import { Amounts, HttpStatusCode, TalerError, TalerErrorCode, parsePaytoUri } fr
import { ErrorType, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser";
import { useAccountDetails } from "../../hooks/access.js";
import { Props, State } from "./index.js";
-import { assertUnreachable } from "../HomePage.js";
+import { assertUnreachable } from "../WithdrawalOperationPage.js";
-export function useComponentState({ account, goToBusinessAccount, goToConfirmOperation }: Props): State {
+export function useComponentState({ account, goToConfirmOperation }: Props): State {
const result = useAccountDetails(account);
const { i18n } = useTranslationContext();
@@ -76,7 +76,6 @@ export function useComponentState({ account, goToBusinessAccount, goToConfirmOpe
return {
status: "ready",
- goToBusinessAccount,
goToConfirmOperation,
error: undefined,
account,
diff --git a/packages/demobank-ui/src/pages/AccountPage/views.tsx b/packages/demobank-ui/src/pages/AccountPage/views.tsx
index 00643ec3e..8fff37624 100644
--- a/packages/demobank-ui/src/pages/AccountPage/views.tsx
+++ b/packages/demobank-ui/src/pages/AccountPage/views.tsx
@@ -53,7 +53,7 @@ function ShowDemoInfo(): VNode {
</Attention>
}
-export function ReadyView({ account, limit, goToBusinessAccount, goToConfirmOperation }: State.Ready): VNode<{}> {
+export function ReadyView({ account, limit, goToConfirmOperation }: State.Ready): VNode<{}> {
return <Fragment>
<ShowDemoInfo />
diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx
index 6d1d35288..b18f29d86 100644
--- a/packages/demobank-ui/src/pages/LoginForm.tsx
+++ b/packages/demobank-ui/src/pages/LoginForm.tsx
@@ -23,7 +23,7 @@ import { useBankCoreApiContext } from "../context/config.js";
import { useBackendState } from "../hooks/backend.js";
import { bankUiSettings } from "../settings.js";
import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
-import { assertUnreachable } from "./HomePage.js";
+import { assertUnreachable } from "./WithdrawalOperationPage.js";
import { doAutoFocus } from "./PaytoWireTransferForm.js";
diff --git a/packages/demobank-ui/src/pages/OperationState/state.ts b/packages/demobank-ui/src/pages/OperationState/state.ts
index 9e34a846b..136a2b505 100644
--- a/packages/demobank-ui/src/pages/OperationState/state.ts
+++ b/packages/demobank-ui/src/pages/OperationState/state.ts
@@ -23,7 +23,7 @@ import { useBackendState } from "../../hooks/backend.js";
import { useSettings } from "../../hooks/settings.js";
import { buildRequestErrorMessage, withRuntimeErrorHandling } from "../../utils.js";
import { Props, State } from "./index.js";
-import { assertUnreachable } from "../HomePage.js";
+import { assertUnreachable } from "../WithdrawalOperationPage.js";
import { mutate } from "swr";
export function useComponentState({ currency, onClose }: Props): utils.RecursiveState<State> {
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index d859c10d7..63cb3e865 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -43,7 +43,7 @@ import {
} from "../utils.js";
import { useBankCoreApiContext } from "../context/config.js";
import { useBackendState } from "../hooks/backend.js";
-import { assertUnreachable } from "./HomePage.js";
+import { assertUnreachable } from "./WithdrawalOperationPage.js";
import { mutate } from "swr";
const logger = new Logger("PaytoWireTransferForm");
@@ -128,7 +128,7 @@ export function PaytoWireTransferForm({
if (rawPaytoInput) {
const p = parsePaytoUri(rawPaytoInput)
if (!p) return;
- sendingAmount = p.params.amount
+ sendingAmount = p.params.amount as AmountString
delete p.params.amount
//if this payto is valid then it already have message
payto_uri = stringifyPaytoUri(p)
@@ -137,7 +137,7 @@ export function PaytoWireTransferForm({
const ibanPayto = buildPayto("iban", iban, undefined);
ibanPayto.params.message = encodeURIComponent(subject);
payto_uri = stringifyPaytoUri(ibanPayto);
- sendingAmount = `${limit.currency}:${trimmedAmountStr}`
+ sendingAmount = `${limit.currency}:${trimmedAmountStr}` as AmountString
}
const puri = payto_uri;
@@ -446,7 +446,7 @@ export function InputAmount(
);
}
-export function RenderAmount({ value, negative }: { value: AmountJson, negative?: boolean }): VNode {
+export function RenderAmount({ value, negative, noCurrency }: { value: AmountJson, negative?: boolean, noCurrency?: boolean }): VNode {
const { config } = useBankCoreApiContext()
const str = Amounts.stringifyValue(value)
const sep_pos = str.indexOf(FRAC_SEPARATOR)
@@ -456,11 +456,11 @@ export function RenderAmount({ value, negative }: { value: AmountJson, negative?
const small = str.substring(limit)
return <span class="whitespace-nowrap">
{negative ? "-" : undefined}
- {value.currency} {normal} <sup class="-ml-2">{small}</sup>
+ {noCurrency ? undefined : value.currency} {normal} <sup class="-ml-2">{small}</sup>
</span>
}
return <span class="whitespace-nowrap">
{negative ? "-" : undefined}
- {value.currency} {str}
+ {noCurrency ? undefined : value.currency} {str}
</span>
} \ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/ProfileNavigation.tsx b/packages/demobank-ui/src/pages/ProfileNavigation.tsx
index c061c9742..20a1ececd 100644
--- a/packages/demobank-ui/src/pages/ProfileNavigation.tsx
+++ b/packages/demobank-ui/src/pages/ProfileNavigation.tsx
@@ -1,7 +1,7 @@
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useBankCoreApiContext } from "../context/config.js";
-import { assertUnreachable } from "./HomePage.js";
+import { assertUnreachable } from "./WithdrawalOperationPage.js";
export function ProfileNavigation({ current }: { current: "details" | "credentials" | "cashouts" }): VNode {
const { i18n } = useTranslationContext()
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx b/packages/demobank-ui/src/pages/QrCodeSection.tsx
index 64f9ec5ab..8948827aa 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx
@@ -32,7 +32,7 @@ import { useEffect } from "preact/hooks";
import { QR } from "../components/QR.js";
import { buildRequestErrorMessage, withRuntimeErrorHandling } from "../utils.js";
import { useBankCoreApiContext } from "../context/config.js";
-import { assertUnreachable } from "./HomePage.js";
+import { assertUnreachable } from "./WithdrawalOperationPage.js";
export function QrCodeSection({
withdrawUri,
diff --git a/packages/demobank-ui/src/pages/ShowAccountDetails.tsx b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
index 21724474a..74346985a 100644
--- a/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
+++ b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
@@ -8,7 +8,7 @@ import { useBankCoreApiContext } from "../context/config.js";
import { useAccountDetails } from "../hooks/access.js";
import { useBackendState } from "../hooks/backend.js";
import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
-import { assertUnreachable } from "./HomePage.js";
+import { assertUnreachable } from "./WithdrawalOperationPage.js";
import { LoginForm } from "./LoginForm.js";
import { AccountForm } from "./admin/AccountForm.js";
import { ProfileNavigation } from "./ProfileNavigation.js";
diff --git a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
index e3f0de8cc..ef3737e81 100644
--- a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
+++ b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
@@ -5,7 +5,7 @@ import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
import { useBankCoreApiContext } from "../context/config.js";
import { useBackendState } from "../hooks/backend.js";
import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
-import { assertUnreachable } from "./HomePage.js";
+import { assertUnreachable } from "./WithdrawalOperationPage.js";
import { doAutoFocus } from "./PaytoWireTransferForm.js";
import { ProfileNavigation } from "./ProfileNavigation.js";
diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
index 28d5d7749..f1ff49068 100644
--- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
+++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
@@ -37,7 +37,7 @@ import { useBankCoreApiContext } from "../context/config.js";
import { useBackendState } from "../hooks/backend.js";
import { useSettings } from "../hooks/settings.js";
import { buildRequestErrorMessage, undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
-import { assertUnreachable } from "./HomePage.js";
+import { assertUnreachable } from "./WithdrawalOperationPage.js";
import { OperationState } from "./OperationState/index.js";
import { InputAmount, doAutoFocus } from "./PaytoWireTransferForm.js";
@@ -62,7 +62,7 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
if (!!settings.currentWithdrawalOperationId) {
return <Attention type="warning" title={i18n.str`There is an operation already`}>
- <span ref={focus ? doAutoFocus : undefined}/>
+ <span ref={focus ? doAutoFocus : undefined} />
<i18n.Translate>
To complete or cancel the operation click <a class="font-semibold text-yellow-700 hover:text-yellow-600" href={`#/operation/${settings.currentWithdrawalOperationId}`}>here</a>
</i18n.Translate>
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index 87637f7ef..895094c28 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -40,7 +40,7 @@ import { buildRequestErrorMessage, undefinedIfEmpty, withRuntimeErrorHandling }
import { useSettings } from "../hooks/settings.js";
import { RenderAmount } from "./PaytoWireTransferForm.js";
import { useBankCoreApiContext } from "../context/config.js";
-import { assertUnreachable } from "./HomePage.js";
+import { assertUnreachable } from "./WithdrawalOperationPage.js";
import { mutate } from "swr";
const logger = new Logger("WithdrawalConfirmationQuestion");
diff --git a/packages/demobank-ui/src/pages/HomePage.tsx b/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx
index bd85cea1e..7ef2a6c39 100644
--- a/packages/demobank-ui/src/pages/HomePage.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx
@@ -15,62 +15,23 @@
*/
import {
- HttpStatusCode,
Logger,
TranslatedString,
parseWithdrawUri,
- stringifyWithdrawUri,
+ stringifyWithdrawUri
} from "@gnu-taler/taler-util";
import {
- ErrorType,
- HttpResponse,
- HttpResponsePaginated,
- notify,
notifyError,
- useTranslationContext,
+ useTranslationContext
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { Loading } from "../components/Loading.js";
import { useBankCoreApiContext } from "../context/config.js";
import { useSettings } from "../hooks/settings.js";
-import { AccountPage } from "./AccountPage/index.js";
-import { LoginForm } from "./LoginForm.js";
import { WithdrawalQRCode } from "./WithdrawalQRCode.js";
const logger = new Logger("AccountPage");
-/**
- * show content based on state:
- * - LoginForm if the user is not logged in
- * - qr code if withdrawal in progress
- * - else account information
- * Use the handler to catch error cases
- *
- * @param param0
- * @returns
- */
-export function HomePage({
- onRegister,
- account,
- goToConfirmOperation,
- goToBusinessAccount,
-}: {
- account: string,
- onRegister: () => void;
- goToBusinessAccount: () => void;
- goToConfirmOperation: (id: string) => void;
-}): VNode {
- const { i18n } = useTranslationContext();
-
- return (
- <AccountPage
- account={account}
- goToConfirmOperation={goToConfirmOperation}
- goToBusinessAccount={goToBusinessAccount}
- />
- );
-}
-
export function WithdrawalOperationPage({
operationId,
onContinue,
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 51edbc95f..5c300d0ab 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -26,7 +26,7 @@ import { Fragment, VNode, h } from "preact";
import { ErrorLoading } from "../components/ErrorLoading.js";
import { Loading } from "../components/Loading.js";
import { useWithdrawalDetails } from "../hooks/access.js";
-import { assertUnreachable } from "./HomePage.js";
+import { assertUnreachable } from "./WithdrawalOperationPage.js";
import { QrCodeSection } from "./QrCodeSection.js";
import { WithdrawalConfirmationQuestion } from "./WithdrawalConfirmationQuestion.js";
import { Attention } from "../components/Attention.js";
diff --git a/packages/demobank-ui/src/pages/admin/Account.tsx b/packages/demobank-ui/src/pages/admin/Account.tsx
index 103747414..a1e80ccb9 100644
--- a/packages/demobank-ui/src/pages/admin/Account.tsx
+++ b/packages/demobank-ui/src/pages/admin/Account.tsx
@@ -4,7 +4,7 @@ import { Fragment, VNode, h } from "preact";
import { ErrorLoading } from "../../components/ErrorLoading.js";
import { Loading } from "../../components/Loading.js";
import { useAccountDetails } from "../../hooks/access.js";
-import { assertUnreachable } from "../HomePage.js";
+import { assertUnreachable } from "../WithdrawalOperationPage.js";
import { LoginForm } from "../LoginForm.js";
import { PaytoWireTransferForm } from "../PaytoWireTransferForm.js";
import { useBackendState } from "../../hooks/backend.js";
diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
index bce089560..410683dcb 100644
--- a/packages/demobank-ui/src/pages/admin/AccountForm.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
@@ -6,7 +6,7 @@ import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { TalerCorebankApi, buildPayto, parsePaytoUri } from "@gnu-taler/taler-util";
import { doAutoFocus } from "../PaytoWireTransferForm.js";
import { CopyButton } from "../../components/CopyButton.js";
-import { assertUnreachable } from "../HomePage.js";
+import { assertUnreachable } from "../WithdrawalOperationPage.js";
const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/;
const EMAIL_REGEX =
diff --git a/packages/demobank-ui/src/pages/admin/AccountList.tsx b/packages/demobank-ui/src/pages/admin/AccountList.tsx
index 39b43b9b1..7d3dd5595 100644
--- a/packages/demobank-ui/src/pages/admin/AccountList.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountList.tsx
@@ -3,10 +3,10 @@ import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { ErrorLoading } from "../../components/ErrorLoading.js";
import { Loading } from "../../components/Loading.js";
+import { useBankCoreApiContext } from "../../context/config.js";
import { useBusinessAccounts } from "../../hooks/circuit.js";
-import { assertUnreachable } from "../HomePage.js";
import { RenderAmount } from "../PaytoWireTransferForm.js";
-import { useBankCoreApiContext } from "../../context/config.js";
+import { assertUnreachable } from "../WithdrawalOperationPage.js";
interface Props {
onCreateAccount: () => void;
@@ -36,119 +36,122 @@ export function AccountList({ onRemoveAccount, onShowAccountDetails, onUpdateAcc
}
const { accounts } = result.data.body;
- return <div class="px-4 sm:px-6 lg:px-8">
- <div class="sm:flex sm:items-center">
- <div class="sm:flex-auto">
- <h1 class="text-base font-semibold leading-6 text-gray-900">
- <i18n.Translate>Accounts</i18n.Translate>
- </h1>
- <p class="mt-2 text-sm text-gray-700">
- <i18n.Translate>A list of all business account in the bank.</i18n.Translate>
- </p>
- </div>
- <div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
- <button type="button" class="block rounded-md bg-indigo-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
- onClick={(e) => {
- e.preventDefault()
- onCreateAccount()
- }}>
- <i18n.Translate>Create account</i18n.Translate>
- </button>
+ return <Fragment>
+ <div class="px-4 sm:px-6 lg:px-8 mt-4">
+ <div class="sm:flex sm:items-center">
+ <div class="sm:flex-auto">
+ <h1 class="text-base font-semibold leading-6 text-gray-900">
+ <i18n.Translate>Accounts</i18n.Translate>
+ </h1>
+ <p class="mt-2 text-sm text-gray-700">
+ <i18n.Translate>A list of all business account in the bank.</i18n.Translate>
+ </p>
+ </div>
+ <div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
+ <button type="button" class="block rounded-md bg-indigo-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
+ onClick={(e) => {
+ e.preventDefault()
+ onCreateAccount()
+ }}>
+ <i18n.Translate>Create account</i18n.Translate>
+ </button>
+ </div>
</div>
- </div>
- <div class="mt-8 flow-root">
- <div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
- <div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
- {!accounts.length ? (
- <div></div>
- ) : (
- <table class="min-w-full divide-y divide-gray-300">
- <thead>
- <tr>
- <th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-0">{i18n.str`Username`}</th>
- <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">{i18n.str`Name`}</th>
- <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">{i18n.str`Balance`}</th>
- <th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-0">
- <span class="sr-only">{i18n.str`Actions`}</span>
- </th>
- </tr>
- </thead>
- <tbody class="divide-y divide-gray-200">
- {accounts.map((item, idx) => {
- const balance = !item.balance
- ? undefined
- : Amounts.parse(item.balance.amount);
- const noBalance = Amounts.isZero(item.balance.amount)
- const balanceIsDebit =
- item.balance &&
- item.balance.credit_debit_indicator == "debit";
+ <div class="mt-8 flow-root">
+ <div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
+ <div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
+ {!accounts.length ? (
+ <div></div>
+ ) : (
+ <table class="min-w-full divide-y divide-gray-300">
+ <thead>
+ <tr>
+ <th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-0">{i18n.str`Username`}</th>
+ <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">{i18n.str`Name`}</th>
+ <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">{i18n.str`Balance`}</th>
+ <th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-0">
+ <span class="sr-only">{i18n.str`Actions`}</span>
+ </th>
+ </tr>
+ </thead>
+ <tbody class="divide-y divide-gray-200">
+ {accounts.map((item, idx) => {
+ const balance = !item.balance
+ ? undefined
+ : Amounts.parse(item.balance.amount);
+ const noBalance = Amounts.isZero(item.balance.amount)
+ const balanceIsDebit =
+ item.balance &&
+ item.balance.credit_debit_indicator == "debit";
- return <tr key={idx}>
- <td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-0">
- <a href="#" class="text-indigo-600 hover:text-indigo-900"
- onClick={(e) => {
- e.preventDefault();
- onShowAccountDetails(item.username)
- }}
- >
- {item.username}
- </a>
+ return <tr key={idx}>
+ <td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-0">
+ <a href="#" class="text-indigo-600 hover:text-indigo-900"
+ onClick={(e) => {
+ e.preventDefault();
+ onShowAccountDetails(item.username)
+ }}
+ >
+ {item.username}
+ </a>
- </td>
- <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
- {item.name}
- </td>
- <td data-negative={noBalance ? undefined : balanceIsDebit ? "true" : "false"} class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 data-[negative=false]:text-green-600 data-[negative=true]:text-red-600 ">
- {!balance ? (
- i18n.str`unknown`
- ) : (
- <span class="amount">
- <RenderAmount value={balance} negative={balanceIsDebit} />
- </span>
- )}
- </td>
- <td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-0">
- <a href="#" class="text-indigo-600 hover:text-indigo-900"
- onClick={(e) => {
- e.preventDefault();
- onUpdateAccountPassword(item.username)
- }}
- >
- change password
- </a>
- <br />
- {config.have_cashout ?
- <Fragment>
+ </td>
+ <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
+ {item.name}
+ </td>
+ <td data-negative={noBalance ? undefined : balanceIsDebit ? "true" : "false"} class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 data-[negative=false]:text-green-600 data-[negative=true]:text-red-600 ">
+ {!balance ? (
+ i18n.str`unknown`
+ ) : (
+ <span class="amount">
+ <RenderAmount value={balance} negative={balanceIsDebit} />
+ </span>
+ )}
+ </td>
+ <td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-0">
+ <a href="#" class="text-indigo-600 hover:text-indigo-900"
+ onClick={(e) => {
+ e.preventDefault();
+ onUpdateAccountPassword(item.username)
+ }}
+ >
+ change password
+ </a>
+ <br />
+ {config.have_cashout ?
+ <Fragment>
+ <a href="#" class="text-indigo-600 hover:text-indigo-900" onClick={(e) => {
+ e.preventDefault();
+ onShowCashoutForAccount(item.username)
+ }}
+ >
+ cashouts
+ </a>
+ <br />
+ </Fragment>
+ : undefined}
+ {noBalance ?
<a href="#" class="text-indigo-600 hover:text-indigo-900" onClick={(e) => {
e.preventDefault();
- onShowCashoutForAccount(item.username)
+ onRemoveAccount(item.username)
}}
>
- cashouts
+ remove
</a>
- <br />
- </Fragment>
- : undefined}
- {noBalance ?
- <a href="#" class="text-indigo-600 hover:text-indigo-900" onClick={(e) => {
- e.preventDefault();
- onRemoveAccount(item.username)
- }}
- >
- remove
- </a>
- : undefined}
- </td>
- </tr>
- })}
+ : undefined}
+ </td>
+ </tr>
+ })}
- </tbody>
- </table>
- )}
+ </tbody>
+ </table>
+ )}
+ </div>
</div>
</div>
</div>
- </div>
+ </Fragment>
+
} \ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/admin/AdminHome.tsx b/packages/demobank-ui/src/pages/admin/AdminHome.tsx
index 01f9f6dbd..78827d3a2 100644
--- a/packages/demobank-ui/src/pages/admin/AdminHome.tsx
+++ b/packages/demobank-ui/src/pages/admin/AdminHome.tsx
@@ -1,7 +1,14 @@
+import { AmountJson, AmountString, Amounts, TalerCorebankApi, TalerError } from "@gnu-taler/taler-util";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
+import { useState } from "preact/hooks";
import { Transactions } from "../../components/Transactions/index.js";
+import { MonitorMetrics, useLastMonitorInfo } from "../../hooks/circuit.js";
+import { RenderAmount } from "../PaytoWireTransferForm.js";
import { WireTransfer } from "./Account.js";
import { AccountList } from "./AccountList.js";
+import { assertUnreachable } from "../WithdrawalOperationPage.js";
+import { ErrorLoading } from "../../components/ErrorLoading.js";
/**
* Query account information and show QR code if there is pending withdrawal
@@ -17,6 +24,10 @@ interface Props {
}
export function AdminHome({ onCreateAccount, onRegister, onRemoveAccount, onShowAccountDetails, onShowCashoutForAccount, onUpdateAccountPassword }: Props): VNode {
return <Fragment>
+ <Metrics />
+ <WireTransfer onRegister={onRegister} />
+
+ <Transactions account="admin" />
<AccountList
onCreateAccount={onCreateAccount}
onRemoveAccount={onRemoveAccount}
@@ -25,8 +36,167 @@ export function AdminHome({ onCreateAccount, onRegister, onRemoveAccount, onShow
onUpdateAccountPassword={onUpdateAccountPassword}
/>
- <WireTransfer onRegister={onRegister} />
+ </Fragment>
+}
- <Transactions account="admin" />
+function Metrics(): VNode {
+ const { i18n } = useTranslationContext()
+ const [metricType, setMetricType] = useState<TalerCorebankApi.MonitorTimeframeParam>(TalerCorebankApi.MonitorTimeframeParam.day);
+
+ const resp = useLastMonitorInfo(new Date(), metricType);
+ console.log(resp)
+ if (!resp) return <Fragment />;
+ if (resp instanceof TalerError) {
+ return <ErrorLoading error={resp} />
+ }
+ if (resp.current.type !== "ok" || resp.current.type !== "ok" || resp.current.type !== "ok") {
+ return <Fragment />
+ }
+ if (resp.previous.type !== "ok" || resp.previous.type !== "ok" || resp.previous.type !== "ok") {
+ return <Fragment />
+ }
+
+ // const metric = getMetricInfo(metricType, current, previous);
+
+ return <Fragment>
+ <div class="sm:hidden">
+ <label for="tabs" class="sr-only"><i18n.Translate>Select a section</i18n.Translate></label>
+ <select id="tabs" name="tabs" class="block w-full rounded-md border-gray-300 focus:border-indigo-500 focus:ring-indigo-500" onChange={(e) => {
+ // const op = e.currentTarget.value as typeof metricType
+ setMetricType(e.currentTarget.value as any)
+ }}>
+ <option value={TalerCorebankApi.MonitorTimeframeParam.hour} selected={metricType == TalerCorebankApi.MonitorTimeframeParam.hour}><i18n.Translate>Last hour</i18n.Translate></option>
+ <option value={TalerCorebankApi.MonitorTimeframeParam.day} selected={metricType == TalerCorebankApi.MonitorTimeframeParam.day}><i18n.Translate>Last day</i18n.Translate></option>
+ <option value={TalerCorebankApi.MonitorTimeframeParam.month} selected={metricType == TalerCorebankApi.MonitorTimeframeParam.month}><i18n.Translate>Last month</i18n.Translate></option>
+ <option value={TalerCorebankApi.MonitorTimeframeParam.year} selected={metricType == TalerCorebankApi.MonitorTimeframeParam.year}><i18n.Translate>Last year</i18n.Translate></option>
+ </select>
+ </div>
+ <div class="hidden sm:block">
+ <nav class="isolate flex divide-x divide-gray-200 rounded-lg shadow" aria-label="Tabs">
+ <a href="#" onClick={(e) => { e.preventDefault(); setMetricType(TalerCorebankApi.MonitorTimeframeParam.hour) }} data-selected={metricType == TalerCorebankApi.MonitorTimeframeParam.hour} class="rounded-l-lg text-gray-500 hover:text-gray-700 data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium hover:bg-gray-50 focus:z-10" >
+ <span><i18n.Translate>Last hour</i18n.Translate></span>
+ <span aria-hidden="true" data-selected={metricType == TalerCorebankApi.MonitorTimeframeParam.hour} class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 bottom-0 h-0.5"></span>
+ </a>
+ <a href="#" onClick={(e) => { e.preventDefault(); setMetricType(TalerCorebankApi.MonitorTimeframeParam.day) }} data-selected={metricType == TalerCorebankApi.MonitorTimeframeParam.day} aria-current="page" class=" text-gray-500 hover:text-gray-700 data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium hover:bg-gray-50 focus:z-10">
+ <span><i18n.Translate>Last day</i18n.Translate></span>
+ <span aria-hidden="true" data-selected={metricType == TalerCorebankApi.MonitorTimeframeParam.day} class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 bottom-0 h-0.5"></span>
+ </a>
+ <a href="#" onClick={(e) => { e.preventDefault(); setMetricType(TalerCorebankApi.MonitorTimeframeParam.month) }} data-selected={metricType == TalerCorebankApi.MonitorTimeframeParam.month} class="rounded-r-lg text-gray-500 hover:text-gray-700 data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium hover:bg-gray-50 focus:z-10">
+ <span><i18n.Translate>Last month</i18n.Translate></span>
+ <span aria-hidden="true" data-selected={metricType == TalerCorebankApi.MonitorTimeframeParam.month} class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 bottom-0 h-0.5"></span>
+ </a>
+ <a href="#" onClick={(e) => { e.preventDefault(); setMetricType(TalerCorebankApi.MonitorTimeframeParam.year) }} data-selected={metricType == TalerCorebankApi.MonitorTimeframeParam.year} class="rounded-r-lg text-gray-500 hover:text-gray-700 data-[selected=true]:text-gray-900 group relative min-w-0 flex-1 overflow-hidden bg-white py-4 px-4 text-center text-sm font-medium hover:bg-gray-50 focus:z-10">
+ <span><i18n.Translate>Last Year</i18n.Translate></span>
+ <span aria-hidden="true" data-selected={metricType == TalerCorebankApi.MonitorTimeframeParam.year} class="bg-transparent data-[selected=true]:bg-indigo-500 absolute inset-x-0 bottom-0 h-0.5"></span>
+ </a>
+ </nav>
+ </div>
+
+ <div class="w-full flex justify-between">
+
+ <h1 class="text-base font-semibold leading-7 text-gray-900 mt-5">
+
+ Trading volume on Thursday
+ </h1>
+ <div class="flex items-center justify-between">
+ {/* <span class="flex flex-grow flex-col">
+ <span class="text-sm text-black font-medium leading-6 " id="availability-label">
+ ASD
+ </span>
+ </span> */}
+ <button type="button" class="bg-gray-200 relative inline-flex h-6 w-48 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false">
+ <span class="sr-only">Use setting</span>
+ {/* <!-- Enabled: "translate-x-5", Not Enabled: "translate-x-0" --> */}
+ <span class="translate-x-0 pointer-events-none relative inline-block h-5 w-32 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out">
+ {/* <!-- Enabled: "opacity-0 duration-100 ease-out", Not Enabled: "opacity-100 duration-200 ease-in" --> */}
+ <span class="opacity-100 duration-200 ease-in absolute inset-0 flex h-full w-full items-center justify-center transition-opacity" aria-hidden="true">
+ Transaction
+ </span>
+ {/* <!-- Enabled: "opacity-100 duration-200 ease-in", Not Enabled: "opacity-0 duration-100 ease-out" --> */}
+ <span class="opacity-0 duration-100 ease-out absolute inset-0 flex h-full w-full items-center justify-center transition-opacity" aria-hidden="true">
+ <svg class="h-3 w-3 text-indigo-600" fill="currentColor" viewBox="0 0 12 12">
+ <path d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z" />
+ </svg>
+ </span>
+ </span>
+ </button>
+ </div>
+
+ </div>
+ <dl class="mt-5 grid grid-cols-1 divide-y divide-gray-200 overflow-hidden rounded-lg bg-white shadow-lg md:grid-cols-3 md:divide-x md:divide-y-0">
+
+ <div class="px-4 py-5 sm:p-6">
+ <dt class="text-base font-normal text-gray-900">
+ <i18n.Translate>Cashin</i18n.Translate>
+ </dt>
+ <MetricValue
+ current={resp.current.body.cashinExternalVolume}
+ previous={resp.previous.body.cashinExternalVolume}
+ />
+ </div>
+
+ <div class="px-4 py-5 sm:p-6">
+ <dt class="text-base font-normal text-gray-900">
+ <i18n.Translate>Cashout</i18n.Translate>
+ </dt>
+ <MetricValue
+ current={resp.current.body.cashoutExternalVolume}
+ previous={resp.previous.body.cashoutExternalVolume}
+ />
+ </div>
+ <div class="px-4 py-5 sm:p-6">
+ <dt class="text-base font-normal text-gray-900">
+ <i18n.Translate>Payout</i18n.Translate>
+ </dt>
+ <MetricValue
+ current={resp.current.body.talerPayoutInternalVolume}
+ previous={resp.previous.body.talerPayoutInternalVolume}
+ />
+ </div>
+ </dl>
</Fragment>
-} \ No newline at end of file
+
+}
+
+
+function MetricValue({ current, previous }: { current: AmountString | undefined, previous: AmountString | undefined }): VNode {
+ const { i18n } = useTranslationContext()
+ const cmp = current && previous ? Amounts.cmp(current, previous) : 0;
+ const currAmount = !current ? undefined : Number.parseFloat(Amounts.stringifyValue(current))
+ const prevAmount = !previous ? undefined : Number.parseFloat(Amounts.stringifyValue(previous))
+
+ const rate = !currAmount || Number.isNaN(currAmount) || !prevAmount || Number.isNaN(prevAmount) ? 0 :
+ cmp === -1 ? 1 - Math.round(currAmount) / Math.round(prevAmount) :
+ cmp === 1 ? (Math.round(currAmount) / Math.round(prevAmount)) - 1 : 0;
+
+ const rateStr = `${(Math.abs(rate) * 100).toFixed(2)}%`
+ return <dd class="mt-1 flex justify-between md:block lg:flex">
+ <div class="flex justify-start items-baseline text-2xl font-semibold text-indigo-600">
+ {!current ? "-" : <RenderAmount value={Amounts.parseOrThrow(current)} />}
+ </div>
+ <div class="flex justify-end items-baseline text-2xl font-semibold text-indigo-600">
+ <small class="ml-2 text-sm font-medium text-gray-500">
+ <i18n.Translate>from</i18n.Translate> {!previous ? "-" : <RenderAmount value={Amounts.parseOrThrow(previous)} />}
+ </small>
+ </div>
+
+ {cmp == 1 &&
+ <div class="inline-flex items-baseline rounded-full px-2.5 py-0.5 text-sm font-medium bg-green-100 text-green-800 md:mt-2 lg:mt-0">
+ <svg class="-ml-1 mr-0.5 h-5 w-5 flex-shrink-0 self-center text-green-500" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+ <path fill-rule="evenodd" d="M10 17a.75.75 0 01-.75-.75V5.612L5.29 9.77a.75.75 0 01-1.08-1.04l5.25-5.5a.75.75 0 011.08 0l5.25 5.5a.75.75 0 11-1.08 1.04l-3.96-4.158V16.25A.75.75 0 0110 17z" clip-rule="evenodd" />
+ </svg>
+ <span class="sr-only"><i18n.Translate>Increased by</i18n.Translate></span>
+ {rateStr}
+ </div>
+ }
+ {cmp == -1 &&
+ <div class="inline-flex items-baseline rounded-full px-2.5 py-0.5 text-sm font-medium bg-red-100 text-red-800 md:mt-2 lg:mt-0">
+ <svg class="-ml-1 mr-0.5 h-5 w-5 flex-shrink-0 self-center text-red-500" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+ <path fill-rule="evenodd" d="M10 3a.75.75 0 01.75.75v10.638l3.96-4.158a.75.75 0 111.08 1.04l-5.25 5.5a.75.75 0 01-1.08 0l-5.25-5.5a.75.75 0 111.08-1.04l3.96 4.158V3.75A.75.75 0 0110 3z" clip-rule="evenodd" />
+ </svg>
+ <span class="sr-only"><i18n.Translate>Descreased by</i18n.Translate></span>
+ {rateStr}
+ </div>
+ }
+ </dd>
+}
diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
index 772ea6e84..ea40001c0 100644
--- a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
@@ -7,7 +7,7 @@ import { getRandomPassword } from "../rnd.js";
import { AccountForm, AccountFormData } from "./AccountForm.js";
import { useBackendState } from "../../hooks/backend.js";
import { useBankCoreApiContext } from "../../context/config.js";
-import { assertUnreachable } from "../HomePage.js";
+import { assertUnreachable } from "../WithdrawalOperationPage.js";
import { mutate } from "swr";
import { Attention } from "../../components/Attention.js";
diff --git a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
index 88961c2cb..4aa17e302 100644
--- a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
@@ -8,7 +8,7 @@ import { Loading } from "../../components/Loading.js";
import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
import { useAccountDetails } from "../../hooks/access.js";
import { buildRequestErrorMessage, undefinedIfEmpty, withRuntimeErrorHandling } from "../../utils.js";
-import { assertUnreachable } from "../HomePage.js";
+import { assertUnreachable } from "../WithdrawalOperationPage.js";
import { LoginForm } from "../LoginForm.js";
import { doAutoFocus } from "../PaytoWireTransferForm.js";
import { useBankCoreApiContext } from "../../context/config.js";
diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
index 4696c899e..5595d3b51 100644
--- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx
+++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
@@ -45,7 +45,7 @@ import {
} from "../../utils.js";
import { LoginForm } from "../LoginForm.js";
import { InputAmount } from "../PaytoWireTransferForm.js";
-import { assertUnreachable } from "../HomePage.js";
+import { assertUnreachable } from "../WithdrawalOperationPage.js";
import { Attention } from "../../components/Attention.js";
interface Props {
diff --git a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
index a8e34e4b9..5c09e2001 100644
--- a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
+++ b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
@@ -37,7 +37,7 @@ import {
undefinedIfEmpty,
withRuntimeErrorHandling
} from "../../utils.js";
-import { assertUnreachable } from "../HomePage.js";
+import { assertUnreachable } from "../WithdrawalOperationPage.js";
interface Props {
id: string;