wallet-core/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx

198 lines
7.1 KiB
TypeScript
Raw Normal View History

2022-12-09 13:09:20 +01:00
/*
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 <http://www.gnu.org/licenses/>
*/
import {
HttpStatusCode,
Logger,
WithdrawUriResult,
} from "@gnu-taler/taler-util";
2023-02-28 23:03:43 +01:00
import {
RequestError,
useTranslationContext,
2023-05-05 13:36:48 +02:00
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
2023-02-08 21:41:19 +01:00
import { useMemo, useState } from "preact/hooks";
import { useAccessAnonAPI } from "../hooks/access.js";
import { notifyError } from "../hooks/notification.js";
2023-02-28 23:03:43 +01:00
import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
2023-02-08 21:41:19 +01:00
import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
2022-12-07 16:38:50 +01:00
const logger = new Logger("WithdrawalConfirmationQuestion");
2023-02-08 21:41:19 +01:00
interface Props {
2023-02-28 23:03:43 +01:00
onConfirmed: () => void;
onAborted: () => void;
withdrawUri: WithdrawUriResult;
2023-02-08 21:41:19 +01:00
}
2022-12-07 16:38:50 +01:00
/**
* Additional authentication required to complete the operation.
* Not providing a back button, only abort.
*/
2023-02-08 21:41:19 +01:00
export function WithdrawalConfirmationQuestion({
2023-02-28 23:03:43 +01:00
onConfirmed,
onAborted,
withdrawUri,
2023-02-08 21:41:19 +01:00
}: Props): VNode {
2022-12-07 16:38:50 +01:00
const { i18n } = useTranslationContext();
2022-12-22 22:38:47 +01:00
const captchaNumbers = useMemo(() => {
return {
a: Math.floor(Math.random() * 10),
b: Math.floor(Math.random() * 10),
};
2023-02-08 21:41:19 +01:00
}, []);
2022-12-22 22:38:47 +01:00
const { confirmWithdrawal, abortWithdrawal } = useAccessAnonAPI();
2022-12-22 22:38:47 +01:00
const [captchaAnswer, setCaptchaAnswer] = useState<string | undefined>();
2023-02-08 21:41:19 +01:00
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,
});
2022-12-07 16:38:50 +01:00
return (
<Fragment>
<h1 class="nav">{i18n.str`Confirm Withdrawal`}</h1>
<article>
<div class="challenge-div">
2022-12-22 22:38:21 +01:00
<form
class="challenge-form"
noValidate
onSubmit={(e) => {
e.preventDefault();
}}
autoCapitalize="none"
autoCorrect="off"
2022-12-22 22:38:21 +01:00
>
2022-12-07 16:38:50 +01:00
<div class="pure-form" id="captcha" name="capcha-form">
<h2>{i18n.str`Authorize withdrawal by solving challenge`}</h2>
<p>
<label for="answer">
{i18n.str`What is`}&nbsp;
<em>
{captchaNumbers.a}&nbsp;+&nbsp;{captchaNumbers.b}
</em>
?&nbsp;
</label>
&nbsp;
<input
name="answer"
id="answer"
2022-12-22 22:38:47 +01:00
value={captchaAnswer ?? ""}
2022-12-07 16:38:50 +01:00
type="text"
autoFocus
required
onInput={(e): void => {
2022-12-22 22:38:47 +01:00
setCaptchaAnswer(e.currentTarget.value);
2022-12-07 16:38:50 +01:00
}}
/>
2023-02-08 21:41:19 +01:00
<ShowInputErrorLabel
message={errors?.answer}
isDirty={captchaAnswer !== undefined}
/>
2022-12-07 16:38:50 +01:00
</p>
<p>
<button
2022-12-22 22:38:21 +01:00
type="submit"
2022-12-07 16:38:50 +01:00
class="pure-button pure-button-primary btn-confirm"
2023-02-08 21:41:19 +01:00
disabled={!!errors}
2022-12-22 22:38:21 +01:00
onClick={async (e) => {
2022-12-07 16:38:50 +01:00
e.preventDefault();
2023-02-08 21:41:19 +01:00
try {
await confirmWithdrawal(
withdrawUri.withdrawalOperationId,
);
2023-02-28 23:03:43 +01:00
onConfirmed();
2023-02-08 21:41:19 +01:00
} catch (error) {
2023-02-28 23:03:43 +01:00
if (error instanceof RequestError) {
notifyError(
2023-02-28 23:03:43 +01:00
buildRequestErrorMessage(i18n, error.cause, {
onClientError: (status) =>
status === HttpStatusCode.Conflict
? i18n.str`The withdrawal has been aborted previously and can't be confirmed`
: status === HttpStatusCode.UnprocessableEntity
? i18n.str`The withdraw operation cannot be confirmed because no exchange and reserve public key selection happened before`
: undefined,
}),
);
} else {
notifyError({
2023-02-28 23:03:43 +01:00
title: i18n.str`Operation failed, please report`,
description:
error instanceof Error
? error.message
: JSON.stringify(error),
});
}
2022-12-07 16:38:50 +01:00
}
}}
>
{i18n.str`Confirm`}
</button>
&nbsp;
<button
class="pure-button pure-button-secondary btn-cancel"
2022-12-22 22:38:21 +01:00
onClick={async (e) => {
e.preventDefault();
2023-02-08 21:41:19 +01:00
try {
await abortWithdrawal(withdrawUri.withdrawalOperationId);
2023-02-28 23:03:43 +01:00
onAborted();
2023-02-08 21:41:19 +01:00
} catch (error) {
2023-02-28 23:03:43 +01:00
if (error instanceof RequestError) {
notifyError(
2023-02-28 23:03:43 +01:00
buildRequestErrorMessage(i18n, error.cause, {
onClientError: (status) =>
status === HttpStatusCode.Conflict
? i18n.str`The reserve operation has been confirmed previously and can't be aborted`
: undefined,
}),
);
} else {
notifyError({
2023-02-28 23:03:43 +01:00
title: i18n.str`Operation failed, please report`,
description:
error instanceof Error
? error.message
: JSON.stringify(error),
});
}
2023-02-08 21:41:19 +01:00
}
2022-12-22 22:38:21 +01:00
}}
2022-12-07 16:38:50 +01:00
>
{i18n.str`Cancel`}
</button>
</p>
</div>
</form>
<div class="hint">
<p>
<i18n.Translate>
A this point, a <b>real</b> bank would ask for an additional
authentication proof (PIN/TAN, one time password, ..), instead
of a simple calculation.
</i18n.Translate>
</p>
</div>
</div>
</article>
</Fragment>
);
}