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.tsx403
1 files changed, 0 insertions, 403 deletions
diff --git a/packages/demobank-ui/src/pages/OperationState/views.tsx b/packages/demobank-ui/src/pages/OperationState/views.tsx
deleted file mode 100644
index b1f09ba2a..000000000
--- a/packages/demobank-ui/src/pages/OperationState/views.tsx
+++ /dev/null
@@ -1,403 +0,0 @@
-/*
- 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 { TranslatedString, stringifyWithdrawUri } from "@gnu-taler/taler-util";
-import { Attention, LocalNotificationBanner, ShowInputErrorLabel, 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 { usePreferences } from "../../hooks/preferences.js";
-import { undefinedIfEmpty } from "../../utils.js";
-import { ShouldBeSameUser } from "../WithdrawalConfirmationQuestion.js";
-import { assertUnreachable } from "../WithdrawalOperationPage.js";
-import { State } from "./index.js";
-
-export function InvalidPaytoView({ payto, onClose }: State.InvalidPayto) {
- return (
- <div>Payto from server is not valid &quot;{payto}&quot;</div>
- );
-}
-export function InvalidWithdrawalView({ uri, onClose }: State.InvalidWithdrawal) {
- return (
- <div>Withdrawal uri from server is not valid &quot;{uri}&quot;</div>
- );
-}
-export function InvalidReserveView({ reserve, onClose }: State.InvalidReserve) {
- return (
- <div>Reserve from server is not valid &quot;{reserve}&quot;</div>
- );
-}
-
-export function NeedConfirmationView({ error, onAbort: doAbort, onConfirm: doConfirm, busy, account }: State.NeedConfirmation) {
- const { i18n } = useTranslationContext()
- const [settings] = usePreferences()
- 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<string | undefined>();
- 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<string, undefined> : undefined);
-
- async function onCancel() {
- errorHandler(async () => {
- if (!doAbort) return;
- 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 () => {
- if (!doConfirm) return;
- 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 (
- <div class="bg-white shadow sm:rounded-lg">
- <LocalNotificationBanner 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>
- </h3>
- <div class="mt-3 text-sm leading-6">
- <ShouldBeSameUser username={account}>
- <form
- class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2"
- autoCapitalize="none"
- autoCorrect="off"
- onSubmit={e => {
- e.preventDefault()
- }}
- >
- <div class="px-4 py-6 sm:p-8">
- <label for="withdraw-amount">{i18n.str`What is`}&nbsp;
- <em>
- {captchaNumbers.a}&nbsp;+&nbsp;{captchaNumbers.b}
- </em>
- ?
- </label>
- <div class="mt-2">
- <div class="relative rounded-md shadow-sm">
- <input
- type="text"
- // class="block w-full rounded-md border-0 py-1.5 pl-16 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
- aria-describedby="answer"
- autoFocus
- class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
- value={captchaAnswer ?? ""}
- required
-
- name="answer"
- id="answer"
- autocomplete="off"
- onChange={(e): void => {
- setCaptchaAnswer(e.currentTarget.value)
- }}
- />
- </div>
- <ShowInputErrorLabel message={errors?.answer} isDirty={captchaAnswer !== undefined} />
- </div>
- </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={(e) => {
- e.preventDefault()
- onCancel()
- }}
- >
- <i18n.Translate>Cancel</i18n.Translate></button>
- <button type="submit"
- class="disabled:opacity-50 disabled:cursor-default cursor-pointer 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"
- disabled={!!errors}
- onClick={(e) => {
- e.preventDefault()
- onConfirm()
- }}
- >
- <i18n.Translate>Transfer</i18n.Translate>
- </button>
- </div>
- </form>
- </ShouldBeSameUser>
- </div>
- </div>
- </div>
-
- );
-}
-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>
- case "account-not-found": 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>
- );
-}
-
-export function ConfirmedView({ error, onClose }: State.Confirmed) {
- const { i18n } = useTranslationContext();
- const [settings, updateSettings] = usePreferences()
- return (
- <Fragment>
-
- <div class="relative ml-auto mr-auto transform overflow-hidden rounded-lg bg-white p-4 text-left shadow-xl transition-all ">
-
- <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-4">
- <div class="flex items-center justify-between">
- <span class="flex flex-grow flex-col">
- <span class="text-sm text-black font-medium leading-6 " id="availability-label">
- <i18n.Translate>Do not show this again</i18n.Translate>
- </span>
- </span>
- <button type="button" data-enabled={!settings.showWithdrawalSuccess} class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description"
- onClick={() => {
- updateSettings("showWithdrawalSuccess", !settings.showWithdrawalSuccess);
- }}>
- <span aria-hidden="true" data-enabled={!settings.showWithdrawalSuccess} class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
- </button>
- </div>
- </div>
- <div class="mt-5 sm:mt-6">
- <button type="button"
- 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"
- onClick={async (e) => {
- e.preventDefault();
- onClose()
- }}>
- <i18n.Translate>Close</i18n.Translate>
- </button>
- </div>
- </Fragment>
-
- );
-}
-
-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 <Fragment>
- <LocalNotificationBanner 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"
- onClick={() => {
- onClose()
- }}
- >
- Cancel
- </button>
- </div>
-
- <div class="bg-white shadow sm:rounded-lg mt-4">
- <div class="p-4">
- <h3 class="text-base font-semibold leading-6 text-gray-900">
- <i18n.Translate>On this device</i18n.Translate>
- </h3>
- <div class="mt-2 sm:flex sm:items-start sm:justify-between">
- <div class="max-w-xl text-sm text-gray-500">
- <p>
- <i18n.Translate>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.</i18n.Translate>
- </p>
- </div>
- <div class="mt-5 sm:ml-6 sm:mt-0 sm:flex sm:flex-shrink-0 sm:items-center">
- <a href={talerWithdrawUri}
- class="inline-flex items-center disabled:opacity-50 disabled:cursor-default cursor-pointer 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>Start</i18n.Translate>
- </a>
- </div>
- </div>
- </div>
- </div>
- <div class="bg-white shadow sm:rounded-lg mt-2">
- <div class="p-4">
- <h3 class="text-base font-semibold leading-6 text-gray-900">
- <i18n.Translate>On a mobile phone</i18n.Translate>
- </h3>
- <div class="mt-2 sm:flex sm:items-start sm:justify-between">
- <div class="max-w-xl text-sm text-gray-500">
- <p>
- <i18n.Translate>Scan the QR code with your mobile device.</i18n.Translate>
- </p>
- </div>
- </div>
- <div class="mt-2 max-w-md ml-auto mr-auto">
- <QR text={talerWithdrawUri} />
- </div>
- </div>
- </div>
-
- </Fragment>
-
-}