gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: fix: take into account dd48 w


From: gnunet
Subject: [taler-wallet-core] branch master updated: fix: take into account dd48 when adding exchanges
Date: Mon, 09 Oct 2023 20:42:26 +0200

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new b173b3ac0 fix: take into account dd48 when adding exchanges
b173b3ac0 is described below

commit b173b3ac0f1a04ef39d82477251dbf4418feb403
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Mon Oct 9 15:42:20 2023 -0300

    fix: take into account dd48 when adding exchanges
---
 packages/taler-wallet-webextension/package.json    |   4 +-
 .../taler-wallet-webextension/src/mui/handlers.ts  |   4 +-
 .../src/wallet/AddExchange/index.ts                |  84 +++++++++
 .../src/wallet/AddExchange/state.ts                | 149 +++++++++++++++
 .../stories.tsx}                                   |  22 +--
 .../src/wallet/AddExchange/test.ts                 | 178 ++++++++++++++++++
 .../src/wallet/AddExchange/views.tsx               | 201 ++++++++++++++++++++
 .../src/wallet/Application.tsx                     |   7 +-
 .../src/wallet/ExchangeAddConfirm.tsx              |  75 --------
 .../src/wallet/ExchangeAddPage.tsx                 | 102 ----------
 .../src/wallet/ExchangeAddSetUrl.stories.tsx       |  68 -------
 .../src/wallet/ExchangeSetUrl.tsx                  | 209 ---------------------
 .../src/wallet/index.stories.tsx                   |   2 -
 packages/web-util/src/utils/request.ts             |   2 +-
 14 files changed, 624 insertions(+), 483 deletions(-)

diff --git a/packages/taler-wallet-webextension/package.json 
b/packages/taler-wallet-webextension/package.json
index fc91ed4cc..65784bd46 100644
--- a/packages/taler-wallet-webextension/package.json
+++ b/packages/taler-wallet-webextension/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@gnu-taler/taler-wallet-webextension",
-  "version": "0.9.3-dev.27",
+  "version": "0.9.3-dev.31",
   "description": "GNU Taler Wallet browser extension",
   "main": "./build/index.js",
   "types": "./build/index.d.ts",
@@ -75,4 +75,4 @@
   "pogen": {
     "domain": "taler-wallet-webex"
   }
-}
\ No newline at end of file
+}
diff --git a/packages/taler-wallet-webextension/src/mui/handlers.ts 
b/packages/taler-wallet-webextension/src/mui/handlers.ts
index ece1b3d85..735e8523f 100644
--- a/packages/taler-wallet-webextension/src/mui/handlers.ts
+++ b/packages/taler-wallet-webextension/src/mui/handlers.ts
@@ -34,8 +34,10 @@ export type SafeHandler<T> = {
   [__safe_handler]: true;
 };
 
