diff options
Diffstat (limited to 'packages/demobank-ui/src/pages/admin/AccountList.tsx')
-rw-r--r-- | packages/demobank-ui/src/pages/admin/AccountList.tsx | 288 |
1 files changed, 175 insertions, 113 deletions
diff --git a/packages/demobank-ui/src/pages/admin/AccountList.tsx b/packages/demobank-ui/src/pages/admin/AccountList.tsx index 4ec25660b..1cee4c58a 100644 --- a/packages/demobank-ui/src/pages/admin/AccountList.tsx +++ b/packages/demobank-ui/src/pages/admin/AccountList.tsx @@ -1,143 +1,205 @@ -import { Amounts, HttpStatusCode, TalerError } from "@gnu-taler/taler-util"; +/* + 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 "../../context/config.js"; import { useBusinessAccounts } from "../../hooks/circuit.js"; import { RenderAmount } from "../PaytoWireTransferForm.js"; -import { assertUnreachable } from "../WithdrawalOperationPage.js"; +import { RouteDefinition } from "../../route.js"; interface Props { - onCreateAccount: () => void; + routeCreate: RouteDefinition<Record<string, never>>; - onShowAccountDetails: (aid: string) => void; - onRemoveAccount: (aid: string) => void; - onUpdateAccountPassword: (aid: string) => void; - onShowCashoutForAccount: (aid: string) => void; + routeShowAccount: RouteDefinition<{ account: string }>; + routeRemoveAccount: RouteDefinition<{ account: string }>; + routeUpdatePasswordAccount: RouteDefinition<{ account: string }>; + routeShowCashoutsAccount: RouteDefinition<{ account: string }>; } -export function AccountList({ onRemoveAccount, onShowAccountDetails, onUpdateAccountPassword, onShowCashoutForAccount, onCreateAccount }: Props): VNode { +export function AccountList({ + routeCreate, + routeRemoveAccount, + routeShowAccount, + routeShowCashoutsAccount, + routeUpdatePasswordAccount, +}: Props): VNode { const result = useBusinessAccounts(); const { i18n } = useTranslationContext(); - const { config } = useBankCoreApiContext() + const { config } = useBankCoreApiContext(); if (!result) { - return <Loading /> + return <Loading />; } if (result instanceof TalerError) { - return <ErrorLoadingWithDebug error={result} /> + return <ErrorLoadingWithDebug error={result} />; } if (result.data.type === "fail") { switch (result.data.case) { - case HttpStatusCode.Unauthorized: return <Fragment /> - default: assertUnreachable(result.data.case) + case HttpStatusCode.Unauthorized: + return <Fragment />; + default: + assertUnreachable(result.data.case); } } const { accounts } = result.data.body; - 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> + 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"> + <a + href={routeCreate.url({})} + 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> - <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> - - - </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 href="#" class="text-indigo-600 hover:text-indigo-900" - onClick={(e) => { - e.preventDefault(); - onUpdateAccountPassword(item.username) - }} - > - <i18n.Translate>change password</i18n.Translate> - </a> - <br /> - {noBalance ? - <a href="#" class="text-indigo-600 hover:text-indigo-900" onClick={(e) => { - e.preventDefault(); - onRemoveAccount(item.username) - }} - > - <i18n.Translate>remove</i18n.Translate> - </a> - : undefined} - </td> + <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"; - </tbody> - </table> - )} + 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={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 + href={routeUpdatePasswordAccount.url({ + account: item.username, + })} + class="text-indigo-600 hover:text-indigo-900" + > + <i18n.Translate>change password</i18n.Translate> + </a> + <br /> + <a + href={routeShowCashoutsAccount.url({ + account: item.username, + })} + class="text-indigo-600 hover:text-indigo-900" + > + <i18n.Translate>cashouts</i18n.Translate> + </a> + <br /> + {noBalance ? ( + <a + 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> </div> </div> </div> - </div> - </Fragment> - -}
\ No newline at end of file + </Fragment> + ); +} |