summaryrefslogtreecommitdiff
path: root/packages/demobank-ui
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2023-10-30 15:27:25 -0300
committerSebastian <sebasjm@gmail.com>2023-10-30 15:27:25 -0300
commit768838285c25cbb1b171f645e8efb37a3c14273a (patch)
tree3404a7ea452a357baf4ebfc6c3b400f601849744 /packages/demobank-ui
parentb7ba3119c1ff0d9ae3432cf0de1ef8cf92fc193c (diff)
downloadwallet-core-768838285c25cbb1b171f645e8efb37a3c14273a.tar.gz
wallet-core-768838285c25cbb1b171f645e8efb37a3c14273a.tar.bz2
wallet-core-768838285c25cbb1b171f645e8efb37a3c14273a.zip
local error impl: errors shown fixed position that are wiped when moved from the view
Diffstat (limited to 'packages/demobank-ui')
-rw-r--r--packages/demobank-ui/src/components/Attention.tsx2
-rw-r--r--packages/demobank-ui/src/components/ShowLocalNotification.tsx43
-rw-r--r--packages/demobank-ui/src/demobank-ui-settings.js2
-rw-r--r--packages/demobank-ui/src/pages/LoginForm.tsx30
-rw-r--r--packages/demobank-ui/src/pages/OperationState/index.ts16
-rw-r--r--packages/demobank-ui/src/pages/OperationState/state.ts142
-rw-r--r--packages/demobank-ui/src/pages/OperationState/views.tsx138
-rw-r--r--packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx27
-rw-r--r--packages/demobank-ui/src/pages/QrCodeSection.tsx19
-rw-r--r--packages/demobank-ui/src/pages/RegistrationPage.tsx8
-rw-r--r--packages/demobank-ui/src/pages/ShowAccountDetails.tsx11
-rw-r--r--packages/demobank-ui/src/pages/UpdateAccountPassword.tsx9
-rw-r--r--packages/demobank-ui/src/pages/WalletWithdrawForm.tsx17
-rw-r--r--packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx23
-rw-r--r--packages/demobank-ui/src/pages/admin/Account.tsx6
-rw-r--r--packages/demobank-ui/src/pages/admin/AccountForm.tsx12
-rw-r--r--packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx22
-rw-r--r--packages/demobank-ui/src/pages/admin/RemoveAccount.tsx18
-rw-r--r--packages/demobank-ui/src/pages/business/CreateCashout.tsx9
-rw-r--r--packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx11
20 files changed, 343 insertions, 222 deletions
diff --git a/packages/demobank-ui/src/components/Attention.tsx b/packages/demobank-ui/src/components/Attention.tsx
index 3313e5796..57d0a4199 100644
--- a/packages/demobank-ui/src/components/Attention.tsx
+++ b/packages/demobank-ui/src/components/Attention.tsx
@@ -9,7 +9,7 @@ interface Props {
children?: ComponentChildren ,
}
export function Attention({ type = "info", title, children, onClose }: Props): VNode {
- return <div class={`group attention-${type} mt-2`}>
+ return <div class={`group attention-${type} mt-2 shadow-lg`}>
<div class="rounded-md group-[.attention-info]:bg-blue-50 group-[.attention-warning]:bg-yellow-50 group-[.attention-danger]:bg-red-50 group-[.attention-success]:bg-green-50 p-4 shadow">
<div class="flex">
<div >
diff --git a/packages/demobank-ui/src/components/ShowLocalNotification.tsx b/packages/demobank-ui/src/components/ShowLocalNotification.tsx
new file mode 100644
index 000000000..bb62a48f0
--- /dev/null
+++ b/packages/demobank-ui/src/components/ShowLocalNotification.tsx
@@ -0,0 +1,43 @@
+import { Notification } from "@gnu-taler/web-util/browser";
+import { h, Fragment, VNode } from "preact";
+import { Attention } from "./Attention.js";
+import { useSettings } from "../hooks/settings.js";
+
+export function ShowLocalNotification({ notification }: { notification?: Notification }): VNode {
+ if (!notification) return <Fragment />
+ switch (notification.message.type) {
+ case "error":
+ return <div class="relative">
+ <div class="fixed top-0 left-0 right-0 z-20 w-full p-4">
+ <Attention type="danger" title={notification.message.title} onClose={() => {
+ notification.remove()
+ }}>
+ {notification.message.description &&
+ <div class="mt-2 text-sm text-red-700">
+ {notification.message.description}
+ </div>
+ }
+ <MaybeShowDebugInfo info={notification.message.debug} />
+ </Attention>
+ </div>
+ </div>
+ case "info":
+ return <div class="relative">
+ <div class="fixed top-0 left-0 right-0 z-20 w-full p-4">
+ <Attention type="success" title={notification.message.title} onClose={() => {
+ notification.remove();
+ }} /></div></div>
+ }
+}
+
+
+function MaybeShowDebugInfo({ info }: { info: any }): VNode {
+ const [settings] = useSettings()
+ if (settings.showDebugInfo) {
+ return <pre class="whitespace-break-spaces ">
+ {info}
+ </pre>
+ }
+ return <Fragment />
+}
+
diff --git a/packages/demobank-ui/src/demobank-ui-settings.js b/packages/demobank-ui/src/demobank-ui-settings.js
index 99c6f3873..827f207f8 100644
--- a/packages/demobank-ui/src/demobank-ui-settings.js
+++ b/packages/demobank-ui/src/demobank-ui-settings.js
@@ -4,7 +4,7 @@
* Global settings for the demobank UI.
*/
globalThis.talerDemobankSettings = {
- backendBaseURL: "http://bank.taler.test/",
+ backendBaseURL: "http://bank.taler.test:1180/",
allowRegistrations: true,
showDemoNav: true,
simplePasswordForRandomAccounts: true,
diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx
index b18f29d86..f21e98343 100644
--- a/packages/demobank-ui/src/pages/LoginForm.tsx
+++ b/packages/demobank-ui/src/pages/LoginForm.tsx
@@ -15,7 +15,7 @@
*/
import { TranslatedString } from "@gnu-taler/taler-util";
-import { notify, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { Notification, useLocalNotification, useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useEffect, useRef, useState } from "preact/hooks";
import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
@@ -25,6 +25,8 @@ import { bankUiSettings } from "../settings.js";
import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
import { assertUnreachable } from "./WithdrawalOperationPage.js";
import { doAutoFocus } from "./PaytoWireTransferForm.js";
+import { Attention } from "../components/Attention.js";
+import { ShowLocalNotification } from "../components/ShowLocalNotification.js";
/**
@@ -37,25 +39,19 @@ export function LoginForm({ reason, onRegister }: { reason?: "not-found" | "forb
const [password, setPassword] = useState<string | undefined>();
const { i18n } = useTranslationContext();
const { api } = useBankCoreApiContext();
-
+ const [notification, notify, handleError] = useLocalNotification()
/**
* Register form may be shown in the initialization step.
- * If this is an error when usgin the app the registration
- * callback is not set
+ * If no register handler then this is invoke
+ * to show a session expired or unauthorized
*/
- const isSessionExpired = !onRegister
+ const isLogginAgain = !onRegister
- // useEffect(() => {
- // if (backend.state.status === "loggedIn") {
- // backend.expired()
- // }
- // },[])
const ref = useRef<HTMLInputElement>(null);
useEffect(function focusInput() {
- //FIXME: show invalidate session and allow relogin
- if (isSessionExpired) {
- localStorage.removeItem("backend-state");
+ if (isLogginAgain && backend.state.status !== "expired") {
+ backend.expired()
window.location.reload()
}
ref.current?.focus();
@@ -78,7 +74,7 @@ export function LoginForm({ reason, onRegister }: { reason?: "not-found" | "forb
async function doLogin() {
if (!username || !password) return;
setBusy({})
- await withRuntimeErrorHandling(i18n, async () => {
+ await handleError(async () => {
const resp = await api.getAuthenticationAPI(username).createAccessToken(password, {
// scope: "readwrite" as "write", //FIX: different than merchant
scope: "readwrite",
@@ -114,7 +110,7 @@ export function LoginForm({ reason, onRegister }: { reason?: "not-found" | "forb
return (
<div class="flex min-h-full flex-col justify-center">
-
+ <ShowLocalNotification notification={notification} />
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
<form class="space-y-6" noValidate
onSubmit={(e) => {
@@ -135,7 +131,7 @@ export function LoginForm({ reason, onRegister }: { reason?: "not-found" | "forb
id="username"
class="block w-full disabled:bg-gray-200 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={username ?? ""}
- disabled={isSessionExpired}
+ disabled={isLogginAgain}
enterkeyhint="next"
placeholder="identification"
autocomplete="username"
@@ -177,7 +173,7 @@ export function LoginForm({ reason, onRegister }: { reason?: "not-found" | "forb
</div>
</div>
- {isSessionExpired ? <div class="flex justify-between">
+ {isLogginAgain ? <div class="flex justify-between">
<button type="submit"
class="rounded-md bg-white-600 px-3 py-1.5 text-sm font-semibold leading-6 text-black shadow-sm hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-600"
onClick={(e) => {
diff --git a/packages/demobank-ui/src/pages/OperationState/index.ts b/packages/demobank-ui/src/pages/OperationState/index.ts
index bc3555c48..b17b0d787 100644
--- a/packages/demobank-ui/src/pages/OperationState/index.ts
+++ b/packages/demobank-ui/src/pages/OperationState/index.ts
@@ -19,7 +19,7 @@ import { utils } from "@gnu-taler/web-util/browser";
import { ErrorLoading } from "../../components/ErrorLoading.js";
import { Loading } from "../../components/Loading.js";
import { useComponentState } from "./state.js";
-import { AbortedView, ConfirmedView, InvalidPaytoView, InvalidReserveView, InvalidWithdrawalView, NeedConfirmationView, ReadyView } from "./views.js";
+import { AbortedView, ConfirmedView, FailedView, InvalidPaytoView, InvalidReserveView, InvalidWithdrawalView, NeedConfirmationView, ReadyView } from "./views.js";
export interface Props {
currency: string;
@@ -29,6 +29,7 @@ export interface Props {
export type State = State.Loading |
State.LoadingError |
State.Ready |
+ State.Failed |
State.Aborted |
State.Confirmed |
State.InvalidPayto |
@@ -42,6 +43,11 @@ export namespace State {
error: undefined;
}
+ export interface Failed {
+ status: "failed";
+ error: TalerCoreBankErrorsByMethod<"createWithdrawal">;
+ }
+
export interface LoadingError {
status: "loading-error";
error: TalerError;
@@ -54,8 +60,7 @@ export namespace State {
status: "ready";
error: undefined;
uri: WithdrawUriResult,
- onClose: () => void;
- onAbort: () => void;
+ onClose: () => Promise<TalerCoreBankErrorsByMethod<"abortWithdrawalById"> | undefined>;
}
export interface InvalidPayto {
@@ -78,8 +83,8 @@ export namespace State {
}
export interface NeedConfirmation {
status: "need-confirmation",
- onAbort: () => void;
- onConfirm: () => void;
+ onAbort: () => Promise<TalerCoreBankErrorsByMethod<"abortWithdrawalById"> | undefined>;
+ onConfirm: () => Promise<TalerCoreBankErrorsByMethod<"confirmWithdrawalById"> | undefined>;
error: undefined;
busy: boolean,
}
@@ -106,6 +111,7 @@ export interface Transaction {
const viewMapping: utils.StateViewMap<State> = {
loading: Loading,
+ "failed": FailedView,
"invalid-payto": InvalidPaytoView,
"invalid-withdrawal": InvalidWithdrawalView,
"invalid-reserve": InvalidReserveView,
diff --git a/packages/demobank-ui/src/pages/OperationState/state.ts b/packages/demobank-ui/src/pages/OperationState/state.ts
index a4890d726..2d33ff78b 100644
--- a/packages/demobank-ui/src/pages/OperationState/state.ts
+++ b/packages/demobank-ui/src/pages/OperationState/state.ts
@@ -14,65 +14,40 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { Amounts, HttpStatusCode, TalerError, TranslatedString, parsePaytoUri, parseWithdrawUri, stringifyWithdrawUri } from "@gnu-taler/taler-util";
-import { RequestError, notify, notifyError, notifyInfo, useTranslationContext, utils } from "@gnu-taler/web-util/browser";
+import { Amounts, FailCasesByMethod, TalerCoreBankErrorsByMethod, TalerError, TalerErrorDetail, TranslatedString, parsePaytoUri, parseWithdrawUri, stringifyWithdrawUri } from "@gnu-taler/taler-util";
+import { notify, notifyError, notifyInfo, useTranslationContext, utils } from "@gnu-taler/web-util/browser";
import { useEffect, useState } from "preact/hooks";
+import { mutate } from "swr";
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, withRuntimeErrorHandling } from "../../utils.js";
-import { Props, State } from "./index.js";
import { assertUnreachable } from "../WithdrawalOperationPage.js";
-import { mutate } from "swr";
+import { Props, State } from "./index.js";
export function useComponentState({ currency, onClose }: Props): utils.RecursiveState<State> {
- const { i18n } = useTranslationContext();
const [settings, updateSettings] = useSettings()
const { state: credentials } = useBackendState()
const creds = credentials.status !== "loggedIn" ? undefined : credentials
const { api } = useBankCoreApiContext()
- // const { createWithdrawal } = useAccessAPI();
- // const { abortWithdrawal, confirmWithdrawal } = useAccessAnonAPI();
- const [busy, setBusy] = useState<Record<string, undefined>>()
+ const [busy, setBusy] = useState<Record<string, undefined>>()
+ const [failure, setFailure] = useState<TalerCoreBankErrorsByMethod<"createWithdrawal"> | undefined>()
const amount = settings.maxWithdrawalAmount
async function doSilentStart() {
//FIXME: if amount is not enough use balance
const parsedAmount = Amounts.parseOrThrow(`${currency}:${amount}`)
if (!creds) return;
- await withRuntimeErrorHandling(i18n, async () => {
- const resp = await api.createWithdrawal(creds, {
- amount: Amounts.stringify(parsedAmount),
- });
- if (resp.type === "fail") {
- switch (resp.case) {
- case "insufficient-funds": return notify({
- type: "error",
- title: i18n.str`The operation was rejected due to insufficient funds.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- });
- case "unauthorized": return notify({
- type: "error",
- title: i18n.str`Unauthorized to make the opeartion, maybe the session has expired or the password changed.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- });
- default: assertUnreachable(resp)
- }
- }
+ const resp = await api.createWithdrawal(creds, {
+ amount: Amounts.stringify(parsedAmount),
+ });
+ if (resp.type === "fail") {
+ setFailure(resp)
+ return;
+ }
+ updateSettings("currentWithdrawalOperationId", resp.body.withdrawal_id)
- const uri = parseWithdrawUri(resp.body.taler_withdraw_uri);
- if (!uri) {
- return notifyError(
- i18n.str`Server responded with an invalid withdraw URI`,
- i18n.str`Withdraw URI: ${resp.body.taler_withdraw_uri}`);
- } else {
- updateSettings("currentWithdrawalOperationId", uri.withdrawalOperationId)
- }
- })
}
const withdrawalOperationId = settings.currentWithdrawalOperationId
@@ -82,6 +57,13 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
}
}, [settings.fastWithdrawal, amount])
+ if (failure) {
+ return {
+ status: "failed",
+ error: failure
+ }
+ }
+
if (!withdrawalOperationId) {
return {
status: "loading",
@@ -92,77 +74,24 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
const wid = withdrawalOperationId
async function doAbort() {
- await withRuntimeErrorHandling(i18n, async () => {
- const resp = await api.abortWithdrawalById(wid);
- if (resp.type === "ok") {
- updateSettings("currentWithdrawalOperationId", undefined)
- onClose();
- } else {
- switch (resp.case) {
- case "previously-confirmed": return notify({
- type: "error",
- title: i18n.str`The reserve operation has been confirmed previously and can't be aborted`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- case "invalid-id": return notify({
- type: "error",
- title: i18n.str`The operation id is invalid.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- });
- case "not-found": return notify({
- type: "error",
- title: i18n.str`The operation was not found.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- });
- default: assertUnreachable(resp)
- }
- }
- })
+ const resp = await api.abortWithdrawalById(wid);
+ if (resp.type === "ok") {
+ updateSettings("currentWithdrawalOperationId", undefined)
+ onClose();
+ } else {
+ return resp;
+ }
}
- async function doConfirm() {
+ async function doConfirm(): Promise<TalerCoreBankErrorsByMethod<"confirmWithdrawalById"> | undefined> {
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!`)
- }
- } else {
- switch (resp.case) {
- case "previously-aborted": return notify({
- type: "error",
- title: i18n.str`The withdrawal has been aborted previously and can't be confirmed`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- case "no-exchange-or-reserve-selected": return notify({
- type: "error",
- title: i18n.str`The withdraw operation cannot be confirmed because no exchange and reserve public key selection happened before`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- case "invalid-id": return notify({
- type: "error",
- title: i18n.str`The operation id is invalid.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- });
- case "not-found": return notify({
- type: "error",
- title: i18n.str`The operation was not found.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- });
- default: assertUnreachable(resp)
- }
- }
- })
+ const resp = await api.confirmWithdrawalById(wid);
setBusy(undefined)
+ if (resp.type === "ok") {
+ mutate(() => true)//clean withdrawal state
+ } else {
+ return resp;
+ }
}
const uri = stringifyWithdrawUri({
@@ -261,7 +190,6 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive
error: undefined,
uri: parsedUri,
onClose: doAbort,
- onAbort: doAbort,
}
}
diff --git a/packages/demobank-ui/src/pages/OperationState/views.tsx b/packages/demobank-ui/src/pages/OperationState/views.tsx
index 2cb7385db..b7d7e5520 100644
--- a/packages/demobank-ui/src/pages/OperationState/views.tsx
+++ b/packages/demobank-ui/src/pages/OperationState/views.tsx
@@ -14,8 +14,8 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { stringifyWithdrawUri } from "@gnu-taler/taler-util";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { TranslatedString, stringifyWithdrawUri } from "@gnu-taler/taler-util";
+import { notifyInfo, useLocalNotification, useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useEffect, useMemo, useState } from "preact/hooks";
import { QR } from "../../components/QR.js";
@@ -23,6 +23,10 @@ import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
import { useSettings } from "../../hooks/settings.js";
import { undefinedIfEmpty } from "../../utils.js";
import { State } from "./index.js";
+import { ShowLocalNotification } from "../../components/ShowLocalNotification.js";
+import { ErrorLoading } from "../../components/ErrorLoading.js";
+import { Attention } from "../../components/Attention.js";
+import { assertUnreachable } from "../WithdrawalOperationPage.js";
export function InvalidPaytoView({ payto, onClose }: State.InvalidPayto) {
return (
@@ -40,8 +44,10 @@ export function InvalidReserveView({ reserve, onClose }: State.InvalidReserve) {
);
}
-export function NeedConfirmationView({ error, onAbort, onConfirm, busy }: State.NeedConfirmation) {
+export function NeedConfirmationView({ error, onAbort: doAbort, onConfirm: doConfirm, busy }: State.NeedConfirmation) {
const { i18n } = useTranslationContext()
+ const [settings] = useSettings()
+ const [notification, notify, errorHandler] = useLocalNotification()
const captchaNumbers = useMemo(() => {
return {
@@ -61,8 +67,76 @@ export function NeedConfirmationView({ error, onAbort, onConfirm, busy }: State.
: undefined,
}) ?? (busy ? {} as Record<string, undefined> : undefined);
+ async function onCancel() {
+ errorHandler(async () => {
+ const resp = await doAbort()
+ if (!resp) return;
+ switch (resp.case) {
+ case "previously-confirmed": return notify({
+ type: "error",
+ title: i18n.str`The reserve operation has been confirmed previously and can't be aborted`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "invalid-id": return notify({
+ type: "error",
+ title: i18n.str`The operation id is invalid.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ });
+ case "not-found": return notify({
+ type: "error",
+ title: i18n.str`The operation was not found.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ });
+ default: assertUnreachable(resp)
+ }
+ })
+ }
+
+ async function onConfirm() {
+ errorHandler(async () => {
+ const hasError = await doConfirm()
+ if (!hasError) {
+ if (!settings.showWithdrawalSuccess) {
+ notifyInfo(i18n.str`Wire transfer completed!`)
+ }
+ return
+ }
+ switch (hasError.case) {
+ case "previously-aborted": return notify({
+ type: "error",
+ title: i18n.str`The withdrawal has been aborted previously and can't be confirmed`,
+ description: hasError.detail.hint as TranslatedString,
+ debug: hasError.detail,
+ })
+ case "no-exchange-or-reserve-selected": return notify({
+ type: "error",
+ title: i18n.str`The withdraw operation cannot be confirmed because no exchange and reserve public key selection happened before`,
+ description: hasError.detail.hint as TranslatedString,
+ debug: hasError.detail,
+ })
+ case "invalid-id": return notify({
+ type: "error",
+ title: i18n.str`The operation id is invalid.`,
+ description: hasError.detail.hint as TranslatedString,
+ debug: hasError.detail,
+ });
+ case "not-found": return notify({
+ type: "error",
+ title: i18n.str`The operation was not found.`,
+ description: hasError.detail.hint as TranslatedString,
+ debug: hasError.detail,
+ });
+ default: assertUnreachable(hasError)
+ }
+ })
+ }
+
return (
<div class="bg-white shadow sm:rounded-lg">
+ <ShowLocalNotification notification={notification} />
<div class="px-4 py-5 sm:p-6">
<h3 class="text-base font-semibold text-gray-900">
<i18n.Translate>Confirm the withdrawal operation</i18n.Translate>
@@ -161,7 +235,10 @@ export function NeedConfirmationView({ error, onAbort, onConfirm, busy }: State.
</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={onAbort}
+ onClick={(e) => {
+ e.preventDefault()
+ onCancel()
+ }}
>
<i18n.Translate>Cancel</i18n.Translate></button>
<button type="submit"
@@ -246,6 +323,25 @@ export function NeedConfirmationView({ error, onAbort, onConfirm, busy }: State.
);
}
+export function FailedView({ error }: State.Failed) {
+ const { i18n } = useTranslationContext();
+ switch (error.case) {
+ case "unauthorized": return <Attention type="danger"
+ title={i18n.str`Unauthorized to make the operation, maybe the session has expired or the password changed.`}>
+ <div class="mt-2 text-sm text-red-700">
+ {error.detail.hint}
+ </div>
+ </Attention>
+ case "insufficient-funds": return <Attention type="danger"
+ title={i18n.str`The operation was rejected due to insufficient funds.`}>
+ <div class="mt-2 text-sm text-red-700">
+ {error.detail.hint}
+ </div>
+ </Attention>
+ default: assertUnreachable(error)
+ }
+}
+
export function AbortedView({ error, onClose }: State.Aborted) {
return (
<div>aborted</div>
@@ -308,8 +404,9 @@ export function ConfirmedView({ error, onClose }: State.Confirmed) {
);
}
-export function ReadyView({ uri, onClose }: State.Ready): VNode<{}> {
+export function ReadyView({ uri, onClose: doClose }: State.Ready): VNode<{}> {
const { i18n } = useTranslationContext();
+ const [notification, notify, errorHandler] = useLocalNotification()
useEffect(() => {
//Taler Wallet WebExtension is listening to headers response and tab updates.
@@ -320,7 +417,38 @@ export function ReadyView({ uri, onClose }: State.Ready): VNode<{}> {
document.title = `${document.title} ${uri.withdrawalOperationId}`;
}, []);
const talerWithdrawUri = stringifyWithdrawUri(uri);
+
+ async function onClose() {
+ errorHandler(async () => {
+ const hasError = await doClose()
+ if (!hasError) return;
+ switch (hasError.case) {
+ case "previously-confirmed": return notify({
+ type: "error",
+ title: i18n.str`The reserve operation has been confirmed previously and can't be aborted`,
+ description: hasError.detail.hint as TranslatedString,
+ debug: hasError.detail,
+ })
+ case "invalid-id": return notify({
+ type: "error",
+ title: i18n.str`The operation id is invalid.`,
+ description: hasError.detail.hint as TranslatedString,
+ debug: hasError.detail,
+ });
+ case "not-found": return notify({
+ type: "error",
+ title: i18n.str`The operation was not found.`,
+ description: hasError.detail.hint as TranslatedString,
+ debug: hasError.detail,
+ });
+ default: assertUnreachable(hasError)
+ }
+ })
+ }
+
return <Fragment>
+ <ShowLocalNotification notification={notification} />
+
<div class="flex justify-end mt-4">
<button type="button"
class="inline-flex items-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500"
diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
index 63cb3e865..6649d224e 100644
--- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
@@ -18,33 +18,30 @@ import {
AmountJson,
AmountString,
Amounts,
- HttpStatusCode,
Logger,
- TalerError,
+ PaytoString,
TranslatedString,
buildPayto,
parsePaytoUri,
stringifyPaytoUri
} from "@gnu-taler/taler-util";
import {
- RequestError,
- notify,
- notifyError,
- useTranslationContext,
+ useLocalNotification,
+ useTranslationContext
} from "@gnu-taler/web-util/browser";
import { Fragment, Ref, VNode, h } from "preact";
-import { useEffect, useRef, useState } from "preact/hooks";
+import { useState } from "preact/hooks";
+import { mutate } from "swr";
import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
+import { useBankCoreApiContext } from "../context/config.js";
+import { useBackendState } from "../hooks/backend.js";
import {
- buildRequestErrorMessage,
undefinedIfEmpty,
validateIBAN,
- withRuntimeErrorHandling,
+ withRuntimeErrorHandling
} from "../utils.js";
-import { useBankCoreApiContext } from "../context/config.js";
-import { useBackendState } from "../hooks/backend.js";
import { assertUnreachable } from "./WithdrawalOperationPage.js";
-import { mutate } from "swr";
+import { ShowLocalNotification } from "../components/ShowLocalNotification.js";
const logger = new Logger("PaytoWireTransferForm");
@@ -82,6 +79,7 @@ export function PaytoWireTransferForm({
const trimmedAmountStr = amount?.trim();
const parsedAmount = Amounts.parse(`${limit.currency}:${trimmedAmountStr}`);
const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/;
+ const [notification, notify, handleError] = useLocalNotification()
const errorsWire = undefinedIfEmpty({
iban: !iban
@@ -122,7 +120,7 @@ export function PaytoWireTransferForm({
});
async function doSend() {
- let payto_uri: string | undefined;
+ let payto_uri: PaytoString | undefined;
let sendingAmount: AmountString | undefined;
if (credentials.status !== "loggedIn") return;
if (rawPaytoInput) {
@@ -141,7 +139,7 @@ export function PaytoWireTransferForm({
}
const puri = payto_uri;
- await withRuntimeErrorHandling(i18n, async () => {
+ await handleError(async () => {
const res = await api.createTransaction(credentials, {
payto_uri: puri,
amount: sendingAmount,
@@ -367,6 +365,7 @@ export function PaytoWireTransferForm({
<i18n.Translate>Send</i18n.Translate>
</button>
</div>
+ <ShowLocalNotification notification={notification} />
</form>
</div >
)
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx b/packages/demobank-ui/src/pages/QrCodeSection.tsx
index 9ae1cf268..ca2a89f48 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.tsx
+++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx
@@ -15,24 +15,21 @@
*/
import {
- HttpStatusCode,
stringifyWithdrawUri,
- TalerError,
TranslatedString,
- WithdrawUriResult,
+ WithdrawUriResult
} from "@gnu-taler/taler-util";
import {
- notify,
- notifyError,
- RequestError,
- useTranslationContext,
+ useLocalNotification,
+ useTranslationContext
} from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
import { useEffect } from "preact/hooks";
import { QR } from "../components/QR.js";
-import { buildRequestErrorMessage, withRuntimeErrorHandling } from "../utils.js";
import { useBankCoreApiContext } from "../context/config.js";
+import { withRuntimeErrorHandling } from "../utils.js";
import { assertUnreachable } from "./WithdrawalOperationPage.js";
+import { ShowLocalNotification } from "../components/ShowLocalNotification.js";
export function QrCodeSection({
withdrawUri,
@@ -51,18 +48,19 @@ export function QrCodeSection({
document.title = `${document.title} ${withdrawUri.withdrawalOperationId}`;
}, []);
const talerWithdrawUri = stringifyWithdrawUri(withdrawUri);
+ const [notification, notify, handleError] = useLocalNotification()
const { api } = useBankCoreApiContext()
async function doAbort() {
- await withRuntimeErrorHandling(i18n, async () => {
+ await handleError(async () => {
const resp = await api.abortWithdrawalById(withdrawUri.withdrawalOperationId);
if (resp.type === "ok") {
onAborted();
} else {
switch (resp.case) {
case "previously-confirmed": return notify({
- type: "info",
+ type: "error",
title: i18n.str`The reserve operation has been confirmed previously and can't be aborted`
})
case "invalid-id": return notify({
@@ -87,6 +85,7 @@ export function QrCodeSection({
return (
<Fragment>
+ <ShowLocalNotification notification={notification} />
<div class="bg-white shadow-xl sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-base font-semibold leading-6 text-gray-900">
diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx b/packages/demobank-ui/src/pages/RegistrationPage.tsx
index 3520405c5..fdf2c0e9d 100644
--- a/packages/demobank-ui/src/pages/RegistrationPage.tsx
+++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx
@@ -15,7 +15,7 @@
*/
import { AccessToken, Logger, TranslatedString } from "@gnu-taler/taler-util";
import {
- notify,
+ useLocalNotification,
useTranslationContext
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
@@ -26,6 +26,7 @@ import { useBackendState } from "../hooks/backend.js";
import { bankUiSettings } from "../settings.js";
import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
import { getRandomPassword, getRandomUsername } from "./rnd.js";
+import { ShowLocalNotification } from "../components/ShowLocalNotification.js";
const logger = new Logger("RegistrationPage");
@@ -60,6 +61,7 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
const [phone, setPhone] = useState<string | undefined>();
const [email, setEmail] = useState<string | undefined>();
const [repeatPassword, setRepeatPassword] = useState<string | undefined>();
+ const [notification, notify, handleError] = useLocalNotification()
const { api } = useBankCoreApiContext()
// const { register } = useTestingAPI();
@@ -93,7 +95,7 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
});
async function doRegistrationAndLogin(name: string | undefined, username: string, password: string) {
- await withRuntimeErrorHandling(i18n, async () => {
+ await handleError(async () => {
const creationResponse = await api.createAccount("" as AccessToken, { name: name ?? "", username, password });
if (creationResponse.type === "fail") {
switch (creationResponse.case) {
@@ -171,7 +173,7 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
return (
<Fragment>
- <h1 class="nav"></h1>
+ <ShowLocalNotification notification={notification} />
<div class="flex min-h-full flex-col justify-center">
<div class="sm:mx-auto sm:w-full sm:max-w-sm">
diff --git a/packages/demobank-ui/src/pages/ShowAccountDetails.tsx b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
index b109441a6..eb8ea8f20 100644
--- a/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
+++ b/packages/demobank-ui/src/pages/ShowAccountDetails.tsx
@@ -1,5 +1,5 @@
import { TalerCorebankApi, TalerError, TranslatedString } from "@gnu-taler/taler-util";
-import { notify, notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { notifyInfo, useLocalNotification, useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { ErrorLoading } from "../components/ErrorLoading.js";
@@ -8,10 +8,11 @@ import { useBankCoreApiContext } from "../context/config.js";
import { useAccountDetails } from "../hooks/access.js";
import { useBackendState } from "../hooks/backend.js";
import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
-import { assertUnreachable } from "./WithdrawalOperationPage.js";
import { LoginForm } from "./LoginForm.js";
-import { AccountForm } from "./admin/AccountForm.js";
import { ProfileNavigation } from "./ProfileNavigation.js";
+import { assertUnreachable } from "./WithdrawalOperationPage.js";
+import { AccountForm } from "./admin/AccountForm.js";
+import { ShowLocalNotification } from "../components/ShowLocalNotification.js";
export function ShowAccountDetails({
account,
@@ -31,6 +32,7 @@ export function ShowAccountDetails({
const [update, setUpdate] = useState(false);
const [submitAccount, setSubmitAccount] = useState<TalerCorebankApi.AccountData | undefined>();
+ const [notification, notify, handleError] = useLocalNotification()
const result = useAccountDetails(account);
if (!result) {
@@ -50,7 +52,7 @@ export function ShowAccountDetails({
async function doUpdate() {
if (!update || !submitAccount || !creds) return;
- await withRuntimeErrorHandling(i18n, async () => {
+ await handleError(async () => {
const resp = await api.updateAccount(creds, {
cashout_address: submitAccount.cashout_payto_uri,
challenge_contact_data: undefinedIfEmpty({
@@ -93,6 +95,7 @@ export function ShowAccountDetails({
return (
<Fragment>
+ <ShowLocalNotification notification={notification} />
{accountIsTheCurrentUser ?
<ProfileNavigation current="details" />
:
diff --git a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
index b14c6d90b..d30216f3f 100644
--- a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
+++ b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx
@@ -1,13 +1,14 @@
-import { notify, notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { notifyInfo, useLocalNotification, useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
import { useBankCoreApiContext } from "../context/config.js";
import { useBackendState } from "../hooks/backend.js";
import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
-import { assertUnreachable } from "./WithdrawalOperationPage.js";
import { doAutoFocus } from "./PaytoWireTransferForm.js";
import { ProfileNavigation } from "./ProfileNavigation.js";
+import { assertUnreachable } from "./WithdrawalOperationPage.js";
+import { ShowLocalNotification } from "../components/ShowLocalNotification.js";
export function UpdateAccountPassword({
account: accountName,
@@ -41,11 +42,12 @@ export function UpdateAccountPassword({
? i18n.str`password doesn't match`
: undefined,
});
+ const [notification, notify, handleError] = useLocalNotification()
async function doChangePassword() {
if (!!errors || !password || !token) return;
- await withRuntimeErrorHandling(i18n, async () => {
+ await handleError(async () => {
const resp = await api.updatePassword({ username: accountName, token }, {
old_password: current,
new_password: password,
@@ -77,6 +79,7 @@ export function UpdateAccountPassword({
return (
<Fragment>
+ <ShowLocalNotification notification={notification} />
{accountIsTheCurrentUser ?
<ProfileNavigation current="credentials" /> :
<h1 class="text-base font-semibold leading-6 text-gray-900">
diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
index 0637a8af4..abdebf9bf 100644
--- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
+++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx
@@ -17,17 +17,14 @@
import {
AmountJson,
Amounts,
- HttpStatusCode,
Logger,
- TalerError,
TranslatedString,
parseWithdrawUri
} from "@gnu-taler/taler-util";
import {
- RequestError,
- notify,
notifyError,
- useTranslationContext,
+ useLocalNotification,
+ useTranslationContext
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { forwardRef } from "preact/compat";
@@ -36,10 +33,11 @@ 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, withRuntimeErrorHandling } from "../utils.js";
-import { assertUnreachable } from "./WithdrawalOperationPage.js";
+import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
import { OperationState } from "./OperationState/index.js";
import { InputAmount, doAutoFocus } from "./PaytoWireTransferForm.js";
+import { assertUnreachable } from "./WithdrawalOperationPage.js";
+import { ShowLocalNotification } from "../components/ShowLocalNotification.js";
const logger = new Logger("WalletWithdrawForm");
const RefAmount = forwardRef(InputAmount);
@@ -59,6 +57,7 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
const { api } = useBankCoreApiContext()
const [amountStr, setAmountStr] = useState<string | undefined>(`${settings.maxWithdrawalAmount}`);
+ const [notification, notify, handleError] = useLocalNotification()
if (!!settings.currentWithdrawalOperationId) {
return <Attention type="warning" title={i18n.str`There is an operation already`}>
@@ -88,7 +87,7 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
async function doStart() {
if (!parsedAmount || !creds) return;
- await withRuntimeErrorHandling(i18n, async () => {
+ await handleError(async () => {
const resp = await api.createWithdrawal(creds, {
amount: Amounts.stringify(parsedAmount),
});
@@ -136,6 +135,8 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: {
e.preventDefault()
}}
>
+ <ShowLocalNotification notification={notification} />
+
<div class="px-4 py-6 ">
<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">
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
index 5e0fa322f..89538e305 100644
--- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -16,32 +16,28 @@
import {
AmountJson,
- Amounts,
- HttpStatusCode,
Logger,
PaytoUri,
PaytoUriIBAN,
PaytoUriTalerBank,
- TalerError,
TranslatedString,
WithdrawUriResult
} from "@gnu-taler/taler-util";
import {
- RequestError,
- notify,
- notifyError,
notifyInfo,
- useTranslationContext,
+ useLocalNotification,
+ useTranslationContext
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useMemo, useState } from "preact/hooks";
+import { mutate } from "swr";
import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
-import { buildRequestErrorMessage, undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
+import { useBankCoreApiContext } from "../context/config.js";
import { useSettings } from "../hooks/settings.js";
+import { undefinedIfEmpty, withRuntimeErrorHandling } from "../utils.js";
import { RenderAmount } from "./PaytoWireTransferForm.js";
-import { useBankCoreApiContext } from "../context/config.js";
import { assertUnreachable } from "./WithdrawalOperationPage.js";
-import { mutate } from "swr";
+import { ShowLocalNotification } from "../components/ShowLocalNotification.js";
const logger = new Logger("WithdrawalConfirmationQuestion");
@@ -72,6 +68,7 @@ export function WithdrawalConfirmationQuestion({
b: Math.floor(Math.random() * 10),
};
}, []);
+ const [notification, notify, handleError] = useLocalNotification()
const { api } = useBankCoreApiContext()
const [captchaAnswer, setCaptchaAnswer] = useState<string | undefined>();
@@ -89,7 +86,7 @@ export function WithdrawalConfirmationQuestion({
async function doTransfer() {
setBusy({})
- await withRuntimeErrorHandling(i18n, async () => {
+ await handleError(async () => {
const resp = await api.confirmWithdrawalById(withdrawUri.withdrawalOperationId);
if (resp.type === "ok") {
mutate(() => true)// clean any info that we have
@@ -131,7 +128,7 @@ export function WithdrawalConfirmationQuestion({
async function doCancel() {
setBusy({})
- await withRuntimeErrorHandling(i18n, async () => {
+ await handleError(async () => {
const resp = await api.abortWithdrawalById(withdrawUri.withdrawalOperationId);
if (resp.type === "ok") {
onAborted();
@@ -164,6 +161,8 @@ export function WithdrawalConfirmationQuestion({
return (
<Fragment>
+ <ShowLocalNotification notification={notification} />
+
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-base font-semibold text-gray-900">
diff --git a/packages/demobank-ui/src/pages/admin/Account.tsx b/packages/demobank-ui/src/pages/admin/Account.tsx
index 1818de655..7109b082f 100644
--- a/packages/demobank-ui/src/pages/admin/Account.tsx
+++ b/packages/demobank-ui/src/pages/admin/Account.tsx
@@ -23,9 +23,9 @@ export function WireTransfer({ toAccount, onRegister, onCancel, onSuccess }: { o
}
if (result.type === "fail") {
switch (result.case) {
- case "unauthorized": return <LoginForm reason="forbidden" onRegister={onRegister} />
- case "not-found": return <LoginForm reason="not-found" onRegister={onRegister} />
- case "no-rights": return <LoginForm reason="not-found" onRegister={onRegister} />
+ case "unauthorized": return <LoginForm reason="forbidden" />
+ case "not-found": return <LoginForm reason="not-found" />
+ case "no-rights": return <LoginForm reason="not-found" />
default: assertUnreachable(result)
}
}
diff --git a/packages/demobank-ui/src/pages/admin/AccountForm.tsx b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
index 410683dcb..fa3a28057 100644
--- a/packages/demobank-ui/src/pages/admin/AccountForm.tsx
+++ b/packages/demobank-ui/src/pages/admin/AccountForm.tsx
@@ -3,7 +3,7 @@ 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 { PaytoString, TalerCorebankApi, buildPayto, parsePaytoUri } from "@gnu-taler/taler-util";
import { doAutoFocus } from "../PaytoWireTransferForm.js";
import { CopyButton } from "../../components/CopyButton.js";
import { assertUnreachable } from "../WithdrawalOperationPage.js";
@@ -52,7 +52,7 @@ export function AccountForm({
: buildPayto("iban", newForm.cashout_payto_uri, undefined);;
const errors = undefinedIfEmpty<RecursivePartial<typeof initial>>({
- cashout_payto_uri: !newForm.cashout_payto_uri
+ cashout_payto_uri: (!newForm.cashout_payto_uri
? i18n.str`required`
: !parsed
? i18n.str`does not follow the pattern`
@@ -60,7 +60,7 @@ export function AccountForm({
? i18n.str`only "IBAN" target are supported`
: !IBAN_REGEX.test(parsed.iban)
? i18n.str`IBAN should have just uppercased letters and numbers`
- : validateIBAN(parsed.iban, i18n),
+ : validateIBAN(parsed.iban, i18n)) as PaytoString,
contact_data: undefinedIfEmpty({
email: !newForm.contact_data?.email
? i18n.str`required`
@@ -165,7 +165,7 @@ export function AccountForm({
</div>
- {purpose !== "create" && (<RenderPaytoDisabledField paytoURI={form.payto_uri} />)}
+ {purpose !== "create" && (<RenderPaytoDisabledField paytoURI={form.payto_uri as PaytoString} />)}
<div class="sm:col-span-5">
<label
@@ -252,7 +252,7 @@ export function AccountForm({
disabled={purpose === "show"}
value={form.cashout_payto_uri ?? ""}
onChange={(e) => {
- form.cashout_payto_uri = e.currentTarget.value;
+ form.cashout_payto_uri = e.currentTarget.value as PaytoString;
updateForm(structuredClone(form));
}}
autocomplete="off"
@@ -303,7 +303,7 @@ function initializeFromTemplate(
}
-function RenderPaytoDisabledField({ paytoURI }: { paytoURI: string | undefined }): VNode {
+function RenderPaytoDisabledField({ paytoURI }: { paytoURI: PaytoString | undefined }): VNode {
const { i18n } = useTranslationContext()
const payto = parsePaytoUri(paytoURI ?? "");
if (payto?.isKnown) {
diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
index ea40001c0..f2c1d5456 100644
--- a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx
@@ -1,15 +1,16 @@
-import { HttpStatusCode, TalerCorebankApi, TalerError, TranslatedString } from "@gnu-taler/taler-util";
-import { RequestError, notify, notifyError, notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { TalerCorebankApi, TranslatedString } from "@gnu-taler/taler-util";
+import { notifyInfo, useLocalNotification, 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, AccountFormData } from "./AccountForm.js";
-import { useBackendState } from "../../hooks/backend.js";
-import { useBankCoreApiContext } from "../../context/config.js";
-import { assertUnreachable } from "../WithdrawalOperationPage.js";
import { mutate } from "swr";
import { Attention } from "../../components/Attention.js";
+import { useBankCoreApiContext } from "../../context/config.js";
+import { useBackendState } from "../../hooks/backend.js";
+import { withRuntimeErrorHandling } from "../../utils.js";
+import { assertUnreachable } from "../WithdrawalOperationPage.js";
+import { getRandomPassword } from "../rnd.js";
+import { AccountForm, AccountFormData } from "./AccountForm.js";
+import { ShowLocalNotification } from "../../components/ShowLocalNotification.js";
export function CreateNewAccount({
onCancel,
@@ -25,10 +26,11 @@ export function CreateNewAccount({
const { api } = useBankCoreApiContext();
const [submitAccount, setSubmitAccount] = useState<AccountFormData | undefined>();
+ const [notification, notify, handleError] = useLocalNotification()
async function doCreate() {
if (!submitAccount || !token) return;
- await withRuntimeErrorHandling(i18n, async () => {
+ await handleError(async () => {
const account: TalerCorebankApi.RegisterAccountRequest = {
cashout_payto_uri: submitAccount.cashout_payto_uri,
challenge_contact_data: submitAccount.contact_data,
@@ -85,6 +87,8 @@ export function CreateNewAccount({
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">
+ <ShowLocalNotification notification={notification} />
+
<div class="px-4 sm:px-0">
<h2 class="text-base font-semibold leading-7 text-gray-900">
<i18n.Translate>New business account</i18n.Translate>
diff --git a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
index 89f634080..1a5255595 100644
--- a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
+++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx
@@ -1,18 +1,19 @@
-import { Amounts, HttpStatusCode, TalerError, TranslatedString } from "@gnu-taler/taler-util";
-import { HttpResponsePaginated, RequestError, notify, notifyError, notifyInfo, useTranslationContext } from "@gnu-taler/web-util/browser";
+import { Amounts, TalerError, TranslatedString } from "@gnu-taler/taler-util";
+import { notifyInfo, useLocalNotification, useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { Attention } from "../../components/Attention.js";
import { ErrorLoading } from "../../components/ErrorLoading.js";
import { Loading } from "../../components/Loading.js";
import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
+import { useBankCoreApiContext } from "../../context/config.js";
import { useAccountDetails } from "../../hooks/access.js";
-import { buildRequestErrorMessage, undefinedIfEmpty, withRuntimeErrorHandling } from "../../utils.js";
-import { assertUnreachable } from "../WithdrawalOperationPage.js";
+import { useBackendState } from "../../hooks/backend.js";
+import { undefinedIfEmpty } from "../../utils.js";
import { LoginForm } from "../LoginForm.js";
import { doAutoFocus } from "../PaytoWireTransferForm.js";
-import { useBankCoreApiContext } from "../../context/config.js";
-import { useBackendState } from "../../hooks/backend.js";
+import { assertUnreachable } from "../WithdrawalOperationPage.js";
+import { ShowLocalNotification } from "../../components/ShowLocalNotification.js";
export function RemoveAccount({
account,
@@ -32,6 +33,7 @@ export function RemoveAccount({
const { state } = useBackendState();
const token = state.status !== "loggedIn" ? undefined : state.token
const { api } = useBankCoreApiContext()
+ const [notification, notify, handleError] = useLocalNotification()
if (!result) {
return <Loading />
@@ -61,7 +63,7 @@ export function RemoveAccount({
async function doRemove() {
if (!token) return;
- await withRuntimeErrorHandling(i18n, async () => {
+ await handleError(async () => {
const resp = await api.deleteAccount({ username: account, token });
if (resp.type === "ok") {
notifyInfo(i18n.str`Account removed`);
@@ -111,6 +113,8 @@ export function RemoveAccount({
return (
<div>
+ <ShowLocalNotification notification={notification} />
+
<Attention type="warning" title={i18n.str`You are going to remove the account`}>
<i18n.Translate>This step can't be undone.</i18n.Translate>
</Attention>
diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
index a71915622..8d90e9205 100644
--- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx
+++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx
@@ -19,7 +19,7 @@ import {
TranslatedString
} from "@gnu-taler/taler-util";
import {
- notify,
+ useLocalNotification,
useTranslationContext
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
@@ -43,6 +43,7 @@ import {
import { LoginForm } from "../LoginForm.js";
import { InputAmount } from "../PaytoWireTransferForm.js";
import { assertUnreachable } from "../WithdrawalOperationPage.js";
+import { ShowLocalNotification } from "../../components/ShowLocalNotification.js";
interface Props {
account: string;
@@ -76,6 +77,7 @@ export function CreateCashout({
const creds = state.status !== "loggedIn" ? undefined : state
const { api, config } = useBankCoreApiContext()
const [form, setForm] = useState<Partial<FormType>>({ isDebit: true });
+ const [notification, notify, handleError] = useLocalNotification()
if (!config.have_cashout) {
return <Attention type="warning" title={i18n.str`Unable to create a cashout`} onClose={onCancel}>
@@ -144,7 +146,7 @@ export function CreateCashout({
useEffect(() => {
async function doAsync() {
- await withRuntimeErrorHandling(i18n, async () => {
+ await handleError(async () => {
const resp = await (form.isDebit ?
calculateFromDebit(amount, sellFee, safeSellRate) :
calculateFromCredit(amount, sellFee, safeSellRate));
@@ -176,6 +178,7 @@ export function CreateCashout({
return (
<div>
+ <ShowLocalNotification notification={notification} />
<h1>New cashout</h1>
<form class="pure-form">
<fieldset>
@@ -360,7 +363,7 @@ export function CreateCashout({
e.preventDefault();
if (errors || !creds) return;
- await withRuntimeErrorHandling(i18n, async () => {
+ await handleError(async () => {
const resp = await api.createCashout(creds, {
amount_credit: Amounts.stringify(calc.credit),
amount_debit: Amounts.stringify(calc.debit),
diff --git a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
index b8e566348..7e7ed21cb 100644
--- a/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
+++ b/packages/demobank-ui/src/pages/business/ShowCashoutDetails.tsx
@@ -18,13 +18,14 @@ import {
TranslatedString
} from "@gnu-taler/taler-util";
import {
- notify,
+ useLocalNotification,
useTranslationContext
} from "@gnu-taler/web-util/browser";
import { format } from "date-fns";
import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { mutate } from "swr";
+import { Attention } from "../../components/Attention.js";
import { ErrorLoading } from "../../components/ErrorLoading.js";
import { Loading } from "../../components/Loading.js";
import { ShowInputErrorLabel } from "../../components/ShowInputErrorLabel.js";
@@ -38,7 +39,7 @@ import {
withRuntimeErrorHandling
} from "../../utils.js";
import { assertUnreachable } from "../WithdrawalOperationPage.js";
-import { Attention } from "../../components/Attention.js";
+import { ShowLocalNotification } from "../../components/ShowLocalNotification.js";
interface Props {
id: string;
@@ -54,6 +55,7 @@ export function ShowCashoutDetails({
const { api } = useBankCoreApiContext()
const result = useCashoutDetails(id);
const [code, setCode] = useState<string | undefined>(undefined);
+ const [notification, notify, handleError] = useLocalNotification()
if (!result) {
return <Loading />
@@ -76,6 +78,7 @@ export function ShowCashoutDetails({
const isPending = String(result.body.status).toUpperCase() === "PENDING";
return (
<div>
+ <ShowLocalNotification notification={notification} />
<h1>Cashout details {id}</h1>
<form class="pure-form">
<fieldset>
@@ -161,7 +164,7 @@ export function ShowCashoutDetails({
onClick={async (e) => {
e.preventDefault();
if (!creds) return;
- await withRuntimeErrorHandling(i18n, async () => {
+ await handleError(async () => {
const resp = await api.abortCashoutById(creds, id);
if (resp.type === "ok") {
onCancel();
@@ -203,7 +206,7 @@ export function ShowCashoutDetails({
onClick={async (e) => {
e.preventDefault();
if (!creds || !code) return;
- await withRuntimeErrorHandling(i18n, async () => {
+ await handleError(async () => {
const resp = await api.confirmCashoutById(creds, id, {
tan: code,
});