+type UnsafeHandler<T> = ((p: T) => Promise<void>) | ((p: T) => void);
+
 export function withSafe<T>(
-  handler: (p: T) => Promise<void>,
+  handler: UnsafeHandler<T>,
   onError: (e: Error) => void,
 ): SafeHandler<T> {
   const sh = async function (p: T): Promise<void> {
diff --git a/packages/taler-wallet-webextension/src/wallet/AddExchange/index.ts 
b/packages/taler-wallet-webextension/src/wallet/AddExchange/index.ts
new file mode 100644
index 000000000..cece582e9
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/AddExchange/index.ts
@@ -0,0 +1,84 @@
+/*
+ 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 { TalerConfigResponse } from "@gnu-taler/taler-util";
+import { ErrorAlertView } from "../../components/CurrentAlerts.js";
+import { Loading } from "../../components/Loading.js";
+import { ErrorAlert } from "../../context/alert.js";
+import { compose, StateViewMap } from "../../utils/index.js";
+import { useComponentState } from "./state.js";
+import { ConfirmView, VerifyView } from "./views.js";
+import { HttpResponse, InputFieldHandler } from "@gnu-taler/web-util/browser";
+import { TextFieldHandler } from "../../mui/handlers.js";
+
+export interface Props {
+  currency?: string;
+  onBack: () => Promise<void>;
+  noDebounce?: boolean;
+}
+
+export type State = State.Loading
+  | State.LoadingUriError
+  | State.Confirm
+  | State.Verify;
+
+export namespace State {
+  export interface Loading {
+    status: "loading";
+    error: undefined;
+  }
+
+  export interface LoadingUriError {
+    status: "error";
+    error: ErrorAlert;
+  }
+
+  export interface BaseInfo {
+    error: undefined;
+  }
+  export interface Confirm extends BaseInfo {
+    status: "confirm";
+    url: string;
+    onCancel: () => Promise<void>;
+    onConfirm: () => Promise<void>;
+    error: undefined;
+  }
+  export interface Verify extends BaseInfo {
+    status: "verify";
+    error: undefined;
+
+    onCancel: () => Promise<void>;
+    onAccept: () => Promise<void>;
+
+    url: TextFieldHandler,
+    knownExchanges: URL[],
+    result: HttpResponse<TalerConfigResponse, unknown> | undefined,
+    expectedCurrency: string | undefined,
+  }
+}
+
+const viewMapping: StateViewMap<State> = {
+  loading: Loading,
+  error: ErrorAlertView,
+  confirm: ConfirmView,
+  verify: VerifyView,
+};
+
+export const AddExchange = compose(
+  "AddExchange",
+  (p: Props) => useComponentState(p),
+  viewMapping,
+);
diff --git a/packages/taler-wallet-webextension/src/wallet/AddExchange/state.ts 
b/packages/taler-wallet-webextension/src/wallet/AddExchange/state.ts
new file mode 100644
index 000000000..fc1762331
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/AddExchange/state.ts
@@ -0,0 +1,149 @@
+/*
+ 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 { useState, useEffect, useCallback } from "preact/hooks";
+import { Props, State } from "./index.js";
+import { ExchangeEntryStatus, TalerConfigResponse, TranslatedString, 
canonicalizeBaseUrl } from "@gnu-taler/taler-util";
+import { useBackendContext } from "../../context/backend.js";
+import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { RecursiveState } from "../../utils/index.js";
+import { HttpResponse, useApiContext } from "@gnu-taler/web-util/browser";
+import { alertFromError } from "../../context/alert.js";
+import { withSafe } from "../../mui/handlers.js";
+
+export function useComponentState({ onBack, currency, noDebounce }: Props): 
RecursiveState<State> {
+  const [verified, setVerified] = useState<
+    { url: string; config: TalerConfigResponse } | undefined
+  >(undefined);
+
+  const api = useBackendContext();
+  const hook = useAsyncAsHook(() =>
+    api.wallet.call(WalletApiOperation.ListExchanges, {}),
+  );
+  const walletExchanges = !hook ? [] : hook.hasError ? [] : 
hook.response.exchanges
+  const used = walletExchanges.filter(e => e.exchangeEntryStatus === 
ExchangeEntryStatus.Used);
+  const preset = walletExchanges.filter(e => e.exchangeEntryStatus === 
ExchangeEntryStatus.Preset);
+
+
+  if (!verified) {
+    return (): State => {
+      const { request } = useApiContext();
+      const ccc = useCallback(async (str: string) => {
+        const c = canonicalizeBaseUrl(str)
+        const found = used.findIndex((e) => e.exchangeBaseUrl === c);
+        if (found !== -1) {
+          throw Error("This exchange is already active")
+        }
+        const result = await request<TalerConfigResponse>(c, "/keys")
+        return result
+      }, [used])
+      const { result, value: url, update, error: requestError } = 
useDebounce<HttpResponse<TalerConfigResponse, unknown>>(ccc, noDebounce ?? 
false)
+      const [inputError, setInputError] = useState<string>()
+
+      return {
+        status: "verify",
+        error: undefined,
+        onCancel: onBack,
+        expectedCurrency: currency,
+        onAccept: async () => {
+          if (!url || !result || !result.ok) return;
+          setVerified({ url, config: result.data })
+        },
+        result,
+        knownExchanges: preset.map(e => new URL(e.exchangeBaseUrl)),
+        url: {
+          value: url ?? "",
+          error: inputError ?? requestError,
+          onInput: withSafe(update, (e) => {
+            setInputError(e.message)
+          })
+        },
+      };
+    }
+  }
+
+  async function onConfirm() {
+    if (!verified) return;
+    await api.wallet.call(WalletApiOperation.AddExchange, {
+      exchangeBaseUrl: canonicalizeBaseUrl(verified.url),
+      forceUpdate: true,
+    });
+    onBack();
+  }
+
+  return {
+    status: "confirm",
+    error: undefined,
+    onCancel: onBack,
+    onConfirm,
+    url: verified.url
+  };
+}
+
+
+
+function useDebounce<T>(
+  onTrigger: (v: string) => Promise<T>,
+  disabled: boolean,
+): {
+  loading: boolean;
+  error?: string;
+  value: string | undefined;
+  result: T | undefined;
+  update: (s: string) => void;
+} {
+  const [value, setValue] = useState<string>();
+  const [dirty, setDirty] = useState(false);
+  const [loading, setLoading] = useState(false);
+  const [result, setResult] = useState<T | undefined>(undefined);
+  const [error, setError] = useState<string | undefined>(undefined);
+
+  const [handler, setHandler] = useState<any | undefined>(undefined);
+
+  if (!disabled) {
+    useEffect(() => {
+      if (!value) return;
+      clearTimeout(handler);
+      const h = setTimeout(async () => {
+        setDirty(true);
+        setLoading(true);
+        try {
+          const result = await onTrigger(value);
+          setResult(result);
+          setError(undefined);
+          setLoading(false);
+        } catch (e) {
+          const errorMessage =
+            e instanceof Error ? e.message : `unknown error: ${e}`;
+          setError(errorMessage);
+          setLoading(false);
+          setResult(undefined);
+        }
+      }, 500);
+      setHandler(h);
+    }, [value, setHandler, onTrigger]);
+  }
+
+  return {
+    error: dirty ? error : undefined,
+    loading: loading,
+    result: result,
+    value: value,
+    update: disabled ? onTrigger : setValue ,
+  };
+}
+
diff --git 
a/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/AddExchange/stories.tsx
similarity index 57%
rename from 
packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.stories.tsx
rename to packages/taler-wallet-webextension/src/wallet/AddExchange/stories.tsx
index 140fc58dc..4e2610743 100644
--- 
a/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/AddExchange/stories.tsx
@@ -20,26 +20,10 @@
  */
 
 import * as tests from "@gnu-taler/web-util/testing";
-import { ExchangeAddConfirmPage as TestedComponent } from 
"./ExchangeAddConfirm.js";
+import { ConfirmView, VerifyView } from "./views.js";
 
 export default {
-  title: "exchange add confirm",
-  component: TestedComponent,
-  argTypes: {
-    onRetry: { action: "onRetry" },
-    onDelete: { action: "onDelete" },
-    onBack: { action: "onBack" },
-  },
+  title: "example",
 };
 
-export const TermsNotFound = tests.createExample(TestedComponent, {
-  url: "https://exchange.demo.taler.net/";,
-});
-
-export const NewTerms = tests.createExample(TestedComponent, {
-  url: "https://exchange.demo.taler.net/";,
-});
-
-export const TermsChanged = tests.createExample(TestedComponent, {
-  url: "https://exchange.demo.taler.net/";,
-});
+// export const Ready = tests.createExample(ReadyView, {});
diff --git a/packages/taler-wallet-webextension/src/wallet/AddExchange/test.ts 
b/packages/taler-wallet-webextension/src/wallet/AddExchange/test.ts
new file mode 100644
index 000000000..c9ae58afd
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/AddExchange/test.ts
@@ -0,0 +1,178 @@
+/*
+ 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 { expect } from "chai";
+import { createWalletApiMock } from "../../test-utils.js";
+import * as tests from "@gnu-taler/web-util/testing";
+import { Props } from "./index.js";
+import { useComponentState } from "./state.js";
+import { nullFunction } from "../../mui/handlers.js";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { ExchangeEntryStatus, ExchangeTosStatus, ExchangeUpdateStatus } from 
"@gnu-taler/taler-util";
+const props: Props = {
+  onBack: nullFunction,
+  noDebounce: true,
+};
+
+describe("AddExchange states", () => {
+  it("should start in 'verify' state", async () => {
+    const { handler, TestingContext } = createWalletApiMock();
+
+    handler.addWalletCallResponse(WalletApiOperation.ListExchanges, {}, {
+      exchanges:[{
+        exchangeBaseUrl: "http://exchange.local/";,
+        ageRestrictionOptions: [],
+        currency: "ARS",
+        exchangeEntryStatus: ExchangeEntryStatus.Ephemeral,
+        tosStatus: ExchangeTosStatus.Pending,
+        exchangeUpdateStatus: ExchangeUpdateStatus.Failed,
+        paytoUris: [],
+      }]
+    })
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      useComponentState,
+      props,
+      [
+        (state) => {
+          expect(state.status).equal("verify");
+          if (state.status !== "verify") return;
+          expect(state.url.value).eq("");
+          expect(state.expectedCurrency).is.undefined;
+          expect(state.result).is.undefined;          
+        },
+        (state) => {
+          expect(state.status).equal("verify");
+          if (state.status !== "verify") return;
+          expect(state.url.value).eq("");
+          expect(state.expectedCurrency).is.undefined;
+          expect(state.result).is.undefined;
+        },
+      ],
+      TestingContext,
+    );
+
+    expect(hookBehavior).deep.equal({ result: "ok" });
+    expect(handler.getCallingQueueState()).eq("empty");
+  });
+
+
+
+  it("should not be able to add a known exchange", async () => {
+    const { handler, TestingContext } = createWalletApiMock();
+
+    handler.addWalletCallResponse(WalletApiOperation.ListExchanges, {}, {
+      exchanges:[{
+        exchangeBaseUrl: "http://exchange.local/";,
+        ageRestrictionOptions: [],
+        currency: "ARS",
+        exchangeEntryStatus: ExchangeEntryStatus.Used,
+        tosStatus: ExchangeTosStatus.Pending,
+        exchangeUpdateStatus: ExchangeUpdateStatus.Ready,
+        paytoUris: [],
+      }]
+    })
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      useComponentState,
+      props,
+      [
+        (state) => {
+          expect(state.status).equal("verify");
+          if (state.status !== "verify") return;
+          expect(state.url.value).eq("");
+          expect(state.expectedCurrency).is.undefined;
+          expect(state.result).is.undefined;          
+        },
+        (state) => {
+          expect(state.status).equal("verify");
+          if (state.status !== "verify") return;
+          expect(state.url.value).eq("");
+          expect(state.expectedCurrency).is.undefined;
+          expect(state.result).is.undefined;
+          expect(state.error).is.undefined;
+          expect(state.url.onInput).is.not.undefined;
+          if (!state.url.onInput) return;
+          state.url.onInput("http://exchange.local/";)
+        },
+        (state) => {
+          expect(state.status).equal("verify");
+          if (state.status !== "verify") return;
+          expect(state.url.value).eq("");
+          expect(state.expectedCurrency).is.undefined;
+          expect(state.result).is.undefined;
+          expect(state.url.error).eq("This exchange is already active");
+          expect(state.url.onInput).is.not.undefined;
+        },
+      ],
+      TestingContext,
+    );
+
+    expect(hookBehavior).deep.equal({ result: "ok" });
+    expect(handler.getCallingQueueState()).eq("empty");
+  });
+
+
+  it("should be able to add a preset exchange", async () => {
+    const { handler, TestingContext } = createWalletApiMock();
+
+    handler.addWalletCallResponse(WalletApiOperation.ListExchanges, {}, {
+      exchanges:[{
+        exchangeBaseUrl: "http://exchange.local/";,
+        ageRestrictionOptions: [],
+        currency: "ARS",
+        exchangeEntryStatus: ExchangeEntryStatus.Preset,
+        tosStatus: ExchangeTosStatus.Pending,
+        exchangeUpdateStatus: ExchangeUpdateStatus.Ready,
+        paytoUris: [],
+      }]
+    })
+
+    const hookBehavior = await tests.hookBehaveLikeThis(
+      useComponentState,
+      props,
+      [
+        (state) => {
+          expect(state.status).equal("verify");
+          if (state.status !== "verify") return;
+          expect(state.url.value).eq("");
+          expect(state.expectedCurrency).is.undefined;
+          expect(state.result).is.undefined;          
+        },
+        (state) => {
+          expect(state.status).equal("verify");
+          if (state.status !== "verify") return;
+          expect(state.url.value).eq("");
+          expect(state.expectedCurrency).is.undefined;
+          expect(state.result).is.undefined;
+          expect(state.error).is.undefined;
+          expect(state.url.onInput).is.not.undefined;
+          if (!state.url.onInput) return;
+          state.url.onInput("http://exchange.local/";)
+        },
+      ],
+      TestingContext,
+    );
+
+    expect(hookBehavior).deep.equal({ result: "ok" });
+    expect(handler.getCallingQueueState()).eq("empty");
+  });
+});
diff --git 
a/packages/taler-wallet-webextension/src/wallet/AddExchange/views.tsx 
b/packages/taler-wallet-webextension/src/wallet/AddExchange/views.tsx
new file mode 100644
index 000000000..e1bc7f0f6
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/AddExchange/views.tsx
@@ -0,0 +1,201 @@
+/*
+ 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 { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { Fragment, h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { ErrorMessage } from "../../components/ErrorMessage.js";
+import { Input, LightText, SubTitle, Title, WarningBox } from 
"../../components/styled/index.js";
+import { TermsOfService } from "../../components/TermsOfService/index.js";
+import { Button } from "../../mui/Button.js";
+import { State } from "./index.js";
+
+
+export function VerifyView({
+  expectedCurrency,
+  onCancel,
+  onAccept,
+  result,
+  knownExchanges,
+  url,
+}: State.Verify): VNode {
+  const { i18n } = useTranslationContext();
+
+  return (
+    <Fragment>
+      <section>
+        {!expectedCurrency ? (
+          <Title>
+            <i18n.Translate>Add new exchange</i18n.Translate>
+          </Title>
+        ) : (
+          <SubTitle>
+            <i18n.Translate>Add exchange for 
{expectedCurrency}</i18n.Translate>
+          </SubTitle>
+        )}
+        {!result && (
+          <LightText>
+            <i18n.Translate>
+              Enter the URL of an exchange you trust.
+            </i18n.Translate>
+          </LightText>
+        )}
+        {result && (
+          <LightText>
+            <i18n.Translate>
+              An exchange has been found! Review the information and click next
+            </i18n.Translate>
+          </LightText>
+        )}
+        {result && result.ok && expectedCurrency && expectedCurrency !== 
result.data.currency && (
+          <WarningBox>
+            <i18n.Translate>
+              This exchange doesn&apos;t match the expected currency
+              <b>{expectedCurrency}</b>
+            </i18n.Translate>
+          </WarningBox>
+        )}
+        {result && !result.ok && !result.loading && (
+          <ErrorMessage
+            title={i18n.str`Unable to verify this exchange`}
+            description={result.message}
+          />
+        )}
+        <p>
+          <Input invalid={result && !result.ok} >
+            <label>URL</label>
+            <input
+              type="text"
+              placeholder="https://";
+              value={url.value}
+              onInput={(e) => {
+                if (url.onInput) {
+                  url.onInput(e.currentTarget.value)
+                }
+              }}
+            />
+          </Input>
+          {result && result.loading && (
+            <div>
+              <i18n.Translate>loading</i18n.Translate>...
+            </div>
+          )}
+          {result && result.ok && !result.loading && (
+            <Fragment>
+              <Input>
+                <label>
+                  <i18n.Translate>Version</i18n.Translate>
+                </label>
+                <input type="text" disabled value={result.data.version} />
+              </Input>
+              <Input>
+                <label>
+                  <i18n.Translate>Currency</i18n.Translate>
+                </label>
+                <input type="text" disabled value={result.data.currency} />
+              </Input>
+            </Fragment>
+          )}
+        </p>
+        {url.error && (
+          <ErrorMessage
+            title={i18n.str`Can't use this URL`}
+            description={url.error}
+          />
+        )}
+      </section>
+      <footer>
+        <Button variant="contained" color="secondary" onClick={onCancel}>
+          <i18n.Translate>Cancel</i18n.Translate>
+        </Button>
+        <Button
+          variant="contained"
+          disabled={
+            !result ||
+            result.loading ||
+            !result.ok ||
+            (!!expectedCurrency && expectedCurrency !== result.data.currency)
+          }
+          onClick={onAccept}
+        >
+          <i18n.Translate>Next</i18n.Translate>
+        </Button>
+      </footer>
+      <section>
+        <ul>
+          {knownExchanges.map(ex => {
+            return <li><a href="#" onClick={(e) => {
+              if (url.onInput) {
+                url.onInput(ex.href)
+              }
+              e.preventDefault()
+            }}>
+            {ex.href}</a></li>
+          })}
+        </ul>
+      </section>
+    </Fragment>
+  );
+}
+
+
+export function ConfirmView({
+  url,
+  onCancel,
+  onConfirm,
+}: State.Confirm): VNode {
+  const { i18n } = useTranslationContext();
+
+  const [accepted, setAccepted] = useState(false);
+
+  return (
+    <Fragment>
+      <section>
+        <Title>
+          <i18n.Translate>Review terms of service</i18n.Translate>
+        </Title>
+        <div>
+          <i18n.Translate>Exchange URL</i18n.Translate>:
+          <a href={url} target="_blank" rel="noreferrer">
+            {url}
+          </a>
+        </div>
+      </section>
+
+      <TermsOfService key="terms" exchangeUrl={url} onChange={setAccepted} />
+
+      <footer>
+        <Button
+          key="cancel"
+          variant="contained"
+          color="secondary"
+          onClick={onCancel}
+        >
+          <i18n.Translate>Cancel</i18n.Translate>
+        </Button>
+        <Button
+          key="add"
+          variant="contained"
+          color="success"
+          disabled={!accepted}
+          onClick={onConfirm}
+        >
+          <i18n.Translate>Add exchange</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 98515aac0..18291a25a 100644
--- a/packages/taler-wallet-webextension/src/wallet/Application.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Application.tsx
@@ -65,15 +65,15 @@ import {
   WithdrawPageFromParams,
   WithdrawPageFromURI,
 } from "../cta/Withdraw/index.js";
+import { useIsOnline } from "../hooks/useIsOnline.js";
 import { strings } from "../i18n/strings.js";
-import { platform } from "../platform/foreground.js";
 import CloseIcon from "../svg/close_24px.inline.svg";
 import { AddBackupProviderPage } from "./AddBackupProvider/index.js";
+import { AddExchange } from "./AddExchange/index.js";
 import { BackupPage } from "./BackupPage.js";
 import { DepositPage } from "./DepositPage/index.js";
 import { DestinationSelectionPage } from "./DestinationSelection/index.js";
 import { DeveloperPage } from "./DeveloperPage.js";
-import { ExchangeAddPage } from "./ExchangeAddPage.js";
 import { HistoryPage } from "./History.js";
 import { NotificationsPage } from "./Notifications/index.js";
 import { ProviderDetailPage } from "./ProviderDetailPage.js";
@@ -81,7 +81,6 @@ import { QrReaderPage } from "./QrReader.js";
 import { SettingsPage } from "./Settings.js";
 import { TransactionPage } from "./Transaction.js";
 import { WelcomePage } from "./Welcome.js";
-import { useIsOnline } from "../hooks/useIsOnline.js";
 
 export function Application(): VNode {
   const { i18n } = useTranslationContext();
@@ -143,7 +142,7 @@ export function Application(): VNode {
             path={Pages.settingsExchangeAdd.pattern}
             component={() => (
               <WalletTemplate>
-                <ExchangeAddPage onBack={() => redirectTo(Pages.balance)} />
+                <AddExchange onBack={() => redirectTo(Pages.balance)} />
               </WalletTemplate>
             )}
           />
diff --git 
a/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.tsx 
b/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.tsx
deleted file mode 100644
index 0d4f234b3..000000000
--- a/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.tsx
+++ /dev/null
@@ -1,75 +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 { Fragment, h, VNode } from "preact";
-import { useState } from "preact/hooks";
-import { Title } from "../components/styled/index.js";
-import { TermsOfService } from "../components/TermsOfService/index.js";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Button } from "../mui/Button.js";
-
-export interface Props {
-  url: string;
-  onCancel: () => Promise<void>;
-  onConfirm: () => Promise<void>;
-}
-
-export function ExchangeAddConfirmPage({
-  url,
-  onCancel,
-  onConfirm,
-}: Props): VNode {
-  const { i18n } = useTranslationContext();
-
-  const [accepted, setAccepted] = useState(false);
-
-  return (
-    <Fragment>
-      <section>
-        <Title>
-          <i18n.Translate>Review terms of service</i18n.Translate>
-        </Title>
-        <div>
-          <i18n.Translate>Exchange URL</i18n.Translate>:
-          <a href={url} target="_blank" rel="noreferrer">
-            {url}
-          </a>
-        </div>
-      </section>
-
-      <TermsOfService key="terms" exchangeUrl={url} onChange={setAccepted} />
-
-      <footer>
-        <Button
-          key="cancel"
-          variant="contained"
-          color="secondary"
-          onClick={onCancel}
-        >
-          <i18n.Translate>Cancel</i18n.Translate>
-        </Button>
-        <Button
-          key="add"
-          variant="contained"
-          color="success"
-          disabled={!accepted}
-          onClick={onConfirm}
-        >
-          <i18n.Translate>Add exchange</i18n.Translate>
-        </Button>
-      </footer>
-    </Fragment>
-  );
-}
diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeAddPage.tsx 
b/packages/taler-wallet-webextension/src/wallet/ExchangeAddPage.tsx
deleted file mode 100644
index 9be12fb28..000000000
--- a/packages/taler-wallet-webextension/src/wallet/ExchangeAddPage.tsx
+++ /dev/null
@@ -1,102 +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 {
-  canonicalizeBaseUrl,
-  TalerConfigResponse,
-} from "@gnu-taler/taler-util";
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import { h, VNode } from "preact";
-import { useState } from "preact/hooks";
-import { useBackendContext } from "../context/backend.js";
-import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
-import { queryToSlashKeys } from "../utils/index.js";
-import { ExchangeAddConfirmPage } from "./ExchangeAddConfirm.js";
-import { ExchangeSetUrlPage } from "./ExchangeSetUrl.js";
-
-interface Props {
-  currency?: string;
-  onBack: () => Promise<void>;
-}
-
-export function ExchangeAddPage({ currency, onBack }: Props): VNode {
-  const [verifying, setVerifying] = useState<
-    { url: string; config: TalerConfigResponse } | undefined
-  >(undefined);
-
-  const api = useBackendContext();
-  const knownExchangesResponse = useAsyncAsHook(() =>
-    api.wallet.call(WalletApiOperation.ListExchanges, {}),
-  );
-  const knownExchanges = !knownExchangesResponse
-    ? []
-    : knownExchangesResponse.hasError
-    ? []
-    : knownExchangesResponse.response.exchanges;
-
-  if (!verifying) {
-    return (
-      <ExchangeSetUrlPage
-        onCancel={onBack}
-        expectedCurrency={currency}
-        onVerify={async (url) => {
-          const found =
-            knownExchanges.findIndex((e) => e.exchangeBaseUrl === url) !== -1;
-
-          if (found) {
-            throw Error("This exchange is already known");
-          }
-          return {
-            name: "1",
-            version: "15:0:0",
-            currency: "ARS",
-          };
-        }}
-        onConfirm={
-          async (url) => {
-            setVerifying({
-              url,
-              config: {
-                name: "1",
-                version: "15:0:0",
-                currency: "ARS",
-              },
-            });
-            return undefined;
-          }
-          // queryToSlashKeys<TalerConfigResponse>(url)
-          //   .then((config) => {
-          //     setVerifying({ url, config });
-          //   })
-          //   .catch((e) => e.message)
-        }
-      />
-    );
-  }
-  return (
-    <ExchangeAddConfirmPage
-      url={verifying.url}
-      onCancel={onBack}
-      onConfirm={async () => {
-        await api.wallet.call(WalletApiOperation.AddExchange, {
-          exchangeBaseUrl: canonicalizeBaseUrl(verifying.url),
-          forceUpdate: true,
-        });
-        onBack();
-      }}
-    />
-  );
-}
diff --git 
a/packages/taler-wallet-webextension/src/wallet/ExchangeAddSetUrl.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/ExchangeAddSetUrl.stories.tsx
deleted file mode 100644
index e69268b08..000000000
--- 
a/packages/taler-wallet-webextension/src/wallet/ExchangeAddSetUrl.stories.tsx
+++ /dev/null
@@ -1,68 +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 { queryToSlashKeys } from "../utils/index.js";
-import { ExchangeSetUrlPage as TestedComponent } from "./ExchangeSetUrl.js";
-
-export default {
-  title: "exchange add set url",
-};
-
-export const ExpectedUSD = tests.createExample(TestedComponent, {
-  expectedCurrency: "USD",
-  onVerify: queryToSlashKeys,
-});
-
-export const ExpectedKUDOS = tests.createExample(TestedComponent, {
-  expectedCurrency: "KUDOS",
-  onVerify: queryToSlashKeys,
-});
-
-export const InitialState = tests.createExample(TestedComponent, {
-  onVerify: queryToSlashKeys,
-});
-
-const knownExchanges = [
-  {
-    currency: "TESTKUDOS",
-    exchangeBaseUrl: "https://exchange.demo.taler.net/";,
-    tos: {
-      currentVersion: "1",
-      acceptedVersion: "1",
-      content: "content of tos",
-      contentType: "text/plain",
-    },
-    paytoUris: [],
-  },
-];
-
-export const WithDemoAsKnownExchange = tests.createExample(TestedComponent, {
-  onVerify: async (url) => {
-    const found =
-      knownExchanges.findIndex((e) => e.exchangeBaseUrl === url) !== -1;
-
-    if (found) {
-      throw Error("This exchange is already known");
-    }
-    return queryToSlashKeys(url);
-  },
-});
diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSetUrl.tsx 
b/packages/taler-wallet-webextension/src/wallet/ExchangeSetUrl.tsx
deleted file mode 100644
index 4fea3bc98..000000000
--- a/packages/taler-wallet-webextension/src/wallet/ExchangeSetUrl.tsx
+++ /dev/null
@@ -1,209 +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 {
-  canonicalizeBaseUrl,
-  TalerConfigResponse,
-} from "@gnu-taler/taler-util";
-import { Fragment, h, VNode } from "preact";
-import { useEffect, useState } from "preact/hooks";
-import { ErrorMessage } from "../components/ErrorMessage.js";
-import {
-  Input,
-  LightText,
-  SubTitle,
-  Title,
-  WarningBox,
-} from "../components/styled/index.js";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Button } from "../mui/Button.js";
-
-export interface Props {
-  initialValue?: string;
-  expectedCurrency?: string;
-  onCancel: () => Promise<void>;
-  onVerify: (s: string) => Promise<TalerConfigResponse | undefined>;
-  onConfirm: (url: string) => Promise<string | undefined>;
-  withError?: string;
-}
-
-function useEndpointStatus<T>(
-  endpoint: string,
-  onVerify: (e: string) => Promise<T>,
-): {
-  loading: boolean;
-  error?: string;
-  endpoint: string;
-  result: T | undefined;
-  updateEndpoint: (s: string) => void;
-} {
-  const [value, setValue] = useState<string>(endpoint);
-  const [dirty, setDirty] = useState(false);
-  const [loading, setLoading] = useState(false);
-  const [result, setResult] = useState<T | undefined>(undefined);
-  const [error, setError] = useState<string | undefined>(undefined);
-
-  const [handler, setHandler] = useState<number | undefined>(undefined);
-
-  useEffect(() => {
-    if (!value) return;
-    window.clearTimeout(handler);
-    const h = window.setTimeout(async () => {
-      setDirty(true);
-      setLoading(true);
-      try {
-        const url = canonicalizeBaseUrl(value);
-        const result = await onVerify(url);
-        setResult(result);
-        setError(undefined);
-        setLoading(false);
-      } catch (e) {
-        const errorMessage =
-          e instanceof Error ? e.message : `unknown error: ${e}`;
-        setError(errorMessage);
-        setLoading(false);
-        setResult(undefined);
-      }
-    }, 500);
-    setHandler(h);
-  }, [value, setHandler, onVerify]);
-
-  return {
-    error: dirty ? error : undefined,
-    loading: loading,
-    result: result,
-    endpoint: value,
-    updateEndpoint: setValue,
-  };
-}
-
-export function ExchangeSetUrlPage({
-  initialValue,
-  expectedCurrency,
-  onCancel,
-  onVerify,
-  onConfirm,
-}: Props): VNode {
-  const { i18n } = useTranslationContext();
-  const { loading, result, endpoint, updateEndpoint, error } =
-    useEndpointStatus(initialValue ?? "", onVerify);
-
-  const [confirmationError, setConfirmationError] = useState<
-    string | undefined
-  >(undefined);
-
-  return (
-    <Fragment>
-      <section>
-        {!expectedCurrency ? (
-          <Title>
-            <i18n.Translate>Add new exchange</i18n.Translate>
-          </Title>
-        ) : (
-          <SubTitle>
-            <i18n.Translate>Add exchange for 
{expectedCurrency}</i18n.Translate>
-          </SubTitle>
-        )}
-        {!result && (
-          <LightText>
-            <i18n.Translate>
-              Enter the URL of an exchange you trust.
-            </i18n.Translate>
-          </LightText>
-        )}
-        {result && (
-          <LightText>
-            <i18n.Translate>
-              An exchange has been found! Review the information and click next
-            </i18n.Translate>
-          </LightText>
-        )}
-        {result && expectedCurrency && expectedCurrency !== result.currency && 
(
-          <WarningBox>
-            <i18n.Translate>
-              This exchange doesn&apos;t match the expected currency
-              <b>{expectedCurrency}</b>
-            </i18n.Translate>
-          </WarningBox>
-        )}
-        {error && (
-          <ErrorMessage
-            title={i18n.str`Unable to verify this exchange`}
-            description={error}
-          />
-        )}
-        {confirmationError && (
-          <ErrorMessage
-            title={i18n.str`Unable to add this exchange`}
-            description={confirmationError}
-          />
-        )}
-        <p>
-          <Input invalid={!!error}>
-            <label>URL</label>
-            <input
-              type="text"
-              placeholder="https://";
-              value={endpoint}
-              onInput={(e) => updateEndpoint(e.currentTarget.value)}
-            />
-          </Input>
-          {loading && (
-            <div>
-              <i18n.Translate>loading</i18n.Translate>...
-            </div>
-          )}
-          {result && !loading && (
-            <Fragment>
-              <Input>
-                <label>
-                  <i18n.Translate>Version</i18n.Translate>
-                </label>
-                <input type="text" disabled value={result.version} />
-              </Input>
-              <Input>
-                <label>
-                  <i18n.Translate>Currency</i18n.Translate>
-                </label>
-                <input type="text" disabled value={result.currency} />
-              </Input>
-            </Fragment>
-          )}
-        </p>
-      </section>
-      <footer>
-        <Button variant="contained" color="secondary" onClick={onCancel}>
-          <i18n.Translate>Cancel</i18n.Translate>
-        </Button>
-        <Button
-          variant="contained"
-          disabled={
-            !result ||
-            !!error ||
-            (!!expectedCurrency && expectedCurrency !== result.currency)
-          }
-          onClick={() => {
-            const url = canonicalizeBaseUrl(endpoint);
-            return onConfirm(url).then((r) =>
-              r ? setConfirmationError(r) : undefined,
-            );
-          }}
-        >
-          <i18n.Translate>Next</i18n.Translate>
-        </Button>
-      </footer>
-    </Fragment>
-  );
-}
diff --git a/packages/taler-wallet-webextension/src/wallet/index.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/index.stories.tsx
index 05e141dc6..989292326 100644
--- a/packages/taler-wallet-webextension/src/wallet/index.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/index.stories.tsx
@@ -21,8 +21,6 @@
 
 export * as a1 from "./Backup.stories.js";
 export * as a4 from "./DepositPage/stories.js";
-export * as a5 from "./ExchangeAddConfirm.stories.js";
-export * as a6 from "./ExchangeAddSetUrl.stories.js";
 export * as a7 from "./History.stories.js";
 export * as a8 from "./AddBackupProvider/stories.js";
 export * as a10 from "./ProviderDetail.stories.js";
diff --git a/packages/web-util/src/utils/request.ts 
b/packages/web-util/src/utils/request.ts
index ef4d8e847..f8a892d99 100644
--- a/packages/web-util/src/utils/request.ts
+++ b/packages/web-util/src/utils/request.ts
@@ -76,7 +76,7 @@ export async function defaultRequestHandler<T>(
       type: ErrorType.UNEXPECTED,
       exception: undefined,
       loading: false,
-      message: `invalid URL: "${validURL}"`,
+      message: `invalid URL: "${baseUrl}${endpoint}"`,
     };
     throw new RequestError(error)
   }

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]