summaryrefslogtreecommitdiff
path: root/packages/demobank-ui/src/pages/OperationState/views.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/demobank-ui/src/pages/OperationState/views.tsx')
-rw-r--r--packages/demobank-ui/src/pages/OperationState/views.tsx138
1 files changed, 133 insertions, 5 deletions
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"