gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: components renaming to follow


From: gnunet
Subject: [taler-wallet-core] branch master updated: components renaming to follow react pattern
Date: Thu, 17 Jun 2021 03:27:56 +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 0b497660 components renaming to follow react pattern
0b497660 is described below

commit 0b4976601fe2ecb0462fe72ae188b5cbba06d9cc
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Wed Jun 16 18:21:03 2021 -0300

    components renaming to follow react pattern
---
 .../src/components/DebugCheckbox.tsx               |  47 ++
 .../src/hooks/useExtendedPermissions.ts            |  53 ++
 .../src/hooks/useExtendedPermissions.tsx           |  24 -
 .../src/hooks/useTalerActionURL.ts                 |  93 +++
 .../src/popup/Balance.tsx                          | 173 ++++
 .../taler-wallet-webextension/src/popup/Debug.tsx  |  63 ++
 .../src/popup/History.tsx                          | 227 ++++++
 .../src/popup/Settings.tsx                         |  34 +
 .../{popup.stories.tsx => Transaction.stories.tsx} |  37 +-
 .../src/popup/Transaction.tsx                      | 327 ++++++++
 .../taler-wallet-webextension/src/popup/popup.tsx  | 899 +--------------------
 .../src/popupEntryPoint.tsx                        |  43 +-
 .../src/wallet/{pay.tsx => Pay.tsx}                |  13 +-
 .../src/wallet/{refund.tsx => Refund.tsx}          |  21 +-
 .../src/wallet/{tip.tsx => Tip.tsx}                |  14 +-
 .../src/wallet/{welcome.tsx => Welcome.tsx}        |  42 +-
 .../{withdraw.stories.tsx => Withdraw.stories.tsx} |   2 +-
 .../src/wallet/{withdraw.tsx => Withdraw.tsx}      |  14 +-
 .../src/walletEntryPoint.tsx                       |  20 +-
 19 files changed, 1068 insertions(+), 1078 deletions(-)

