diff options
Diffstat (limited to 'packages/demobank-ui/src/pages/admin')
8 files changed, 284 insertions, 215 deletions
diff --git a/packages/demobank-ui/src/pages/admin/Account.tsx b/packages/demobank-ui/src/pages/admin/Account.tsx index bf2fa86f0..103747414 100644 --- a/packages/demobank-ui/src/pages/admin/Account.tsx +++ b/packages/demobank-ui/src/pages/admin/Account.tsx @@ -3,15 +3,15 @@ import { notifyInfo, 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 { useBackendContext } from "../../context/backend.js"; import { useAccountDetails } from "../../hooks/access.js"; import { assertUnreachable } from "../HomePage.js"; import { LoginForm } from "../LoginForm.js"; import { PaytoWireTransferForm } from "../PaytoWireTransferForm.js"; +import { useBackendState } from "../../hooks/backend.js"; -export function AdminAccount({ onRegister }: { onRegister: () => void }): VNode { +export function WireTransfer({ toAccount, onRegister, onCancel, onSuccess }: { onSuccess?: () => void; toAccount?: string, onCancel?: () => void, onRegister?: () => void }): VNode { const { i18n } = useTranslationContext(); - const r = useBackendContext(); + const r = useBackendState(); const account = r.state.status !== "loggedOut" ? r.state.username : "admin"; const result = useAccountDetails(account); @@ -41,11 +41,13 @@ export function AdminAccount({ onRegister }: { onRegister: () => void }): VNode return ( <PaytoWireTransferForm title={i18n.str`Make a wire transfer`} + toAccount={toAccount} limit={limit} onSuccess={() => { notifyInfo(i18n.str`Wire transfer created!`); + if (onSuccess) onSuccess() }} - onCancel={undefined} + onCancel={onCancel} /> ); } diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx b/packages/demobank-ui/src/pages/admin/AccountForm.tsx index 8470930bf..bce089560 100644 --- a/packages/demobank-ui/src/pages/admin/AccountForm.tsx +++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx @@ -1,16 +1,20 @@ -import { ComponentChildren, VNode, h } from "preact"; +import { ComponentChildren, Fragment, VNode, h } from "preact"; import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js"; import { PartialButDefined, RecursivePartial, WithIntermediate, undefinedIfEmpty, validateIBAN } from "../../utils.js"; import { useEffect, useRef, useState } from "preact/hooks"; 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"; const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/; const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; const REGEX_JUST_NUMBERS_REGEX = /^\+[0-9 ]*$/; +export type AccountFormData = TalerCorebankApi.AccountData & { username: string } + /** * Create valid account object to update or create * Take template as initial values for the form @@ -21,6 +25,7 @@ const REGEX_JUST_NUMBERS_REGEX = /^\+[0-9 ]*$/; */ export function AccountForm({ template, + username, purpose, onChange, focus, @@ -28,11 +33,12 @@ export function AccountForm({ }: { focus?: boolean, children: ComponentChildren, + username?: string, template: TalerCorebankApi.AccountData | undefined; - onChange: (a: TalerCorebankApi.AccountData | undefined) => void; + onChange: (a: AccountFormData | undefined) => void; purpose: "create" | "update" | "show"; }): VNode { - const initial = initializeFromTemplate(template); + const initial = initializeFromTemplate(username, template); const [form, setForm] = useState(initial); const [errors, setErrors] = useState< RecursivePartial<typeof initial> | undefined @@ -69,14 +75,8 @@ export function AccountForm({ ? i18n.str`phone number can't have other than numbers` : undefined, }), - // iban: !newForm.iban - // ? undefined //optional field - // : !IBAN_REGEX.test(newForm.iban) - // ? i18n.str`IBAN should have just uppercased letters and numbers` - // : validateIBAN(newForm.iban, i18n), name: !newForm.name ? i18n.str`required` : undefined, - - // username: !newForm.username ? i18n.str`required` : undefined, + username: !newForm.username ? i18n.str`required` : undefined, }); setErrors(errors); setForm(newForm); @@ -95,7 +95,7 @@ export function AccountForm({ <div class="px-4 py-6 sm:p-8"> <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6"> - {/* <div class="sm:col-span-5"> + <div class="sm:col-span-5"> <label class="block text-sm font-medium leading-6 text-gray-900" for="username" @@ -105,7 +105,7 @@ export function AccountForm({ </label> <div class="mt-2"> <input - ref={focus ? doAutoFocus : undefined} + ref={focus && purpose === "create" ? doAutoFocus : undefined} type="text" class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" name="username" @@ -128,7 +128,7 @@ export function AccountForm({ <p class="mt-2 text-sm text-gray-500" > <i18n.Translate>account identification in the bank</i18n.Translate> </p> - </div> */} + </div> <div class="sm:col-span-5"> <label @@ -165,27 +165,7 @@ export function AccountForm({ </div> - {purpose !== "create" && (<div class="sm:col-span-5"> - <label - class="block text-sm font-medium leading-6 text-gray-900" - for="internal-iban" - > - {i18n.str`Internal IBAN`} - </label> - <div class="mt-2"> - <input - type="text" - class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" - name="internal-iban" - id="internal-iban" - disabled={true} - value={form.payto_uri ?? ""} - /> - </div> - <p class="mt-2 text-sm text-gray-500" > - <i18n.Translate>international bank account number</i18n.Translate> - </p> - </div>)} + {purpose !== "create" && (<RenderPaytoDisabledField paytoURI={form.payto_uri} />)} <div class="sm:col-span-5"> <label @@ -264,6 +244,7 @@ export function AccountForm({ <div class="mt-2"> <input type="text" + ref={focus && purpose === "update" ? doAutoFocus : undefined} data-error={!!errors?.cashout_payto_uri && form.cashout_payto_uri !== undefined} class="block w-full disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" name="cashout" @@ -294,8 +275,9 @@ export function AccountForm({ } function initializeFromTemplate( + username: string | undefined, account: TalerCorebankApi.AccountData | undefined, -): WithIntermediate<TalerCorebankApi.AccountData> { +): WithIntermediate<AccountFormData> { const emptyAccount = { cashout_payto_uri: undefined, contact_data: undefined, @@ -314,8 +296,136 @@ function initializeFromTemplate( if (typeof initial.contact_data === "undefined") { initial.contact_data = emptyContact; } - // initial.contact_data.email; + const result: WithIntermediate<AccountFormData> = initial as any // FIXME: check types + result.username = username + return initial as any; } +function RenderPaytoDisabledField({ paytoURI }: { paytoURI: string | undefined }): VNode { + const { i18n } = useTranslationContext() + const payto = parsePaytoUri(paytoURI ?? ""); + if (payto?.isKnown) { + if (payto.targetType === "iban") { + const value = payto.iban; + return <div class="sm:col-span-5"> + <label + class="block text-sm font-medium leading-6 text-gray-900" + for="internal-iban" + > + {i18n.str`Internal IBAN`} + </label> + <div class="mt-2"> + <div class="flex justify-between"> + <input + type="text" + class="mr-4 w-full block-inline disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" + name="internal-iban" + id="internal-iban" + disabled={true} + value={value ?? ""} + /> + <CopyButton + class="p-2 rounded-full text-black shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 " + getContent={() => value ?? ""} + /> + </div> + </div> + <p class="mt-2 text-sm text-gray-500" > + <i18n.Translate>international bank account number</i18n.Translate> + </p> + </div> + } + if (payto.targetType === "x-taler-bank") { + const value = payto.account; + return <div class="sm:col-span-5"> + <label + class="block text-sm font-medium leading-6 text-gray-900" + for="account-id" + > + {i18n.str`Account ID`} + </label> + <div class="mt-2"> + <div class="flex justify-between"> + <input + type="text" + class="mr-4 w-full block-inline disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" + name="account-id" + id="account-id" + disabled={true} + value={value ?? ""} + /> + <CopyButton + class="p-2 rounded-full text-black shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 " + getContent={() => value ?? ""} + /> + </div> + </div> + <p class="mt-2 text-sm text-gray-500" > + <i18n.Translate>internal account id</i18n.Translate> + </p> + </div> + } + if (payto.targetType === "bitcoin") { + const value = payto.targetPath; + return <div class="sm:col-span-5"> + <label + class="block text-sm font-medium leading-6 text-gray-900" + for="account-id" + > + {i18n.str`Bitcoin address`} + </label> + <div class="mt-2"> + <div class="flex justify-between"> + <input + type="text" + class="mr-4 w-full block-inline disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" + name="account-id" + id="account-id" + disabled={true} + value={value ?? ""} + /> + <CopyButton + class="p-2 rounded-full text-black shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 " + getContent={() => value ?? ""} + /> + </div> + </div> + <p class="mt-2 text-sm text-gray-500" > + <i18n.Translate>bitcoin address</i18n.Translate> + </p> + </div> + } + assertUnreachable(payto) + } + + const value = paytoURI ?? "" + return <div class="sm:col-span-5"> + <label + class="block text-sm font-medium leading-6 text-gray-900" + for="internal-payto" + > + {i18n.str`Internal account`} + </label> + <div class="mt-2"> + <div class="flex justify-between"> + <input + type="text" + class="mr-4 w-full block-inline disabled:bg-gray-100 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" + name="internal-payto" + id="internal-payto" + disabled={true} + value={value ?? ""} + /> + <CopyButton + class="p-2 rounded-full text-black shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 " + getContent={() => value ?? ""} + /> + </div> + </div> + <p class="mt-2 text-sm text-gray-500" > + <i18n.Translate>generic payto URI</i18n.Translate> + </p> + </div> +} diff --git a/packages/demobank-ui/src/pages/admin/AccountList.tsx b/packages/demobank-ui/src/pages/admin/AccountList.tsx index 8a1e8294a..39b43b9b1 100644 --- a/packages/demobank-ui/src/pages/admin/AccountList.tsx +++ b/packages/demobank-ui/src/pages/admin/AccountList.tsx @@ -1,22 +1,26 @@ import { Amounts, TalerError } from "@gnu-taler/taler-util"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { VNode, h } from "preact"; +import { Fragment, VNode, h } from "preact"; import { ErrorLoading } from "../../components/ErrorLoading.js"; import { Loading } from "../../components/Loading.js"; import { useBusinessAccounts } from "../../hooks/circuit.js"; import { assertUnreachable } from "../HomePage.js"; import { RenderAmount } from "../PaytoWireTransferForm.js"; -import { AccountAction } from "./Home.js"; +import { useBankCoreApiContext } from "../../context/config.js"; interface Props { - onAction: (type: AccountAction, account: string) => void; - account: string | undefined; onCreateAccount: () => void; + + onShowAccountDetails: (aid: string) => void; + onRemoveAccount: (aid: string) => void; + onUpdateAccountPassword: (aid: string) => void; + onShowCashoutForAccount: (aid: string) => void; } -export function AccountList({ account, onAction, onCreateAccount }: Props): VNode { +export function AccountList({ onRemoveAccount, onShowAccountDetails, onUpdateAccountPassword, onShowCashoutForAccount, onCreateAccount }: Props): VNode { const result = useBusinessAccounts(); const { i18n } = useTranslationContext(); + const { config } = useBankCoreApiContext() if (!result) { return <Loading /> @@ -74,6 +78,7 @@ export function AccountList({ account, onAction, onCreateAccount }: Props): VNod 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"; @@ -83,7 +88,7 @@ export function AccountList({ account, onAction, onCreateAccount }: Props): VNod <a href="#" class="text-indigo-600 hover:text-indigo-900" onClick={(e) => { e.preventDefault(); - onAction("show-details", item.username) + onShowAccountDetails(item.username) }} > {item.username} @@ -94,7 +99,7 @@ export function AccountList({ account, onAction, onCreateAccount }: Props): VNod <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500"> {item.name} </td> - <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500"> + <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` ) : ( @@ -107,27 +112,34 @@ export function AccountList({ account, onAction, onCreateAccount }: Props): VNod <a href="#" class="text-indigo-600 hover:text-indigo-900" onClick={(e) => { e.preventDefault(); - onAction("update-password", item.username) + onUpdateAccountPassword(item.username) }} > change password </a> <br /> - <a href="#" class="text-indigo-600 hover:text-indigo-900" onClick={(e) => { - e.preventDefault(); - onAction("show-cashout", item.username) - }} - > - cashouts - </a> - <br /> - <a href="#" class="text-indigo-600 hover:text-indigo-900" onClick={(e) => { - e.preventDefault(); - onAction("remove-account", item.username) - }} - > - remove - </a> + {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(); + onRemoveAccount(item.username) + }} + > + remove + </a> + : undefined} </td> </tr> })} diff --git a/packages/demobank-ui/src/pages/admin/AdminHome.tsx b/packages/demobank-ui/src/pages/admin/AdminHome.tsx new file mode 100644 index 000000000..01f9f6dbd --- /dev/null +++ b/packages/demobank-ui/src/pages/admin/AdminHome.tsx @@ -0,0 +1,32 @@ +import { Fragment, VNode, h } from "preact"; +import { Transactions } from "../../components/Transactions/index.js"; +import { WireTransfer } from "./Account.js"; +import { AccountList } from "./AccountList.js"; + +/** + * Query account information and show QR code if there is pending withdrawal + */ +interface Props { + onRegister: () => void; + + onCreateAccount: () => void; + onShowAccountDetails: (aid: string) => void; + onRemoveAccount: (aid: string) => void; + onUpdateAccountPassword: (aid: string) => void; + onShowCashoutForAccount: (aid: string) => void; +} +export function AdminHome({ onCreateAccount, onRegister, onRemoveAccount, onShowAccountDetails, onShowCashoutForAccount, onUpdateAccountPassword }: Props): VNode { + return <Fragment> + <AccountList + onCreateAccount={onCreateAccount} + onRemoveAccount={onRemoveAccount} + onShowCashoutForAccount={onShowCashoutForAccount} + onShowAccountDetails={onShowAccountDetails} + onUpdateAccountPassword={onUpdateAccountPassword} + /> + + <WireTransfer onRegister={onRegister} /> + + <Transactions account="admin" /> + </Fragment> +}
\ No newline at end of file diff --git a/packages/demobank-ui/src/pages/admin/CashoutListForAccount.tsx b/packages/demobank-ui/src/pages/admin/CashoutListForAccount.tsx new file mode 100644 index 000000000..466dc1a4b --- /dev/null +++ b/packages/demobank-ui/src/pages/admin/CashoutListForAccount.tsx @@ -0,0 +1,47 @@ +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { Fragment, VNode, h } from "preact"; +import { Cashouts } from "../../components/Cashouts/index.js"; +import { useBackendState } from "../../hooks/backend.js"; +import { ProfileNavigation } from "../ProfileNavigation.js"; + +interface Props { + account: string, + onClose: () => void, + onSelected: (cid: string) => void +} + +export function CashoutListForAccount({ account, onSelected, onClose }: Props): VNode { + const { i18n } = useTranslationContext(); + + const { state: credentials } = useBackendState(); + const token = credentials.status !== "loggedIn" ? undefined : credentials.token + + const accountIsTheCurrentUser = credentials.status === "loggedIn" ? + credentials.username === account : false + + return <Fragment> + {accountIsTheCurrentUser ? + <ProfileNavigation current="cashouts" /> + : + <h1 class="text-base font-semibold leading-6 text-gray-900"> + <i18n.Translate>Cashout for account {account}</i18n.Translate> + </h1> + } + <Cashouts + account={account} + onSelected={onSelected} + /> + <p> + <input + class="pure-button" + type="submit" + value={i18n.str`Close`} + onClick={async (e) => { + e.preventDefault(); + onClose(); + }} + /> + </p> + </Fragment> +} + diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx index e10c3ad41..772ea6e84 100644 --- a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx +++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx @@ -1,21 +1,22 @@ import { HttpStatusCode, TalerCorebankApi, TalerError, TranslatedString } from "@gnu-taler/taler-util"; -import { RequestError, notify, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { RequestError, notify, notifyError, notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { buildRequestErrorMessage, withRuntimeErrorHandling } from "../../utils.js"; import { getRandomPassword } from "../rnd.js"; -import { AccountForm } from "./AccountForm.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 { mutate } from "swr"; +import { Attention } from "../../components/Attention.js"; export function CreateNewAccount({ onCancel, onCreateSuccess, }: { onCancel: () => void; - onCreateSuccess: (password: string) => void; + onCreateSuccess: () => void; }): VNode { const { i18n } = useTranslationContext(); // const { createAccount } = useAdminAccountAPI(); @@ -23,9 +24,7 @@ export function CreateNewAccount({ const token = credentials.status !== "loggedIn" ? undefined : credentials.token const { api } = useBankCoreApiContext(); - const [submitAccount, setSubmitAccount] = useState< - TalerCorebankApi.AccountData | undefined - >(); + const [submitAccount, setSubmitAccount] = useState<AccountFormData | undefined>(); async function doCreate() { if (!submitAccount || !token) return; @@ -35,14 +34,17 @@ export function CreateNewAccount({ challenge_contact_data: submitAccount.contact_data, internal_payto_uri: submitAccount.payto_uri, name: submitAccount.name, - username: "",//FIXME: not in account data + username: submitAccount.username,//FIXME: not in account data password: getRandomPassword(), }; const resp = await api.createAccount(token, account); if (resp.type === "ok") { mutate(() => true)// clean account list - onCreateSuccess(account.password); + notifyInfo( + i18n.str`Account created with password "${account.password}". The user must change the password on the next login.`, + ); + onCreateSuccess(); } else { switch (resp.case) { case "invalid-input": return notify({ @@ -75,6 +77,12 @@ export function CreateNewAccount({ }) } + if (!(credentials.status === "loggedIn" && credentials.isUserAdministrator)) { + return <Attention type="warning" title={i18n.str`Can't create accounts`} onClose={onCancel}> + <i18n.Translate>Only system admin can create accounts.</i18n.Translate> + </Attention> + } + return ( <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 bg-gray-100 my-4 px-4 pb-4 rounded-lg"> <div class="px-4 sm:px-0"> diff --git a/packages/demobank-ui/src/pages/admin/Home.tsx b/packages/demobank-ui/src/pages/admin/Home.tsx deleted file mode 100644 index 71ea8ce1b..000000000 --- a/packages/demobank-ui/src/pages/admin/Home.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import { notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser"; -import { Fragment, VNode, h } from "preact"; -import { useState } from "preact/hooks"; -import { Cashouts } from "../../components/Cashouts/index.js"; -import { Transactions } from "../../components/Transactions/index.js"; -import { ShowAccountDetails } from "../ShowAccountDetails.js"; -import { UpdateAccountPassword } from "../UpdateAccountPassword.js"; -import { ShowCashoutDetails } from "../business/Home.js"; -import { AdminAccount } from "./Account.js"; -import { AccountList } from "./AccountList.js"; -import { CreateNewAccount } from "./CreateNewAccount.js"; -import { RemoveAccount } from "./RemoveAccount.js"; - -/** - * Query account information and show QR code if there is pending withdrawal - */ -interface Props { - onRegister: () => void; -} -export type AccountAction = "show-details" | - "show-cashout" | - "update-password" | - "remove-account" | - "show-cashouts-details"; - -export function AdminHome({ onRegister }: Props): VNode { - const [action, setAction] = useState<{ - type: AccountAction, - account: string - } | undefined>() - - const [createAccount, setCreateAccount] = useState(false); - - const { i18n } = useTranslationContext(); - - if (action) { - switch (action.type) { - case "show-cashouts-details": return <ShowCashoutDetails - id={action.account} - onCancel={() => { - setAction(undefined); - }} - /> - case "show-cashout": return ( - <div> - <div> - <h1 class="nav welcome-text"> - <i18n.Translate>Cashout for account {action.account}</i18n.Translate> - </h1> - </div> - <Cashouts - account={action.account} - onSelected={(id) => { - setAction({ - type: "show-cashouts-details", - account: action.account - }); - }} - /> - <p> - <input - class="pure-button" - type="submit" - value={i18n.str`Close`} - onClick={async (e) => { - e.preventDefault(); - setAction(undefined); - }} - /> - </p> - </div> - ) - case "update-password": return <UpdateAccountPassword - account={action.account} - onUpdateSuccess={() => { - notifyInfo(i18n.str`Password changed`); - setAction(undefined); - }} - onCancel={() => { - setAction(undefined); - }} - /> - case "remove-account": return <RemoveAccount - account={action.account} - onUpdateSuccess={() => { - notifyInfo(i18n.str`Account removed`); - setAction(undefined); - }} - onCancel={() => { - setAction(undefined); - }} - /> - case "show-details": return <ShowAccountDetails - account={action.account} - onChangePassword={() => { - setAction({ - type: "update-password", - account: action.account, - }) - }} - onUpdateSuccess={() => { - notifyInfo(i18n.str`Account updated`); - setAction(undefined); - }} - onClear={() => { - setAction(undefined); - }} - /> - } - } - - if (createAccount) { - return ( - <CreateNewAccount - onCancel={() => setCreateAccount(false)} - onCreateSuccess={(password) => { - notifyInfo( - i18n.str`Account created with password "${password}". The user must change the password on the next login.`, - ); - setCreateAccount(false); - }} - /> - ); - } - - return ( - <Fragment> - - <AccountList - onCreateAccount={() => { - setCreateAccount(true); - }} - account={undefined} - onAction={(type, account) => setAction({ account, type })} - - /> - - <AdminAccount onRegister={onRegister} /> - - <Transactions account="admin" /> - </Fragment> - ); -}
\ No newline at end of file diff --git a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx index 9a212ebd0..88961c2cb 100644 --- a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx +++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx @@ -1,5 +1,5 @@ import { Amounts, HttpStatusCode, TalerError, TranslatedString } from "@gnu-taler/taler-util"; -import { HttpResponsePaginated, RequestError, notify, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { HttpResponsePaginated, RequestError, notify, notifyError, notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { Attention } from "../../components/Attention.js"; @@ -63,6 +63,7 @@ export function RemoveAccount({ await withRuntimeErrorHandling(i18n, async () => { const resp = await api.deleteAccount({ username: account, token }); if (resp.type === "ok") { + notifyInfo(i18n.str`Account removed`); onUpdateSuccess(); } else { switch (resp.case) { |