diff options
Diffstat (limited to 'packages/bank-ui/src/pages/WithdrawalQRCode.tsx')
-rw-r--r-- | packages/bank-ui/src/pages/WithdrawalQRCode.tsx | 310 |
1 files changed, 310 insertions, 0 deletions
diff --git a/packages/bank-ui/src/pages/WithdrawalQRCode.tsx b/packages/bank-ui/src/pages/WithdrawalQRCode.tsx new file mode 100644 index 000000000..b61f0cc8f --- /dev/null +++ b/packages/bank-ui/src/pages/WithdrawalQRCode.tsx @@ -0,0 +1,310 @@ +/* + This file is part of GNU Taler + (C) 2022-2024 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 { + Amounts, + HttpStatusCode, + TalerError, + WithdrawUriResult, + assertUnreachable, + parsePaytoUri, +} from "@gnu-taler/taler-util"; +import { + Attention, + Loading, + notifyInfo, + useTranslationContext, +} from "@gnu-taler/web-util/browser"; +import { VNode, h } from "preact"; +import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js"; +import { useWithdrawalDetails } from "../hooks/account.js"; +import { RouteDefinition } from "@gnu-taler/web-util/browser"; +import { QrCodeSection } from "./QrCodeSection.js"; +import { WithdrawalConfirmationQuestion } from "./WithdrawalConfirmationQuestion.js"; + +interface Props { + withdrawUri: WithdrawUriResult; + onOperationAborted: () => void; + routeClose: RouteDefinition; + routeWithdrawalDetails: RouteDefinition<{ wopid: string }>; + onAuthorizationRequired: () => void; +} +/** + * Offer the QR code (and a clickable taler://-link) to + * permit the passing of exchange and reserve details to + * the bank. Poll the backend until such operation is done. + */ +export function WithdrawalQRCode({ + withdrawUri, + onOperationAborted, + routeClose, + routeWithdrawalDetails, + onAuthorizationRequired, +}: Props): VNode { + const { i18n } = useTranslationContext(); + const result = useWithdrawalDetails(withdrawUri.withdrawalOperationId); + + if (!result) { + return <Loading />; + } + if (result instanceof TalerError) { + return <ErrorLoadingWithDebug error={result} />; + } + if (result.type === "fail") { + switch (result.case) { + case HttpStatusCode.BadRequest: + case HttpStatusCode.NotFound: + return <OperationNotFound routeClose={routeClose} />; + default: + assertUnreachable(result); + } + } + + const { body: data } = result; + + if (data.status === "aborted") { + return ( + <div class="relative ml-auto mr-auto transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6"> + <div> + <div class="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-yellow-100"> + <svg + class="h-5 w-5 text-yellow-400" + viewBox="0 0 20 20" + fill="currentColor" + aria-hidden="true" + > + <path + fill-rule="evenodd" + d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" + clip-rule="evenodd" + /> + </svg> + </div> + <div class="mt-3 text-center sm:mt-5"> + <h3 + class="text-base font-semibold leading-6 text-gray-900" + id="modal-title" + > + <i18n.Translate>Operation aborted</i18n.Translate> + </h3> + <div class="mt-2"> + <p class="text-sm text-gray-500"> + <i18n.Translate> + The wire transfer to the payment provider's account was + aborted from somewhere else, your balance was not affected. + </i18n.Translate> + </p> + </div> + </div> + </div> + <div class="mt-5 sm:mt-6"> + <a + href={routeClose.url({})} + name="continue" + class="inline-flex w-full justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" + > + <i18n.Translate>Continue</i18n.Translate> + </a> + </div> + </div> + ); + } + + if (data.status === "confirmed") { + return ( + <div class="relative ml-auto mr-auto transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6"> + <div> + <div class="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100"> + <svg + class="h-6 w-6 text-green-600" + fill="none" + viewBox="0 0 24 24" + stroke-width="1.5" + stroke="currentColor" + aria-hidden="true" + > + <path + stroke-linecap="round" + stroke-linejoin="round" + d="M4.5 12.75l6 6 9-13.5" + /> + </svg> + </div> + <div class="mt-3 text-center sm:mt-5"> + <h3 + class="text-base font-semibold leading-6 text-gray-900" + id="modal-title" + > + <i18n.Translate>Withdrawal confirmed</i18n.Translate> + </h3> + <div class="mt-2"> + <p class="text-sm text-gray-500"> + <i18n.Translate> + The wire transfer to the Taler operator has been initiated. + You will soon receive the requested amount in your Taler + wallet. + </i18n.Translate> + </p> + </div> + </div> + </div> + <div class="mt-5 sm:mt-6"> + <a + href={routeClose.url({})} + name="done" + class="inline-flex w-full justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" + > + <i18n.Translate>Done</i18n.Translate> + </a> + </div> + </div> + ); + } + + if (data.status === "pending") { + return ( + <QrCodeSection + withdrawUri={withdrawUri} + onAborted={() => { + notifyInfo(i18n.str`Operation canceled`); + onOperationAborted(); + }} + /> + ); + } + + const account = !data.selected_exchange_account + ? undefined + : parsePaytoUri(data.selected_exchange_account); + + if (!data.selected_reserve_pub && account) { + return ( + <Attention + type="danger" + title={i18n.str`The operation is marked as 'selected' but some step in the withdrawal failed`} + > + <i18n.Translate> + The account is selected but no withdrawal identification found. + </i18n.Translate> + </Attention> + ); + } + + if (!account && data.selected_reserve_pub) { + return ( + <Attention + type="danger" + title={i18n.str`The operation is marked as 'selected' but some step in the withdrawal failed`} + > + <i18n.Translate> + There is a withdrawal identification but no account has been selected + or the selected account is invalid. + </i18n.Translate> + </Attention> + ); + } + + if (!account || !data.selected_reserve_pub) { + return ( + <Attention + type="danger" + title={i18n.str`The operation is marked as 'selected' but some step in the withdrawal failed`} + > + <i18n.Translate> + No withdrawal ID found and no account has been selected or the + selected account is invalid. + </i18n.Translate> + </Attention> + ); + } + + return ( + <WithdrawalConfirmationQuestion + withdrawUri={withdrawUri} + routeHere={routeWithdrawalDetails} + details={{ + username: data.username, + account, + reserve: data.selected_reserve_pub, + amount: Amounts.parseOrThrow(data.amount), + }} + onAuthorizationRequired={onAuthorizationRequired} + onAborted={() => { + notifyInfo(i18n.str`Operation canceled`); + onOperationAborted(); + }} + /> + ); +} + +export function OperationNotFound({ + routeClose, +}: { + routeClose: RouteDefinition | undefined; +}): VNode { + const { i18n } = useTranslationContext(); + return ( + <div class="relative ml-auto mr-auto transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6"> + <div> + <div class="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100 "> + <svg + class="h-6 w-6 text-red-600" + fill="none" + viewBox="0 0 24 24" + stroke-width="1.5" + stroke="currentColor" + aria-hidden="true" + > + <path + stroke-linecap="round" + stroke-linejoin="round" + d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" + /> + </svg> + </div> + + <div class="mt-3 text-center sm:mt-5"> + <h3 + class="text-base font-semibold leading-6 text-gray-900" + id="modal-title" + > + <i18n.Translate>Operation not found</i18n.Translate> + </h3> + <div class="mt-2"> + <p class="text-sm text-gray-500"> + <i18n.Translate> + This operation is not known by the server. The operation id is + wrong or the server deleted the operation information before + reaching here. + </i18n.Translate> + </p> + </div> + </div> + </div> + {routeClose && ( + <div class="mt-5 sm:mt-6"> + <a + href={routeClose.url({})} + name="continue to dashboard" + class="inline-flex w-full justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" + > + <i18n.Translate>Cotinue to dashboard</i18n.Translate> + </a> + </div> + )} + </div> + ); +} |