diff --git 
a/packages/taler-wallet-webextension/src/components/DebugCheckbox.tsx 
b/packages/taler-wallet-webextension/src/components/DebugCheckbox.tsx
new file mode 100644
index 00000000..7534629f
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/components/DebugCheckbox.tsx
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ (C) 2016 GNUnet e.V.
+
+ 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.
+
+ 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
+ TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+ import { JSX } from "preact";
+
+export function DebugCheckbox({ enabled, onToggle }: { enabled: boolean; 
onToggle: () => void; }): JSX.Element {
+  return (
+    <div>
+      <input
+        checked={enabled}
+        onClick={onToggle}
+        type="checkbox"
+        id="checkbox-perm"
+        style={{ width: "1.5em", height: "1.5em", verticalAlign: "middle" }} />
+      <label
+        htmlFor="checkbox-perm"
+        style={{ marginLeft: "0.5em", fontWeight: "bold" }}
+      >
+        Automatically open wallet based on page content
+      </label>
+      <span
+        style={{
+          color: "#383838",
+          fontSize: "smaller",
+          display: "block",
+          marginLeft: "2em",
+        }}
+      >
+        (Enabling this option below will make using the wallet faster, but
+        requires more permissions from your browser.)
+      </span>
+    </div>
+  );
+}
diff --git 
a/packages/taler-wallet-webextension/src/hooks/useExtendedPermissions.ts 
b/packages/taler-wallet-webextension/src/hooks/useExtendedPermissions.ts
new file mode 100644
index 00000000..809863dc
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/hooks/useExtendedPermissions.ts
@@ -0,0 +1,53 @@
+import { useState, useEffect } from "preact/hooks";
+import * as wxApi from "../wxApi";
+import { getPermissionsApi } from "../compat";
+import { extendedPermissions } from "../permissions";
+
+
+export function useExtendedPermissions(): [boolean, () => void] {
+  const [enabled, setEnabled] = useState(false);
+
+  const toggle = () => {
+    setEnabled(v => !v);
+    handleExtendedPerm(enabled).then(result => {
+      setEnabled(result);
+    });
+  };
+
+  useEffect(() => {
+    async function getExtendedPermValue(): Promise<void> {
+      const res = await wxApi.getExtendedPermissions();
+      setEnabled(res.newValue);
+    }
+    getExtendedPermValue();
+  }, []);
+  return [enabled, toggle];
+}
+
+async function handleExtendedPerm(isEnabled: boolean): Promise<boolean> {
+  let nextVal: boolean | undefined;
+
+  if (!isEnabled) {
+    const granted = await new Promise<boolean>((resolve, reject) => {
+      // We set permissions here, since apparently FF wants this to be done
+      // as the result of an input event ...
+      getPermissionsApi().request(extendedPermissions, (granted: boolean) => {
+        if (chrome.runtime.lastError) {
+          console.error("error requesting permissions");
+          console.error(chrome.runtime.lastError);
+          reject(chrome.runtime.lastError);
+          return;
+        }
+        console.log("permissions granted:", granted);
+        resolve(granted);
+      });
+    });
+    const res = await wxApi.setExtendedPermissions(granted);
+    nextVal = res.newValue;
+  } else {
+    const res = await wxApi.setExtendedPermissions(false);
+    nextVal = res.newValue;
+  }
+  console.log("new permissions applied:", nextVal ?? false);
+  return nextVal ?? false
+}
\ No newline at end of file
diff --git 
a/packages/taler-wallet-webextension/src/hooks/useExtendedPermissions.tsx 
b/packages/taler-wallet-webextension/src/hooks/useExtendedPermissions.tsx
deleted file mode 100644
index f5c788cf..00000000
--- a/packages/taler-wallet-webextension/src/hooks/useExtendedPermissions.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { useState, useEffect } from "preact/hooks";
-import * as wxApi from "../wxApi";
-import { handleExtendedPerm } from "../wallet/welcome";
-
-
-export function useExtendedPermissions(): [boolean, () => void] {
-  const [enabled, setEnabled] = useState(false);
-
-  const toggle = () => {
-    setEnabled(v => !v);
-    handleExtendedPerm(enabled).then(result => {
-      setEnabled(result);
-    });
-  };
-
-  useEffect(() => {
-    async function getExtendedPermValue(): Promise<void> {
-      const res = await wxApi.getExtendedPermissions();
-      setEnabled(res.newValue);
-    }
-    getExtendedPermValue();
-  }, []);
-  return [enabled, toggle];
-}
diff --git a/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts 
b/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts
new file mode 100644
index 00000000..b884ca94
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts
@@ -0,0 +1,93 @@
+import { classifyTalerUri, TalerUriType } from "@gnu-taler/taler-util";
+import { useEffect, useState } from "preact/hooks";
+
+export function useTalerActionURL(): [string | undefined, (s: boolean) => 
void] {
+  const [talerActionUrl, setTalerActionUrl] = useState<string | undefined>(
+    undefined
+  );
+  const [dismissed, setDismissed] = useState(false);
+  useEffect(() => {
+    async function check(): Promise<void> {
+      const talerUri = await findTalerUriInActiveTab();
+      if (talerUri) {
+        const actionUrl = actionForTalerUri(talerUri);
+        setTalerActionUrl(actionUrl);
+      }
+    }
+    check();
+  }, []);
+  const url = dismissed ? undefined : talerActionUrl;
+  return [url, setDismissed];
+}
+
+function actionForTalerUri(talerUri: string): string | undefined {
+  const uriType = classifyTalerUri(talerUri);
+  switch (uriType) {
+    case TalerUriType.TalerWithdraw:
+      return makeExtensionUrlWithParams("static/wallet.html#/withdraw", {
+        talerWithdrawUri: talerUri,
+      });
+    case TalerUriType.TalerPay:
+      return makeExtensionUrlWithParams("static/wallet.html#/pay", {
+        talerPayUri: talerUri,
+      });
+    case TalerUriType.TalerTip:
+      return makeExtensionUrlWithParams("static/wallet.html#/tip", {
+        talerTipUri: talerUri,
+      });
+    case TalerUriType.TalerRefund:
+      return makeExtensionUrlWithParams("static/wallet.html#/refund", {
+        talerRefundUri: talerUri,
+      });
+    case TalerUriType.TalerNotifyReserve:
+      // FIXME: implement
+      break;
+    default:
+      console.warn(
+        "Response with HTTP 402 has Taler header, but header value is not a 
taler:// URI.",
+      );
+      break;
+  }
+  return undefined;
+}
+
+function makeExtensionUrlWithParams(
+  url: string,
+  params?: { [name: string]: string | undefined },
+): string {
+  const innerUrl = new URL(chrome.extension.getURL("/" + url));
+  if (params) {
+    for (const key in params) {
+      const p = params[key];
+      if (p) {
+        innerUrl.searchParams.set(key, p);
+      }
+    }
+  }
+  return innerUrl.href;
+}
+
+async function findTalerUriInActiveTab(): Promise<string | undefined> {
+  return new Promise((resolve, reject) => {
+    chrome.tabs.executeScript(
+      {
+        code: `
+        (() => {
+          let x = document.querySelector("a[href^='taler://'") || 
document.querySelector("a[href^='taler+http://'");
+          return x ? x.href.toString() : null;
+        })();
+      `,
+        allFrames: false,
+      },
+      (result) => {
+        if (chrome.runtime.lastError) {
+          console.error(chrome.runtime.lastError);
+          resolve(undefined);
+          return;
+        }
+        console.log("got result", result);
+        resolve(result[0]);
+      },
+    );
+  });
+}
diff --git a/packages/taler-wallet-webextension/src/popup/Balance.tsx 
b/packages/taler-wallet-webextension/src/popup/Balance.tsx
new file mode 100644
index 00000000..77d2c420
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/popup/Balance.tsx
@@ -0,0 +1,173 @@
+/*
+ This file is part of TALER
+ (C) 2016 GNUnet e.V.
+
+ 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.
+
+ 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
+ TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+import {
+  Amounts,
+  BalancesResponse,
+  Balance, i18n, AmountJson, amountFractionalBase
+} from "@gnu-taler/taler-util";
+import { Component, JSX } from "preact";
+import { PageLink, renderAmount } from "../renderHtml";
+import * as wxApi from "../wxApi";
+
+
+/**
+ * Render an amount as a large number with a small currency symbol.
+ */
+function bigAmount(amount: AmountJson): JSX.Element {
+  const v = amount.value + amount.fraction / amountFractionalBase;
+  return (
+    <span>
+      <span style={{ fontSize: "5em", display: "block" }}>{v}</span>{" "}
+      <span>{amount.currency}</span>
+    </span>
+  );
+}
+
+function EmptyBalanceView(): JSX.Element {
+  return (
+    <p><i18n.Translate>
+      You have no balance to show. Need some{" "}
+      <PageLink pageName="/welcome">help</PageLink> getting started?
+    </i18n.Translate></p>
+  );
+}
+
+
+export class BalancePage extends Component<any, any> {
+  private balance?: BalancesResponse;
+  private gotError = false;
+  private canceler: (() => void) | undefined = undefined;
+  private unmount = false;
+  private updateBalanceRunning = false;
+
+  componentWillMount(): void {
+    this.canceler = wxApi.onUpdateNotification(() => this.updateBalance());
+    this.updateBalance();
+  }
+
+  componentWillUnmount(): void {
+    console.log("component WalletBalanceView will unmount");
+    if (this.canceler) {
+      this.canceler();
+    }
+    this.unmount = true;
+  }
+
+  async updateBalance(): Promise<void> {
+    if (this.updateBalanceRunning) {
+      return;
+    }
+    this.updateBalanceRunning = true;
+    let balance: BalancesResponse;
+    try {
+      balance = await wxApi.getBalance();
+    } catch (e) {
+      if (this.unmount) {
+        return;
+      }
+      this.gotError = true;
+      console.error("could not retrieve balances", e);
+      this.setState({});
+      return;
+    } finally {
+      this.updateBalanceRunning = false;
+    }
+    if (this.unmount) {
+      return;
+    }
+    this.gotError = false;
+    console.log("got balance", balance);
+    this.balance = balance;
+    this.setState({});
+  }
+
+  formatPending(entry: Balance): JSX.Element {
+    let incoming: JSX.Element | undefined;
+    let payment: JSX.Element | undefined;
+
+    const available = Amounts.parseOrThrow(entry.available);
+    const pendingIncoming = Amounts.parseOrThrow(entry.pendingIncoming);
+    const pendingOutgoing = Amounts.parseOrThrow(entry.pendingOutgoing);
+
+    console.log(
+      "available: ",
+      entry.pendingIncoming ? renderAmount(entry.available) : null
+    );
+    console.log(
+      "incoming: ",
+      entry.pendingIncoming ? renderAmount(entry.pendingIncoming) : null
+    );
+
+    if (!Amounts.isZero(pendingIncoming)) {
+      incoming = (
+        <span><i18n.Translate>
+          <span style={{ color: "darkgreen" }}>
+            {"+"}
+            {renderAmount(entry.pendingIncoming)}
+          </span>{" "}
+          incoming
+        </i18n.Translate></span>
+      );
+    }
+
+    const l = [incoming, payment].filter((x) => x !== undefined);
+    if (l.length === 0) {
+      return <span />;
+    }
+
+    if (l.length === 1) {
+      return <span>({l})</span>;
+    }
+    return (
+      <span>
+        ({l[0]}, {l[1]})
+      </span>
+    );
+  }
+
+  render(): JSX.Element {
+    const wallet = this.balance;
+    if (this.gotError) {
+      return (
+        <div className="balance">
+          <p>{i18n.str`Error: could not retrieve balance information.`}</p>
+          <p>
+            Click <PageLink pageName="welcome.html">here</PageLink> for help 
and
+            diagnostics.
+          </p>
+        </div>
+      );
+    }
+    if (!wallet) {
+      return <span></span>;
+    }
+
+    const listing = wallet.balances.map((entry) => {
+      const av = Amounts.parseOrThrow(entry.available);
+      return (
+        <p key={av.currency}>
+          {bigAmount(av)} {this.formatPending(entry)}
+        </p>
+      );
+    });
+    return listing.length > 0 ? (
+      <div className="balance">{listing}</div>
+    ) : (
+      <EmptyBalanceView />
+    );
+  }
+}
diff --git a/packages/taler-wallet-webextension/src/popup/Debug.tsx 
b/packages/taler-wallet-webextension/src/popup/Debug.tsx
new file mode 100644
index 00000000..073dac2c
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/popup/Debug.tsx
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ (C) 2016 GNUnet e.V.
+
+ 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.
+
+ 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
+ TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { JSX } from "preact";
+import { Diagnostics } from "../components/Diagnostics";
+import * as wxApi from "../wxApi";
+
+
+export function DebugPage(props: any): JSX.Element {
+  return (
+    <div>
+      <p>Debug tools:</p>
+      <button onClick={openExtensionPage("/static/popup.html")}>wallet 
tab</button>
+      <br />
+      <button onClick={confirmReset}>reset</button>
+      <button onClick={reload}>reload chrome extension</button>
+      <Diagnostics />
+    </div>
+  );
+}
+
+export function reload(): void {
+  try {
+    chrome.runtime.reload();
+    window.close();
+  } catch (e) {
+    // Functionality missing in firefox, ignore!
+  }
+}
+
+export async function confirmReset(): Promise<void> {
+  if (
+    confirm(
+      "Do you want to IRREVOCABLY DESTROY everything inside your" +
+      " wallet and LOSE ALL YOUR COINS?",
+    )
+  ) {
+    await wxApi.resetDb();
+    window.close();
+  }
+}
+
+export function openExtensionPage(page: string) {
+  return () => {
+    chrome.tabs.create({
+      url: chrome.extension.getURL(page),
+    });
+  };
+}
+
diff --git a/packages/taler-wallet-webextension/src/popup/History.tsx 
b/packages/taler-wallet-webextension/src/popup/History.tsx
new file mode 100644
index 00000000..ffcec5e4
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/popup/History.tsx
@@ -0,0 +1,227 @@
+/*
+ This file is part of TALER
+ (C) 2016 GNUnet e.V.
+
+ 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.
+
+ 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
+ TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { AmountString, Timestamp, Transaction, TransactionsResponse, 
TransactionType } from "@gnu-taler/taler-util";
+import { JSX } from "preact";
+import { useEffect, useState } from "preact/hooks";
+import * as wxApi from "../wxApi";
+import { Pages } from "./popup";
+
+
+export function HistoryPage(props: any): JSX.Element {
+  const [transactions, setTransactions] = useState<
+    TransactionsResponse | undefined
+  >(undefined);
+
+  useEffect(() => {
+    const fetchData = async (): Promise<void> => {
+      const res = await wxApi.getTransactions();
+      setTransactions(res);
+    };
+    fetchData();
+  }, []);
+
+  if (!transactions) {
+    return <div>Loading ...</div>;
+  }
+
+  const txs = [...transactions.transactions].reverse();
+
+  return (
+    <div>
+      {txs.map((tx, i) => (
+        <TransactionItem key={i} tx={tx} />
+      ))}
+    </div>
+  );
+}
+
+function TransactionItem(props: { tx: Transaction }): JSX.Element {
+  const tx = props.tx;
+  switch (tx.type) {
+    case TransactionType.Withdrawal:
+      return (
+        <TransactionLayout
+          id={tx.transactionId}
+          amount={tx.amountEffective}
+          debitCreditIndicator={"credit"}
+          title="Withdrawal"
+          subtitle={`via ${tx.exchangeBaseUrl}`}
+          timestamp={tx.timestamp}
+          iconPath="/static/img/ri-bank-line.svg"
+          pending={tx.pending}
+        ></TransactionLayout>
+      );
+    case TransactionType.Payment:
+      return (
+        <TransactionLayout
+          id={tx.transactionId}
+          amount={tx.amountEffective}
+          debitCreditIndicator={"debit"}
+          title="Payment"
+          subtitle={tx.info.summary}
+          timestamp={tx.timestamp}
+          iconPath="/static/img/ri-shopping-cart-line.svg"
+          pending={tx.pending}
+        ></TransactionLayout>
+      );
+    case TransactionType.Refund:
+      return (
+        <TransactionLayout
+          id={tx.transactionId}
+          amount={tx.amountEffective}
+          debitCreditIndicator={"credit"}
+          title="Refund"
+          subtitle={tx.info.summary}
+          timestamp={tx.timestamp}
+          iconPath="/static/img/ri-refund-2-line.svg"
+          pending={tx.pending}
+        ></TransactionLayout>
+      );
+    case TransactionType.Tip:
+      return (
+        <TransactionLayout
+          id={tx.transactionId}
+          amount={tx.amountEffective}
+          debitCreditIndicator={"credit"}
+          title="Tip"
+          subtitle={`from ${new URL(tx.merchantBaseUrl).hostname}`}
+          timestamp={tx.timestamp}
+          iconPath="/static/img/ri-hand-heart-line.svg"
+          pending={tx.pending}
+        ></TransactionLayout>
+      );
+    case TransactionType.Refresh:
+      return (
+        <TransactionLayout
+          id={tx.transactionId}
+          amount={tx.amountEffective}
+          debitCreditIndicator={"credit"}
+          title="Refresh"
+          subtitle={`via exchange ${tx.exchangeBaseUrl}`}
+          timestamp={tx.timestamp}
+          iconPath="/static/img/ri-refresh-line.svg"
+          pending={tx.pending}
+        ></TransactionLayout>
+      );
+    case TransactionType.Deposit:
+      return (
+        <TransactionLayout
+          id={tx.transactionId}
+          amount={tx.amountEffective}
+          debitCreditIndicator={"debit"}
+          title="Refresh"
+          subtitle={`to ${tx.targetPaytoUri}`}
+          timestamp={tx.timestamp}
+          iconPath="/static/img/ri-refresh-line.svg"
+          pending={tx.pending}
+        ></TransactionLayout>
+      );
+  }
+}
+
+function TransactionLayout(props: TransactionLayoutProps): JSX.Element {
+  const date = new Date(props.timestamp.t_ms);
+  const dateStr = date.toLocaleString([], {
+    dateStyle: "medium",
+    timeStyle: "short",
+  } as any);
+  return (
+    <div
+      style={{
+        display: "flex",
+        flexDirection: "row",
+        border: "1px solid gray",
+        borderRadius: "0.5em",
+        margin: "0.5em 0",
+        justifyContent: "space-between",
+        padding: "0.5em",
+      }}
+    >
+      <img src={props.iconPath} />
+      <div
+        style={{ display: "flex", flexDirection: "column", marginLeft: "1em" }}
+      >
+        <div style={{ fontSize: "small", color: "gray" }}>{dateStr}</div>
+        <div style={{ fontVariant: "small-caps", fontSize: "x-large" }}>
+          <a href={Pages.transaction.replace(':tid', 
props.id)}><span>{props.title}</span></a>
+          {props.pending ? (
+            <span style={{ color: "darkblue" }}> (Pending)</span>
+          ) : null}
+        </div>
+
+        <div>{props.subtitle}</div>
+      </div>
+      <TransactionAmount
+        pending={props.pending}
+        amount={props.amount}
+        debitCreditIndicator={props.debitCreditIndicator}
+      />
+    </div>
+  );
+}
+
+interface TransactionLayoutProps {
+  debitCreditIndicator: "debit" | "credit" | "unknown";
+  amount: AmountString | "unknown";
+  timestamp: Timestamp;
+  title: string;
+  id: string;
+  subtitle: string;
+  iconPath: string;
+  pending: boolean;
+}
+
+interface TransactionAmountProps {
+  debitCreditIndicator: "debit" | "credit" | "unknown";
+  amount: AmountString | "unknown";
+  pending: boolean;
+}
+
+function TransactionAmount(props: TransactionAmountProps): JSX.Element {
+  const [currency, amount] = props.amount.split(":");
+  let sign: string;
+  switch (props.debitCreditIndicator) {
+    case "credit":
+      sign = "+";
+      break;
+    case "debit":
+      sign = "-";
+      break;
+    case "unknown":
+      sign = "";
+  }
+  const style: JSX.AllCSSProperties = {
+    marginLeft: "auto",
+    display: "flex",
+    flexDirection: "column",
+    alignItems: "center",
+    alignSelf: "center"
+  };
+  if (props.pending) {
+    style.color = "gray";
+  }
+  return (
+    <div style={{ ...style }}>
+      <div style={{ fontSize: "x-large" }}>
+        {sign}
+        {amount}
+      </div>
+      <div>{currency}</div>
+    </div>
+  );
+}
+
diff --git a/packages/taler-wallet-webextension/src/popup/Settings.tsx 
b/packages/taler-wallet-webextension/src/popup/Settings.tsx
new file mode 100644
index 00000000..5028b597
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/popup/Settings.tsx
@@ -0,0 +1,34 @@
+/*
+ This file is part of TALER
+ (C) 2016 GNUnet e.V.
+
+ 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.
+
+ 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
+ TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+
+import { PermissionsCheckbox } from "../components/PermissionsCheckbox";
+import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
+
+
+export function SettingsPage() {
+  const [permissionsEnabled, togglePermissions] = useExtendedPermissions();
+  return (
+    <div>
+      <h2>Permissions</h2>
+      <PermissionsCheckbox enabled={permissionsEnabled} 
onToggle={togglePermissions} />
+      {/*
+            <h2>Developer mode</h2>
+            <DebugCheckbox enabled={permissionsEnabled} 
onToggle={togglePermissions} />
+            */}
+    </div>
+  );
+}
diff --git a/packages/taler-wallet-webextension/src/popup/popup.stories.tsx 
b/packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx
similarity index 79%
rename from packages/taler-wallet-webextension/src/popup/popup.stories.tsx
rename to packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx
index 0cb51a33..3df2687f 100644
--- a/packages/taler-wallet-webextension/src/popup/popup.stories.tsx
+++ b/packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx
@@ -26,11 +26,12 @@ import {
   TransactionWithdrawal,
   WithdrawalType
 } from '@gnu-taler/taler-util';
