summaryrefslogtreecommitdiff
path: root/packages/demobank-ui
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2023-10-19 15:10:18 -0300
committerSebastian <sebasjm@gmail.com>2023-10-19 15:10:18 -0300
commit7582855e2723e11de25f10b6d5ba8a0757616765 (patch)
tree320f30c7202c88ba63542b93de1cbda533a73356 /packages/demobank-ui
parent9e925a2f56677600973c4659f82776a6a56339bb (diff)
downloadwallet-core-7582855e2723e11de25f10b6d5ba8a0757616765.tar.gz
wallet-core-7582855e2723e11de25f10b6d5ba8a0757616765.tar.bz2
wallet-core-7582855e2723e11de25f10b6d5ba8a0757616765.zip
some ui fixes
Diffstat (limited to 'packages/demobank-ui')
-rw-r--r--packages/demobank-ui/src/components/Transactions/views.tsx16
-rw-r--r--packages/demobank-ui/src/hooks/access.ts22
-rw-r--r--packages/demobank-ui/src/hooks/circuit.ts15
-rw-r--r--packages/demobank-ui/src/i18n/strings.ts2
-rw-r--r--packages/demobank-ui/src/pages/LoginForm.tsx69
-rw-r--r--packages/demobank-ui/src/pages/OperationState/state.ts58
-rw-r--r--packages/demobank-ui/src/pages/PaymentOptions.tsx52
-rw-r--r--packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx54
-rw-r--r--packages/demobank-ui/src/pages/QrCodeSection.tsx28
-rw-r--r--packages/demobank-ui/src/pages/RegistrationPage.tsx163
-rw-r--r--packages/demobank-ui/src/pages/ShowAccountDetails.tsx77
-rw-r--r--packages/demobank-ui/src/pages/UpdateAccountPassword.tsx36
-rw-r--r--packages/demobank-ui/src/pages/WalletWithdrawForm.tsx18
-rw-r--r--packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx47
-rw-r--r--packages/demobank-ui/src/pages/WithdrawalQRCode.tsx16
-rw-r--r--packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx17
-rw-r--r--packages/demobank-ui/src/pages/admin/RemoveAccount.tsx15
-rw-r--r--packages/demobank-ui/src/pages/business/Home.tsx93
-rw-r--r--packages/demobank-ui/src/utils.ts27
19 files changed, 306 insertions, 519 deletions
diff --git a/packages/demobank-ui/src/components/Transactions/views.tsx b/packages/demobank-ui/src/components/Transactions/views.tsx
index 404e25619..5cdb47a0c 100644
--- a/packages/demobank-ui/src/components/Transactions/views.tsx
+++ b/packages/demobank-ui/src/components/Transactions/views.tsx
@@ -17,7 +17,7 @@
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { format } from "date-fns";
import { Fragment, h, VNode } from "preact";
-import { RenderAmount } from "../../pages/PaytoWireTransferForm.js";
+import { doAutoFocus, RenderAmount } from "../../pages/PaytoWireTransferForm.js";
import { State } from "./index.js";
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
@@ -57,7 +57,7 @@ export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode
<th scope="col" class="pl-2 py-3.5 text-left text-sm font-semibold text-gray-900 ">{i18n.str`Date`}</th>
<th scope="col" class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm font-semibold text-gray-900 ">{i18n.str`Amount`}</th>
<th scope="col" class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm font-semibold text-gray-900 ">{i18n.str`Counterpart`}</th>
- <th scope="col" class="pl-2 py-3.5 text-left text-sm font-semibold text-gray-900 ">{i18n.str`Subject`}</th>
+ <th scope="col" class="hidden sm:table-cell pl-2 py-3.5 text-left text-sm font-semibold text-gray-900 ">{i18n.str`Subject`}</th>
</tr>
</thead>
<tbody>
@@ -70,10 +70,7 @@ export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode
</tr>
{txs.map(item => {
const time = item.when.t_ms === "never" ? "" : format(item.when.t_ms, "HH:mm:ss")
- const amount = <Fragment>
- { }
- </Fragment>
- return (<tr key={idx}>
+ return (<tr key={idx} class="border-b border-gray-200 last:border-none">
<td class="relative py-2 pl-2 pr-2 text-sm ">
<div class="font-medium text-gray-900">{time}</div>
<dl class="font-normal sm:hidden">
@@ -91,6 +88,11 @@ export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode
<dd class="mt-1 truncate text-gray-500 sm:hidden">
{item.negative ? i18n.str`to` : i18n.str`from`} {item.counterpart}
</dd>
+ <dd class="mt-1 text-gray-500 sm:hidden" >
+ <pre class="break-words w-56 whitespace-break-spaces p-2 rounded-md mx-auto my-2 bg-gray-100">
+ {item.subject}
+ </pre>
+ </dd>
</dl>
</td>
<td data-negative={item.negative ? "true" : "false"}
@@ -101,7 +103,7 @@ export function ReadyView({ transactions, onNext, onPrev }: State.Ready): VNode
)}
</td>
<td class="hidden sm:table-cell px-3 py-3.5 text-sm text-gray-500">{item.counterpart}</td>
- <td class="px-3 py-3.5 text-sm text-gray-500 break-all min-w-md">{item.subject}</td>
+ <td class="hidden sm:table-cell px-3 py-3.5 text-sm text-gray-500 break-all min-w-md">{item.subject}</td>
</tr>)
})}
</Fragment>
diff --git a/packages/demobank-ui/src/hooks/access.ts b/packages/demobank-ui/src/hooks/access.ts
index 2533d32fe..7023b8803 100644
--- a/packages/demobank-ui/src/hooks/access.ts
+++ b/packages/demobank-ui/src/hooks/access.ts
@@ -38,7 +38,8 @@ export function useAccountDetails(account: string) {
return await api.getAccount({ username, token })
}
const token = credentials.status !== "loggedIn" ? undefined : credentials.token
- const { data, error } = useSWR<TalerCoreBankResultByMethod<"getAccount">, TalerHttpError>([account, token], fetcher, {
+ const { data, error } = useSWR<TalerCoreBankResultByMethod<"getAccount">, TalerHttpError>(
+ [account, token, "getAccount"], fetcher, {
refreshInterval: 0,
refreshWhenHidden: false,
revalidateOnFocus: false,
@@ -65,7 +66,7 @@ export function useWithdrawalDetails(wid: string) {
}
const { data, error } = useSWR<TalerCoreBankResultByMethod<"getWithdrawalById">, TalerHttpError>(
- [wid], fetcher, {
+ [wid, "getWithdrawalById"], fetcher, {
refreshInterval: 1000,
refreshWhenHidden: false,
revalidateOnFocus: false,
@@ -92,7 +93,7 @@ export function useTransactionDetails(account: string, tid: number) {
}
const { data, error } = useSWR<TalerCoreBankResultByMethod<"getTransactionById">, TalerHttpError>(
- [account, token, tid], fetcher, {
+ [account, token, tid, "getTransactionById"], fetcher, {
refreshInterval: 0,
refreshWhenHidden: false,
revalidateOnFocus: false,
@@ -121,7 +122,18 @@ export function usePublicAccounts(initial?: number) {
})
}
- const { data, error } = useSWR<TalerCoreBankResultByMethod<"getPublicAccounts">, TalerHttpError>([offset], fetcher);
+ const { data, error } = useSWR<TalerCoreBankResultByMethod<"getPublicAccounts">, TalerHttpError>(
+ [offset, "getPublicAccounts"], fetcher, {
+ refreshInterval: 0,
+ refreshWhenHidden: false,
+ revalidateOnFocus: false,
+ revalidateOnReconnect: false,
+ refreshWhenOffline: false,
+ errorRetryCount: 0,
+ errorRetryInterval: 1,
+ shouldRetryOnError: false,
+ keepPreviousData: true,
+ });
const isLastPage =
data && data.body.public_accounts.length < PAGE_SIZE;
@@ -174,7 +186,7 @@ export function useTransactions(account: string, initial?: number) {
}
const { data, error } = useSWR<TalerCoreBankResultByMethod<"getTransactions">, TalerHttpError>(
- [account, token, offset], fetcher, {
+ [account, token, offset, "getTransactions"], fetcher, {
refreshInterval: 0,
refreshWhenHidden: false,
refreshWhenOffline: false,
diff --git a/packages/demobank-ui/src/hooks/circuit.ts b/packages/demobank-ui/src/hooks/circuit.ts
index 208663f8b..0f7af5fe5 100644
--- a/packages/demobank-ui/src/hooks/circuit.ts
+++ b/packages/demobank-ui/src/hooks/circuit.ts
@@ -106,9 +106,8 @@ export function useRatiosAndFeeConfig() {
return api.getConversionRates()
}
- const { data, error } = useSWR<
- TalerCoreBankResultByMethod<"getConversionRates">, TalerHttpError
- >([], fetcher, {
+ const { data, error } = useSWR<TalerCoreBankResultByMethod<"getConversionRates">, TalerHttpError>(
+ [, "getConversionRates"], fetcher, {
refreshInterval: 60 * 1000,
refreshWhenHidden: false,
revalidateOnFocus: false,
@@ -138,7 +137,7 @@ export function useBusinessAccounts() {
const [offset, setOffset] = useState<string | undefined>();
- function fetcher(token: AccessToken, offset?: string) {
+ function fetcher([token, offset]: [AccessToken, string]) {
return api.getAccounts(token, {
limit: MAX_RESULT_SIZE,
offset,
@@ -147,7 +146,7 @@ export function useBusinessAccounts() {
}
const { data, error } = useSWR<TalerCoreBankResultByMethod<"getAccounts">, TalerHttpError>(
- [token, offset], fetcher, {
+ [token, offset, "getAccounts"], fetcher, {
refreshInterval: 0,
refreshWhenHidden: false,
revalidateOnFocus: false,
@@ -192,8 +191,8 @@ function notUndefined(c: CashoutWithId | undefined): c is CashoutWithId {
}
export function useCashouts(account: string) {
const { state: credentials } = useBackendState();
- const token = credentials.status !== "loggedIn" ? undefined : credentials.token
const { api } = useBankCoreApiContext();
+ const token = credentials.status !== "loggedIn" ? undefined : credentials.token
async function fetcher([username, token]: [string, AccessToken]) {
const list = await api.getAccountCashouts({ username, token })
@@ -212,7 +211,7 @@ export function useCashouts(account: string) {
}
const { data, error } = useSWR<OperationOk<{ cashouts: CashoutWithId[] }>, TalerHttpError>(
- [account, token], fetcher, {
+ [account, token, "getAccountCashouts"], fetcher, {
refreshInterval: 0,
refreshWhenHidden: false,
revalidateOnFocus: false,
@@ -239,7 +238,7 @@ export function useCashoutDetails(cashoutId: string) {
}
const { data, error } = useSWR<TalerCoreBankResultByMethod<"getCashoutById">, TalerHttpError>(
- [creds?.username, creds?.token, cashoutId], fetcher, {
+ [creds?.username, creds?.token, cashoutId, "getCashoutById"], fetcher, {
refreshInterval: 0,
refreshWhenHidden: false,
revalidateOnFocus: false,
diff --git a/packages/demobank-ui/src/i18n/strings.ts b/packages/demobank-ui/src/i18n/strings.ts
index 1b19f7df7..86d1fff5b 100644
--- a/packages/demobank-ui/src/i18n/strings.ts
+++ b/packages/demobank-ui/src/i18n/strings.ts
@@ -167,7 +167,7 @@ strings["en"] = {
"Transfer creation gave response error": [""],
"Wire transfer created!": [""],
"Amount to withdraw:": ["Amount to withdraw"],
- Withdraw: ["Confirm withdrawal"],
+ Withdraw: ["Withdraw"],
"No credentials given.": [""],
"Could not create withdrawal operation": [""],
"Withdrawal creation gave response error": [""],
diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx
index a8167cca5..981b0f880 100644
--- a/packages/demobank-ui/src/pages/LoginForm.tsx
+++ b/packages/demobank-ui/src/pages/LoginForm.tsx
@@ -21,7 +21,7 @@ import { useEffect, useRef, useState } from "preact/hooks";
import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
import { useBackendContext } from "../context/backend.js";
import { bankUiSettings } from "../settings.js";
-import { undefinedIfEmpty } from "../utils.js";
+import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
import { doAutoFocus } from "./PaytoWireTransferForm.js";
import { useBankCoreApiContext } from "../context/config.js";
import { assertUnreachable } from "./HomePage.js";
@@ -78,43 +78,36 @@ export function LoginForm({ reason, onRegister }: { reason?: "not-found" | "forb
async function doLogin() {
if (!username || !password) return;
setBusy({})
- const data: TalerAuthentication.TokenRequest = {
- // scope: "readwrite" as "write", //FIX: different than merchant
- scope: "readwrite",
- duration: {
- d_us: "forever" //FIX: should return shortest
- // d_us: 60 * 60 * 24 * 7 * 1000 * 1000
- },
- refreshable: true,
- }
- const resp = await api.getAuthenticationAPI(username).createAccessToken(password, {
- // scope: "readwrite" as "write", //FIX: different than merchant
- scope: "readwrite",
- duration: {
- d_us: "forever" //FIX: should return shortest
- // d_us: 60 * 60 * 24 * 7 * 1000 * 1000
- },
- refreshable: true,
- })
- if (resp.type === "ok") {
- backend.logIn({ username, token: resp.body.access_token });
- } else {
- switch (resp.case) {
- case "wrong-credentials": return notify({
- type: "error",
- title: i18n.str`Wrong credentials for "${username}"`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- case "not-found": return notify({
- type: "error",
- title: i18n.str`Account not found`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- default: assertUnreachable(resp)
+ await withRuntimeErrorHandling(i18n, async () => {
+ const resp = await api.getAuthenticationAPI(username).createAccessToken(password, {
+ // scope: "readwrite" as "write", //FIX: different than merchant
+ scope: "readwrite",
+ duration: {
+ d_us: "forever" //FIX: should return shortest
+ // d_us: 60 * 60 * 24 * 7 * 1000 * 1000
+ },
+ refreshable: true,
+ })
+ if (resp.type === "ok") {
+ backend.logIn({ username, token: resp.body.access_token });
+ } else {
+ switch (resp.case) {
+ case "wrong-credentials": return notify({
+ type: "error",
+ title: i18n.str`Wrong credentials for "${username}"`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "not-found": return notify({
+ type: "error",
+ title: i18n.str`Account not found`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ default: assertUnreachable(resp)
+ }
}
- }
+ })
setPassword(undefined);
setBusy(undefined)
}
@@ -198,7 +191,7 @@ export function LoginForm({ reason, onRegister }: { reason?: "not-found" | "forb
<button type="submit"
class="rounded-md bg-indigo-600 disabled:bg-gray-300 px-3 py-1.5 text-sm font-semibold leading-6 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) => {
+ onClick={async (e) => {
e.preventDefault()
doLogin()
}}
diff --git a/packages/demobank-ui/src/pages/OperationState/state.ts b/packages/demobank-ui/src/pages/OperationState/state.ts
index 148571ec9..c9c1fa238 100644
--- a/packages/demobank-ui/src/pages/OperationState/state.ts
+++ b/packages/demobank-ui/src/pages/OperationState/state.ts
@@ -21,7 +21,7 @@ import { useBankCoreApiContext } from "../../context/config.js";
import { useWithdrawalDetails } from "../../hooks/access.js";
import { useBackendState } from "../../hooks/backend.js";
import { useSettings } from "../../hooks/settings.js";
-import { buildRequestErrorMessage } from "../../utils.js";
+import { buildRequestErrorMessage, withRuntimeErrorHandling } from "../../utils.js";
import { Props, State } from "./index.js";
import { assertUnreachable } from "../HomePage.js";
import { mutate } from "swr";
@@ -41,9 +41,8 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
async function doSilentStart() {
//FIXME: if amount is not enough use balance
const parsedAmount = Amounts.parseOrThrow(`${currency}:${amount}`)
-
- try {
- if (!creds) return;
+ if (!creds) return;
+ await withRuntimeErrorHandling(i18n, async () => {
const resp = await api.createWithdrawal(creds, {
amount: Amounts.stringify(parsedAmount),
});
@@ -67,18 +66,7 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
} else {
updateSettings("currentWithdrawalOperationId", uri.withdrawalOperationId)
}
- } catch (error) {
- if (error instanceof TalerError) {
- notify(buildRequestErrorMessage(i18n, error))
- } else {
- notifyError(
- i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString
- )
- }
- }
+ })
}
const withdrawalOperationId = settings.currentWithdrawalOperationId
@@ -98,10 +86,9 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
const wid = withdrawalOperationId
async function doAbort() {
- try {
- setBusy({})
+ setBusy({})
+ await withRuntimeErrorHandling(i18n, async () => {
const resp = await api.abortWithdrawalById(wid);
- setBusy(undefined)
if (resp.type === "ok") {
onClose();
} else {
@@ -115,30 +102,19 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
default: assertUnreachable(resp.case)
}
}
- } catch (error) {
- if (error instanceof TalerError) {
- notify(buildRequestErrorMessage(i18n, error))
- } else {
- notifyError(
- i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString
- )
- }
- }
+ })
+ setBusy(undefined)
}
async function doConfirm() {
- try {
- setBusy({})
+ setBusy({})
+ await withRuntimeErrorHandling(i18n, async () => {
const resp = await api.confirmWithdrawalById(wid);
if (resp.type === "ok") {
mutate(() => true)//clean withdrawal state
if (!settings.showWithdrawalSuccess) {
notifyInfo(i18n.str`Wire transfer completed!`)
}
- setBusy(undefined)
} else {
switch (resp.case) {
case "previously-aborted": return notify({
@@ -156,18 +132,8 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
default: assertUnreachable(resp)
}
}
- } catch (error) {
- if (error instanceof TalerError) {
- notify(buildRequestErrorMessage(i18n, error))
- } else {
- notifyError(
- i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString
- )
- }
- }
+ })
+ setBusy(undefined)
}
const uri = stringifyWithdrawUri({
diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx b/packages/demobank-ui/src/pages/PaymentOptions.tsx
index f60ba3270..c36d58691 100644
--- a/packages/demobank-ui/src/pages/PaymentOptions.tsx
+++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx
@@ -18,7 +18,7 @@ import { AmountJson } from "@gnu-taler/taler-util";
import { notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser";
import { h, VNode } from "preact";
import { useState } from "preact/hooks";
-import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js";
+import { PaytoWireTransferForm, doAutoFocus } from "./PaytoWireTransferForm.js";
import { WalletWithdrawForm } from "./WalletWithdrawForm.js";
import { useSettings } from "../hooks/settings.js";
@@ -30,7 +30,7 @@ export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJ
const { i18n } = useTranslationContext();
const [settings] = useSettings();
- const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>();
+ const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>("wire-transfer");
return (
<div class="mt-2">
@@ -46,28 +46,28 @@ export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJ
<input type="radio" name="project-type" value="Newsletter" class="sr-only" aria-labelledby="project-type-0-label" aria-describedby="project-type-0-description-0 project-type-0-description-1" onClick={() => {
setTab("charge-wallet")
}} />
- <span class="flex flex-1">
+ <div class="flex flex-col">
+ <span class="flex">
<div class="text-4xl mr-4 my-auto">&#x1F4B5;</div>
- <span class="flex flex-col">
- <span id="project-type-0-label" class="block text-sm font-medium text-gray-900">
- <i18n.Translate>a <b>Taler</b> wallet</i18n.Translate>
- </span>
- <span id="project-type-0-description-0" class="mt-1 flex items-center text-sm text-gray-500">
- <i18n.Translate>Withdraw digital money into your mobile wallet or browser extension</i18n.Translate>
+ <span class="grow self-center text-lg text-gray-900 align-middle text-center">
+ <i18n.Translate>a <b>Taler</b> wallet</i18n.Translate>
</span>
+ <svg class="self-center flex-none h-5 w-5 text-indigo-600" style={{ visibility: tab === "charge-wallet" ? "visible" : "hidden" }} viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
+ </svg>
+ </span>
+ <div class="mt-1 flex items-center text-sm text-gray-500">
+ <i18n.Translate>Withdraw digital money into your mobile wallet or browser extension</i18n.Translate>
+ </div>
{!!settings.currentWithdrawalOperationId &&
- <span class="inline-flex items-center gap-x-1.5 w-fit rounded-full bg-green-100 px-2 py-1 text-xs font-medium text-green-700">
+ <span class="flex items-center gap-x-1.5 w-fit rounded-md bg-green-100 px-2 py-1 text-xs font-medium text-green-700 whitespace-pre">
<svg class="h-1.5 w-1.5 fill-green-500" viewBox="0 0 6 6" aria-hidden="true">
<circle cx="3" cy="3" r="3" />
</svg>
<i18n.Translate>operation ready</i18n.Translate>
</span>
}
- </span>
- </span>
- <svg class="h-5 w-5 text-indigo-600" style={{ visibility: tab === "charge-wallet" ? "visible" : "hidden" }} viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
- <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
- </svg>
+ </div>
</label>
@@ -75,20 +75,20 @@ export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJ
<input type="radio" name="project-type" value="Existing Customers" class="sr-only" aria-labelledby="project-type-1-label" aria-describedby="project-type-1-description-0 project-type-1-description-1" onClick={() => {
setTab("wire-transfer")
}} />
- <span class="flex flex-1">
- <div class="text-4xl mr-4 my-auto">&#x2194;</div>
- <span class="flex flex-col">
- <span id="project-type-1-label" class="block text-sm font-medium text-gray-900">
+ <div class="flex flex-col">
+ <span class="flex">
+ <div class="text-4xl mr-4 my-auto">&#x2194;</div>
+ <span class="grow self-center text-lg font-medium text-gray-900 align-middle text-center">
<i18n.Translate>another bank account</i18n.Translate>
</span>
- <span id="project-type-1-description-0" class="mt-1 flex items-center text-sm text-gray-500">
- <i18n.Translate>Make a wire transfer to an account which you know the bank account number</i18n.Translate>
- </span>
+ <svg class="self-center flex-none h-5 w-5 text-indigo-600" style={{ visibility: tab === "wire-transfer" ? "visible" : "hidden" }} viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
+ </svg>
</span>
- </span>
- <svg class="h-5 w-5 text-indigo-600" style={{ visibility: tab === "wire-transfer" ? "visible" : "hidden" }} viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
- <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
- </svg>
+ <div class="mt-1 flex items-center text-sm text-gray-500">
+ <i18n.Translate>Make a wire transfer to an account which you know the bank account number</i18n.Translate>
+ </div>
+ </div>
</label>
</div>
{tab === "charge-wallet" && (
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index 7861bb0b3..e713324c5 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -39,6 +39,7 @@ import {
buildRequestErrorMessage,
undefinedIfEmpty,
validateIBAN,
+ withRuntimeErrorHandling,
} from "../utils.js";
import { useBankCoreApiContext } from "../context/config.js";
import { useBackendState } from "../hooks/backend.js";
@@ -60,8 +61,7 @@ export function PaytoWireTransferForm({
onCancel: (() => void) | undefined;
limit: AmountJson;
}): VNode {
- const [isRawPayto, setIsRawPayto] = useState(false);
- // FIXME: remove this
+ const [isRawPayto, setIsRawPayto] = useState(true);
const { state: credentials } = useBackendState()
const { api } = useBankCoreApiContext();
const [iban, setIban] = useState<string | undefined>();
@@ -73,10 +73,6 @@ export function PaytoWireTransferForm({
);
const { i18n } = useTranslationContext();
const ibanRegex = "^[A-Z][A-Z][0-9]+$";
- const ref = useRef<HTMLInputElement>(null);
- useEffect(() => {
- if (focus) ref.current?.focus();
- }, [focus, isRawPayto]);
const trimmedAmountStr = amount?.trim();
const parsedAmount = Amounts.parse(`${limit.currency}:${trimmedAmountStr}`);
@@ -100,8 +96,6 @@ export function PaytoWireTransferForm({
: undefined,
});
- // const { createTransaction } = useAccessAPI();
-
const parsed = !rawPaytoInput ? undefined : parsePaytoUri(rawPaytoInput);
const errorsPayto = undefinedIfEmpty({
@@ -125,12 +119,13 @@ export function PaytoWireTransferForm({
async function doSend() {
let payto_uri: string | undefined;
let sendingAmount: AmountString | undefined;
+ if (credentials.status !== "loggedIn") return;
if (rawPaytoInput) {
const p = parsePaytoUri(rawPaytoInput)
if (!p) return;
sendingAmount = p.params.amount
delete p.params.amount
- //it should have message
+ //if this payto is valid then it already have message
payto_uri = stringifyPaytoUri(p)
} else {
if (!iban || !subject) return;
@@ -139,11 +134,11 @@ export function PaytoWireTransferForm({
payto_uri = stringifyPaytoUri(ibanPayto);
sendingAmount = `${limit.currency}:${trimmedAmountStr}`
}
+ const puri = payto_uri;
- try {
- if (credentials.status !== "loggedIn") return;
+ await withRuntimeErrorHandling(i18n, async () => {
const res = await api.createTransaction(credentials, {
- payto_uri,
+ payto_uri: puri,
amount: sendingAmount,
});
mutate(() => true)
@@ -168,32 +163,20 @@ export function PaytoWireTransferForm({
setAmount(undefined);
setIban(undefined);
setSubject(undefined);
- rawPaytoInputSetter(undefined)
- } catch (error) {
- if (error instanceof TalerError) {
- notify(buildRequestErrorMessage(i18n, error))
- } else {
- notifyError(
- i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString
- )
- }
- }
-
+ rawPaytoInputSetter(undefined)
+ })
}
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">
{/**
* FIXME: Scan a qr code
*/}
- <div class="px-4 sm:px-0">
+ <div class="">
<h2 class="text-base font-semibold leading-7 text-gray-900">
{title}
</h2>
<div>
- <div class="px-4 mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-1 sm:gap-x-4">
+ <div class="px-2 mt-2 grid grid-cols-1 gap-y-4 sm:gap-x-4">
<label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none" + (!isRawPayto ? "border-indigo-600 ring-2 ring-indigo-600" : "border-gray-300")}>
<input type="radio" name="project-type" value="Newsletter" class="sr-only" aria-labelledby="project-type-0-label" aria-describedby="project-type-0-description-0 project-type-0-description-1" onChange={() => {
if (parsed && parsed.isKnown && parsed.targetType === "iban") {
@@ -207,8 +190,6 @@ export function PaytoWireTransferForm({
setSubject(subject)
}
}
- //payto://iban/DE9714548806481?amount=LOCAL%3A2&message=011Y8V8KDCPFDEKPDZTHS7KZ14GHX7BVWKRDDPZ1N75TJ90166T0
- //payto://iban/DE9714548806481?receiver-name=Exchanger&amount=LOCAL%3A2&message=011Y8V8KDCPFDEKPDZTHS7KZ14GHX7BVWKRDDPZ1N75TJ90166T0
setIsRawPayto(false)
}} />
<span class="flex flex-1">
@@ -236,7 +217,7 @@ export function PaytoWireTransferForm({
}} />
<span class="flex flex-1">
<span class="flex flex-col">
- <span class="block text-sm font-medium text-gray-900">
+ <span class="block text-sm font-medium text-gray-900">
<i18n.Translate>Import payto:// URI</i18n.Translate>
</span>
</span>
@@ -247,17 +228,16 @@ export function PaytoWireTransferForm({
</div>
<form
- class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2 w-fit mx-auto"
+ class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-md sm:rounded-xl md:col-span-2 w-fit mx-auto"
autoCapitalize="none"
autoCorrect="off"
onSubmit={e => {
e.preventDefault()
}}
>
- <div class="px-4 py-6 sm:p-8">
+ <div class="p-4 sm:p-8">
{!isRawPayto ?
<div class="grid max-w-xs grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
-
<div class="sm:col-span-5">
<label for="iban" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Recipient`}</label>
<div class="mt-2">
@@ -338,8 +318,8 @@ export function PaytoWireTransferForm({
name="address"
id="address"
type="textarea"
- rows={3}
- class="block overflow-hidden w-64 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"
+ rows={5}
+ class="block overflow-hidden w-44 sm:w-96 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={rawPaytoInput ?? ""}
required
placeholder={i18n.str`payto://iban/[receiver-iban]?message=[subject]&amount=[${limit.currency}:X.Y]`}
@@ -393,7 +373,7 @@ export function doAutoFocus(element: HTMLElement | null) {
element.scrollIntoView({
behavior: "smooth",
block: "center",
- inline: "center"
+ inline: "center",
})
}, 100)
}
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx b/packages/demobank-ui/src/pages/QrCodeSection.tsx
index a37de383d..64f9ec5ab 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx
@@ -30,7 +30,7 @@ import {
import { Fragment, h, VNode } from "preact";
import { useEffect } from "preact/hooks";
import { QR } from "../components/QR.js";
-import { buildRequestErrorMessage } from "../utils.js";
+import { buildRequestErrorMessage, withRuntimeErrorHandling } from "../utils.js";
import { useBankCoreApiContext } from "../context/config.js";
import { assertUnreachable } from "./HomePage.js";
@@ -55,36 +55,22 @@ export function QrCodeSection({
const { api } = useBankCoreApiContext()
async function doAbort() {
- try {
+ await withRuntimeErrorHandling(i18n, async () => {
const result = await api.abortWithdrawalById(withdrawUri.withdrawalOperationId);
if (result.type === "ok") {
onAborted();
} else {
switch (result.case) {
- case "previously-confirmed": {
- notify({
- type: "info",
- title: i18n.str`The reserve operation has been confirmed previously and can't be aborted`
- })
- break;
- }
+ case "previously-confirmed": return notify({
+ type: "info",
+ title: i18n.str`The reserve operation has been confirmed previously and can't be aborted`
+ })
default: {
assertUnreachable(result.case)
}
}
}
- } catch (error) {
- if (error instanceof TalerError) {
- notify(buildRequestErrorMessage(i18n, error))
- } else {
- notifyError(
- i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString
- )
- }
- }
+ })
}
return (
diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx b/packages/demobank-ui/src/pages/RegistrationPage.tsx
index fda2d904d..ce38a9fb8 100644
--- a/packages/demobank-ui/src/pages/RegistrationPage.tsx
+++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx
@@ -24,7 +24,7 @@ import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { useBackendContext } from "../context/backend.js";
import { bankUiSettings } from "../settings.js";
-import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
+import { buildRequestErrorMessage, undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
import { getRandomPassword, getRandomUsername } from "./rnd.js";
import { useBankCoreApiContext } from "../context/config.js";
@@ -95,118 +95,80 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
});
async function doRegistrationAndLogin(name: string | undefined, username: string, password: string) {
- const creationResponse = await api.createAccount("" as AccessToken, { name: name ?? "", username, password });
- if (creationResponse.type === "fail") {
- switch (creationResponse.case) {
- case "invalid-input": return notify({
- type: "error",
- title: i18n.str`Some of the input fields are invalid.`,
- description: creationResponse.detail.hint as TranslatedString,
- debug: creationResponse.detail,
- })
- case "unable-to-create": return notify({
- type: "error",
- title: i18n.str`Unable to create that account.`,
- description: creationResponse.detail.hint as TranslatedString,
- debug: creationResponse.detail,
- })
- case "unauthorized": return notify({
- type: "error",
- title: i18n.str`No enough permission to create that account.`,
- description: creationResponse.detail.hint as TranslatedString,
- debug: creationResponse.detail,
- })
- case "already-exist": return notify({
- type: "error",
- title: i18n.str`That username is already taken`,
- description: creationResponse.detail.hint as TranslatedString,
- debug: creationResponse.detail,
- })
- default: assertUnreachable(creationResponse)
+ await withRuntimeErrorHandling(i18n, async () => {
+ const creationResponse = await api.createAccount("" as AccessToken, { name: name ?? "", username, password });
+ if (creationResponse.type === "fail") {
+ switch (creationResponse.case) {
+ case "invalid-input": return notify({
+ type: "error",
+ title: i18n.str`Some of the input fields are invalid.`,
+ description: creationResponse.detail.hint as TranslatedString,
+ debug: creationResponse.detail,
+ })
+ case "unable-to-create": return notify({
+ type: "error",
+ title: i18n.str`Unable to create that account.`,
+ description: creationResponse.detail.hint as TranslatedString,
+ debug: creationResponse.detail,
+ })
+ case "unauthorized": return notify({
+ type: "error",
+ title: i18n.str`No enough permission to create that account.`,
+ description: creationResponse.detail.hint as TranslatedString,
+ debug: creationResponse.detail,
+ })
+ case "already-exist": return notify({
+ type: "error",
+ title: i18n.str`That username is already taken`,
+ description: creationResponse.detail.hint as TranslatedString,
+ debug: creationResponse.detail,
+ })
+ default: assertUnreachable(creationResponse)
+ }
}
- }
- const resp = await api.getAuthenticationAPI(username).createAccessToken(password, {
- // scope: "readwrite" as "write", //FIX: different than merchant
- scope: "readwrite",
- duration: {
- d_us: "forever" //FIX: should return shortest
- // d_us: 60 * 60 * 24 * 7 * 1000 * 1000
- },
- refreshable: true,
- })
+ const resp = await api.getAuthenticationAPI(username).createAccessToken(password, {
+ scope: "readwrite",
+ duration: { d_us: "forever" },
+ refreshable: true,
+ })
- if (resp.type === "ok") {
- backend.logIn({ username, token: resp.body.access_token });
- } else {
- switch (resp.case) {
- case "wrong-credentials": return notify({
- type: "error",
- title: i18n.str`Wrong credentials for "${username}"`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- case "not-found": return notify({
- type: "error",
- title: i18n.str`Account not found`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- default: assertUnreachable(resp)
+ if (resp.type === "ok") {
+ backend.logIn({ username, token: resp.body.access_token });
+ } else {
+ switch (resp.case) {
+ case "wrong-credentials": return notify({
+ type: "error",
+ title: i18n.str`Wrong credentials for "${username}"`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "not-found": return notify({
+ type: "error",
+ title: i18n.str`Account not found`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ default: assertUnreachable(resp)
+ }
}
- }
+ })
}
async function doRegistrationStep() {
if (!username || !password) return;
- try {
- await doRegistrationAndLogin(name, username, password)
- setUsername(undefined);
- onComplete();
- } catch (error) {
- if (error instanceof TalerError) {
- notify(buildRequestErrorMessage(i18n, error))
- } else {
- notifyError(
- i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString
- )
- }
- }
+ await doRegistrationAndLogin(name, username, password)
+ setUsername(undefined);
setPassword(undefined);
setRepeatPassword(undefined);
+ onComplete();
}
- async function delay(ms: number): Promise<void> {
- return new Promise((resolve) => {
- setTimeout(() => {
- resolve(undefined);
- }, ms)
- })
- }
async function doRandomRegistration(tries: number = 3) {
const user = getRandomUsername();
const pass = getRandomPassword();
- try {
- setUsername(undefined);
- setPassword(undefined);
- setRepeatPassword(undefined);
- const username = `_${user.first}-${user.second}_`
- await doRegistrationAndLogin(name, username, pass)
- onComplete();
- } catch (error) {
- if (error instanceof TalerError) {
- notify(buildRequestErrorMessage(i18n, error))
- } else {
- notifyError(
- i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString
- )
- }
- }
+ const username = `_${user.first}-${user.second}_`
+ await doRegistrationAndLogin(name, username, pass)
+ onComplete();
}
return (
@@ -403,8 +365,9 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
<button type="submit"
class=" rounded-md bg-indigo-600 disabled:bg-gray-300 px-3 py-1.5 text-sm font-semibold leading-6 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) => {
+ onClick={async (e) => {
e.preventDefault()
+
doRegistrationStep()
}}
>
diff --git a/packages/demobank-ui/src/pages/ShowAccountDetails.tsx b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
index 3534f9733..c65b90503 100644
--- a/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
+++ b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
@@ -7,7 +7,7 @@ import { Loading } from "../components/Loading.js";
import { useBankCoreApiContext } from "../context/config.js";
import { useAccountDetails } from "../hooks/access.js";
import { useBackendState } from "../hooks/backend.js";
-import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
+import { buildRequestErrorMessage, undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
import { assertUnreachable } from "./HomePage.js";
import { LoginForm } from "./LoginForm.js";
import { AccountForm } from "./admin/AccountForm.js";
@@ -49,50 +49,41 @@ export function ShowAccountDetails({
async function doUpdate() {
if (!update) {
setUpdate(true);
- } else {
- if (!submitAccount || !creds) return;
- try {
- const resp = await api.updateAccount(creds, {
- cashout_address: submitAccount.cashout_payto_uri,
- challenge_contact_data: undefinedIfEmpty({
- email: submitAccount.contact_data?.email,
- phone: submitAccount.contact_data?.phone,
- }),
- is_exchange: false,
- name: submitAccount.name,
- });
- if (resp.type === "ok") {
- onUpdateSuccess();
- } else {
- switch (resp.case) {
- case "unauthorized": return notify({
- type: "error",
- title: i18n.str`The rights to change the account are not sufficient`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- case "not-found": return notify({
- type: "error",
- title: i18n.str`The username was not found`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- default: assertUnreachable(resp)
- }
- }
- } catch (error) {
- if (error instanceof TalerError) {
- notify(buildRequestErrorMessage(i18n, error))
- } else {
- notifyError(
- i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString
- )
+ return;
+ }
+ if (!submitAccount || !creds) return;
+ await withRuntimeErrorHandling(i18n, async () => {
+ const resp = await api.updateAccount(creds, {
+ cashout_address: submitAccount.cashout_payto_uri,
+ challenge_contact_data: undefinedIfEmpty({
+ email: submitAccount.contact_data?.email,
+ phone: submitAccount.contact_data?.phone,
+ }),
+ is_exchange: false,
+ name: submitAccount.name,
+ });
+
+ if (resp.type === "ok") {
+ onUpdateSuccess();
+ } else {
+ switch (resp.case) {
+ case "unauthorized": return notify({
+ type: "error",
+ title: i18n.str`The rights to change the account are not sufficient`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "not-found": return notify({
+ type: "error",
+ title: i18n.str`The username was not found`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ default: assertUnreachable(resp)
}
}
- }
+ })
+
}
return (
diff --git a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
index ac6e9fa9b..d82dac4b1 100644
--- a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
+++ b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
@@ -3,7 +3,7 @@ import { HttpResponsePaginated, RequestError, notify, notifyError, useTranslatio
import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
-import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
+import { buildRequestErrorMessage, undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
import { doAutoFocus } from "./PaytoWireTransferForm.js";
import { useBankCoreApiContext } from "../context/config.js";
import { assertUnreachable } from "./HomePage.js";
@@ -39,7 +39,7 @@ export function UpdateAccountPassword({
async function doChangePassword() {
if (!!errors || !password || !creds) return;
- try {
+ await withRuntimeErrorHandling(i18n, async () => {
const resp = await api.updatePassword(creds, {
new_password: password,
});
@@ -47,32 +47,18 @@ export function UpdateAccountPassword({
onUpdateSuccess();
} else {
switch (resp.case) {
- case "unauthorized": {
- notify({
- type: "error",
- title: i18n.str`Not authorized to change the password, maybe the session is invalid.`
- })
- break;
- }
- case "not-found": {
- notify({
- type: "error",
- title: i18n.str`Account not found`
- })
- break;
- }
+ case "unauthorized": return notify({
+ type: "error",
+ title: i18n.str`Not authorized to change the password, maybe the session is invalid.`
+ })
+ case "not-found": return notify({
+ type: "error",
+ title: i18n.str`Account not found`
+ })
default: assertUnreachable(resp)
}
}
- } catch (error) {
- if (error instanceof TalerError) {
- notify(buildRequestErrorMessage(i18n, error))
- } else {
- notifyError(i18n.str`Operation failed, please report`, (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString)
- }
- }
+ })
}
return (
diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
index 2d80bad1f..28d5d7749 100644
--- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
+++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
@@ -36,7 +36,7 @@ import { Attention } from "../components/Attention.js";
import { useBankCoreApiContext } from "../context/config.js";
import { useBackendState } from "../hooks/backend.js";
import { useSettings } from "../hooks/settings.js";
-import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
+import { buildRequestErrorMessage, undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
import { assertUnreachable } from "./HomePage.js";
import { OperationState } from "./OperationState/index.js";
import { InputAmount, doAutoFocus } from "./PaytoWireTransferForm.js";
@@ -62,6 +62,7 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
if (!!settings.currentWithdrawalOperationId) {
return <Attention type="warning" title={i18n.str`There is an operation already`}>
+ <span ref={focus ? doAutoFocus : undefined}/>
<i18n.Translate>
To complete or cancel the operation click <a class="font-semibold text-yellow-700 hover:text-yellow-600" href={`#/operation/${settings.currentWithdrawalOperationId}`}>here</a>
</i18n.Translate>
@@ -87,7 +88,7 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
async function doStart() {
if (!parsedAmount || !creds) return;
- try {
+ await withRuntimeErrorHandling(i18n, async () => {
const result = await api.createWithdrawal(creds, {
amount: Amounts.stringify(parsedAmount),
});
@@ -110,18 +111,7 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
default: assertUnreachable(result.case)
}
}
- } catch (error) {
- if (error instanceof TalerError) {
- notify(buildRequestErrorMessage(i18n, error))
- } else {
- notifyError(
- i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString
- )
- }
- }
+ })
}
return <form
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index 602ec9bd8..87637f7ef 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -36,7 +36,7 @@ import {
import { Fragment, VNode, h } from "preact";
import { useMemo, useState } from "preact/hooks";
import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
-import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
+import { buildRequestErrorMessage, undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
import { useSettings } from "../hooks/settings.js";
import { RenderAmount } from "./PaytoWireTransferForm.js";
import { useBankCoreApiContext } from "../context/config.js";
@@ -88,8 +88,8 @@ export function WithdrawalConfirmationQuestion({
}) ?? busy;
async function doTransfer() {
- try {
- setBusy({})
+ setBusy({})
+ await withRuntimeErrorHandling(i18n, async () => {
const resp = await api.confirmWithdrawalById(withdrawUri.withdrawalOperationId);
if (resp.type === "ok") {
mutate(() => true)// clean any info that we have
@@ -113,53 +113,28 @@ export function WithdrawalConfirmationQuestion({
default: assertUnreachable(resp)
}
}
- } catch (error) {
- if (error instanceof TalerError) {
- notify(buildRequestErrorMessage(i18n, error))
- } else {
- notifyError(
- i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString
- )
- }
- }
+ })
setBusy(undefined)
}
async function doCancel() {
- try {
- setBusy({})
+ setBusy({})
+ await withRuntimeErrorHandling(i18n, async () => {
const resp = await api.abortWithdrawalById(withdrawUri.withdrawalOperationId);
if (resp.type === "ok") {
onAborted();
} else {
switch (resp.case) {
- case "previously-confirmed": {
- notify({
- type: "error",
- title: i18n.str`The reserve operation has been confirmed previously and can't be aborted`
- });
- break;
- }
+ case "previously-confirmed": return notify({
+ type: "error",
+ title: i18n.str`The reserve operation has been confirmed previously and can't be aborted`
+ });
default: {
assertUnreachable(resp.case)
}
}
}
- } catch (error) {
- if (error instanceof TalerError) {
- notify(buildRequestErrorMessage(i18n, error))
- } else {
- notifyError(
- i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString
- )
- }
- }
+ })
setBusy(undefined)
}
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 15910201e..7266e4de4 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -29,6 +29,7 @@ import { useWithdrawalDetails } from "../hooks/access.js";
import { assertUnreachable } from "./HomePage.js";
import { QrCodeSection } from "./QrCodeSection.js";
import { WithdrawalConfirmationQuestion } from "./WithdrawalConfirmationQuestion.js";
+import { Attention } from "../components/Attention.js";
const logger = new Logger("WithdrawalQRCode");
@@ -139,18 +140,21 @@ export function WithdrawalQRCode({
}
if (!data.selected_reserve_pub) {
- return <div>
- the exchange is selcted but no reserve pub
- </div>
+ return <Attention type="danger"
+ title={i18n.str`The operation is incomplete or some step in the withdrawal failed`} >
+ <i18n.Translate>The exchange is selected but no reserve public key found.</i18n.Translate>
+ </Attention>
}
const account = !data.selected_exchange_account ? undefined : parsePaytoUri(data.selected_exchange_account)
if (!account) {
- return <div>
- the exchange is selcted but no account
- </div>
+ return <Attention type="danger"
+ title={i18n.str`The operation is incomplete or some step in the withdrawal failed`} >
+ <i18n.Translate>The exchange is selected but the exchange payto URI is missing or invalid.</i18n.Translate>
+ </Attention>
}
+
return (
<WithdrawalConfirmationQuestion
withdrawUri={withdrawUri}
diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
index f6176e772..e10c3ad41 100644
--- a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
@@ -2,7 +2,7 @@ import { HttpStatusCode, TalerCorebankApi, TalerError, TranslatedString } from "
import { RequestError, notify, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
-import { buildRequestErrorMessage } from "../../utils.js";
+import { buildRequestErrorMessage, withRuntimeErrorHandling } from "../../utils.js";
import { getRandomPassword } from "../rnd.js";
import { AccountForm } from "./AccountForm.js";
import { useBackendState } from "../../hooks/backend.js";
@@ -29,7 +29,7 @@ export function CreateNewAccount({
async function doCreate() {
if (!submitAccount || !token) return;
- try {
+ await withRuntimeErrorHandling(i18n, async () => {
const account: TalerCorebankApi.RegisterAccountRequest = {
cashout_payto_uri: submitAccount.cashout_payto_uri,
challenge_contact_data: submitAccount.contact_data,
@@ -72,18 +72,7 @@ export function CreateNewAccount({
default: assertUnreachable(resp)
}
}
- } catch (error) {
- if (error instanceof TalerError) {
- notify(buildRequestErrorMessage(i18n, error))
- } else {
- notifyError(
- i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString
- )
- }
- }
+ })
}
return (
diff --git a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
index ce8a53ca1..9a212ebd0 100644
--- a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
@@ -7,7 +7,7 @@ import { ErrorLoading } from "../../components/ErrorLoading.js";
import { Loading } from "../../components/Loading.js";
import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
import { useAccountDetails } from "../../hooks/access.js";
-import { buildRequestErrorMessage, undefinedIfEmpty } from "../../utils.js";
+import { buildRequestErrorMessage, undefinedIfEmpty, withRuntimeErrorHandling } from "../../utils.js";
import { assertUnreachable } from "../HomePage.js";
import { LoginForm } from "../LoginForm.js";
import { doAutoFocus } from "../PaytoWireTransferForm.js";
@@ -60,7 +60,7 @@ export function RemoveAccount({
async function doRemove() {
if (!token) return;
- try {
+ await withRuntimeErrorHandling(i18n, async () => {
const resp = await api.deleteAccount({ username: account, token });
if (resp.type === "ok") {
onUpdateSuccess();
@@ -95,16 +95,7 @@ export function RemoveAccount({
}
}
}
- } catch (error) {
- if (error instanceof TalerError) {
- notify(buildRequestErrorMessage(i18n, error))
- } else {
- notifyError(i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString);
- }
- }
+ })
}
const errors = undefinedIfEmpty({
diff --git a/packages/demobank-ui/src/pages/business/Home.tsx b/packages/demobank-ui/src/pages/business/Home.tsx
index 03d7895e3..d7beda01d 100644
--- a/packages/demobank-ui/src/pages/business/Home.tsx
+++ b/packages/demobank-ui/src/pages/business/Home.tsx
@@ -45,6 +45,7 @@ import {
TanChannel,
buildRequestErrorMessage,
undefinedIfEmpty,
+ withRuntimeErrorHandling,
} from "../../utils.js";
import { LoginForm } from "../LoginForm.js";
import { InputAmount } from "../PaytoWireTransferForm.js";
@@ -241,41 +242,15 @@ function CreateCashout({
);
useEffect(() => {
- if (form.isDebit) {
- calculateFromDebit(amount, sellFee, sellRate)
- .then((r) => {
- setCalc(r);
- })
- .catch((error) => {
- if (error instanceof TalerError) {
- notify(buildRequestErrorMessage(i18n, error))
- } else {
- notifyError(
- i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString
- )
- }
- });
- } else {
- calculateFromCredit(amount, sellFee, sellRate)
- .then((r) => {
- setCalc(r);
- })
- .catch((error) => {
- if (error instanceof TalerError) {
- notify(buildRequestErrorMessage(i18n, error))
- } else {
- notifyError(
- i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString
- )
- }
- });
+ async function doAsync() {
+ await withRuntimeErrorHandling(i18n, async () => {
+ const resp = await (form.isDebit ?
+ calculateFromDebit(amount, sellFee, sellRate) :
+ calculateFromCredit(amount, sellFee, sellRate));
+ setCalc(resp)
+ })
}
+ doAsync()
}, [form.amount, form.isDebit]);
const balanceAfter = Amounts.sub(account.balance, calc.debit).amount;
@@ -484,7 +459,7 @@ function CreateCashout({
e.preventDefault();
if (errors || !creds) return;
- try {
+ await withRuntimeErrorHandling(i18n, async () => {
const resp = await api.createCashout(creds, {
amount_credit: Amounts.stringify(calc.credit),
amount_debit: Amounts.stringify(calc.debit),
@@ -529,18 +504,7 @@ function CreateCashout({
default: assertUnreachable(resp)
}
}
- } catch (error) {
- if (error instanceof TalerError) {
- notify(buildRequestErrorMessage(i18n, error))
- } else {
- notifyError(
- i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString
- )
- }
- }
+ })
}}
>
{i18n.str`Create`}
@@ -669,7 +633,7 @@ export function ShowCashoutDetails({
onClick={async (e) => {
e.preventDefault();
if (!creds) return;
- try {
+ await withRuntimeErrorHandling(i18n, async () => {
const resp = await api.abortCashoutById(creds, id);
if (resp.type === "ok") {
onCancel();
@@ -692,18 +656,7 @@ export function ShowCashoutDetails({
}
}
}
- } catch (error) {
- if (error instanceof TalerError) {
- notify(buildRequestErrorMessage(i18n, error))
- } else {
- notifyError(
- i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString
- )
- }
- }
+ })
}}
>
{i18n.str`Abort`}
@@ -715,9 +668,8 @@ export function ShowCashoutDetails({
class="pure-button pure-button-primary "
onClick={async (e) => {
e.preventDefault();
- if (!creds) return;
- try {
- if (!code) return;
+ if (!creds || !code) return;
+ await withRuntimeErrorHandling(i18n, async () => {
const resp = await api.confirmCashoutById(creds, id, {
tan: code,
});
@@ -745,19 +697,8 @@ export function ShowCashoutDetails({
})
default: assertUnreachable(resp)
}
- }
- } catch (error) {
- if (error instanceof TalerError) {
- notify(buildRequestErrorMessage(i18n, error))
- } else {
- notifyError(
- i18n.str`Operation failed, please report`,
- (error instanceof Error
- ? error.message
- : JSON.stringify(error)) as TranslatedString
- )
- }
- }
+ }
+ })
}}
>
{i18n.str`Confirm`}
diff --git a/packages/demobank-ui/src/utils.ts b/packages/demobank-ui/src/utils.ts
index 310e80cd6..437618150 100644
--- a/packages/demobank-ui/src/utils.ts
+++ b/packages/demobank-ui/src/utils.ts
@@ -19,6 +19,8 @@ import {
ErrorNotification,
ErrorType,
HttpError,
+ notify,
+ notifyError,
useTranslationContext,
} from "@gnu-taler/web-util/browser";
@@ -92,10 +94,27 @@ export enum CashoutStatus {
export const PAGE_SIZE = 20;
export const MAX_RESULT_SIZE = PAGE_SIZE * 2 - 1;
-export function buildRequestErrorMessage(
- i18n: ReturnType<typeof useTranslationContext>["i18n"],
- cause: TalerError<{}>,
-): ErrorNotification {
+type Translator = ReturnType<typeof useTranslationContext>["i18n"]
+
+export async function withRuntimeErrorHandling<T>(i18n: Translator, cb: () => Promise<T>): Promise<void> {
+ try {
+ await cb()
+ } catch (error: unknown) {
+ if (error instanceof TalerError) {
+ notify(buildRequestErrorMessage(i18n, error))
+ } else {
+ notifyError(
+ i18n.str`Operation failed, please report`,
+ (error instanceof Error
+ ? error.message
+ : JSON.stringify(error)) as TranslatedString
+ )
+ }
+ }
+}
+
+
+export function buildRequestErrorMessage( i18n: Translator, cause: TalerError): ErrorNotification {
let result: ErrorNotification;
switch (cause.errorDetail.code) {
case TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT: {