aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src/wallet
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2022-11-16 16:04:52 -0300
committerSebastian <sebasjm@gmail.com>2022-11-16 16:05:13 -0300
commit1a63d56bfdd091cc7aefdf1e25f3a074bfdf5e0e (patch)
tree7255cf4a5b51af4807e2a01a370497413a78968f /packages/taler-wallet-webextension/src/wallet
parent53164dc47b1138235a0c797affaa6fb37ea43239 (diff)
downloadwallet-core-1a63d56bfdd091cc7aefdf1e25f3a074bfdf5e0e.tar.xz
fix #7411, also making the backup payment visible
Diffstat (limited to 'packages/taler-wallet-webextension/src/wallet')
-rw-r--r--packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts95
-rw-r--r--packages/taler-wallet-webextension/src/wallet/AddBackupProvider/state.ts260
-rw-r--r--packages/taler-wallet-webextension/src/wallet/AddBackupProvider/stories.tsx109
-rw-r--r--packages/taler-wallet-webextension/src/wallet/AddBackupProvider/test.ts79
-rw-r--r--packages/taler-wallet-webextension/src/wallet/AddBackupProvider/views.tsx172
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Application.tsx9
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts4
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts42
-rw-r--r--packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts4
-rw-r--r--packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts19
-rw-r--r--packages/taler-wallet-webextension/src/wallet/ExchangeSelection/views.tsx7
-rw-r--r--packages/taler-wallet-webextension/src/wallet/ManageAccount/index.ts8
-rw-r--r--packages/taler-wallet-webextension/src/wallet/ManageAccount/state.ts34
-rw-r--r--packages/taler-wallet-webextension/src/wallet/QrReader.stories.tsx2
-rw-r--r--packages/taler-wallet-webextension/src/wallet/index.stories.tsx4
15 files changed, 798 insertions, 50 deletions
diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts
new file mode 100644
index 000000000..3205588af
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts
@@ -0,0 +1,95 @@
+/*
+ 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 {
+ AmountJson,
+ BackupBackupProviderTerms,
+ TalerErrorDetail,
+} from "@gnu-taler/taler-util";
+import { SyncTermsOfServiceResponse } from "@gnu-taler/taler-wallet-core";
+import { Loading } from "../../components/Loading.js";
+import { HookError } from "../../hooks/useAsyncAsHook.js";
+import {
+ ButtonHandler,
+ TextFieldHandler,
+ ToggleHandler,
+} from "../../mui/handlers.js";
+import { compose, StateViewMap } from "../../utils/index.js";
+import { wxApi } from "../../wxApi.js";
+import { useComponentState } from "./state.js";
+import {
+ LoadingUriView,
+ SelectProviderView,
+ ConfirmProviderView,
+} from "./views.js";
+
+export interface Props {
+ currency: string;
+ onBack: () => Promise<void>;
+ onComplete: (pid: string) => Promise<void>;
+ onPaymentRequired: (uri: string) => Promise<void>;
+}
+
+export type State =
+ | State.Loading
+ | State.LoadingUriError
+ | State.ConfirmProvider
+ | State.SelectProvider;
+
+export namespace State {
+ export interface Loading {
+ status: "loading";
+ error: undefined;
+ }
+
+ export interface LoadingUriError {
+ status: "loading-error";
+ error: HookError;
+ }
+
+ export interface ConfirmProvider {
+ status: "confirm-provider";
+ error: undefined | TalerErrorDetail;
+ url: string;
+ provider: SyncTermsOfServiceResponse;
+ tos: ToggleHandler;
+ onCancel: ButtonHandler;
+ onAccept: ButtonHandler;
+ }
+
+ export interface SelectProvider {
+ status: "select-provider";
+ url: TextFieldHandler;
+ urlOk: boolean;
+ name: TextFieldHandler;
+ onConfirm: ButtonHandler;
+ onCancel: ButtonHandler;
+ error: undefined | TalerErrorDetail;
+ }
+}
+
+const viewMapping: StateViewMap<State> = {
+ loading: Loading,
+ "loading-error": LoadingUriView,
+ "select-provider": SelectProviderView,
+ "confirm-provider": ConfirmProviderView,
+};
+
+export const AddBackupProviderPage = compose(
+ "AddBackupProvider",
+ (p: Props) => useComponentState(p, wxApi),
+ viewMapping,
+);
diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/state.ts b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/state.ts
new file mode 100644
index 000000000..0b3c17902
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/state.ts
@@ -0,0 +1,260 @@
+/*
+ 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 {
+ canonicalizeBaseUrl,
+ Codec,
+ TalerErrorDetail,
+} from "@gnu-taler/taler-util";
+import {
+ codecForSyncTermsOfServiceResponse,
+ WalletApiOperation,
+} from "@gnu-taler/taler-wallet-core";
+import { useEffect, useState } from "preact/hooks";
+import { assertUnreachable } from "../../utils/index.js";
+import { wxApi } from "../../wxApi.js";
+import { Props, State } from "./index.js";
+
+type UrlState<T> = UrlOk<T> | UrlError;
+
+interface UrlOk<T> {
+ status: "ok";
+ result: T;
+}
+type UrlError =
+ | UrlNetworkError
+ | UrlClientError
+ | UrlServerError
+ | UrlParsingError
+ | UrlReadError;
+
+interface UrlNetworkError {
+ status: "network-error";
+ href: string;
+}
+interface UrlClientError {
+ status: "client-error";
+ code: number;
+}
+interface UrlServerError {
+ status: "server-error";
+ code: number;
+}
+interface UrlParsingError {
+ status: "parsing-error";
+ json: any;
+}
+interface UrlReadError {
+ status: "url-error";
+}
+
+function useDebounceEffect(
+ time: number,
+ cb: undefined | (() => Promise<void>),
+ deps: Array<any>,
+): void {
+ const [currentTimer, setCurrentTimer] = useState<any>();
+ useEffect(() => {
+ if (currentTimer !== undefined) clearTimeout(currentTimer);
+ if (cb !== undefined) {
+ const tid = setTimeout(cb, time);
+ setCurrentTimer(tid);
+ }
+ }, deps);
+}
+
+function useUrlState<T>(
+ host: string | undefined,
+ path: string,
+ codec: Codec<T>,
+): UrlState<T> | undefined {
+ const [state, setState] = useState<UrlState<T> | undefined>();
+
+ let href: string | undefined;
+ try {
+ if (host) {
+ const isHttps =
+ host.startsWith("https://") && host.length > "https://".length;
+ const isHttp =
+ host.startsWith("http://") && host.length > "http://".length;
+ const withProto = isHttp || isHttps ? host : `https://${host}`;
+ const baseUrl = canonicalizeBaseUrl(withProto);
+ href = new URL(path, baseUrl).href;
+ }
+ } catch (e) {
+ setState({
+ status: "url-error",
+ });
+ }
+ const constHref = href;
+
+ useDebounceEffect(
+ 500,
+ constHref == undefined
+ ? undefined
+ : async () => {
+ const req = await fetch(constHref).catch((e) => {
+ return setState({
+ status: "network-error",
+ href: constHref,
+ });
+ });
+ if (!req) return;
+
+ if (req.status >= 400 && req.status < 500) {
+ setState({
+ status: "client-error",
+ code: req.status,
+ });
+ return;
+ }
+ if (req.status > 500) {
+ setState({
+ status: "server-error",
+ code: req.status,
+ });
+ return;
+ }
+
+ const json = await req.json();
+ try {
+ const result = codec.decode(json);
+ setState({ status: "ok", result });
+ } catch (e: any) {
+ setState({ status: "parsing-error", json });
+ }
+ },
+ [host, path],
+ );
+
+ return state;
+}
+
+export function useComponentState(
+ { currency, onBack, onComplete, onPaymentRequired }: Props,
+ api: typeof wxApi,
+): State {
+ const [url, setHost] = useState<string | undefined>();
+ const [name, setName] = useState<string | undefined>();
+ const [tos, setTos] = useState(false);
+ const urlState = useUrlState(
+ url,
+ "config",
+ codecForSyncTermsOfServiceResponse(),
+ );
+ const [operationError, setOperationError] = useState<
+ TalerErrorDetail | undefined
+ >();
+ const [showConfirm, setShowConfirm] = useState(false);
+
+ async function addBackupProvider() {
+ if (!url || !name) return;
+
+ const resp = await api.wallet.call(WalletApiOperation.AddBackupProvider, {
+ backupProviderBaseUrl: url,
+ name: name,
+ activate: true,
+ });
+
+ switch (resp.status) {
+ case "payment-required":
+ return onPaymentRequired(resp.talerUri);
+ case "error":
+ return setOperationError(resp.error);
+ case "ok":
+ return onComplete(url);
+ default:
+ assertUnreachable(resp);
+ }
+ }
+
+ if (showConfirm && urlState && urlState.status === "ok") {
+ return {
+ status: "confirm-provider",
+ error: operationError,
+ onAccept: {
+ onClick: !tos ? undefined : addBackupProvider,
+ },
+ onCancel: {
+ onClick: onBack,
+ },
+ provider: urlState.result,
+ tos: {
+ value: tos,
+ button: {
+ onClick: async () => setTos(!tos),
+ },
+ },
+ url: url ?? "",
+ };
+ }
+
+ return {
+ status: "select-provider",
+ error: undefined,
+ name: {
+ value: name || "",
+ onInput: async (e) => setName(e),
+ error:
+ name === undefined ? undefined : !name ? "Can't be empty" : undefined,
+ },
+ onCancel: {
+ onClick: onBack,
+ },
+ onConfirm: {
+ onClick:
+ !urlState || urlState.status !== "ok" || !name
+ ? undefined
+ : async () => {
+ setShowConfirm(true);
+ },
+ },
+ urlOk: urlState?.status === "ok",
+ url: {
+ value: url || "",
+ onInput: async (e) => setHost(e),
+ error: errorString(urlState),
+ },
+ };
+}
+
+function errorString(state: undefined | UrlState<any>): string | undefined {
+ if (!state) return state;
+ switch (state.status) {
+ case "ok":
+ return undefined;
+ case "client-error": {
+ switch (state.code) {
+ case 404:
+ return "Not found";
+ case 401:
+ return "Unauthorized";
+ case 403:
+ return "Forbidden";
+ default:
+ return `Server says it a client error: ${state.code}.`;
+ }
+ }
+ case "server-error":
+ return `Server had a problem ${state.code}.`;
+ case "parsing-error":
+ return `Server response doesn't have the right format.`;
+ case "network-error":
+ return `Unable to connect to ${state.href}.`;
+ case "url-error":
+ return "URL is not complete";
+ }
+}
diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/stories.tsx b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/stories.tsx
new file mode 100644
index 000000000..ae3e1b091
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/stories.tsx
@@ -0,0 +1,109 @@
+/*
+ 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 { createExample } from "../../test-utils.js";
+import { ConfirmProviderView, SelectProviderView } from "./views.js";
+
+export default {
+ title: "wallet/backup/confirm",
+};
+
+export const DemoService = createExample(ConfirmProviderView, {
+ url: "https://sync.demo.taler.net/",
+ provider: {
+ annual_fee: "KUDOS:0.1",
+ storage_limit_in_megabytes: 20,
+ version: "1",
+ },
+ tos: {
+ button: {},
+ },
+ onAccept: {},
+ onCancel: {},
+});
+
+export const FreeService = createExample(ConfirmProviderView, {
+ url: "https://sync.taler:9667/",
+ provider: {
+ annual_fee: "ARS:0",
+ storage_limit_in_megabytes: 20,
+ version: "1",
+ },
+ tos: {
+ button: {},
+ },
+ onAccept: {},
+ onCancel: {},
+});
+
+export const Initial = createExample(SelectProviderView, {
+ url: { value: "" },
+ name: { value: "" },
+ onCancel: {},
+ onConfirm: {},
+});
+
+export const WithValue = createExample(SelectProviderView, {
+ url: {
+ value: "sync.demo.taler.net",
+ },
+ name: {
+ value: "Demo backup service",
+ },
+ onCancel: {},
+ onConfirm: {},
+});
+
+export const WithConnectionError = createExample(SelectProviderView, {
+ url: {
+ value: "sync.demo.taler.net",
+ error: "Network error",
+ },
+ name: {
+ value: "Demo backup service",
+ },
+ onCancel: {},
+ onConfirm: {},
+});
+
+export const WithClientError = createExample(SelectProviderView, {
+ url: {
+ value: "sync.demo.taler.net",
+ error: "URL may not be right: (404) Not Found",
+ },
+ name: {
+ value: "Demo backup service",
+ },
+ onCancel: {},
+ onConfirm: {},
+});
+
+export const WithServerError = createExample(SelectProviderView, {
+ url: {
+ value: "sync.demo.taler.net",
+ error: "Try another server: (500) Internal Server Error",
+ },
+ name: {
+ value: "Demo backup service",
+ },
+ onCancel: {},
+ onConfirm: {},
+});
diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/test.ts b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/test.ts
new file mode 100644
index 000000000..1143853f8
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/test.ts
@@ -0,0 +1,79 @@
+/*
+ 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 { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { expect } from "chai";
+import {
+ createWalletApiMock,
+ mountHook,
+ nullFunction,
+} from "../../test-utils.js";
+import { Props } from "./index.js";
+import { useComponentState } from "./state.js";
+
+const props: Props = {
+ currency: "KUDOS",
+ onBack: nullFunction,
+ onComplete: nullFunction,
+ onPaymentRequired: nullFunction,
+};
+describe("AddBackupProvider states", () => {
+ it("should start in 'select-provider' state", async () => {
+ const { handler, mock } = createWalletApiMock();
+
+ // handler.addWalletCallResponse(
+ // WalletApiOperation.ListKnownBankAccounts,
+ // undefined,
+ // {
+ // accounts: [],
+ // },
+ // );
+
+ const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
+ mountHook(() => useComponentState(props, mock));
+
+ {
+ const state = pullLastResultOrThrow();
+ expect(state.status).equal("select-provider");
+ if (state.status !== "select-provider") return;
+ expect(state.name.value).eq("");
+ expect(state.url.value).eq("");
+ }
+
+ //FIXME: this should not make an extra update
+ /**
+ * this may be due to useUrlState because is using an effect over
+ * a dependency with a timeout
+ */
+ // NOTE: do not remove this comment, keeping as an example
+ // await waitForStateUpdate()
+ // {
+ // const state = pullLastResultOrThrow();
+ // expect(state.status).equal("select-provider");
+ // if (state.status !== "select-provider") return;
+ // expect(state.name.value).eq("")
+ // expect(state.url.value).eq("")
+ // }
+
+ await assertNoPendingUpdate();
+ expect(handler.getCallingQueueState()).eq("empty");
+ });
+});
diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/views.tsx b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/views.tsx
new file mode 100644
index 000000000..b633a595f
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/views.tsx
@@ -0,0 +1,172 @@
+/*
+ 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 } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
+import { Checkbox } from "../../components/Checkbox.js";
+import { LoadingError } from "../../components/LoadingError.js";
+import {
+ LightText,
+ SmallLightText,
+ SubTitle,
+ TermsOfService,
+ Title,
+} from "../../components/styled/index.js";
+import { useTranslationContext } from "../../context/translation.js";
+import { Button } from "../../mui/Button.js";
+import { TextField } from "../../mui/TextField.js";
+import { State } from "./index.js";
+
+export function LoadingUriView({ error }: State.LoadingUriError): VNode {
+ const { i18n } = useTranslationContext();
+
+ return (
+ <LoadingError
+ title={<i18n.Translate>Could not load</i18n.Translate>}
+ error={error}
+ />
+ );
+}
+
+export function ConfirmProviderView({
+ url,
+ provider,
+ tos,
+ onCancel,
+ onAccept,
+}: State.ConfirmProvider): VNode {
+ const { i18n } = useTranslationContext();
+ const noFee = Amounts.isZero(provider.annual_fee);
+ return (
+ <Fragment>
+ <section>
+ <Title>
+ <i18n.Translate>Review terms of service</i18n.Translate>
+ </Title>
+ <div>
+ <i18n.Translate>Provider URL</i18n.Translate>:{" "}
+ <a href={url} target="_blank" rel="noreferrer">
+ {url}
+ </a>
+ </div>
+ <SmallLightText>
+ <i18n.Translate>
+ Please review and accept this provider&apos;s terms of service
+ </i18n.Translate>
+ </SmallLightText>
+ <SubTitle>
+ 1. <i18n.Translate>Pricing</i18n.Translate>
+ </SubTitle>
+ <p>
+ {noFee ? (
+ <i18n.Translate>free of charge</i18n.Translate>
+ ) : (
+ <i18n.Translate>
+ {provider.annual_fee} per year of service
+ </i18n.Translate>
+ )}
+ </p>
+ <SubTitle>
+ 2. <i18n.Translate>Storage</i18n.Translate>
+ </SubTitle>
+ <p>
+ <i18n.Translate>
+ {provider.storage_limit_in_megabytes} megabytes of storage per year
+ of service
+ </i18n.Translate>
+ </p>
+ {/* replace with <TermsOfService /> */}
+ <Checkbox
+ label={<i18n.Translate>Accept terms of service</i18n.Translate>}
+ name="terms"
+ onToggle={tos.button.onClick}
+ enabled={tos.value}
+ />
+ </section>
+ <footer>
+ <Button
+ variant="contained"
+ color="secondary"
+ onClick={onCancel.onClick}
+ >
+ <i18n.Translate>Cancel</i18n.Translate>
+ </Button>
+ <Button variant="contained" color="primary" onClick={onAccept.onClick}>
+ {noFee ? (
+ <i18n.Translate>Add provider</i18n.Translate>
+ ) : (
+ <i18n.Translate>Pay</i18n.Translate>
+ )}
+ </Button>
+ </footer>
+ </Fragment>
+ );
+}
+
+export function SelectProviderView({
+ url,
+ name,
+ urlOk,
+ onCancel,
+ onConfirm,
+}: State.SelectProvider): VNode {
+ const { i18n } = useTranslationContext();
+ return (
+ <Fragment>
+ <section>
+ <Title>
+ <i18n.Translate>Add backup provider</i18n.Translate>
+ </Title>
+ <LightText>
+ <i18n.Translate>
+ Backup providers may charge for their service
+ </i18n.Translate>
+ </LightText>
+ <p>
+ <TextField
+ label={<i18n.Translate>URL</i18n.Translate>}
+ placeholder="https://"
+ color={urlOk ? "success" : undefined}
+ value={url.value}
+ error={url.error}
+ onChange={url.onInput}
+ />
+ </p>
+ <p>
+ <TextField
+ label={<i18n.Translate>Name</i18n.Translate>}
+ placeholder="provider name"
+ value={name.value}
+ error={name.error}
+ onChange={name.onInput}
+ />
+ </p>
+ </section>
+ <footer>
+ <Button
+ variant="contained"
+ color="secondary"
+ onClick={onCancel.onClick}
+ >
+ <i18n.Translate>Cancel</i18n.Translate>
+ </Button>
+ <Button variant="contained" color="primary" onClick={onConfirm.onClick}>
+ <i18n.Translate>Next</i18n.Translate>
+ </Button>
+ </footer>
+ </Fragment>
+ );
+}
diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx b/packages/taler-wallet-webextension/src/wallet/Application.tsx
index 5934dec00..6b265c1ba 100644
--- a/packages/taler-wallet-webextension/src/wallet/Application.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Application.tsx
@@ -65,6 +65,7 @@ import { InvoiceCreatePage } from "../cta/InvoiceCreate/index.js";
import { TransferPickupPage } from "../cta/TransferPickup/index.js";
import { InvoicePayPage } from "../cta/InvoicePay/index.js";
import { RecoveryPage } from "../cta/Recovery/index.js";
+import { AddBackupProviderPage } from "./AddBackupProvider/index.js";
export function Application(): VNode {
const [globalNotification, setGlobalNotification] = useState<
@@ -221,7 +222,13 @@ export function Application(): VNode {
/>
<Route
path={Pages.backupProviderAdd}
- component={ProviderAddPage}
+ component={AddBackupProviderPage}
+ onPaymentRequired={(uri: string) =>
+ redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`)
+ }
+ onComplete={(pid: string) =>
+ redirectTo(Pages.backupProviderDetail({ pid }))
+ }
onBack={() => redirectTo(Pages.backup)}
/>
diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts
index 85896da26..373045833 100644
--- a/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts
+++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts
@@ -20,7 +20,7 @@ import { HookError } from "../../hooks/useAsyncAsHook.js";
import {
ButtonHandler,
SelectFieldHandler,
- TextFieldHandler
+ TextFieldHandler,
} from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js";
import { wxApi } from "../../wxApi.js";
@@ -31,7 +31,7 @@ import {
LoadingErrorView,
NoAccountToDepositView,
NoEnoughBalanceView,
- ReadyView
+ ReadyView,
} from "./views.js";
export interface Props {
diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts
index d8b752d44..91883c823 100644
--- a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts
+++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts
@@ -21,7 +21,7 @@ import {
KnownBankAccountsInfo,
parsePaytoUri,
PaytoUri,
- stringifyPaytoUri
+ stringifyPaytoUri,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks";
@@ -37,10 +37,16 @@ export function useComponentState(
const currency = parsed !== undefined ? parsed.currency : currencyStr;
const hook = useAsyncAsHook(async () => {
- const { balances } = await api.wallet.call(WalletApiOperation.GetBalances, {});
- const { accounts } = await api.wallet.call(WalletApiOperation.ListKnownBankAccounts, {
- currency
- });
+ const { balances } = await api.wallet.call(
+ WalletApiOperation.GetBalances,
+ {},
+ );
+ const { accounts } = await api.wallet.call(
+ WalletApiOperation.ListKnownBankAccounts,
+ {
+ currency,
+ },
+ );
return { accounts, balances };
});
@@ -120,13 +126,13 @@ export function useComponentState(
},
};
}
- const firstAccount = accounts[0].uri
+ const firstAccount = accounts[0].uri;
const currentAccount = !selectedAccount ? firstAccount : selectedAccount;
if (fee === undefined && parsedAmount) {
- getFeeForAmount(currentAccount, parsedAmount, api).then(initialFee => {
- setFee(initialFee)
- })
+ getFeeForAmount(currentAccount, parsedAmount, api).then((initialFee) => {
+ setFee(initialFee);
+ });
return {
status: "loading",
error: undefined,
@@ -177,10 +183,10 @@ export function useComponentState(
const amountError = !isDirty
? undefined
: !parsedAmount
- ? "Invalid amount"
- : Amounts.cmp(balance, parsedAmount) === -1
- ? `Too much, your current balance is ${Amounts.stringifyValue(balance)}`
- : undefined;
+ ? "Invalid amount"
+ : Amounts.cmp(balance, parsedAmount) === -1
+ ? `Too much, your current balance is ${Amounts.stringifyValue(balance)}`
+ : undefined;
const unableToDeposit =
!parsedAmount || //no amount specified
@@ -194,8 +200,9 @@ export function useComponentState(
const depositPaytoUri = stringifyPaytoUri(currentAccount);
const amount = Amounts.stringify(parsedAmount);
await api.wallet.call(WalletApiOperation.CreateDepositGroup, {
- amount, depositPaytoUri
- })
+ amount,
+ depositPaytoUri,
+ });
onSuccess(currency);
}
@@ -242,8 +249,9 @@ async function getFeeForAmount(
const depositPaytoUri = `payto://${p.targetType}/${p.targetPath}`;
const amount = Amounts.stringify(a);
return await api.wallet.call(WalletApiOperation.GetFeeForDeposit, {
- amount, depositPaytoUri
- })
+ amount,
+ depositPaytoUri,
+ });
}
export function labelForAccountType(id: string) {
diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts
index ddfaa71f9..a95830f8e 100644
--- a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts
+++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts
@@ -18,7 +18,7 @@ import {
DenomOperationMap,
ExchangeFullDetails,
ExchangeListItem,
- FeeDescriptionPair
+ FeeDescriptionPair,
} from "@gnu-taler/taler-util";
import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js";
@@ -33,7 +33,7 @@ import {
NoExchangesView,
PrivacyContentView,
ReadyView,
- TosContentView
+ TosContentView,
} from "./views.js";
export interface Props {
diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts
index ee839cad7..0a66dc381 100644
--- a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts
+++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts
@@ -15,7 +15,10 @@
*/
import { DenomOperationMap, FeeDescription } from "@gnu-taler/taler-util";
-import { createPairTimeline, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import {
+ createPairTimeline,
+ WalletApiOperation,
+} from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { wxApi } from "../../wxApi.js";
@@ -41,15 +44,23 @@ export function useComponentState(
exchanges.length == 0 ? undefined : exchanges[selectedIdx];
const selected = !selectedExchange
? undefined
- : await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, { exchangeBaseUrl: selectedExchange.exchangeBaseUrl });
+ : await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, {
+ exchangeBaseUrl: selectedExchange.exchangeBaseUrl,
+ });
const initialExchange =
selectedIdx === initialValue ? undefined : exchanges[initialValue];
const original = !initialExchange
? undefined
- : await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, { exchangeBaseUrl: initialExchange.exchangeBaseUrl });
+ : await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, {
+ exchangeBaseUrl: initialExchange.exchangeBaseUrl,
+ });
- return { exchanges, selected: selected?.exchange, original: original?.exchange };
+ return {
+ exchanges,
+ selected: selected?.exchange,
+ original: original?.exchange,
+ };
}, [value]);
const [showingTos, setShowingTos] = useState<string | undefined>(undefined);
diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/views.tsx b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/views.tsx
index d9a33c5c2..be059630f 100644
--- a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/views.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/views.tsx
@@ -161,11 +161,14 @@ export function NoExchangesView({
title={<i18n.Translate>Could not find any exchange</i18n.Translate>}
/>
);
-
}
return (
<ErrorMessage
- title={<i18n.Translate>Could not find any exchange for the currency {currency}</i18n.Translate>}
+ title={
+ <i18n.Translate>
+ Could not find any exchange for the currency {currency}
+ </i18n.Translate>
+ }
/>
);
}
diff --git a/packages/taler-wallet-webextension/src/wallet/ManageAccount/index.ts b/packages/taler-wallet-webextension/src/wallet/ManageAccount/index.ts
index cd591be74..df4e7586f 100644
--- a/packages/taler-wallet-webextension/src/wallet/ManageAccount/index.ts
+++ b/packages/taler-wallet-webextension/src/wallet/ManageAccount/index.ts
@@ -20,7 +20,7 @@ import { HookError } from "../../hooks/useAsyncAsHook.js";
import {
ButtonHandler,
SelectFieldHandler,
- TextFieldHandler
+ TextFieldHandler,
} from "../../mui/handlers.js";
import { compose, StateViewMap } from "../../utils/index.js";
import { wxApi } from "../../wxApi.js";
@@ -58,13 +58,13 @@ export namespace State {
alias: TextFieldHandler;
onAccountAdded: ButtonHandler;
onCancel: ButtonHandler;
- accountByType: AccountByType,
- deleteAccount: (a: KnownBankAccountsInfo) => Promise<void>,
+ accountByType: AccountByType;
+ deleteAccount: (a: KnownBankAccountsInfo) => Promise<void>;
}
}
export type AccountByType = {
- [key: string]: KnownBankAccountsInfo[]
+ [key: string]: KnownBankAccountsInfo[];
};
const viewMapping: StateViewMap<State> = {
diff --git a/packages/taler-wallet-webextension/src/wallet/ManageAccount/state.ts b/packages/taler-wallet-webextension/src/wallet/ManageAccount/state.ts
index f180fc1c0..1f920f05f 100644
--- a/packages/taler-wallet-webextension/src/wallet/ManageAccount/state.ts
+++ b/packages/taler-wallet-webextension/src/wallet/ManageAccount/state.ts
@@ -14,7 +14,11 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { KnownBankAccountsInfo, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
+import {
+ KnownBankAccountsInfo,
+ parsePaytoUri,
+ stringifyPaytoUri,
+} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@@ -25,7 +29,9 @@ export function useComponentState(
{ currency, onAccountAdded, onCancel }: Props,
api: typeof wxApi,
): State {
- const hook = useAsyncAsHook(() => api.wallet.call(WalletApiOperation.ListKnownBankAccounts, { currency }));
+ const hook = useAsyncAsHook(() =>
+ api.wallet.call(WalletApiOperation.ListKnownBankAccounts, { currency }),
+ );
const [payto, setPayto] = useState("");
const [alias, setAlias] = useState("");
@@ -61,34 +67,34 @@ export function useComponentState(
const normalizedPayto = stringifyPaytoUri(uri);
await api.wallet.call(WalletApiOperation.AddKnownBankAccounts, {
- alias, currency, payto: normalizedPayto
+ alias,
+ currency,
+ payto: normalizedPayto,
});
onAccountAdded(payto);
}
- const paytoUriError =
- found
- ? "that account is already present"
- : undefined;
+ const paytoUriError = found ? "that account is already present" : undefined;
- const unableToAdd = !type || !alias || paytoUriError !== undefined || uri === undefined;
+ const unableToAdd =
+ !type || !alias || paytoUriError !== undefined || uri === undefined;
const accountByType: AccountByType = {
iban: [],
bitcoin: [],
"x-taler-bank": [],
- }
+ };
- hook.response.accounts.forEach(acc => {
- accountByType[acc.uri.targetType].push(acc)
+ hook.response.accounts.forEach((acc) => {
+ accountByType[acc.uri.targetType].push(acc);
});
async function deleteAccount(account: KnownBankAccountsInfo): Promise<void> {
const payto = stringifyPaytoUri(account.uri);
await api.wallet.call(WalletApiOperation.ForgetKnownBankAccounts, {
- payto
- })
- hook?.retry()
+ payto,
+ });
+ hook?.retry();
}
return {
diff --git a/packages/taler-wallet-webextension/src/wallet/QrReader.stories.tsx b/packages/taler-wallet-webextension/src/wallet/QrReader.stories.tsx
index ba5a78570..caf833e79 100644
--- a/packages/taler-wallet-webextension/src/wallet/QrReader.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/QrReader.stories.tsx
@@ -23,7 +23,7 @@ import { createExample } from "../test-utils.js";
import { QrReaderPage } from "./QrReader.js";
export default {
- title: "wallet/qr",
+ title: "wallet/qr reader",
};
export const Reading = createExample(QrReaderPage, {});
diff --git a/packages/taler-wallet-webextension/src/wallet/index.stories.tsx b/packages/taler-wallet-webextension/src/wallet/index.stories.tsx
index d63f25ead..42808b573 100644
--- a/packages/taler-wallet-webextension/src/wallet/index.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/index.stories.tsx
@@ -25,8 +25,7 @@ import * as a4 from "./DepositPage/stories.js";
import * as a5 from "./ExchangeAddConfirm.stories.js";
import * as a6 from "./ExchangeAddSetUrl.stories.js";
import * as a7 from "./History.stories.js";
-import * as a8 from "./ProviderAddConfirmProvider.stories.js";
-import * as a9 from "./ProviderAddSetUrl.stories.js";
+import * as a8 from "./AddBackupProvider/stories.js";
import * as a10 from "./ProviderDetail.stories.js";
import * as a11 from "./ReserveCreated.stories.js";
import * as a12 from "./Settings.stories.js";
@@ -47,7 +46,6 @@ export default [
a6,
a7,
a8,
- a9,
a10,
a11,
a12,