summaryrefslogtreecommitdiff
path: root/packages/bank-ui/src/pages/admin/AccountList.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/bank-ui/src/pages/admin/AccountList.tsx')
-rw-r--r--packages/bank-ui/src/pages/admin/AccountList.tsx234
1 files changed, 234 insertions, 0 deletions
diff --git a/packages/bank-ui/src/pages/admin/AccountList.tsx b/packages/bank-ui/src/pages/admin/AccountList.tsx
new file mode 100644
index 000000000..6402c2bcd
--- /dev/null
+++ b/packages/bank-ui/src/pages/admin/AccountList.tsx
@@ -0,0 +1,234 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022-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 {
+ Amounts,
+ HttpStatusCode,
+ TalerError,
+ assertUnreachable,
+} from "@gnu-taler/taler-util";
+import { Loading, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { Fragment, VNode, h } from "preact";
+import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js";
+import { useBankCoreApiContext } from "@gnu-taler/web-util/browser";
+import { useBusinessAccounts } from "../../hooks/regional.js";
+import { RouteDefinition } from "@gnu-taler/web-util/browser";
+import { RenderAmount } from "../PaytoWireTransferForm.js";
+
+interface Props {
+ routeCreate: RouteDefinition;
+
+ routeShowAccount: RouteDefinition<{ account: string }>;
+ routeRemoveAccount: RouteDefinition<{ account: string }>;
+ routeUpdatePasswordAccount: RouteDefinition<{ account: string }>;
+}
+
+export function AccountList({
+ routeCreate,
+ routeRemoveAccount,
+ routeShowAccount,
+ routeUpdatePasswordAccount,
+}: Props): VNode {
+ const result = useBusinessAccounts();
+ const { i18n } = useTranslationContext();
+ const { config } = useBankCoreApiContext();
+
+ if (!result) {
+ return <Loading />;
+ }
+ if (result instanceof TalerError) {
+ return <ErrorLoadingWithDebug error={result} />;
+ }
+ if (result.type === "fail") {
+ switch (result.case) {
+ case HttpStatusCode.Unauthorized:
+ return <Fragment />;
+ default:
+ assertUnreachable(result.case);
+ }
+ }
+
+ const onGoStart = result.isFirstPage ? undefined : result.loadFirst;
+ const onGoNext = result.isLastPage ? undefined : result.loadNext;
+
+ const accounts = result.body;
+ return (
+ <Fragment>
+ <div class="px-4 sm:px-6 lg:px-8 mt-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>
+ </div>
+ <div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
+ <a
+ href={routeCreate.url({})}
+ name="create account"
+ 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"
+ >
+ <i18n.Translate>Create account</i18n.Translate>
+ </a>
+ </div>
+ </div>
+ <div class="mt-4 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>{/* FIXME: ADD empty list */}</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
+ name={`show account ${item.username}`}
+ href={routeShowAccount.url({
+ account: item.username,
+ })}
+ class="text-indigo-600 hover:text-indigo-900"
+ >
+ {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}
+ spec={config.currency_specification}
+ />
+ </span>
+ )}
+ </td>
+ <td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-0">
+ <a
+ name={`update password ${item.username}`}
+ href={routeUpdatePasswordAccount.url({
+ account: item.username,
+ })}
+ class="text-indigo-600 hover:text-indigo-900"
+ >
+ <i18n.Translate>Change password</i18n.Translate>
+ </a>
+ <br />
+ {/* {config.allow_conversion ?
+ <Fragment>
+
+ <a
+ name={`show cashout ${item.username}`}
+ href={routeShowCashoutsAccount.url({
+ account: item.username,
+ })}
+ class="text-indigo-600 hover:text-indigo-900"
+ >
+ <i18n.Translate>Cashouts</i18n.Translate>
+ </a>
+ <br />
+ </Fragment>
+ : undefined} */}
+ {noBalance ? (
+ <a
+ name={`remove account ${item.username}`}
+ href={routeRemoveAccount.url({
+ account: item.username,
+ })}
+ class="text-indigo-600 hover:text-indigo-900"
+ >
+ <i18n.Translate>Remove</i18n.Translate>
+ </a>
+ ) : undefined}
+ </td>
+ </tr>
+ );
+ })}
+ </tbody>
+ </table>
+ )}
+ </div>
+ <nav
+ class="flex items-center justify-between border-t border-gray-200 bg-white px-4 py-3 sm:px-6 rounded-lg"
+ aria-label="Pagination"
+ >
+ <div class="flex flex-1 justify-between sm:justify-end">
+ <button
+ name="first page"
+ class="relative disabled:bg-gray-100 disabled:text-gray-500 inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline-offset-0"
+ disabled={!onGoStart}
+ onClick={onGoStart}
+ >
+ <i18n.Translate>First page</i18n.Translate>
+ </button>
+ <button
+ name="next page"
+ class="relative disabled:bg-gray-100 disabled:text-gray-500 ml-3 inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline-offset-0"
+ disabled={!onGoNext}
+ onClick={onGoNext}
+ >
+ <i18n.Translate>Next</i18n.Translate>
+ </button>
+ </div>
+ </nav>
+ </div>
+ </div>
+ </div>
+ </Fragment>
+ );
+}