/* This file is part of GNU Taler (C) 2022 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ 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"; 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 (
Payto from server is not valid "{payto}"
); } export function InvalidWithdrawalView({ uri, onClose }: State.InvalidWithdrawal) { return (
Withdrawal uri from server is not valid "{uri}"
); } export function InvalidReserveView({ reserve, onClose }: State.InvalidReserve) { return (
Reserve from server is not valid "{reserve}"
); } 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 { a: Math.floor(Math.random() * 10), b: Math.floor(Math.random() * 10), }; }, []); const [captchaAnswer, setCaptchaAnswer] = useState(); const answer = parseInt(captchaAnswer ?? "", 10); const errors = undefinedIfEmpty({ answer: !captchaAnswer ? i18n.str`Answer the question before continue` : Number.isNaN(answer) ? i18n.str`The answer should be a number` : answer !== captchaNumbers.a + captchaNumbers.b ? i18n.str`The answer "${answer}" to "${captchaNumbers.a} + ${captchaNumbers.b}" is wrong.` : undefined, }) ?? (busy ? {} as Record : 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, }); case "insufficient-funds": return notify({ type: "error", title: i18n.str`Your balance is not enough.`, description: hasError.detail.hint as TranslatedString, debug: hasError.detail, }); default: assertUnreachable(hasError) } }) } return (

Confirm the withdrawal operation

{ e.preventDefault() }} >
{ setCaptchaAnswer(e.currentTarget.value) }} />
{/*

Wire transfer details

{((): VNode => { switch (details.account.targetType) { case "iban": { const p = details.account as PaytoUriIBAN const name = p.params["receiver-name"] return
Exchange account
{p.iban}
{name &&
Exchange name
{p.params["receiver-name"]}
}
} case "x-taler-bank": { const p = details.account as PaytoUriTalerBank const name = p.params["receiver-name"] return
Exchange account
{p.account}
{name &&
Exchange name
{p.params["receiver-name"]}
}
} default: return
Exchange account
{details.account.targetPath}
} })()}
Withdrawal identification
{details.reserve}
Amount
To be added
// {/* Amounts.stringifyValue(details.amount)
*/}
); } export function FailedView({ error }: State.Failed) { const { i18n } = useTranslationContext(); switch (error.case) { case "unauthorized": return
{error.detail.hint}
case "insufficient-funds": return
{error.detail.hint}
case "account-not-found": return
{error.detail.hint}
default: assertUnreachable(error) } } export function AbortedView({ error, onClose }: State.Aborted) { return (
aborted
); } export function ConfirmedView({ error, onClose }: State.Confirmed) { const { i18n } = useTranslationContext(); const [settings, updateSettings] = useSettings() return (

The wire transfer to the Taler operator has been initiated. You will soon receive the requested amount in your Taler wallet.

Do not show this again
); } export function ReadyView({ uri, onClose: doClose }: State.Ready): VNode<{}> { const { i18n } = useTranslationContext(); const [notification, notify, errorHandler] = useLocalNotification() const talerWithdrawUri = stringifyWithdrawUri(uri); useEffect(() => { //Taler Wallet WebExtension is listening to headers response and tab updates. //In the SPA there is no header response with the Taler URI so //this hack manually triggers the tab update after the QR is in the DOM. // WebExtension will be using // https://developer.chrome.com/docs/extensions/reference/tabs/#event-onUpdated document.title = `${document.title} ${uri.withdrawalOperationId}`; const meta = document.createElement("meta") meta.setAttribute("name", "taler-uri") meta.setAttribute("content", talerWithdrawUri) document.head.insertBefore(meta, document.head.children.length ? document.head.children[0] : null) }, []); 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

On this device

If you are using a desktop browser you can open the popup now or click the link if you have the "Inject Taler support" option enabled.

On a mobile phone

Scan the QR code with your mobile device.

}