aboutsummaryrefslogtreecommitdiff
path: root/packages/demobank-ui/src/pages/admin
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2023-10-21 20:25:38 -0300
committerSebastian <sebasjm@gmail.com>2023-10-21 20:25:50 -0300
commit2ac73949e7cb8de44e56f2fecae617efab15671e (patch)
tree144a97d71bc9fa964675ef0cc764087ceb14e8eb /packages/demobank-ui/src/pages/admin
parent4b98b693d696d90f30f0a6546b0e1f4bc181a5f2 (diff)
downloadwallet-core-2ac73949e7cb8de44e56f2fecae617efab15671e.tar.xz
more ui
Diffstat (limited to 'packages/demobank-ui/src/pages/admin')
-rw-r--r--packages/demobank-ui/src/pages/admin/Account.tsx10
-rw-r--r--packages/demobank-ui/src/pages/admin/AccountForm.tsx182
-rw-r--r--packages/demobank-ui/src/pages/admin/AccountList.tsx58
-rw-r--r--packages/demobank-ui/src/pages/admin/AdminHome.tsx32
-rw-r--r--packages/demobank-ui/src/pages/admin/CashoutListForAccount.tsx47
-rw-r--r--packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx24
-rw-r--r--packages/demobank-ui/src/pages/admin/Home.tsx143
-rw-r--r--packages/demobank-ui/src/pages/admin/RemoveAccount.tsx3
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) {