diff options
Diffstat (limited to 'packages/demobank-ui/src/pages/OperationState/views.tsx')
-rw-r--r-- | packages/demobank-ui/src/pages/OperationState/views.tsx | 138 |
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" |