-import { WalletTransactionView as Component } from './popup';
+import { FunctionalComponent } from 'preact';
+import { TransactionView as TestedComponent } from './Transaction';
 
 export default {
-  title: 'popup/transaction details',
-  component: Component,
+  title: 'popup/transaction/details',
+  component: TestedComponent,
   decorators: [
     (Story: any) => <div>
       <link key="1" rel="stylesheet" type="text/css" href="/style/pure.css" />
@@ -114,32 +115,32 @@ const exampleData = {
   } as TransactionRefund,
 }
 
-function dynamic<T>(props: any) {
+function createExample<Props>(Component: FunctionalComponent<Props>, props: 
Partial<Props>) {
   const r = (args: any) => <Component {...args} />
   r.args = props
   return r
 }
 
-export const NotYetLoaded = dynamic({});
+export const NotYetLoaded = createExample(TestedComponent,{});
 
-export const Withdraw = dynamic({
+export const Withdraw = createExample(TestedComponent,{
   transaction: exampleData.withdraw
 });
 
-export const WithdrawPending = dynamic({
+export const WithdrawPending = createExample(TestedComponent,{
   transaction: { ...exampleData.withdraw, pending: true },
 });
 
 
-export const Payment = dynamic({
+export const Payment = createExample(TestedComponent,{
   transaction: exampleData.payment
 });
 
-export const PaymentPending = dynamic({
+export const PaymentPending = createExample(TestedComponent,{
   transaction: { ...exampleData.payment, pending: true },
 });
 
-export const PaymentWithProducts = dynamic({
+export const PaymentWithProducts = createExample(TestedComponent,{
   transaction: {
     ...exampleData.payment,
     info: {
@@ -154,35 +155,35 @@ export const PaymentWithProducts = dynamic({
 });
 
 
-export const Deposit = dynamic({
+export const Deposit = createExample(TestedComponent,{
   transaction: exampleData.deposit
 });
 
-export const DepositPending = dynamic({
+export const DepositPending = createExample(TestedComponent,{
   transaction: { ...exampleData.deposit, pending: true }
 });
 
-export const Refresh = dynamic({
+export const Refresh = createExample(TestedComponent,{
   transaction: exampleData.refresh
 });
 
-export const Tip = dynamic({
+export const Tip = createExample(TestedComponent,{
   transaction: exampleData.tip
 });
 
-export const TipPending = dynamic({
+export const TipPending = createExample(TestedComponent,{
   transaction: { ...exampleData.tip, pending: true }
 });
 
-export const Refund = dynamic({
+export const Refund = createExample(TestedComponent,{
   transaction: exampleData.refund
 });
 
-export const RefundPending = dynamic({
+export const RefundPending = createExample(TestedComponent,{
   transaction: { ...exampleData.refund, pending: true }
 });
 
-export const RefundWithProducts = dynamic({
+export const RefundWithProducts = createExample(TestedComponent,{
   transaction: {
     ...exampleData.refund,
     info: {
diff --git a/packages/taler-wallet-webextension/src/popup/Transaction.tsx 
b/packages/taler-wallet-webextension/src/popup/Transaction.tsx
new file mode 100644
index 00000000..b1179228
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/popup/Transaction.tsx
@@ -0,0 +1,327 @@
+/*
+ This file is part of TALER
+ (C) 2016 GNUnet e.V.
+
+ 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.
+
+ 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
+ TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { Amounts, i18n, Transaction, TransactionType } from 
"@gnu-taler/taler-util";
+import { format } from "date-fns";
+import { JSX } from "preact";
+import { route } from 'preact-router';
+import { useEffect, useState } from "preact/hooks";
+import * as wxApi from "../wxApi";
+import { Pages } from "./popup";
+
+
+export function TransactionPage({ tid }: { tid: string; }): JSX.Element {
+  const [transaction, setTransaction] = useState<
+    Transaction | undefined
+  >(undefined);
+
+  useEffect(() => {
+    const fetchData = async (): Promise<void> => {
+      const res = await wxApi.getTransactions();
+      const ts = res.transactions.filter(t => t.transactionId === tid);
+      if (ts.length === 1) {
+        setTransaction(ts[0]);
+      } else {
+        route(Pages.history);
+      }
+    };
+    fetchData();
+  }, []);
+
+  return <TransactionView
+    transaction={transaction}
+    onDelete={() => wxApi.deleteTransaction(tid).then(_ => history.go(-1))}
+    onBack={() => { history.go(-1); }} />;
+}
+
+export interface WalletTransactionProps {
+  transaction?: Transaction,
+  onDelete: () => void,
+  onBack: () => void,
+}
+
+export function TransactionView({ transaction, onDelete, onBack }: 
WalletTransactionProps) {
+  if (!transaction) {
+    return <div><i18n.Translate>Loading ...</i18n.Translate></div>;
+  }
+
+  function Footer() {
+    return <footer style={{ marginTop: 'auto', display: 'flex' }}>
+      <button onClick={onBack}><i18n.Translate>back</i18n.Translate></button>
+      <div style={{ width: '100%', flexDirection: 'row', justifyContent: 
'flex-end', display: 'flex' }}>
+        <button 
onClick={onDelete}><i18n.Translate>remove</i18n.Translate></button>
+
+      </div>
+
+    </footer>
+  }
+
+  function Pending() {
+    if (!transaction?.pending) return null
+    return <span style={{ fontWeight: 'normal', fontSize: 16, color: 'gray' 
}}>(pending...)</span>
+  }
+
+  if (transaction.type === TransactionType.Withdrawal) {
+    return (
+      <div style={{ display: 'flex', flexDirection: 'column', flex: 1, 
minHeight: '20rem' }} >
+        <section>
+          <h1>Withdrawal <Pending /></h1>
+          <p>
+            From <b>{transaction.exchangeBaseUrl}</b>
+          </p>
+          <table class={transaction.pending ? "detailsTable pending" : 
"detailsTable"}>
+            <tr>
+              <td>Amount subtracted</td>
+              <td>{transaction.amountRaw}</td>
+            </tr>
+            <tr>
+              <td>Amount received</td>
+              <td>{transaction.amountEffective}</td>
+            </tr>
+            <tr>
+              <td>Exchange fee</td>
+              <td>{Amounts.stringify(
+                Amounts.sub(
+                  Amounts.parseOrThrow(transaction.amountRaw),
+                  Amounts.parseOrThrow(transaction.amountEffective),
+                ).amount
+              )}</td>
+            </tr>
+            <tr>
+              <td>When</td>
+              <td>{transaction.timestamp.t_ms === "never" ? "never" : 
format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
+            </tr>
+          </table>
+        </section>
+        <Footer />
+      </div>
+    );
+  }
+
+  if (transaction.type === TransactionType.Payment) {
+    return (
+      <div style={{ display: 'flex', flexDirection: 'column', flex: 1, 
minHeight: '20rem' }} >
+        <section>
+          <h1>Payment ({transaction.proposalId.substring(0, 10)}...) <Pending 
/></h1>
+          <p>
+            To <b>{transaction.info.merchant.name}</b>
+          </p>
+          <table class={transaction.pending ? "detailsTable pending" : 
"detailsTable"}>
+            <tr>
+              <td>Order id</td>
+              <td>{transaction.info.orderId}</td>
+            </tr>
+            <tr>
+              <td>Summary</td>
+              <td>{transaction.info.summary}</td>
+            </tr>
+            {transaction.info.products && transaction.info.products.length > 0 
&&
+              <tr>
+                <td>Products</td>
+                <td><ol style={{ margin: 0, textAlign: 'left' }}>
+                  {transaction.info.products.map(p =>
+                    <li>{p.description}</li>
+                  )}</ol></td>
+              </tr>
+            }
+            <tr>
+              <td>Order amount</td>
+              <td>{transaction.amountRaw}</td>
+            </tr>
+            <tr>
+              <td>Order amount and fees</td>
+              <td>{transaction.amountEffective}</td>
+            </tr>
+            <tr>
+              <td>Exchange fee</td>
+              <td>{Amounts.stringify(
+                Amounts.sub(
+                  Amounts.parseOrThrow(transaction.amountEffective),
+                  Amounts.parseOrThrow(transaction.amountRaw),
+                ).amount
+              )}</td>
+            </tr>
+            <tr>
+              <td>When</td>
+              <td>{transaction.timestamp.t_ms === "never" ? "never" : 
format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
+            </tr>
+          </table>
+        </section>
+        <Footer />
+      </div>
+    );
+  }
+
+  if (transaction.type === TransactionType.Deposit) {
+    return (
+      <div style={{ display: 'flex', flexDirection: 'column', flex: 1, 
minHeight: '20rem' }} >
+        <section>
+          <h1>Deposit ({transaction.depositGroupId}) <Pending /></h1>
+          <p>
+            To <b>{transaction.targetPaytoUri}</b>
+          </p>
+          <table class={transaction.pending ? "detailsTable pending" : 
"detailsTable"}>
+            <tr>
+              <td>Amount deposit</td>
+              <td>{transaction.amountRaw}</td>
+            </tr>
+            <tr>
+              <td>Amount deposit and fees</td>
+              <td>{transaction.amountEffective}</td>
+            </tr>
+            <tr>
+              <td>Exchange fee</td>
+              <td>{Amounts.stringify(
+                Amounts.sub(
+                  Amounts.parseOrThrow(transaction.amountEffective),
+                  Amounts.parseOrThrow(transaction.amountRaw),
+                ).amount
+              )}</td>
+            </tr>
+            <tr>
+              <td>When</td>
+              <td>{transaction.timestamp.t_ms === "never" ? "never" : 
format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
+            </tr>
+          </table>
+        </section>
+        <Footer />
+      </div>
+    );
+  }
+
+  if (transaction.type === TransactionType.Refresh) {
+    return (
+      <div style={{ display: 'flex', flexDirection: 'column', flex: 1, 
minHeight: '20rem' }} >
+        <section>
+          <h1>Refresh <Pending /></h1>
+          <p>
+            From <b>{transaction.exchangeBaseUrl}</b>
+          </p>
+          <table class={transaction.pending ? "detailsTable pending" : 
"detailsTable"}>
+            <tr>
+              <td>Amount refreshed</td>
+              <td>{transaction.amountRaw}</td>
+            </tr>
+            <tr>
+              <td>Fees</td>
+              <td>{transaction.amountEffective}</td>
+            </tr>
+            <tr>
+              <td>When</td>
+              <td>{transaction.timestamp.t_ms === "never" ? "never" : 
format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
+            </tr>
+          </table>
+        </section>
+        <Footer />
+      </div>
+    );
+  }
+
+  if (transaction.type === TransactionType.Tip) {
+    return (
+      <div style={{ display: 'flex', flexDirection: 'column', flex: 1, 
minHeight: '20rem' }} >
+        <section>
+          <h1>Tip <Pending /></h1>
+          <p>
+            From <b>{transaction.merchantBaseUrl}</b>
+          </p>
+          <table class={transaction.pending ? "detailsTable pending" : 
"detailsTable"}>
+            <tr>
+              <td>Amount deduce</td>
+              <td>{transaction.amountRaw}</td>
+            </tr>
+            <tr>
+              <td>Amount received</td>
+              <td>{transaction.amountEffective}</td>
+            </tr>
+            <tr>
+              <td>Exchange fee</td>
+              <td>{Amounts.stringify(
+                Amounts.sub(
+                  Amounts.parseOrThrow(transaction.amountRaw),
+                  Amounts.parseOrThrow(transaction.amountEffective),
+                ).amount
+              )}</td>
+            </tr>
+            <tr>
+              <td>When</td>
+              <td>{transaction.timestamp.t_ms === "never" ? "never" : 
format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
+            </tr>
+          </table>
+        </section>
+        <Footer />
+      </div>
+    );
+  }
+
+  const TRANSACTION_FROM_REFUND = /[a-z]*:([\w]{10}).*/
+  if (transaction.type === TransactionType.Refund) {
+    return (
+      <div style={{ display: 'flex', flexDirection: 'column', flex: 1, 
minHeight: '20rem' }} >
+        <section>
+          <h1>Refund 
({TRANSACTION_FROM_REFUND.exec(transaction.refundedTransactionId)![1]}...) 
<Pending /></h1>
+          <p>
+            From <b>{transaction.info.merchant.name}</b>
+          </p>
+          <table class={transaction.pending ? "detailsTable pending" : 
"detailsTable"}>
+            <tr>
+              <td>Order id</td>
+              <td>{transaction.info.orderId}</td>
+            </tr>
+            <tr>
+              <td>Summary</td>
+              <td>{transaction.info.summary}</td>
+            </tr>
+            {transaction.info.products && transaction.info.products.length > 0 
&&
+              <tr>
+                <td>Products</td>
+                <td><ol>
+                  {transaction.info.products.map(p =>
+                    <li>{p.description}</li>
+                  )}</ol></td>
+              </tr>
+            }
+            <tr>
+              <td>Amount deduce</td>
+              <td>{transaction.amountRaw}</td>
+            </tr>
+            <tr>
+              <td>Amount received</td>
+              <td>{transaction.amountEffective}</td>
+            </tr>
+            <tr>
+              <td>Exchange fee</td>
+              <td>{Amounts.stringify(
+                Amounts.sub(
+                  Amounts.parseOrThrow(transaction.amountRaw),
+                  Amounts.parseOrThrow(transaction.amountEffective),
+                ).amount
+              )}</td>
+            </tr>
+            <tr>
+              <td>When</td>
+              <td>{transaction.timestamp.t_ms === "never" ? "never" : 
format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
+            </tr>
+          </table>
+        </section>
+        <Footer />
+      </div>
+    );
+  }
+
+
+  return <div></div>
+}
diff --git a/packages/taler-wallet-webextension/src/popup/popup.tsx 
b/packages/taler-wallet-webextension/src/popup/popup.tsx
index 0f76d772..95b87fad 100644
--- a/packages/taler-wallet-webextension/src/popup/popup.tsx
+++ b/packages/taler-wallet-webextension/src/popup/popup.tsx
@@ -25,29 +25,9 @@
  * Imports.
  */
 import {
-  AmountJson,
-  Amounts,
-  BalancesResponse,
-  Balance,
-  classifyTalerUri,
-  TalerUriType,
-  TransactionsResponse,
-  Transaction,
-  TransactionType,
-  AmountString,
-  Timestamp,
-  amountFractionalBase,
-  i18n,
+  classifyTalerUri, i18n, TalerUriType
 } from "@gnu-taler/taler-util";
-import { format } from "date-fns";
-import { Component, ComponentChildren, Fragment, JSX } from "preact";
-import { route } from 'preact-router';
-import { useEffect, useState } from "preact/hooks";
-import { Diagnostics } from "../components/Diagnostics";
-import { PermissionsCheckbox } from "../components/PermissionsCheckbox";
-import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
-import { PageLink, renderAmount } from "../renderHtml";
-import * as wxApi from "../wxApi";
+import { ComponentChildren, JSX } from "preact";
 
 export enum Pages {
   balance = '/balance',
@@ -86,878 +66,3 @@ export function WalletNavBar({ current }: { current?: 
string }) {
   );
 }
 
-/**
- * Render an amount as a large number with a small currency symbol.
- */
-function bigAmount(amount: AmountJson): JSX.Element {
-  const v = amount.value + amount.fraction / amountFractionalBase;
-  return (
-    <span>
-      <span style={{ fontSize: "5em", display: "block" }}>{v}</span>{" "}
-      <span>{amount.currency}</span>
-    </span>
-  );
-}
-
-function EmptyBalanceView(): JSX.Element {
-  return (
-    <p><i18n.Translate>
-      You have no balance to show. Need some{" "}
-      <PageLink pageName="/welcome">help</PageLink> getting started?
-    </i18n.Translate></p>
-  );
-}
-
-export class WalletBalanceView extends Component<any, any> {
-  private balance?: BalancesResponse;
-  private gotError = false;
-  private canceler: (() => void) | undefined = undefined;
-  private unmount = false;
-  private updateBalanceRunning = false;
-
-  componentWillMount(): void {
-    this.canceler = wxApi.onUpdateNotification(() => this.updateBalance());
-    this.updateBalance();
-  }
-
-  componentWillUnmount(): void {
-    console.log("component WalletBalanceView will unmount");
-    if (this.canceler) {
-      this.canceler();
-    }
-    this.unmount = true;
-  }
-
-  async updateBalance(): Promise<void> {
-    if (this.updateBalanceRunning) {
-      return;
-    }
-    this.updateBalanceRunning = true;
-    let balance: BalancesResponse;
-    try {
-      balance = await wxApi.getBalance();
-    } catch (e) {
-      if (this.unmount) {
-        return;
-      }
-      this.gotError = true;
-      console.error("could not retrieve balances", e);
-      this.setState({});
-      return;
-    } finally {
-      this.updateBalanceRunning = false;
-    }
-    if (this.unmount) {
-      return;
-    }
-    this.gotError = false;
-    console.log("got balance", balance);
-    this.balance = balance;
-    this.setState({});
-  }
-
-  formatPending(entry: Balance): JSX.Element {
-    let incoming: JSX.Element | undefined;
-    let payment: JSX.Element | undefined;
-
-    const available = Amounts.parseOrThrow(entry.available);
-    const pendingIncoming = Amounts.parseOrThrow(entry.pendingIncoming);
-    const pendingOutgoing = Amounts.parseOrThrow(entry.pendingOutgoing);
-
-    console.log(
-      "available: ",
-      entry.pendingIncoming ? renderAmount(entry.available) : null,
-    );
-    console.log(
-      "incoming: ",
-      entry.pendingIncoming ? renderAmount(entry.pendingIncoming) : null,
-    );
-
-    if (!Amounts.isZero(pendingIncoming)) {
-      incoming = (
-        <span><i18n.Translate>
-          <span style={{ color: "darkgreen" }}>
-            {"+"}
-            {renderAmount(entry.pendingIncoming)}
-          </span>{" "}
-          incoming
-        </i18n.Translate></span>
-      );
-    }
-
-    const l = [incoming, payment].filter((x) => x !== undefined);
-    if (l.length === 0) {
-      return <span />;
-    }
-
-    if (l.length === 1) {
-      return <span>({l})</span>;
-    }
-    return (
-      <span>
-        ({l[0]}, {l[1]})
-      </span>
-    );
-  }
-
-  render(): JSX.Element {
-    const wallet = this.balance;
-    if (this.gotError) {
-      return (
-        <div className="balance">
-          <p>{i18n.str`Error: could not retrieve balance information.`}</p>
-          <p>
-            Click <PageLink pageName="welcome.html">here</PageLink> for help 
and
-            diagnostics.
-          </p>
-        </div>
-      );
-    }
-    if (!wallet) {
-      return <span></span>;
-    }
-    console.log(wallet);
-    const listing = wallet.balances.map((entry) => {
-      const av = Amounts.parseOrThrow(entry.available);
-      return (
-        <p key={av.currency}>
-          {bigAmount(av)} {this.formatPending(entry)}
-        </p>
-      );
-    });
-    return listing.length > 0 ? (
-      <div className="balance">{listing}</div>
-    ) : (
-      <EmptyBalanceView />
-    );
-  }
-}
-
-interface TransactionAmountProps {
-  debitCreditIndicator: "debit" | "credit" | "unknown";
-  amount: AmountString | "unknown";
-  pending: boolean;
-}
-
-function TransactionAmount(props: TransactionAmountProps): JSX.Element {
-  const [currency, amount] = props.amount.split(":");
-  let sign: string;
-  switch (props.debitCreditIndicator) {
-    case "credit":
-      sign = "+";
-      break;
-    case "debit":
-      sign = "-";
-      break;
-    case "unknown":
-      sign = "";
-  }
-  const style: JSX.AllCSSProperties = {
-    marginLeft: "auto",
-    display: "flex",
-    flexDirection: "column",
-    alignItems: "center",
-    alignSelf: "center"
-  };
-  if (props.pending) {
-    style.color = "gray";
-  }
-  return (
-    <div style={{ ...style }}>
-      <div style={{ fontSize: "x-large" }}>
-        {sign}
-        {amount}
-      </div>
-      <div>{currency}</div>
-    </div>
-  );
-}
-
-interface TransactionLayoutProps {
-  debitCreditIndicator: "debit" | "credit" | "unknown";
-  amount: AmountString | "unknown";
-  timestamp: Timestamp;
-  title: string;
-  id: string;
-  subtitle: string;
-  iconPath: string;
-  pending: boolean;
-}
-
-function TransactionLayout(props: TransactionLayoutProps): JSX.Element {
-  const date = new Date(props.timestamp.t_ms);
-  const dateStr = date.toLocaleString([], {
-    dateStyle: "medium",
-    timeStyle: "short",
-  } as any);
-  return (
-    <div
-      style={{
-        display: "flex",
-        flexDirection: "row",
-        border: "1px solid gray",
-        borderRadius: "0.5em",
-        margin: "0.5em 0",
-        justifyContent: "space-between",
-        padding: "0.5em",
-      }}
-    >
-      <img src={props.iconPath} />
-      <div
-        style={{ display: "flex", flexDirection: "column", marginLeft: "1em" }}
-      >
-        <div style={{ fontSize: "small", color: "gray" }}>{dateStr}</div>
-        <div style={{ fontVariant: "small-caps", fontSize: "x-large" }}>
-          <a href={Pages.transaction.replace(':tid', 
props.id)}><span>{props.title}</span></a>
-          {props.pending ? (
-            <span style={{ color: "darkblue" }}> (Pending)</span>
-          ) : null}
-        </div>
-
-        <div>{props.subtitle}</div>
-      </div>
-      <TransactionAmount
-        pending={props.pending}
-        amount={props.amount}
-        debitCreditIndicator={props.debitCreditIndicator}
-      />
-    </div>
-  );
-}
-
-function TransactionItem(props: { tx: Transaction }): JSX.Element {
-  const tx = props.tx;
-  switch (tx.type) {
-    case TransactionType.Withdrawal:
-      return (
-        <TransactionLayout
-          id={tx.transactionId}
-          amount={tx.amountEffective}
-          debitCreditIndicator={"credit"}
-          title="Withdrawal"
-          subtitle={`via ${tx.exchangeBaseUrl}`}
-          timestamp={tx.timestamp}
-          iconPath="/static/img/ri-bank-line.svg"
-          pending={tx.pending}
-        ></TransactionLayout>
-      );
-    case TransactionType.Payment:
-      return (
-        <TransactionLayout
-          id={tx.transactionId}
-          amount={tx.amountEffective}
-          debitCreditIndicator={"debit"}
-          title="Payment"
-          subtitle={tx.info.summary}
-          timestamp={tx.timestamp}
-          iconPath="/static/img/ri-shopping-cart-line.svg"
-          pending={tx.pending}
-        ></TransactionLayout>
-      );
-    case TransactionType.Refund:
-      return (
-        <TransactionLayout
-          id={tx.transactionId}
-          amount={tx.amountEffective}
-          debitCreditIndicator={"credit"}
-          title="Refund"
-          subtitle={tx.info.summary}
-          timestamp={tx.timestamp}
-          iconPath="/static/img/ri-refund-2-line.svg"
-          pending={tx.pending}
-        ></TransactionLayout>
-      );
-    case TransactionType.Tip:
-      return (
-        <TransactionLayout
-          id={tx.transactionId}
-          amount={tx.amountEffective}
-          debitCreditIndicator={"credit"}
-          title="Tip"
-          subtitle={`from ${new URL(tx.merchantBaseUrl).hostname}`}
-          timestamp={tx.timestamp}
-          iconPath="/static/img/ri-hand-heart-line.svg"
-          pending={tx.pending}
-        ></TransactionLayout>
-      );
-    case TransactionType.Refresh:
-      return (
-        <TransactionLayout
-          id={tx.transactionId}
-          amount={tx.amountEffective}
-          debitCreditIndicator={"credit"}
-          title="Refresh"
-          subtitle={`via exchange ${tx.exchangeBaseUrl}`}
-          timestamp={tx.timestamp}
-          iconPath="/static/img/ri-refresh-line.svg"
-          pending={tx.pending}
-        ></TransactionLayout>
-      );
-    case TransactionType.Deposit:
-      return (
-        <TransactionLayout
-          id={tx.transactionId}
-          amount={tx.amountEffective}
-          debitCreditIndicator={"debit"}
-          title="Refresh"
-          subtitle={`to ${tx.targetPaytoUri}`}
-          timestamp={tx.timestamp}
-          iconPath="/static/img/ri-refresh-line.svg"
-          pending={tx.pending}
-        ></TransactionLayout>
-      );
-  }
-}
-
-export function WalletHistory(props: any): JSX.Element {
-  const [transactions, setTransactions] = useState<
-    TransactionsResponse | undefined
-  >(undefined);
-
-  useEffect(() => {
-    const fetchData = async (): Promise<void> => {
-      const res = await wxApi.getTransactions();
-      setTransactions(res);
-    };
-    fetchData();
-  }, []);
-
-  if (!transactions) {
-    return <div>Loading ...</div>;
-  }
-
-  const txs = [...transactions.transactions].reverse();
-
-  return (
-    <div>
-      {txs.map((tx, i) => (
-        <TransactionItem key={i} tx={tx} />
-      ))}
-    </div>
-  );
-}
-
-interface WalletTransactionProps {
-  transaction?: Transaction,
-  onDelete: () => void,
-  onBack: () => void,
-}
-
-export function WalletTransactionView({ transaction, onDelete, onBack }: 
WalletTransactionProps) {
-  if (!transaction) {
-    return <div><i18n.Translate>Loading ...</i18n.Translate></div>;
-  }
-
-  function Footer() {
-    return <footer style={{ marginTop: 'auto', display: 'flex' }}>
-      <button onClick={onBack}><i18n.Translate>back</i18n.Translate></button>
-      <div style={{ width: '100%', flexDirection: 'row', justifyContent: 
'flex-end', display: 'flex' }}>
-        <button 
onClick={onDelete}><i18n.Translate>remove</i18n.Translate></button>
-
-      </div>
-
-    </footer>
-  }
-
-  function Pending() {
-    if (!transaction?.pending) return null
-    return <span style={{ fontWeight: 'normal', fontSize: 16, color: 'gray' 
}}>(pending...)</span>
-  }
-
-  if (transaction.type === TransactionType.Withdrawal) {
-    return (
-      <div style={{ display: 'flex', flexDirection: 'column', flex: 1, 
minHeight: '20rem' }} >
-        <section>
-          <h1>Withdrawal <Pending /></h1>
-          <p>
-            From <b>{transaction.exchangeBaseUrl}</b>
-          </p>
-          <table class={transaction.pending ? "detailsTable pending" : 
"detailsTable"}>
-            <tr>
-              <td>Amount subtracted</td>
-              <td>{transaction.amountRaw}</td>
-            </tr>
-            <tr>
-              <td>Amount received</td>
-              <td>{transaction.amountEffective}</td>
-            </tr>
-            <tr>
-              <td>Exchange fee</td>
-              <td>{Amounts.stringify(
-                Amounts.sub(
-                  Amounts.parseOrThrow(transaction.amountRaw),
-                  Amounts.parseOrThrow(transaction.amountEffective),
-                ).amount
-              )}</td>
-            </tr>
-            <tr>
-              <td>When</td>
-              <td>{transaction.timestamp.t_ms === "never" ? "never" : 
format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
-            </tr>
-          </table>
-        </section>
-        <Footer />
-      </div>
-    );
-  }
-
-  if (transaction.type === TransactionType.Payment) {
-    return (
-      <div style={{ display: 'flex', flexDirection: 'column', flex: 1, 
minHeight: '20rem' }} >
-        <section>
-          <h1>Payment ({transaction.proposalId.substring(0, 10)}...) <Pending 
/></h1>
-          <p>
-            To <b>{transaction.info.merchant.name}</b>
-          </p>
-          <table class={transaction.pending ? "detailsTable pending" : 
"detailsTable"}>
-            <tr>
-              <td>Order id</td>
-              <td>{transaction.info.orderId}</td>
-            </tr>
-            <tr>
-              <td>Summary</td>
-              <td>{transaction.info.summary}</td>
-            </tr>
-            {transaction.info.products && transaction.info.products.length > 0 
&&
-              <tr>
-                <td>Products</td>
-                <td><ol style={{ margin: 0, textAlign: 'left' }}>
-                  {transaction.info.products.map(p =>
-                    <li>{p.description}</li>
-                  )}</ol></td>
-              </tr>
-            }
-            <tr>
-              <td>Order amount</td>
-              <td>{transaction.amountRaw}</td>
-            </tr>
-            <tr>
-              <td>Order amount and fees</td>
-              <td>{transaction.amountEffective}</td>
-            </tr>
-            <tr>
-              <td>Exchange fee</td>
-              <td>{Amounts.stringify(
-                Amounts.sub(
-                  Amounts.parseOrThrow(transaction.amountEffective),
-                  Amounts.parseOrThrow(transaction.amountRaw),
-                ).amount
-              )}</td>
-            </tr>
-            <tr>
-              <td>When</td>
-              <td>{transaction.timestamp.t_ms === "never" ? "never" : 
format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
-            </tr>
-          </table>
-        </section>
-        <Footer />
-      </div>
-    );
-  }
-
-  if (transaction.type === TransactionType.Deposit) {
-    return (
-      <div style={{ display: 'flex', flexDirection: 'column', flex: 1, 
minHeight: '20rem' }} >
-        <section>
-          <h1>Deposit ({transaction.depositGroupId}) <Pending /></h1>
-          <p>
-            To <b>{transaction.targetPaytoUri}</b>
-          </p>
-          <table class={transaction.pending ? "detailsTable pending" : 
"detailsTable"}>
-            <tr>
-              <td>Amount deposit</td>
-              <td>{transaction.amountRaw}</td>
-            </tr>
-            <tr>
-              <td>Amount deposit and fees</td>
-              <td>{transaction.amountEffective}</td>
-            </tr>
-            <tr>
-              <td>Exchange fee</td>
-              <td>{Amounts.stringify(
-                Amounts.sub(
-                  Amounts.parseOrThrow(transaction.amountEffective),
-                  Amounts.parseOrThrow(transaction.amountRaw),
-                ).amount
-              )}</td>
-            </tr>
-            <tr>
-              <td>When</td>
-              <td>{transaction.timestamp.t_ms === "never" ? "never" : 
format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
-            </tr>
-          </table>
-        </section>
-        <Footer />
-      </div>
-    );
-  }
-
-  if (transaction.type === TransactionType.Refresh) {
-    return (
-      <div style={{ display: 'flex', flexDirection: 'column', flex: 1, 
minHeight: '20rem' }} >
-        <section>
-          <h1>Refresh <Pending /></h1>
-          <p>
-            From <b>{transaction.exchangeBaseUrl}</b>
-          </p>
-          <table class={transaction.pending ? "detailsTable pending" : 
"detailsTable"}>
-            <tr>
-              <td>Amount refreshed</td>
-              <td>{transaction.amountRaw}</td>
-            </tr>
-            <tr>
-              <td>Fees</td>
-              <td>{transaction.amountEffective}</td>
-            </tr>
-            <tr>
-              <td>When</td>
-              <td>{transaction.timestamp.t_ms === "never" ? "never" : 
format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
-            </tr>
-          </table>
-        </section>
-        <Footer />
-      </div>
-    );
-  }
-
-  if (transaction.type === TransactionType.Tip) {
-    return (
-      <div style={{ display: 'flex', flexDirection: 'column', flex: 1, 
minHeight: '20rem' }} >
-        <section>
-          <h1>Tip <Pending /></h1>
-          <p>
-            From <b>{transaction.merchantBaseUrl}</b>
-          </p>
-          <table class={transaction.pending ? "detailsTable pending" : 
"detailsTable"}>
-            <tr>
-              <td>Amount deduce</td>
-              <td>{transaction.amountRaw}</td>
-            </tr>
-            <tr>
-              <td>Amount received</td>
-              <td>{transaction.amountEffective}</td>
-            </tr>
-            <tr>
-              <td>Exchange fee</td>
-              <td>{Amounts.stringify(
-                Amounts.sub(
-                  Amounts.parseOrThrow(transaction.amountRaw),
-                  Amounts.parseOrThrow(transaction.amountEffective),
-                ).amount
-              )}</td>
-            </tr>
-            <tr>
-              <td>When</td>
-              <td>{transaction.timestamp.t_ms === "never" ? "never" : 
format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
-            </tr>
-          </table>
-        </section>
-        <Footer />
-      </div>
-    );
-  }
-
-  const TRANSACTION_FROM_REFUND = /[a-z]*:([\w]{10}).*/
-  if (transaction.type === TransactionType.Refund) {
-    return (
-      <div style={{ display: 'flex', flexDirection: 'column', flex: 1, 
minHeight: '20rem' }} >
-        <section>
-          <h1>Refund 
({TRANSACTION_FROM_REFUND.exec(transaction.refundedTransactionId)![1]}...) 
<Pending /></h1>
-          <p>
-            From <b>{transaction.info.merchant.name}</b>
-          </p>
-          <table class={transaction.pending ? "detailsTable pending" : 
"detailsTable"}>
-            <tr>
-              <td>Order id</td>
-              <td>{transaction.info.orderId}</td>
-            </tr>
-            <tr>
-              <td>Summary</td>
-              <td>{transaction.info.summary}</td>
-            </tr>
-            {transaction.info.products && transaction.info.products.length > 0 
&&
-              <tr>
-                <td>Products</td>
-                <td><ol>
-                  {transaction.info.products.map(p =>
-                    <li>{p.description}</li>
-                  )}</ol></td>
-              </tr>
-            }
-            <tr>
-              <td>Amount deduce</td>
-              <td>{transaction.amountRaw}</td>
-            </tr>
-            <tr>
-              <td>Amount received</td>
-              <td>{transaction.amountEffective}</td>
-            </tr>
-            <tr>
-              <td>Exchange fee</td>
-              <td>{Amounts.stringify(
-                Amounts.sub(
-                  Amounts.parseOrThrow(transaction.amountRaw),
-                  Amounts.parseOrThrow(transaction.amountEffective),
-                ).amount
-              )}</td>
-            </tr>
-            <tr>
-              <td>When</td>
-              <td>{transaction.timestamp.t_ms === "never" ? "never" : 
format(transaction.timestamp.t_ms, 'dd/MM/yyyy HH:mm:ss')}</td>
-            </tr>
-          </table>
-        </section>
-        <Footer />
-      </div>
-    );
-  }
-
-
-  return <div></div>
-}
-
-export function WalletTransaction({ tid }: { tid: string }): JSX.Element {
-  const [transaction, setTransaction] = useState<
-    Transaction | undefined
-  >(undefined);
-
-  useEffect(() => {
-    const fetchData = async (): Promise<void> => {
-      const res = await wxApi.getTransactions();
-      const ts = res.transactions.filter(t => t.transactionId === tid)
-      if (ts.length === 1) {
-        setTransaction(ts[0]);
-      } else {
-        route(Pages.history)
-      }
-    };
-    fetchData();
-  }, []);
-
-  return <WalletTransactionView
-    transaction={transaction}
-    onDelete={() => wxApi.deleteTransaction(tid).then(_ => history.go(-1))}
-    onBack={() => { history.go(-1) }}
-  />
-}
-
-export function WalletSettings() {
-  const [permissionsEnabled, togglePermissions] = useExtendedPermissions()
-  return (
-    <div>
-      <h2>Permissions</h2>
-      <PermissionsCheckbox enabled={permissionsEnabled} 
onToggle={togglePermissions} />
-      {/* 
-      <h2>Developer mode</h2>
-      <DebugCheckbox enabled={permissionsEnabled} onToggle={togglePermissions} 
/> 
-      */}
-    </div>
-  );
-}
-
-
-export function DebugCheckbox({ enabled, onToggle }: { enabled: boolean, 
onToggle: () => void }): JSX.Element {
-  return (
-    <div>
-      <input
-        checked={enabled}
-        onClick={onToggle}
-        type="checkbox"
-        id="checkbox-perm"
-        style={{ width: "1.5em", height: "1.5em", verticalAlign: "middle" }}
-      />
-      <label
-        htmlFor="checkbox-perm"
-        style={{ marginLeft: "0.5em", fontWeight: "bold" }}
-      >
-        Automatically open wallet based on page content
-      </label>
-      <span
-        style={{
-          color: "#383838",
-          fontSize: "smaller",
-          display: "block",
-          marginLeft: "2em",
-        }}
-      >
-        (Enabling this option below will make using the wallet faster, but
-        requires more permissions from your browser.)
-      </span>
-    </div>
-  );
-}
-
-function reload(): void {
-  try {
-    chrome.runtime.reload();
-    window.close();
-  } catch (e) {
-    // Functionality missing in firefox, ignore!
-  }
-}
-
-async function confirmReset(): Promise<void> {
-  if (
-    confirm(
-      "Do you want to IRREVOCABLY DESTROY everything inside your" +
-      " wallet and LOSE ALL YOUR COINS?",
-    )
-  ) {
-    await wxApi.resetDb();
-    window.close();
-  }
-}
-
-export function WalletDebug(props: any): JSX.Element {
-  return (
-    <div>
-      <p>Debug tools:</p>
-      <button onClick={openExtensionPage("/static/popup.html")}>wallet 
tab</button>
-      <br />
-      <button onClick={confirmReset}>reset</button>
-      <button onClick={reload}>reload chrome extension</button>
-      <Diagnostics />
-    </div>
-  );
-}
-
-function openExtensionPage(page: string) {
-  return () => {
-    chrome.tabs.create({
-      url: chrome.extension.getURL(page),
-    });
-  };
-}
-
-// function openTab(page: string) {
-//   return (evt: React.SyntheticEvent<any>) => {
-//     evt.preventDefault();
-//     chrome.tabs.create({
-//       url: page,
-//     });
-//   };
-// }
-
-function makeExtensionUrlWithParams(
-  url: string,
-  params?: { [name: string]: string | undefined },
-): string {
-  const innerUrl = new URL(chrome.extension.getURL("/" + url));
-  if (params) {
-    for (const key in params) {
-      const p = params[key];
-      if (p) {
-        innerUrl.searchParams.set(key, p);
-      }
-    }
-  }
-  return innerUrl.href;
-}
-
-export function actionForTalerUri(talerUri: string): string | undefined {
-  const uriType = classifyTalerUri(talerUri);
-  switch (uriType) {
-    case TalerUriType.TalerWithdraw:
-      return makeExtensionUrlWithParams("static/wallet.html#/withdraw", {
-        talerWithdrawUri: talerUri,
-      });
-    case TalerUriType.TalerPay:
-      return makeExtensionUrlWithParams("static/wallet.html#/pay", {
-        talerPayUri: talerUri,
-      });
-    case TalerUriType.TalerTip:
-      return makeExtensionUrlWithParams("static/wallet.html#/tip", {
-        talerTipUri: talerUri,
-      });
-    case TalerUriType.TalerRefund:
-      return makeExtensionUrlWithParams("static/wallet.html#/refund", {
-        talerRefundUri: talerUri,
-      });
-    case TalerUriType.TalerNotifyReserve:
-      // FIXME: implement
-      break;
-    default:
-      console.warn(
-        "Response with HTTP 402 has Taler header, but header value is not a 
taler:// URI.",
-      );
-      break;
-  }
-  return undefined;
-}
-
-export async function findTalerUriInActiveTab(): Promise<string | undefined> {
-  return new Promise((resolve, reject) => {
-    chrome.tabs.executeScript(
-      {
-        code: `
-        (() => {
-          let x = document.querySelector("a[href^='taler://'") || 
document.querySelector("a[href^='taler+http://'");
-          return x ? x.href.toString() : null;
-        })();
-      `,
-        allFrames: false,
-      },
-      (result) => {
-        if (chrome.runtime.lastError) {
-          console.error(chrome.runtime.lastError);
-          resolve(undefined);
-          return;
-        }
-        console.log("got result", result);
-        resolve(result[0]);
-      },
-    );
-  });
-}
-
-// export function WalletPopup(): JSX.Element {
-//   const [talerActionUrl, setTalerActionUrl] = useState<string | undefined>(
-//     undefined,
-//   );
-//   const [dismissed, setDismissed] = useState(false);
-//   useEffect(() => {
-//     async function check(): Promise<void> {
-//       const talerUri = await findTalerUriInActiveTab();
-//       if (talerUri) {
-//         const actionUrl = actionForTalerUri(talerUri);
-//         setTalerActionUrl(actionUrl);
-//       }
-//     }
-//     check();
-//   }, []);
-//   if (talerActionUrl && !dismissed) {
-//     return (
-//       <div style={{ padding: "1em", width: 400 }}>
-//         <h1>Taler Action</h1>
-//         <p>This page has a Taler action. </p>
-//         <p>
-//           <button
-//             onClick={() => {
-//               window.open(talerActionUrl, "_blank");
-//             }}
-//           >
-//             Open
-//           </button>
-//         </p>
-//         <p>
-//           <button onClick={() => setDismissed(true)}>Dismiss</button>
-//         </p>
-//       </div>
-//     );
-//   }
-//   return (
-//     <div>
-//       <Match>{({ path }: any) => <WalletNavBar current={path} />}</Match>
-//       <div style={{ margin: "1em", width: 400 }}>
-//         <Router>
-//           <Route path={Pages.balance} component={WalletBalanceView} />
-//           <Route path={Pages.settings} component={WalletSettings} />
-//           <Route path={Pages.debug} component={WalletDebug} />
-//           <Route path={Pages.history} component={WalletHistory} />
-//           <Route path={Pages.transaction} component={WalletTransaction} />
-//         </Router>
-//       </div>
-//     </div>
-//   );
-// }
-
diff --git a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx 
b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
index 926ae7aa..6cc781aa 100644
--- a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
+++ b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
@@ -23,13 +23,17 @@
 import { render } from "preact";
 import { setupI18n } from "@gnu-taler/taler-util";
 import { strings } from "./i18n/strings";
-import { useEffect, useState } from "preact/hooks";
+import { useEffect } from "preact/hooks";
 import {
-  actionForTalerUri, findTalerUriInActiveTab, Pages, WalletBalanceView, 
WalletDebug, WalletHistory,
-  WalletNavBar, WalletSettings, WalletTransaction, WalletTransactionView
-} from "./popup/popup";
+  Pages, WalletNavBar} from "./popup/popup";
+import { HistoryPage } from "./popup/History";
+import { DebugPage } from "./popup/Debug";
+import { SettingsPage } from "./popup/Settings";
+import { TransactionPage } from "./popup/Transaction";
+import { BalancePage } from "./popup/Balance";
 import Match from "preact-router/match";
 import Router, { route, Route } from "preact-router";
+import { useTalerActionURL } from "./hooks/useTalerActionURL";
 // import { Application } from "./Application";
 
 function main(): void {
@@ -53,25 +57,6 @@ if (document.readyState === "loading") {
   main();
 }
 
-function useTalerActionURL(): [string | undefined, (s: boolean) => void] {
-  const [talerActionUrl, setTalerActionUrl] = useState<string | undefined>(
-    undefined,
-  );
-  const [dismissed, setDismissed] = useState(false);
-  useEffect(() => {
-    async function check(): Promise<void> {
-      const talerUri = await findTalerUriInActiveTab();
-      if (talerUri) {
-        const actionUrl = actionForTalerUri(talerUri);
-        setTalerActionUrl(actionUrl);
-      }
-    }
-    check();
-  }, []);
-  const url = dismissed ? undefined : talerActionUrl
-  return [url, setDismissed]
-}
-
 interface Props {
   url: string;
   onDismiss: (s: boolean) => void;
@@ -105,11 +90,11 @@ function Application() {
       <Match>{({ path }: any) => <WalletNavBar current={path} />}</Match >
       <div style={{ margin: "1em", width: 400 }}>
         <Router>
-          <Route path={Pages.balance} component={WalletBalanceView} />
-          <Route path={Pages.settings} component={WalletSettings} />
-          <Route path={Pages.debug} component={WalletDebug} />
-          <Route path={Pages.history} component={WalletHistory} />
-          <Route path={Pages.transaction} component={WalletTransaction} />
+          <Route path={Pages.balance} component={BalancePage} />
+          <Route path={Pages.settings} component={SettingsPage} />
+          <Route path={Pages.debug} component={DebugPage} />
+          <Route path={Pages.history} component={HistoryPage} />
+          <Route path={Pages.transaction} component={TransactionPage} />
           <Route default component={Redirect} to={Pages.balance} />
         </Router>
       </div>
@@ -118,8 +103,6 @@ function Application() {
 }
 
 
-
-
 function Redirect({ to }: { to: string }): null {
   useEffect(() => {
     route(to, true)
diff --git a/packages/taler-wallet-webextension/src/wallet/pay.tsx 
b/packages/taler-wallet-webextension/src/wallet/Pay.tsx
similarity index 94%
rename from packages/taler-wallet-webextension/src/wallet/pay.tsx
rename to packages/taler-wallet-webextension/src/wallet/Pay.tsx
index e958cd48..23b4e6c1 100644
--- a/packages/taler-wallet-webextension/src/wallet/pay.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Pay.tsx
@@ -45,7 +45,7 @@ interface Props {
   talerPayUri?: string
 }
 
-export function TalerPayDialog({ talerPayUri }: Props): JSX.Element {
+export function PayPage({ talerPayUri }: Props): JSX.Element {
   const [payStatus, setPayStatus] = useState<PreparePayResult | 
undefined>(undefined);
   const [payResult, setPayResult] = useState<ConfirmPayResult | 
undefined>(undefined);
   const [payErrMsg, setPayErrMsg] = useState<string | undefined>("");
@@ -222,14 +222,3 @@ export function TalerPayDialog({ talerPayUri }: Props): 
JSX.Element {
   );
 }
 
-/**
- * @deprecated to be removed
- */
-export function createPayPage(): JSX.Element {
-  const url = new URL(document.location.href);
-  const talerPayUri = url.searchParams.get("talerPayUri");
-  if (!talerPayUri) {
-    throw Error("invalid parameter");
-  }
-  return <TalerPayDialog talerPayUri={talerPayUri} />;
-}
diff --git a/packages/taler-wallet-webextension/src/wallet/refund.tsx 
b/packages/taler-wallet-webextension/src/wallet/Refund.tsx
similarity index 81%
rename from packages/taler-wallet-webextension/src/wallet/refund.tsx
rename to packages/taler-wallet-webextension/src/wallet/Refund.tsx
index 1991bc9d..70221741 100644
--- a/packages/taler-wallet-webextension/src/wallet/refund.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Refund.tsx
@@ -33,7 +33,7 @@ interface Props {
   talerRefundUri?: string
 }
 
-export function RefundStatusView({ talerRefundUri }: Props): JSX.Element {
+export function RefundPage({ talerRefundUri }: Props): JSX.Element {
   const [applyResult, setApplyResult] = useState<ApplyRefundResponse | 
undefined>(undefined);
   const [errMsg, setErrMsg] = useState<string | undefined>(undefined);
 
@@ -87,22 +87,3 @@ export function RefundStatusView({ talerRefundUri }: Props): 
JSX.Element {
     </>
   );
 }
-
-/**
- * @deprecated to be removed
- */
-export function createRefundPage(): JSX.Element {
-  const url = new URL(document.location.href);
-
-  const container = document.getElementById("container");
-  if (!container) {
-    throw Error("fatal: can't mount component, container missing");
-  }
-
-  const talerRefundUri = url.searchParams.get("talerRefundUri");
-  if (!talerRefundUri) {
-    throw Error("taler refund URI required");
-  }
-
-  return <RefundStatusView talerRefundUri={talerRefundUri} />;
-}
diff --git a/packages/taler-wallet-webextension/src/wallet/tip.tsx 
b/packages/taler-wallet-webextension/src/wallet/Tip.tsx
similarity index 87%
rename from packages/taler-wallet-webextension/src/wallet/tip.tsx
rename to packages/taler-wallet-webextension/src/wallet/Tip.tsx
index d832976d..708e8940 100644
--- a/packages/taler-wallet-webextension/src/wallet/tip.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Tip.tsx
@@ -30,7 +30,7 @@ interface Props {
   talerTipUri?: string 
 }
 
-export function TalerTipDialog({ talerTipUri }: Props): JSX.Element {
+export function TipPage({ talerTipUri }: Props): JSX.Element {
   const [updateCounter, setUpdateCounter] = useState<number>(0);
   const [prepareTipResult, setPrepareTipResult] = useState<
     PrepareTipResult | undefined
@@ -95,15 +95,3 @@ export function TalerTipDialog({ talerTipUri }: Props): 
JSX.Element {
     );
   }
 }
-
-/**
- * @deprecated to be removed
- */
-export function createTipPage(): JSX.Element {
-  const url = new URL(document.location.href);
-  const talerTipUri = url.searchParams.get("talerTipUri");
-  if (!talerTipUri) {
-    throw Error("invalid parameter");
-  }
-  return <TalerTipDialog talerTipUri={talerTipUri} />;
-}
diff --git a/packages/taler-wallet-webextension/src/wallet/welcome.tsx 
b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx
similarity index 53%
rename from packages/taler-wallet-webextension/src/wallet/welcome.tsx
rename to packages/taler-wallet-webextension/src/wallet/Welcome.tsx
index 9be62bf8..0f9cc867 100644
--- a/packages/taler-wallet-webextension/src/wallet/welcome.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx
@@ -20,43 +20,12 @@
  * @author Florian Dold
  */
 
-import * as wxApi from "../wxApi";
-import { getPermissionsApi } from "../compat";
-import { extendedPermissions } from "../permissions";
-import { Fragment, JSX } from "preact/jsx-runtime";
+import { JSX } from "preact/jsx-runtime";
 import { PermissionsCheckbox } from "../components/PermissionsCheckbox";
 import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
 import { Diagnostics } from "../components/Diagnostics";
 
-export async function handleExtendedPerm(isEnabled: boolean): Promise<boolean> 
{
-  let nextVal: boolean | undefined;
-
-  if (!isEnabled) {
-    const granted = await new Promise<boolean>((resolve, reject) => {
-      // We set permissions here, since apparently FF wants this to be done
-      // as the result of an input event ...
-      getPermissionsApi().request(extendedPermissions, (granted: boolean) => {
-        if (chrome.runtime.lastError) {
-          console.error("error requesting permissions");
-          console.error(chrome.runtime.lastError);
-          reject(chrome.runtime.lastError);
-          return;
-        }
-        console.log("permissions granted:", granted);
-        resolve(granted);
-      });
-    });
-    const res = await wxApi.setExtendedPermissions(granted);
-    nextVal = res.newValue;
-  } else {
-    const res = await wxApi.setExtendedPermissions(false);
-    nextVal = res.newValue;
-  }
-  console.log("new permissions applied:", nextVal ?? false);
-  return nextVal ?? false
-}
-
-export function Welcome(): JSX.Element {
+export function WelcomePage(): JSX.Element {
   const [permissionsEnabled, togglePermissions] = useExtendedPermissions()
   return (
     <>
@@ -74,10 +43,3 @@ export function Welcome(): JSX.Element {
     </>
   );
 }
-
-/**
- * @deprecated to be removed
- */
-export function createWelcomePage(): JSX.Element {
-  return <Welcome />;
-}
diff --git a/packages/taler-wallet-webextension/src/wallet/withdraw.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/Withdraw.stories.tsx
similarity index 97%
rename from packages/taler-wallet-webextension/src/wallet/withdraw.stories.tsx
rename to packages/taler-wallet-webextension/src/wallet/Withdraw.stories.tsx
index 86f0eec9..24fb17df 100644
--- a/packages/taler-wallet-webextension/src/wallet/withdraw.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Withdraw.stories.tsx
@@ -20,7 +20,7 @@
 */
 
 import { h } from 'preact';
-import { View, ViewProps } from './withdraw';
+import { View, ViewProps } from './Withdraw';
 
 
 export default {
diff --git a/packages/taler-wallet-webextension/src/wallet/withdraw.tsx 
b/packages/taler-wallet-webextension/src/wallet/Withdraw.tsx
similarity index 92%
rename from packages/taler-wallet-webextension/src/wallet/withdraw.tsx
rename to packages/taler-wallet-webextension/src/wallet/Withdraw.tsx
index cb96fa4d..5dc12407 100644
--- a/packages/taler-wallet-webextension/src/wallet/withdraw.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Withdraw.tsx
@@ -111,7 +111,7 @@ export function View({ talerWithdrawUri, details, 
cancelled, selectedExchange, a
   )
 }
 
-export function WithdrawalDialog({ talerWithdrawUri }: Props): JSX.Element {
+export function WithdrawPage({ talerWithdrawUri }: Props): JSX.Element {
   const [details, setDetails] = useState<WithdrawUriInfoResponse | 
undefined>(undefined);
   const [selectedExchange, setSelectedExchange] = useState<
     string | undefined
@@ -159,15 +159,3 @@ export function WithdrawalDialog({ talerWithdrawUri }: 
Props): JSX.Element {
   />
 }
 
-
-/**
- * @deprecated to be removed
- */
-export function createWithdrawPage(): JSX.Element {
-  const url = new URL(document.location.href);
-  const talerWithdrawUri = url.searchParams.get("talerWithdrawUri");
-  if (!talerWithdrawUri) {
-    throw Error("withdraw URI required");
-  }
-  return <WithdrawalDialog talerWithdrawUri={talerWithdrawUri} />;
-}
diff --git a/packages/taler-wallet-webextension/src/walletEntryPoint.tsx 
b/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
index 2d1671dd..cb97ffbe 100644
--- a/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
+++ b/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
@@ -25,11 +25,11 @@ import { setupI18n } from "@gnu-taler/taler-util";
 import { strings } from "./i18n/strings";
 import { createHashHistory } from 'history';
 
-import { WithdrawalDialog } from "./wallet/withdraw";
-import { Welcome } from "./wallet/welcome";
-import { TalerPayDialog } from "./wallet/pay";
-import { RefundStatusView } from "./wallet/refund";
-import { TalerTipDialog } from './wallet/tip';
+import { WithdrawPage } from "./wallet/Withdraw";
+import { WelcomePage } from "./wallet/Welcome";
+import { PayPage } from "./wallet/Pay";
+import { RefundPage } from "./wallet/Refund";
+import { TipPage } from './wallet/Tip';
 import Router, { route, Route } from "preact-router";
 
 
@@ -82,7 +82,7 @@ function Application() {
         </div>
         <h1>Browser Extension Installed!</h1>
         <div>
-          <Welcome />
+          <WelcomePage />
         </div>
       </section>
     }} />
@@ -91,7 +91,7 @@ function Application() {
       return <section id="main">
         <h1>GNU Taler Wallet</h1>
         <article class="fade">
-          <TalerPayDialog talerPayUri={queryParams.talerPayUri} />
+          <PayPage talerPayUri={queryParams.talerPayUri} />
         </article>
       </section>
     }} />
@@ -100,7 +100,7 @@ function Application() {
       return <section id="main">
         <h1>GNU Taler Wallet</h1>
         <article class="fade">
-          <RefundStatusView talerRefundUri={queryParams.talerRefundUri} />
+          <RefundPage talerRefundUri={queryParams.talerRefundUri} />
         </article>
       </section>
     }} />
@@ -109,7 +109,7 @@ function Application() {
       return <section id="main">
         <h1>GNU Taler Wallet</h1>
         <div>
-          <TalerTipDialog talerTipUri={queryParams.talerTipUri} />
+          <TipPage talerTipUri={queryParams.talerTipUri} />
         </div>
       </section>
     }} />
@@ -121,7 +121,7 @@ function Application() {
           </h1>
         </div>
         <div class="fade">
-          <WithdrawalDialog talerWithdrawUri={queryParams.talerWithdrawUri} />
+          <WithdrawPage talerWithdrawUri={queryParams.talerWithdrawUri} />
         </div>
       </section>
     }} />

-- 
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]