summaryrefslogtreecommitdiff
path: root/packages/demobank-ui/src/pages/OperationState
diff options
context:
space:
mode:
Diffstat (limited to 'packages/demobank-ui/src/pages/OperationState')
-rw-r--r--packages/demobank-ui/src/pages/OperationState/index.ts128
-rw-r--r--packages/demobank-ui/src/pages/OperationState/state.ts221
-rw-r--r--packages/demobank-ui/src/pages/OperationState/stories.tsx29
-rw-r--r--packages/demobank-ui/src/pages/OperationState/test.ts32
-rw-r--r--packages/demobank-ui/src/pages/OperationState/views.tsx403
5 files changed, 0 insertions, 813 deletions
diff --git a/packages/demobank-ui/src/pages/OperationState/index.ts b/packages/demobank-ui/src/pages/OperationState/index.ts
deleted file mode 100644
index e3aec21c5..000000000
--- a/packages/demobank-ui/src/pages/OperationState/index.ts
+++ /dev/null
@@ -1,128 +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 { AbsoluteTime, AmountJson, TalerCoreBankErrorsByMethod, TalerError, WithdrawUriResult } from "@gnu-taler/taler-util";
-import { Loading, utils } from "@gnu-taler/web-util/browser";
-import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js";
-import { useComponentState } from "./state.js";
-import { AbortedView, ConfirmedView, FailedView, InvalidPaytoView, InvalidReserveView, InvalidWithdrawalView, NeedConfirmationView, ReadyView } from "./views.js";
-
-export interface Props {
- currency: string;
- onClose: () => void;
-}
-
-export type State = State.Loading |
- State.LoadingError |
- State.Ready |
- State.Failed |
- State.Aborted |
- State.Confirmed |
- State.InvalidPayto |
- State.InvalidWithdrawal |
- State.InvalidReserve |
- State.NeedConfirmation;
-
-export namespace State {
- export interface Loading {
- status: "loading";
- error: undefined;
- }
-
- export interface Failed {
- status: "failed";
- error: TalerCoreBankErrorsByMethod<"createWithdrawal">;
- }
-
- export interface LoadingError {
- status: "loading-error";
- error: TalerError;
- }
-
- /**
- * Need to open the wallet
- */
- export interface Ready {
- status: "ready";
- error: undefined;
- uri: WithdrawUriResult,
- onClose: () => Promise<TalerCoreBankErrorsByMethod<"abortWithdrawalById"> | undefined>;
- }
-
- export interface InvalidPayto {
- status: "invalid-payto",
- error: undefined;
- payto: string | undefined;
- onClose: () => void;
- }
- export interface InvalidWithdrawal {
- status: "invalid-withdrawal",
- error: undefined;
- onClose: () => void;
- uri: string,
- }
- export interface InvalidReserve {
- status: "invalid-reserve",
- error: undefined;
- onClose: () => void;
- reserve: string | undefined;
- }
- export interface NeedConfirmation {
- status: "need-confirmation",
- account: string,
- onAbort: undefined | (() => Promise<TalerCoreBankErrorsByMethod<"abortWithdrawalById"> | undefined>);
- onConfirm: undefined | (() => Promise<TalerCoreBankErrorsByMethod<"confirmWithdrawalById"> | undefined>);
- error: undefined;
- busy: boolean,
- }
- export interface Aborted {
- status: "aborted",
- error: undefined;
- onClose: () => void;
- }
- export interface Confirmed {
- status: "confirmed",
- error: undefined;
- onClose: () => void;
- }
-
-}
-
-export interface Transaction {
- negative: boolean;
- counterpart: string;
- when: AbsoluteTime;
- amount: AmountJson | undefined;
- subject: string;
-}
-
-const viewMapping: utils.StateViewMap<State> = {
- loading: Loading,
- "failed": FailedView,
- "invalid-payto": InvalidPaytoView,
- "invalid-withdrawal": InvalidWithdrawalView,
- "invalid-reserve": InvalidReserveView,
- "need-confirmation": NeedConfirmationView,
- "aborted": AbortedView,
- "confirmed": ConfirmedView,
- "loading-error": ErrorLoadingWithDebug,
- ready: ReadyView,
-};
-
-export const OperationState = utils.compose(
- (p: Props) => useComponentState(p),
- viewMapping,
-);
diff --git a/packages/demobank-ui/src/pages/OperationState/state.ts b/packages/demobank-ui/src/pages/OperationState/state.ts
deleted file mode 100644
index defca6f13..000000000
--- a/packages/demobank-ui/src/pages/OperationState/state.ts
+++ /dev/null
@@ -1,221 +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 { Amounts, FailCasesByMethod, TalerCoreBankErrorsByMethod, TalerError, TalerErrorDetail, TranslatedString, parsePaytoUri, parseWithdrawUri, stringifyWithdrawUri } from "@gnu-taler/taler-util";
-import { notify, notifyError, notifyInfo, useTranslationContext, utils } from "@gnu-taler/web-util/browser";
-import { useEffect, useState } from "preact/hooks";
-import { mutate } from "swr";
-import { useBankCoreApiContext } from "../../context/config.js";
-import { useWithdrawalDetails } from "../../hooks/access.js";
-import { useBackendState } from "../../hooks/backend.js";
-import { usePreferences } from "../../hooks/preferences.js";
-import { assertUnreachable } from "../WithdrawalOperationPage.js";
-import { Props, State } from "./index.js";
-
-export function useComponentState({ currency, onClose }: Props): utils.RecursiveState<State> {
- const [settings, updateSettings] = usePreferences()
- const { state: credentials } = useBackendState()
- const creds = credentials.status !== "loggedIn" ? undefined : credentials
- const { api } = useBankCoreApiContext()
-
- const [busy, setBusy] = useState<Record<string, undefined>>()
- const [failure, setFailure] = useState<TalerCoreBankErrorsByMethod<"createWithdrawal"> | undefined>()
- const amount = settings.maxWithdrawalAmount
-
- async function doSilentStart() {
- //FIXME: if amount is not enough use balance
- const parsedAmount = Amounts.parseOrThrow(`${currency}:${amount}`)
- if (!creds) return;
- const resp = await api.createWithdrawal(creds, {
- amount: Amounts.stringify(parsedAmount),
- });
- if (resp.type === "fail") {
- setFailure(resp)
- return;
- }
- updateSettings("currentWithdrawalOperationId", resp.body.withdrawal_id)
-
- }
-
- const withdrawalOperationId = settings.currentWithdrawalOperationId
- useEffect(() => {
- if (withdrawalOperationId === undefined) {
- doSilentStart()
- }
- }, [settings.fastWithdrawal, amount])
-
- if (failure) {
- return {
- status: "failed",
- error: failure
- }
- }
-
- if (!withdrawalOperationId) {
- return {
- status: "loading",
- error: undefined
- }
- }
-
- const wid = withdrawalOperationId
-
- async function doAbort() {
- if (!creds) return;
- const resp = await api.abortWithdrawalById(creds, wid);
- if (resp.type === "ok") {
- updateSettings("currentWithdrawalOperationId", undefined)
- onClose();
- } else {
- return resp;
- }
- }
-
- async function doConfirm(): Promise<TalerCoreBankErrorsByMethod<"confirmWithdrawalById"> | undefined> {
- if (!creds) return;
- setBusy({})
- const resp = await api.confirmWithdrawalById(creds, wid);
- setBusy(undefined)
- if (resp.type === "ok") {
- mutate(() => true)//clean withdrawal state
- } else {
- return resp;
- }
- }
-
- const uri = stringifyWithdrawUri({
- bankIntegrationApiBaseUrl: api.getIntegrationAPI().baseUrl,
- withdrawalOperationId,
- });
- const parsedUri = parseWithdrawUri(uri);
- if (!parsedUri) {
- return {
- status: "invalid-withdrawal",
- error: undefined,
- uri,
- onClose,
- }
- }
-
- return (): utils.RecursiveState<State> => {
- const result = useWithdrawalDetails(withdrawalOperationId);
- const shouldCreateNewOperation = result && !(result instanceof TalerError)
-
- useEffect(() => {
- if (shouldCreateNewOperation) {
- doSilentStart()
- }
- }, [])
- if (!result) {
- return {
- status: "loading",
- error: undefined
- }
- }
- if (result instanceof TalerError) {
- return {
- status: "loading-error",
- error: result
- }
- }
-
- if (result.type === "fail") {
- switch (result.case) {
- case "invalid-id":
- case "not-found": {
- return {
- status: "aborted",
- error: undefined,
- onClose: async () => {
- updateSettings("currentWithdrawalOperationId", undefined)
- onClose()
- },
- }
- }
- default: assertUnreachable(result)
- }
- }
-
- const { body: data } = result;
- if (data.status === "aborted") {
- return {
- status: "aborted",
- error: undefined,
- onClose: async () => {
- updateSettings("currentWithdrawalOperationId", undefined)
- onClose()
- },
- }
- }
-
- if (data.status === "confirmed") {
- if (!settings.showWithdrawalSuccess) {
- updateSettings("currentWithdrawalOperationId", undefined)
- onClose()
- }
- return {
- status: "confirmed",
- error: undefined,
- onClose: async () => {
- updateSettings("currentWithdrawalOperationId", undefined)
- onClose()
- },
- }
- }
-
- if (data.status === "pending") {
- return {
- status: "ready",
- error: undefined,
- uri: parsedUri,
- onClose: !creds ? (async () => {
- onClose();
- return undefined
- }) : doAbort,
- }
- }
-
- if (!data.selected_reserve_pub) {
- return {
- status: "invalid-reserve",
- error: undefined,
- reserve: data.selected_reserve_pub,
- onClose,
- }
- }
-
- const account = !data.selected_exchange_account ? undefined : parsePaytoUri(data.selected_exchange_account)
-
- if (!account) {
- return {
- status: "invalid-payto",
- error: undefined,
- payto: data.selected_exchange_account,
- onClose,
- }
- }
-
- return {
- status: "need-confirmation",
- error: undefined,
- account: data.username,
- onAbort: !creds ? undefined : doAbort,
- busy: !!busy,
- onConfirm: !creds ? undefined : doConfirm
- }
- }
-
-}
diff --git a/packages/demobank-ui/src/pages/OperationState/stories.tsx b/packages/demobank-ui/src/pages/OperationState/stories.tsx
deleted file mode 100644
index 03917a8fb..000000000
--- a/packages/demobank-ui/src/pages/OperationState/stories.tsx
+++ /dev/null
@@ -1,29 +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/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import * as tests from "@gnu-taler/web-util/testing";
-import { ReadyView } from "./views.js";
-
-export default {
- title: "operation status page",
-};
-
-export const Ready = tests.createExample(ReadyView, {});
diff --git a/packages/demobank-ui/src/pages/OperationState/test.ts b/packages/demobank-ui/src/pages/OperationState/test.ts
deleted file mode 100644
index f4d6cf4b2..000000000
--- a/packages/demobank-ui/src/pages/OperationState/test.ts
+++ /dev/null
@@ -1,32 +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/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import * as tests from "@gnu-taler/web-util/testing";
-import { SwrMockEnvironment } from "@gnu-taler/web-util/testing";
-import { expect } from "chai";
-import { CASHOUT_API_EXAMPLE } from "../../endpoints.js";
-import { Props } from "./index.js";
-import { useComponentState } from "./state.js";
-
-describe("Withdrawal operation states", () => {
- it("should do some tests", async () => {
- });
-});
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>
-
-}