summaryrefslogtreecommitdiff
path: root/packages/demobank-ui/src/pages
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2023-12-04 09:43:03 -0300
committerSebastian <sebasjm@gmail.com>2023-12-04 09:43:23 -0300
commit8407a1d52e3a89f9c005f9820586d2d0a123c177 (patch)
tree2e467910a656f5b34758b4b4d2ceb9e8f41cfc00 /packages/demobank-ui/src/pages
parent8616c67de8de79a39298299eac9dc368749bfc7a (diff)
downloadwallet-core-8407a1d52e3a89f9c005f9820586d2d0a123c177.tar.gz
wallet-core-8407a1d52e3a89f9c005f9820586d2d0a123c177.tar.bz2
wallet-core-8407a1d52e3a89f9c005f9820586d2d0a123c177.zip
api sync, withdrawal info without password, account creation WIP
Diffstat (limited to 'packages/demobank-ui/src/pages')
-rw-r--r--packages/demobank-ui/src/pages/BankFrame.tsx11
-rw-r--r--packages/demobank-ui/src/pages/OperationState/state.ts9
-rw-r--r--packages/demobank-ui/src/pages/RegistrationPage.tsx24
-rw-r--r--packages/demobank-ui/src/pages/WireTransfer.tsx3
-rw-r--r--packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx153
-rw-r--r--packages/demobank-ui/src/pages/WithdrawalQRCode.tsx7
-rw-r--r--packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx15
-rw-r--r--packages/demobank-ui/src/pages/admin/AccountForm.tsx124
-rw-r--r--packages/demobank-ui/src/pages/admin/AccountList.tsx4
-rw-r--r--packages/demobank-ui/src/pages/admin/AdminHome.tsx3
-rw-r--r--packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx33
-rw-r--r--packages/demobank-ui/src/pages/admin/RemoveAccount.tsx3
-rw-r--r--packages/demobank-ui/src/pages/business/CreateCashout.tsx5
-rw-r--r--packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx5
14 files changed, 265 insertions, 134 deletions
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx
index 1762c6b56..0ac9ed8f1 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -132,17 +132,6 @@ export function BankFrame({
);
}
-function MaybeShowDebugInfo({ info }: { info: any }): VNode {
- const [settings] = usePreferences()
- if (settings.showDebugInfo) {
- return <pre class="whitespace-break-spaces ">
- {info}
- </pre>
- }
- return <Fragment />
-}
-
-
function WelcomeAccount({ account: accountName }: { account: string }): VNode {
const { i18n } = useTranslationContext();
return <a href="#/my-profile" class="underline underline-offset-2">
diff --git a/packages/demobank-ui/src/pages/OperationState/state.ts b/packages/demobank-ui/src/pages/OperationState/state.ts
index da924104a..57ede87a7 100644
--- a/packages/demobank-ui/src/pages/OperationState/state.ts
+++ b/packages/demobank-ui/src/pages/OperationState/state.ts
@@ -134,6 +134,7 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
if (result.type === "fail") {
switch (result.case) {
+ case "invalid-id":
case "not-found": {
return {
status: "aborted",
@@ -144,7 +145,7 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
},
}
}
- default: assertUnreachable(result.case)
+ default: assertUnreachable(result)
}
}
@@ -180,9 +181,9 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
status: "ready",
error: undefined,
uri: parsedUri,
- onClose: !creds ? (async () => {
- onClose();
- return undefined
+ onClose: !creds ? (async () => {
+ onClose();
+ return undefined
}) : doAbort,
}
}
diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx b/packages/demobank-ui/src/pages/RegistrationPage.tsx
index 2de6de373..e1d32002b 100644
--- a/packages/demobank-ui/src/pages/RegistrationPage.tsx
+++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx
@@ -96,7 +96,7 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
: undefined,
});
- async function doRegistrationAndLogin(name: string, username: string, password: string) {
+ async function doRegistrationAndLogin(name: string, username: string, password: string, onComplete: () => void) {
await handleError(async () => {
const creationResponse = await api.createAccount("" as AccessToken, { name, username, password });
if (creationResponse.type === "fail") {
@@ -137,6 +137,12 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
description: creationResponse.detail.hint as TranslatedString,
debug: creationResponse.detail,
})
+ case "user-cant-set-debt": return notify({
+ type: "error",
+ title: i18n.str`Only admin is allow to set debt limit.`,
+ description: creationResponse.detail.hint as TranslatedString,
+ debug: creationResponse.detail,
+ })
default: assertUnreachable(creationResponse)
}
}
@@ -165,16 +171,18 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
default: assertUnreachable(resp)
}
}
+ onComplete()
})
}
async function doRegistrationStep() {
if (!username || !password || !name) return;
- await doRegistrationAndLogin(name, username, password)
- setUsername(undefined);
- setPassword(undefined);
- setRepeatPassword(undefined);
- onComplete();
+ await doRegistrationAndLogin(name, username, password, () => {
+ setUsername(undefined);
+ setPassword(undefined);
+ setRepeatPassword(undefined);
+ onComplete();
+ })
}
async function doRandomRegistration(tries: number = 3) {
@@ -183,8 +191,8 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
const pass = settings.simplePasswordForRandomAccounts ? "123" : getRandomPassword();
const username = `_${user.first}-${user.second}_`
const name = `${user.first}, ${user.second}`
- await doRegistrationAndLogin(name, username, pass)
- onComplete();
+ await doRegistrationAndLogin(name, username, pass, onComplete)
+
}
return (
diff --git a/packages/demobank-ui/src/pages/WireTransfer.tsx b/packages/demobank-ui/src/pages/WireTransfer.tsx
index 5e6081b11..a68c085c9 100644
--- a/packages/demobank-ui/src/pages/WireTransfer.tsx
+++ b/packages/demobank-ui/src/pages/WireTransfer.tsx
@@ -8,6 +8,7 @@ import { assertUnreachable } from "./WithdrawalOperationPage.js";
import { LoginForm } from "./LoginForm.js";
import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js";
import { useBackendState } from "../hooks/backend.js";
+import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js";
export function WireTransfer({ toAccount, onRegister, onCancel, onSuccess }: { onSuccess?: () => void; toAccount?: string, onCancel?: () => void, onRegister?: () => void }): VNode {
const { i18n } = useTranslationContext();
@@ -19,7 +20,7 @@ export function WireTransfer({ toAccount, onRegister, onCancel, onSuccess }: { o
return <Loading />
}
if (result instanceof TalerError) {
- return <ErrorLoading error={result} />
+ return <ErrorLoadingWithDebug error={result} />
}
if (result.type === "fail") {
switch (result.case) {
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index e21c0917b..f8913f0ec 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -25,13 +25,14 @@ import {
WithdrawUriResult
} from "@gnu-taler/taler-util";
import {
+ Attention,
ErrorLoading,
Loading,
notifyInfo,
useLocalNotification,
useTranslationContext
} from "@gnu-taler/web-util/browser";
-import { Fragment, VNode, h } from "preact";
+import { ComponentChildren, Fragment, VNode, h } from "preact";
import { useMemo, useState } from "preact/hooks";
import { mutate } from "swr";
import { ShowInputErrorLabel } from "@gnu-taler/web-util/browser";
@@ -45,6 +46,7 @@ import { useBackendState } from "../hooks/backend.js";
import { useWithdrawalDetails } from "../hooks/access.js";
import { OperationState } from "./OperationState/index.js";
import { OperationNotFound } from "./WithdrawalQRCode.js";
+import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js";
const logger = new Logger("WithdrawalConfirmationQuestion");
@@ -54,6 +56,7 @@ interface Props {
details: {
account: PaytoUri,
reserve: string,
+ username: string,
amount: AmountJson,
}
}
@@ -75,15 +78,16 @@ export function WithdrawalConfirmationQuestion({
return <Loading />
}
if (withdrawalInfo instanceof TalerError) {
- return <ErrorLoading error={withdrawalInfo} />
+ return <ErrorLoadingWithDebug error={withdrawalInfo} />
}
if (withdrawalInfo.type === "fail") {
- switch(withdrawalInfo.case) {
- case "not-found": return <OperationNotFound onClose={onAborted} />
- default: assertUnreachable(withdrawalInfo.case)
+ switch (withdrawalInfo.case) {
+ case "not-found": return <OperationNotFound onClose={onAborted} />
+ case "invalid-id": return <OperationNotFound onClose={onAborted} />
+ default: assertUnreachable(withdrawalInfo)
}
}
-
+
const captchaNumbers = useMemo(() => {
return {
a: Math.floor(Math.random() * 10),
@@ -200,67 +204,70 @@ export function WithdrawalConfirmationQuestion({
</h3>
<div class="mt-3 text-sm leading-6">
- <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">
- <h2 class="text-base font-semibold text-gray-900"><i18n.Translate>Answer the next question to authorize the wire transfer.</i18n.Translate></h2>
- </div>
- <form
- class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2"
- autoCapitalize="none"
- autoCorrect="off"
- onSubmit={e => {
- e.preventDefault()
- }}
- >
- <div class="px-4 py-6 sm:p-8">
- <label for="withdraw-amount">{i18n.str`What is`}&nbsp;
- <em>
- {captchaNumbers.a}&nbsp;+&nbsp;{captchaNumbers.b}
- </em>
- ?
- </label>
- <div class="mt-2">
- <div class="relative rounded-md shadow-sm">
- <input
- type="text"
- // class="block w-full rounded-md border-0 py-1.5 pl-16 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
- aria-describedby="answer"
- autoFocus
- class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
- value={captchaAnswer ?? ""}
- required
+ <ShouldBeSameUser username={details.username}>
+ <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">
+ <h2 class="text-base font-semibold text-gray-900"><i18n.Translate>Answer the next question to authorize the wire transfer.</i18n.Translate></h2>
+ </div>
+ <form
+ class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2"
+ autoCapitalize="none"
+ autoCorrect="off"
+ onSubmit={e => {
+ e.preventDefault()
+ }}
+ >
+ <div class="px-4 py-6 sm:p-8">
+ <label for="withdraw-amount">{i18n.str`What is`}&nbsp;
+ <em>
+ {captchaNumbers.a}&nbsp;+&nbsp;{captchaNumbers.b}
+ </em>
+ ?
+ </label>
+
+ <div class="mt-2">
+ <div class="relative rounded-md shadow-sm">
+ <input
+ type="text"
+ // class="block w-full rounded-md border-0 py-1.5 pl-16 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+ aria-describedby="answer"
+ autoFocus
+ class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+ value={captchaAnswer ?? ""}
+ required
- name="answer"
- id="answer"
- autocomplete="off"
- onChange={(e): void => {
- setCaptchaAnswer(e.currentTarget.value)
- }}
- />
+ name="answer"
+ id="answer"
+ autocomplete="off"
+ onChange={(e): void => {
+ setCaptchaAnswer(e.currentTarget.value)
+ }}
+ />
+ </div>
+ <ShowInputErrorLabel message={errors?.answer} isDirty={captchaAnswer !== undefined} />
</div>
- <ShowInputErrorLabel message={errors?.answer} isDirty={captchaAnswer !== undefined} />
</div>
- </div>
- <div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8">
- <button type="button" class="text-sm font-semibold leading-6 text-gray-900"
- onClick={doCancel}
- >
- <i18n.Translate>Cancel</i18n.Translate></button>
- <button type="submit"
- class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 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"
- disabled={!!errors}
- onClick={(e) => {
- e.preventDefault()
- doTransfer()
- }}
- >
- <i18n.Translate>Transfer</i18n.Translate>
- </button>
- </div>
+ <div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8">
+ <button type="button" class="text-sm font-semibold leading-6 text-gray-900"
+ onClick={doCancel}
+ >
+ <i18n.Translate>Cancel</i18n.Translate></button>
+ <button type="submit"
+ class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 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"
+ disabled={!!errors}
+ onClick={(e) => {
+ e.preventDefault()
+ doTransfer()
+ }}
+ >
+ <i18n.Translate>Transfer</i18n.Translate>
+ </button>
+ </div>
- </form>
- </div>
+ </form>
+ </div>
+ </ShouldBeSameUser>
</div>
<div class="px-4 mt-4 ">
<div class="w-full">
@@ -325,6 +332,26 @@ export function WithdrawalConfirmationQuestion({
</div>
</div>
- </Fragment>
+ </Fragment >
);
}
+
+export function ShouldBeSameUser({ username, children }: { username: string, children: ComponentChildren }): VNode {
+ const { state: credentials } = useBackendState();
+ const { i18n } = useTranslationContext()
+ if (credentials.status === "loggedOut") {
+ return <Attention type="info" title={i18n.str`Authentication required`}>
+ <p>You should login as "{username}"</p>
+ </Attention>
+ }
+ if (credentials.username !== username) {
+ return <Attention type="warning" title={i18n.str`This operation was created with other username`}>
+ <p>
+ You can switch to account "{username}" and complete the operation.
+ </p>
+ </Attention>
+ }
+ return <Fragment>
+ {children}
+ </Fragment>
+} \ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 0c3d83c3b..f07790493 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -30,6 +30,7 @@ import { useWithdrawalDetails } from "../hooks/access.js";
import { QrCodeSection } from "./QrCodeSection.js";
import { WithdrawalConfirmationQuestion } from "./WithdrawalConfirmationQuestion.js";
import { assertUnreachable } from "./WithdrawalOperationPage.js";
+import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js";
const logger = new Logger("WithdrawalQRCode");
@@ -53,12 +54,13 @@ export function WithdrawalQRCode({
return <Loading />
}
if (result instanceof TalerError) {
- return <ErrorLoading error={result} />
+ return <ErrorLoadingWithDebug error={result} />
}
if (result.type === "fail") {
switch (result.case) {
+ case "invalid-id":
case "not-found": return <OperationNotFound onClose={onClose} />
- default: assertUnreachable(result.case)
+ default: assertUnreachable(result)
}
}
@@ -159,6 +161,7 @@ export function WithdrawalQRCode({
<WithdrawalConfirmationQuestion
withdrawUri={withdrawUri}
details={{
+ username: data.username,
account,
reserve: data.selected_reserve_pub,
amount: Amounts.parseOrThrow(data.amount)
diff --git a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
index b5579c199..06a88c1c6 100644
--- a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
+++ b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx
@@ -13,6 +13,7 @@ import { ProfileNavigation } from "../ProfileNavigation.js";
import { assertUnreachable } from "../WithdrawalOperationPage.js";
import { AccountForm } from "../admin/AccountForm.js";
import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
+import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js";
export function ShowAccountDetails({
account,
@@ -31,7 +32,7 @@ export function ShowAccountDetails({
credentials.username === account : false
const [update, setUpdate] = useState(false);
- const [submitAccount, setSubmitAccount] = useState<TalerCorebankApi.AccountData | undefined>();
+ const [submitAccount, setSubmitAccount] = useState<TalerCorebankApi.AccountReconfiguration | undefined>();
const [notification, notify, handleError] = useLocalNotification()
const result = useAccountDetails(account);
@@ -39,7 +40,7 @@ export function ShowAccountDetails({
return <Loading />
}
if (result instanceof TalerError) {
- return <ErrorLoading error={result} />
+ return <ErrorLoadingWithDebug error={result} />
}
if (result.type === "fail") {
switch (result.case) {
@@ -55,15 +56,7 @@ export function ShowAccountDetails({
const resp = await api.updateAccount({
token: creds.token,
username: account,
- }, {
- cashout_payto_uri: submitAccount.cashout_payto_uri,
- challenge_contact_data: undefinedIfEmpty({
- email: submitAccount.contact_data?.email,
- phone: submitAccount.contact_data?.phone,
- }),
- is_taler_exchange: false,
- name: submitAccount.name,
- });
+ }, submitAccount);
if (resp.type === "ok") {
notifyInfo(i18n.str`Account updated`);
diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
index c2afb195a..c8abde74b 100644
--- a/packages/demobank-ui/src/pages/admin/AccountForm.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
@@ -1,10 +1,13 @@
-import { PaytoString, TalerCorebankApi, buildPayto, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
+import { AmountString, Amounts, PaytoString, TalerCorebankApi, buildPayto, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
import { CopyButton, ShowInputErrorLabel, useTranslationContext } from "@gnu-taler/web-util/browser";
import { ComponentChildren, Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { PartialButDefined, RecursivePartial, WithIntermediate, undefinedIfEmpty, validateIBAN } from "../../utils.js";
-import { doAutoFocus } from "../PaytoWireTransferForm.js";
+import { InputAmount, doAutoFocus } from "../PaytoWireTransferForm.js";
import { assertUnreachable } from "../WithdrawalOperationPage.js";
+import { useBackendContext } from "../../context/backend.js";
+import { useBankCoreApiContext } from "../../context/config.js";
+import { getRandomPassword } from "../rnd.js";
const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/;
const EMAIL_REGEX =
@@ -13,6 +16,11 @@ const REGEX_JUST_NUMBERS_REGEX = /^\+[0-9 ]*$/;
export type AccountFormData = TalerCorebankApi.AccountData & { username: string }
+type MM = {
+ "create": (a: TalerCorebankApi.RegisterAccountRequest | undefined) => void,
+ "update": (a: TalerCorebankApi.AccountReconfiguration | undefined) => void,
+ "show": undefined
+}
/**
* Create valid account object to update or create
* Take template as initial values for the form
@@ -21,7 +29,7 @@ export type AccountFormData = TalerCorebankApi.AccountData & { username: string
* @param param0
* @returns
*/
-export function AccountForm({
+export function AccountForm<T extends keyof MM>({
template,
username,
purpose,
@@ -34,9 +42,10 @@ export function AccountForm({
children: ComponentChildren,
username?: string,
noCashout?: boolean,
+ admin?: boolean,
template: TalerCorebankApi.AccountData | undefined;
- onChange: (a: AccountFormData | undefined) => void;
- purpose: "create" | "update" | "show";
+ onChange: MM[T];
+ purpose: T;
}): VNode {
const initial = initializeFromTemplate(username, template);
const [form, setForm] = useState(initial);
@@ -45,11 +54,20 @@ export function AccountForm({
>(undefined);
const { i18n } = useTranslationContext();
+ const { config } = useBankCoreApiContext()
+ const [debitAmount, setDebitAmount] = useState<string>()
+
+ const [isExchange, setIsExchange] = useState<boolean>();
+ const [isPublic, setIsPublic] = useState<boolean>();
+
function updateForm(newForm: typeof initial): void {
const parsed = !newForm.cashout_payto_uri
? undefined
: buildPayto("iban", newForm.cashout_payto_uri, undefined);;
+ const trimmedAmountStr = debitAmount?.trim();
+ const parsedAmount = Amounts.parse(`${config.currency}:${trimmedAmountStr}`);
+
const errors = undefinedIfEmpty<RecursivePartial<typeof initial>>({
cashout_payto_uri: (!newForm.cashout_payto_uri
? undefined
@@ -74,20 +92,52 @@ export function AccountForm({
? i18n.str`phone number can't have other than numbers`
: undefined,
}),
+ debit_threshold: !trimmedAmountStr
+ ? i18n.str`required`
+ : !parsedAmount
+ ? i18n.str`not valid`
+ : Amounts.isZero(parsedAmount)
+ ? i18n.str`should be greater than 0`
+ : undefined,
name: !newForm.name ? i18n.str`required` : undefined,
username: !newForm.username ? i18n.str`required` : undefined,
});
setErrors(errors);
setForm(newForm);
+ if (!onChange) return;
+
if (errors) {
onChange(undefined)
} else {
- const cashout = !newForm.cashout_payto_uri? undefined :buildPayto("iban", newForm.cashout_payto_uri, undefined)
- const account: AccountFormData = {
- ...newForm as any,
- cashout_payto_uri: !cashout ? undefined : stringifyPaytoUri(cashout)
+ const cashout = !newForm.cashout_payto_uri ? undefined : buildPayto("iban", newForm.cashout_payto_uri, undefined)
+ const cashoutURI = !cashout ? undefined : stringifyPaytoUri(cashout)
+ switch (purpose) {
+ case "create": {
+ const result: TalerCorebankApi.RegisterAccountRequest = {
+ cashout_payto_uri: cashoutURI,
+ name: newForm.name!,
+ password: getRandomPassword(),
+ username: newForm.username!,
+ challenge_contact_data: undefinedIfEmpty({
+ email: newForm.contact_data?.email,
+ phone: newForm.contact_data?.phone,
+ }),
+ debit_threshold: newForm.debit_threshold as AmountString,
+ // ,
+ // internal_payto_uri
+ }
+ onChange(result)
+ return;
+ }
+ case "update": {
+ const result: TalerCorebankApi.AccountReconfiguration = {
+ cashout_payto_uri: cashoutURI
+ }
+ onChange(result as any)
+ return;
+ }
+ case "show":
}
- onChange(account);
}
}
@@ -181,7 +231,6 @@ export function AccountForm({
for="email"
>
{i18n.str`Email`}
- {purpose === "create" && <b style={{ color: "red" }}> *</b>}
</label>
<div class="mt-2">
<input
@@ -216,7 +265,6 @@ export function AccountForm({
for="phone"
>
{i18n.str`Phone`}
- {purpose === "create" && <b style={{ color: "red" }}> *</b>}
</label>
<div class="mt-2">
<input
@@ -246,7 +294,6 @@ export function AccountForm({
</div>
</div>
-
{!noCashout &&
<div class="sm:col-span-5">
<label
@@ -254,7 +301,6 @@ export function AccountForm({
for="cashout"
>
{i18n.str`Cashout IBAN`}
- {purpose !== "show" && <b style={{ color: "red" }}> *</b>}
</label>
<div class="mt-2">
<input
@@ -269,7 +315,7 @@ export function AccountForm({
onChange={(e) => {
form.cashout_payto_uri = e.currentTarget.value as PaytoString;
if (!form.cashout_payto_uri) {
- form.cashout_payto_uri= undefined
+ form.cashout_payto_uri = undefined
}
updateForm(structuredClone(form));
}}
@@ -286,6 +332,54 @@ export function AccountForm({
</div>
}
+ <div class="sm:col-span-5">
+ <label for="debit" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Max debt`}</label>
+ <InputAmount
+ name="debit"
+ left
+ currency={config.currency}
+ value={debitAmount ?? ""}
+ onChange={(e) => {
+ setDebitAmount(e);
+ }}
+ />
+ <ShowInputErrorLabel
+ message={errors?.debit_threshold ? String(errors?.debit_threshold) : undefined}
+ isDirty={form.debit_threshold !== undefined}
+ />
+ <p class="mt-2 text-sm text-gray-500" >allow user debt</p>
+ </div>
+
+ <div class="sm:col-span-5">
+ <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">
+ <i18n.Translate>Is an exchange</i18n.Translate>
+ </span>
+ </span>
+ <button type="button" data-enabled={!!isExchange} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 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" aria-labelledby="availability-label" aria-describedby="availability-description"
+
+ onClick={() => setIsExchange(!isExchange)}>
+ <span aria-hidden="true" data-enabled={!!isExchange} class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
+ </button>
+ </div>
+ </div>
+
+ <div class="sm:col-span-5">
+ <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">
+ <i18n.Translate>Is public</i18n.Translate>
+ </span>
+ </span>
+ <button type="button" data-enabled={!!isPublic} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 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" aria-labelledby="availability-label" aria-describedby="availability-description"
+
+ onClick={() => setIsPublic(!isPublic)}>
+ <span aria-hidden="true" data-enabled={!!isPublic} class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
+ </button>
+ </div>
+ </div>
+
</div>
</div>
{children}
diff --git a/packages/demobank-ui/src/pages/admin/AccountList.tsx b/packages/demobank-ui/src/pages/admin/AccountList.tsx
index 8350cefd9..71f18a3db 100644
--- a/packages/demobank-ui/src/pages/admin/AccountList.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountList.tsx
@@ -7,6 +7,8 @@ import { useBankCoreApiContext } from "../../context/config.js";
import { useBusinessAccounts } from "../../hooks/circuit.js";
import { RenderAmount } from "../PaytoWireTransferForm.js";
import { assertUnreachable } from "../WithdrawalOperationPage.js";
+import { usePreferences } from "../../hooks/preferences.js";
+import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js";
interface Props {
onCreateAccount: () => void;
@@ -26,7 +28,7 @@ export function AccountList({ onRemoveAccount, onShowAccountDetails, onUpdateAcc
return <Loading />
}
if (result instanceof TalerError) {
- return <ErrorLoading error={result} />
+ return <ErrorLoadingWithDebug error={result} />
}
if (result.data.type === "fail") {
switch (result.data.case) {
diff --git a/packages/demobank-ui/src/pages/admin/AdminHome.tsx b/packages/demobank-ui/src/pages/admin/AdminHome.tsx
index b1acb8160..28078bc09 100644
--- a/packages/demobank-ui/src/pages/admin/AdminHome.tsx
+++ b/packages/demobank-ui/src/pages/admin/AdminHome.tsx
@@ -9,6 +9,7 @@ import { WireTransfer } from "../WireTransfer.js";
import { AccountList } from "./AccountList.js";
import { useBankCoreApiContext } from "../../context/config.js";
import { format, getDate, getHours, getMonth, getYear, setDate, setHours, setMonth, setYear, sub } from "date-fns";
+import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js";
/**
@@ -90,7 +91,7 @@ function Metrics(): VNode {
const resp = useLastMonitorInfo(params.current, params.previous, metricType);
if (!resp) return <Fragment />;
if (resp instanceof TalerError) {
- return <ErrorLoading error={resp} />
+ return <ErrorLoadingWithDebug error={resp} />
}
if (resp.current.type !== "ok" || resp.previous.type !== "ok") {
return <Fragment />
diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
index 2b0be6056..6ff723a31 100644
--- a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
@@ -17,31 +17,33 @@ export function CreateNewAccount({
onCreateSuccess: () => void;
}): VNode {
const { i18n } = useTranslationContext();
- // const { createAccount } = useAdminAccountAPI();
const { state: credentials } = useBackendState()
const token = credentials.status !== "loggedIn" ? undefined : credentials.token
const { api } = useBankCoreApiContext();
- const [submitAccount, setSubmitAccount] = useState<AccountFormData | undefined>();
+ const [submitAccount, setSubmitAccount] = useState<TalerCorebankApi.RegisterAccountRequest | undefined>();
const [notification, notify, handleError] = useLocalNotification()
async function doCreate() {
if (!submitAccount || !token) return;
await handleError(async () => {
- const account: TalerCorebankApi.RegisterAccountRequest = {
- cashout_payto_uri: submitAccount.cashout_payto_uri,
- challenge_contact_data: submitAccount.contact_data,
- internal_payto_uri: submitAccount.payto_uri,
- name: submitAccount.name,
- username: submitAccount.username,//FIXME: not in account data
- password: getRandomPassword(),
- };
+ // const account: TalerCorebankApi.RegisterAccountRequest = {
+ // cashout_payto_uri: submitAccount.cashout_payto_uri,
+ // challenge_contact_data: submitAccount.challenge_contact_data,
+ // internal_payto_uri: submitAccount.internal_payto_uri,
+ // debit_threshold: submitAccount.debit_threshold,
+ // is_public: submitAccount.is_public,
+ // is_taler_exchange: submitAccount.is_taler_exchange,
+ // name: submitAccount.name,
+ // username: submitAccount.username,
+ // password: getRandomPassword(),
+ // };
- const resp = await api.createAccount(token, account);
+ const resp = await api.createAccount(token, submitAccount);
if (resp.type === "ok") {
mutate(() => true)// clean account list
notifyInfo(
- i18n.str`Account created with password "${account.password}". The user must change the password on the next login.`,
+ i18n.str`Account created with password "${submitAccount.password}". The user must change the password on the next login.`,
);
onCreateSuccess();
} else {
@@ -82,6 +84,12 @@ export function CreateNewAccount({
description: resp.detail.hint as TranslatedString,
debug: resp.detail,
})
+ case "user-cant-set-debt": return notify({
+ type: "error",
+ title: i18n.str`Only admin is allow to set debt limit.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
default: assertUnreachable(resp)
}
}
@@ -105,6 +113,7 @@ export function CreateNewAccount({
</div>
<AccountForm
template={undefined}
+ admin
purpose="create"
onChange={(a) => {
setSubmitAccount(a);
diff --git a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
index 051a86ad6..5ee887128 100644
--- a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
@@ -9,6 +9,7 @@ import { undefinedIfEmpty } from "../../utils.js";
import { LoginForm } from "../LoginForm.js";
import { doAutoFocus } from "../PaytoWireTransferForm.js";
import { assertUnreachable } from "../WithdrawalOperationPage.js";
+import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js";
export function RemoveAccount({
account,
@@ -34,7 +35,7 @@ export function RemoveAccount({
return <Loading />
}
if (result instanceof TalerError) {
- return <ErrorLoading error={result} />
+ return <ErrorLoadingWithDebug error={result} />
}
if (result.type === "fail") {
switch (result.case) {
diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
index 67fc47e60..b2ff41e63 100644
--- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx
+++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
@@ -49,6 +49,7 @@ import { LoginForm } from "../LoginForm.js";
import { InputAmount, RenderAmount, doAutoFocus } from "../PaytoWireTransferForm.js";
import { assertUnreachable } from "../WithdrawalOperationPage.js";
import { getRandomPassword, getRandomUsername } from "../rnd.js";
+import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js";
interface Props {
account: string;
@@ -97,7 +98,7 @@ export function CreateCashout({
return <Loading />
}
if (resultAccount instanceof TalerError) {
- return <ErrorLoading error={resultAccount} />
+ return <ErrorLoadingWithDebug error={resultAccount} />
}
if (resultAccount.type === "fail") {
switch (resultAccount.case) {
@@ -111,7 +112,7 @@ export function CreateCashout({
}
if (info instanceof TalerError) {
- return <ErrorLoading error={info} />
+ return <ErrorLoadingWithDebug error={info} />
}
const conversionInfo = info.body.conversion_rate
diff --git a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
index 6fd9eb18c..fcbf0c408 100644
--- a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
+++ b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
@@ -42,6 +42,7 @@ import {
import { assertUnreachable } from "../WithdrawalOperationPage.js";
import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";
import { RenderAmount } from "../PaytoWireTransferForm.js";
+import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js";
interface Props {
id: string;
@@ -70,7 +71,7 @@ export function ShowCashoutDetails({
return <Loading />
}
if (result instanceof TalerError) {
- return <ErrorLoading error={result} />
+ return <ErrorLoadingWithDebug error={result} />
}
if (result.type === "fail") {
switch (result.case) {
@@ -86,7 +87,7 @@ export function ShowCashoutDetails({
}
if (info instanceof TalerError) {
- return <ErrorLoading error={info} />
+ return <ErrorLoadingWithDebug error={info} />
}
const errors = undefinedIfEmpty({