gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] 03/03: some changes:


From: gnunet
Subject: [taler-wallet-core] 03/03: some changes:
Date: Fri, 19 Nov 2021 18:51:50 +0100

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

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

commit a35604fd562a72e4e266bf6a4255d89d3c1374a1
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Fri Nov 19 14:51:27 2021 -0300

    some changes:
    
     - simplify design to reuse more components (from wallet instead of popup)
     - simplify hooks (useAsyncAsHook)
     - updateNotification from backend now filter events by type
     - new balance design proposed by Belen
     - more information when the withdrawal is in process
     - manual withdrawal implementation
     - some bugs killed
---
 .../.storybook/preview.js                          |  12 +-
 .../src/components/BalanceTable.tsx                |  48 ++++
 .../src/components/BankDetailsByPaytoType.tsx      | 109 ++++++++
 .../src/components/styled/index.tsx                | 167 ++++++++++++-
 packages/taler-wallet-webextension/src/cta/Pay.tsx |   4 +-
 .../src/hooks/useAsyncAsHook.ts                    |  10 +-
 .../src/hooks/useBalances.ts                       |  53 ----
 .../src/popup/Backup.stories.tsx                   | 198 ---------------
 .../src/popup/BackupPage.tsx                       | 197 ---------------
 .../src/popup/Balance.stories.tsx                  |   2 +-
 .../src/popup/BalancePage.tsx                      | 195 +++------------
 .../taler-wallet-webextension/src/popup/Debug.tsx  |   2 +-
 .../src/popup/History.stories.tsx                  |   1 +
 .../src/popup/History.tsx                          |  22 +-
 .../popup/ProviderAddConfirmProvider.stories.tsx   |  51 ----
 .../src/popup/ProviderAddPage.tsx                  | 244 ------------------
 .../src/popup/ProviderAddSetUrl.stories.tsx        |  51 ----
 .../src/popup/ProviderDetail.stories.tsx           | 235 -----------------
 .../src/popup/ProviderDetailPage.tsx               | 278 ---------------------
 .../src/popup/Settings.tsx                         |  83 ++++--
 .../src/popup/TalerActionFound.tsx                 |  12 +-
 .../src/popupEntryPoint.tsx                        |  13 +-
 .../taler-wallet-webextension/src/svg/index.tsx    |  40 +++
 .../src/wallet/BackupPage.tsx                      |  13 +-
 .../src/wallet/BalancePage.tsx                     | 110 ++------
 .../src/wallet/CreateManualWithdraw.stories.tsx    |   4 +
 .../src/wallet/CreateManualWithdraw.tsx            |  26 +-
 .../src/wallet/History.stories.tsx                 |   1 +
 .../src/wallet/History.tsx                         |  33 ++-
 .../src/wallet/ManualWithdrawPage.tsx              |  15 +-
 .../src/wallet/ProviderAddPage.tsx                 |  13 +-
 .../src/wallet/ProviderDetailPage.tsx              |  94 +++----
 .../src/wallet/ReserveCreated.tsx                  | 119 +--------
 .../src/wallet/Settings.tsx                        |  73 +++---
 .../src/wallet/Transaction.stories.tsx             |  44 +++-
 .../src/wallet/Transaction.tsx                     | 226 +++++++++++++----
 .../src/wallet/Welcome.tsx                         |  11 +-
 .../src/walletEntryPoint.tsx                       |  46 +++-
 packages/taler-wallet-webextension/src/wxApi.ts    |  49 ++--
 .../taler-wallet-webextension/src/wxBackend.ts     |  10 +-
 40 files changed, 953 insertions(+), 1961 deletions(-)

diff --git a/packages/taler-wallet-webextension/.storybook/preview.js 
b/packages/taler-wallet-webextension/.storybook/preview.js
index 0efb9630..25f9f46b 100644
--- a/packages/taler-wallet-webextension/.storybook/preview.js
+++ b/packages/taler-wallet-webextension/.storybook/preview.js
@@ -18,7 +18,7 @@ import { h, Fragment } from "preact"
 import { NavBar } from '../src/NavigationBar'
 import { LogoHeader } from '../src/components/LogoHeader'
 import { TranslationProvider } from '../src/context/translation'
-
+import { PopupBox, WalletBox } from '../src/components/styled'
 export const parameters = {
   controls: { expanded: true },
   actions: { argTypesRegex: "^on[A-Z].*" },
@@ -58,9 +58,9 @@ export const decorators = [
         // add a fake header so it looks similar
         return <Fragment>
           <NavBar path={path} devMode={path === '/dev'} />
-          <div style={{ width: 400, height: 290 }}>
+          <PopupBox>
             <Story />
-          </div>
+          </PopupBox>
         </Fragment>
       }
 
@@ -125,7 +125,7 @@ export const decorators = [
         <link key="1" rel="stylesheet" type="text/css" 
href="/static/style/pure.css" />
         <link key="2" rel="stylesheet" type="text/css" 
href="/static/style/wallet.css" />
         <Story />
-      </div>      
+      </div>
     }
     if (kind.startsWith('wallet')) {
       const path = /wallet(\/.*).*/.exec(kind)[1];
@@ -157,7 +157,9 @@ export const decorators = [
         </style>
         <LogoHeader />
         <NavBar path={path} devMode={path === '/dev'} />
-        <Story />
+        <WalletBox>
+          <Story />
+        </WalletBox>
       </div>
     }
     return <div>
diff --git a/packages/taler-wallet-webextension/src/components/BalanceTable.tsx 
b/packages/taler-wallet-webextension/src/components/BalanceTable.tsx
new file mode 100644
index 00000000..e1c19cc2
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/components/BalanceTable.tsx
@@ -0,0 +1,48 @@
+/*
+ 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 { amountFractionalBase, Amounts, Balance } from "@gnu-taler/taler-util";
+import { h, VNode } from "preact";
+import { TableWithRoundRows as TableWithRoundedRows } from "./styled/index";
+
+export function BalanceTable({ balances }: { balances: Balance[] }): VNode {
+  const currencyFormatter = new Intl.NumberFormat("en-US");
+  return (
+    <TableWithRoundedRows>
+      {balances.map((entry, idx) => {
+        const av = Amounts.parseOrThrow(entry.available);
+
+        const v = currencyFormatter.format(
+          av.value + av.fraction / amountFractionalBase,
+        );
+        return (
+          <tr key={idx}>
+            <td>{av.currency}</td>
+            <td
+              style={{
+                fontSize: "2em",
+                textAlign: "right",
+                width: "100%",
+              }}
+            >
+              {v}
+            </td>
+          </tr>
+        );
+      })}
+    </TableWithRoundedRows>
+  );
+}
diff --git 
a/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx 
b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
new file mode 100644
index 00000000..71365e08
--- /dev/null
+++ 
b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
@@ -0,0 +1,109 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 Taler Systems SA
+
+ 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 { PaytoUri } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
+import { useEffect, useState } from "preact/hooks";
+import { CopiedIcon, CopyIcon } from "../svg";
+import { ButtonBox, TooltipRight } from "./styled";
+
+export interface BankDetailsProps {
+  payto: PaytoUri | undefined;
+  exchangeBaseUrl: string;
+  subject: string;
+  amount: string;
+}
+
+export function BankDetailsByPaytoType({
+  payto,
+  subject,
+  exchangeBaseUrl,
+  amount,
+}: BankDetailsProps): VNode {
+  const firstPart = !payto ? undefined : !payto.isKnown ? (
+    <Row name="Account" value={payto.targetPath} />
+  ) : payto.targetType === "x-taler-bank" ? (
+    <Fragment>
+      <Row name="Bank host" value={payto.host} />
+      <Row name="Bank account" value={payto.account} />
+    </Fragment>
+  ) : payto.targetType === "iban" ? (
+    <Row name="IBAN" value={payto.iban} />
+  ) : undefined;
+  return (
+    <div style={{ textAlign: "left" }}>
+      <p>Bank transfer details</p>
+      <table>
+        {firstPart}
+        <Row name="Exchange" value={exchangeBaseUrl} />
+        <Row name="Chosen amount" value={amount} />
+        <Row name="Subject" value={subject} literal />
+      </table>
+    </div>
+  );
+}
+
+function Row({
+  name,
+  value,
+  literal,
+}: {
+  name: string;
+  value: string;
+  literal?: boolean;
+}): VNode {
+  const [copied, setCopied] = useState(false);
+  function copyText(): void {
+    navigator.clipboard.writeText(value);
+    setCopied(true);
+  }
+  useEffect(() => {
+    if (copied) {
+      setTimeout(() => {
+        setCopied(false);
+      }, 1000);
+    }
+  }, [copied]);
+  return (
+    <tr>
+      <td>
+        {!copied ? (
+          <ButtonBox onClick={copyText}>
+            <CopyIcon />
+          </ButtonBox>
+        ) : (
+          <TooltipRight content="Copied">
+            <ButtonBox disabled>
+              <CopiedIcon />
+            </ButtonBox>
+          </TooltipRight>
+        )}
+      </td>
+      <td>
+        <b>{name}</b>
+      </td>
+      {literal ? (
+        <td>
+          <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
+            {value}
+          </pre>
+        </td>
+      ) : (
+        <td>{value}</td>
+      )}
+    </tr>
+  );
+}
diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx 
b/packages/taler-wallet-webextension/src/components/styled/index.tsx
index 2db7c61f..b2ca1380 100644
--- a/packages/taler-wallet-webextension/src/components/styled/index.tsx
+++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx
@@ -15,6 +15,8 @@
  */
 
 // need to import linaria types, otherwise compiler will complain
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+// eslint-disable-next-line no-unused-vars
 import type * as Linaria from "@linaria/core";
 
 import { styled } from "@linaria/react";
@@ -78,9 +80,8 @@ export const WalletBox = styled.div<{ noPadding?: boolean }>`
     width: 400px;
   }
   & > section {
-    padding-left: ${({ noPadding }) => (noPadding ? "0px" : "8px")};
-    padding-right: ${({ noPadding }) => (noPadding ? "0px" : "8px")};
-    // this margin will send the section up when used with a header
+    padding: ${({ noPadding }) => (noPadding ? "0px" : "8px")};
+
     margin-bottom: auto;
     overflow: auto;
 
@@ -202,6 +203,152 @@ export const PopupBox = styled.div<{ noPadding?: boolean 
}>`
   }
 `;
 
+export const TableWithRoundRows = styled.table`
+  border-collapse: separate;
+  border-spacing: 0px 10px;
+  margin-top: -10px;
+
+  td {
+    border: solid 1px #000;
+    border-style: solid none;
+    padding: 10px;
+  }
+  td:first-child {
+    border-left-style: solid;
+    border-top-left-radius: 5px;
+    border-bottom-left-radius: 5px;
+  }
+  td:last-child {
+    border-right-style: solid;
+    border-bottom-right-radius: 5px;
+    border-top-right-radius: 5px;
+  }
+`;
+
+const Tooltip = styled.div<{ content: string }>`
+  display: block;
+  position: relative;
+
+  ::before {
+    position: absolute;
+    z-index: 1000001;
+    width: 0;
+    height: 0;
+    color: darkgray;
+    pointer-events: none;
+    content: "";
+    border: 6px solid transparent;
+
+    border-bottom-color: darkgray;
+  }
+
+  ::after {
+    position: absolute;
+    z-index: 1000001;
+    padding: 0.5em 0.75em;
+    font: normal normal 11px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI",
+      Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
+    -webkit-font-smoothing: subpixel-antialiased;
+    color: white;
+    text-align: center;
+    text-decoration: none;
+    text-shadow: none;
+    text-transform: none;
+    letter-spacing: normal;
+    word-wrap: break-word;
+    white-space: pre;
+    pointer-events: none;
+    content: attr(content);
+    background: darkgray;
+    border-radius: 6px;
+  }
+`;
+
+export const TooltipBottom = styled(Tooltip)`
+  ::before {
+    top: auto;
+    right: 50%;
+    bottom: -7px;
+    margin-right: -6px;
+  }
+  ::after {
+    top: 100%;
+    right: -50%;
+    margin-top: 6px;
+  }
+`;
+
+export const TooltipRight = styled(Tooltip)`
+  ::before {
+    top: 0px;
+    left: 16px;
+    transform: rotate(-90deg);
+  }
+  ::after {
+    top: -50%;
+    left: 28px;
+    margin-top: 6px;
+  }
+`;
+
+export const Overlay = styled.div`
+  position: fixed;
+  width: 100%;
+  height: 100%;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(0, 0, 0, 0.5);
+  z-index: 2;
+  cursor: pointer;
+`;
+
+export const CenteredDialog = styled.div`
+  position: absolute;
+  text-align: left;
+
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+
+  top: 50%;
+  left: 50%;
+  /* font-size: 50px; */
+  color: black;
+  transform: translate(-50%, -50%);
+  -ms-transform: translate(-50%, -50%);
+  cursor: initial;
+  background-color: white;
+  border-radius: 10px;
+
+  max-height: 70%;
+
+  & > header {
+    border-top-right-radius: 6px;
+    border-top-left-radius: 6px;
+    padding: 10px;
+    background-color: #f5f5f5;
+    border-bottom: 1px solid #dbdbdb;
+    font-weight: bold;
+  }
+  & > section {
+    padding: 10px;
+    flex-grow: 1;
+    flex-shrink: 1;
+    overflow: auto;
+  }
+  & > footer {
+    border-top: 1px solid #dbdbdb;
+    border-bottom-right-radius: 6px;
+    border-bottom-left-radius: 6px;
+    padding: 10px;
+    background-color: #f5f5f5;
+    display: flex;
+    justify-content: space-between;
+  }
+`;
+
 export const Button = styled.button<{ upperCased?: boolean }>`
   display: inline-block;
   zoom: 1;
@@ -217,7 +364,7 @@ export const Button = styled.button<{ upperCased?: boolean 
}>`
   font-family: inherit;
   font-size: 100%;
   padding: 0.5em 1em;
-  color: #444; /* rgba not supported (IE 8) */
+  /* color: #444; rgba not supported (IE 8) */
   color: rgba(0, 0, 0, 0.8); /* rgba supported */
   border: 1px solid #999; /*IE 6/7/8*/
   border: none rgba(0, 0, 0, 0); /*IE9 + everything else*/
@@ -305,8 +452,7 @@ export const FontIcon = styled.div`
 `;
 export const ButtonBox = styled(Button)`
   padding: 0.5em;
-  width: fit-content;
-  height: 2em;
+  font-size: x-small;
 
   & > ${FontIcon} {
     width: 1em;
@@ -320,6 +466,8 @@ export const ButtonBox = styled(Button)`
   border-radius: 4px;
   border-color: black;
   color: black;
+  /* -webkit-border-horizontal-spacing: 0px;
+  -webkit-border-vertical-spacing: 0px; */
 `;
 
 const ButtonVariant = styled(Button)`
@@ -377,6 +525,7 @@ export const Centered = styled.div`
     margin-top: 15px;
   }
 `;
+
 export const Row = styled.div`
   display: flex;
   margin: 0.5em 0;
@@ -566,6 +715,12 @@ export const ErrorBox = styled.div`
   }
 `;
 
+export const InfoBox = styled(ErrorBox)`
+  color: black;
+  background-color: #d1e7dd;
+  border-color: #badbcc;
+`;
+
 export const SuccessBox = styled(ErrorBox)`
   color: #0f5132;
   background-color: #d1e7dd;
diff --git a/packages/taler-wallet-webextension/src/cta/Pay.tsx 
b/packages/taler-wallet-webextension/src/cta/Pay.tsx
index d5861c47..30b571f0 100644
--- a/packages/taler-wallet-webextension/src/cta/Pay.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Pay.tsx
@@ -49,7 +49,7 @@ import {
   WalletAction,
   WarningBox,
 } from "../components/styled";
-import { useBalances } from "../hooks/useBalances";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
 import * as wxApi from "../wxApi";
 
 interface Props {
@@ -109,7 +109,7 @@ export function PayPage({ talerPayUri }: Props): VNode {
   );
   const [payErrMsg, setPayErrMsg] = useState<string | undefined>(undefined);
 
-  const balance = useBalances();
+  const balance = useAsyncAsHook(wxApi.getBalance);
   const balanceWithoutError = balance?.hasError
     ? []
     : balance?.response.balances || [];
diff --git a/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts 
b/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts
index aa6695c3..38ec5bf7 100644
--- a/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts
+++ b/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts
@@ -13,7 +13,7 @@
  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 { ExchangesListRespose } from "@gnu-taler/taler-util";
+import { ExchangesListRespose, NotificationType } from "@gnu-taler/taler-util";
 import { useEffect, useState } from "preact/hooks";
 import * as wxApi from "../wxApi";
 
@@ -29,7 +29,8 @@ interface HookError {
 
 export type HookResponse<T> = HookOk<T> | HookError | undefined;
 
-export function useAsyncAsHook<T>(fn: () => Promise<T>): HookResponse<T> {
+//"withdraw-group-finished"
+export function useAsyncAsHook<T>(fn: () => Promise<T>, updateOnNotification?: 
Array<NotificationType>): HookResponse<T> {
   const [result, setHookResponse] = useState<HookResponse<T>>(undefined);
   useEffect(() => {
     async function doAsync() {
@@ -43,6 +44,11 @@ export function useAsyncAsHook<T>(fn: () => Promise<T>): 
HookResponse<T> {
       }
     }
     doAsync();
+    if (updateOnNotification && updateOnNotification.length > 0) {
+      return wxApi.onUpdateNotification(updateOnNotification, () => {
+        doAsync()
+      });
+    }
   }, []);
   return result;
 }
diff --git a/packages/taler-wallet-webextension/src/hooks/useBalances.ts 
b/packages/taler-wallet-webextension/src/hooks/useBalances.ts
deleted file mode 100644
index 403ce7b8..00000000
--- a/packages/taler-wallet-webextension/src/hooks/useBalances.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- 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 { BalancesResponse } from "@gnu-taler/taler-util";
-import { useEffect, useState } from "preact/hooks";
-import * as wxApi from "../wxApi";
-
-interface BalancesHookOk {
-  hasError: false;
-  response: BalancesResponse;
-}
-
-interface BalancesHookError {
-  hasError: true;
-  message: string;
-}
-
-export type BalancesHook = BalancesHookOk | BalancesHookError | undefined;
-
-export function useBalances(): BalancesHook {
-  const [balance, setBalance] = useState<BalancesHook>(undefined);
-  useEffect(() => {
-    async function checkBalance() {
-      try {
-        const response = await wxApi.getBalance();
-        console.log("got balance", balance);
-        setBalance({ hasError: false, response });
-      } catch (e) {
-        console.error("could not retrieve balances", e);
-        if (e instanceof Error) {
-          setBalance({ hasError: true, message: e.message });
-        }
-      }
-    }
-    checkBalance();
-    return wxApi.onUpdateNotification(checkBalance);
-  }, []);
-
-  return balance;
-}
diff --git a/packages/taler-wallet-webextension/src/popup/Backup.stories.tsx 
b/packages/taler-wallet-webextension/src/popup/Backup.stories.tsx
deleted file mode 100644
index 232b0da7..00000000
--- a/packages/taler-wallet-webextension/src/popup/Backup.stories.tsx
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021 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 { ProviderPaymentType } from "@gnu-taler/taler-wallet-core";
-import { addDays } from "date-fns";
-import { BackupView as TestedComponent } from "./BackupPage";
-import { createExample } from "../test-utils";
-
-export default {
-  title: "popup/backup/list",
-  component: TestedComponent,
-  argTypes: {
-    onRetry: { action: "onRetry" },
-    onDelete: { action: "onDelete" },
-    onBack: { action: "onBack" },
-  },
-};
-
-export const LotOfProviders = createExample(TestedComponent, {
-  providers: [
-    {
-      active: true,
-      name: "sync.demo",
-      syncProviderBaseUrl: "http://sync.taler:9967/";,
-      lastSuccessfulBackupTimestamp: {
-        t_ms: 1625063925078,
-      },
-      paymentProposalIds: [
-        "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
-      ],
-      paymentStatus: {
-        type: ProviderPaymentType.Paid,
-        paidUntil: {
-          t_ms: 1656599921000,
-        },
-      },
-      terms: {
-        annualFee: "ARS:1",
-        storageLimitInMegabytes: 16,
-        supportedProtocolVersion: "0.0",
-      },
-    },
-    {
-      active: true,
-      name: "sync.demo",
-      syncProviderBaseUrl: "http://sync.taler:9967/";,
-      lastSuccessfulBackupTimestamp: {
-        t_ms: 1625063925078,
-      },
-      paymentProposalIds: [
-        "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
-      ],
-      paymentStatus: {
-        type: ProviderPaymentType.Paid,
-        paidUntil: {
-          t_ms: addDays(new Date(), 13).getTime(),
-        },
-      },
-      terms: {
-        annualFee: "ARS:1",
-        storageLimitInMegabytes: 16,
-        supportedProtocolVersion: "0.0",
-      },
-    },
-    {
-      active: false,
-      name: "sync.demo",
-      syncProviderBaseUrl: "http://sync.demo.taler.net/";,
-      paymentProposalIds: [],
-      paymentStatus: {
-        type: ProviderPaymentType.Pending,
-      },
-      terms: {
-        annualFee: "KUDOS:0.1",
-        storageLimitInMegabytes: 16,
-        supportedProtocolVersion: "0.0",
-      },
-    },
-    {
-      active: false,
-      name: "sync.demo",
-      syncProviderBaseUrl: "http://sync.demo.taler.net/";,
-      paymentProposalIds: [],
-      paymentStatus: {
-        type: ProviderPaymentType.InsufficientBalance,
-      },
-      terms: {
-        annualFee: "KUDOS:0.1",
-        storageLimitInMegabytes: 16,
-        supportedProtocolVersion: "0.0",
-      },
-    },
-    {
-      active: false,
-      name: "sync.demo",
-      syncProviderBaseUrl: "http://sync.demo.taler.net/";,
-      paymentProposalIds: [],
-      paymentStatus: {
-        type: ProviderPaymentType.TermsChanged,
-        newTerms: {
-          annualFee: "USD:2",
-          storageLimitInMegabytes: 8,
-          supportedProtocolVersion: "2",
-        },
-        oldTerms: {
-          annualFee: "USD:1",
-          storageLimitInMegabytes: 16,
-          supportedProtocolVersion: "1",
-        },
-        paidUntil: {
-          t_ms: "never",
-        },
-      },
-      terms: {
-        annualFee: "KUDOS:0.1",
-        storageLimitInMegabytes: 16,
-        supportedProtocolVersion: "0.0",
-      },
-    },
-    {
-      active: false,
-      name: "sync.demo",
-      syncProviderBaseUrl: "http://sync.demo.taler.net/";,
-      paymentProposalIds: [],
-      paymentStatus: {
-        type: ProviderPaymentType.Unpaid,
-      },
-      terms: {
-        annualFee: "KUDOS:0.1",
-        storageLimitInMegabytes: 16,
-        supportedProtocolVersion: "0.0",
-      },
-    },
-    {
-      active: false,
-      name: "sync.demo",
-      syncProviderBaseUrl: "http://sync.demo.taler.net/";,
-      paymentProposalIds: [],
-      paymentStatus: {
-        type: ProviderPaymentType.Unpaid,
-      },
-      terms: {
-        annualFee: "KUDOS:0.1",
-        storageLimitInMegabytes: 16,
-        supportedProtocolVersion: "0.0",
-      },
-    },
-  ],
-});
-
-export const OneProvider = createExample(TestedComponent, {
-  providers: [
-    {
-      active: true,
-      name: "sync.demo",
-      syncProviderBaseUrl: "http://sync.taler:9967/";,
-      lastSuccessfulBackupTimestamp: {
-        t_ms: 1625063925078,
-      },
-      paymentProposalIds: [
-        "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
-      ],
-      paymentStatus: {
-        type: ProviderPaymentType.Paid,
-        paidUntil: {
-          t_ms: 1656599921000,
-        },
-      },
-      terms: {
-        annualFee: "ARS:1",
-        storageLimitInMegabytes: 16,
-        supportedProtocolVersion: "0.0",
-      },
-    },
-  ],
-});
-
-export const Empty = createExample(TestedComponent, {
-  providers: [],
-});
diff --git a/packages/taler-wallet-webextension/src/popup/BackupPage.tsx 
b/packages/taler-wallet-webextension/src/popup/BackupPage.tsx
deleted file mode 100644
index ae93f8a4..00000000
--- a/packages/taler-wallet-webextension/src/popup/BackupPage.tsx
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- 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 { i18n, Timestamp } from "@gnu-taler/taler-util";
-import {
-  ProviderInfo,
-  ProviderPaymentStatus,
-} from "@gnu-taler/taler-wallet-core";
-import {
-  differenceInMonths,
-  formatDuration,
-  intervalToDuration,
-} from "date-fns";
-import { Fragment, h, VNode } from "preact";
-import {
-  BoldLight,
-  ButtonPrimary,
-  ButtonSuccess,
-  Centered,
-  CenteredBoldText,
-  CenteredText,
-  PopupBox,
-  RowBorderGray,
-  SmallLightText,
-  SmallText,
-} from "../components/styled";
-import { useBackupStatus } from "../hooks/useBackupStatus";
-import { Pages } from "../NavigationBar";
-
-interface Props {
-  onAddProvider: () => void;
-}
-
-export function BackupPage({ onAddProvider }: Props): VNode {
-  const status = useBackupStatus();
-  if (!status) {
-    return <div>Loading...</div>;
-  }
-  return (
-    <BackupView
-      providers={status.providers}
-      onAddProvider={onAddProvider}
-      onSyncAll={status.sync}
-    />
-  );
-}
-
-export interface ViewProps {
-  providers: ProviderInfo[];
-  onAddProvider: () => void;
-  onSyncAll: () => Promise<void>;
-}
-
-export function BackupView({
-  providers,
-  onAddProvider,
-  onSyncAll,
-}: ViewProps): VNode {
-  return (
-    <PopupBox>
-      <section>
-        {providers.map((provider, idx) => (
-          <BackupLayout
-            key={idx}
-            status={provider.paymentStatus}
-            timestamp={provider.lastSuccessfulBackupTimestamp}
-            id={provider.syncProviderBaseUrl}
-            active={provider.active}
-            title={provider.name}
-          />
-        ))}
-        {!providers.length && (
-          <Centered style={{ marginTop: 100 }}>
-            <BoldLight>No backup providers configured</BoldLight>
-            <ButtonSuccess onClick={onAddProvider}>
-              <i18n.Translate>Add provider</i18n.Translate>
-            </ButtonSuccess>
-          </Centered>
-        )}
-      </section>
-      {!!providers.length && (
-        <footer>
-          <div />
-          <div>
-            <ButtonPrimary onClick={onSyncAll}>
-              {providers.length > 1 ? (
-                <i18n.Translate>Sync all backups</i18n.Translate>
-              ) : (
-                <i18n.Translate>Sync now</i18n.Translate>
-              )}
-            </ButtonPrimary>
-            <ButtonSuccess onClick={onAddProvider}>Add provider</ButtonSuccess>
-          </div>
-        </footer>
-      )}
-    </PopupBox>
-  );
-}
-
-interface TransactionLayoutProps {
-  status: ProviderPaymentStatus;
-  timestamp?: Timestamp;
-  title: string;
-  id: string;
-  active: boolean;
-}
-
-function BackupLayout(props: TransactionLayoutProps): VNode {
-  const date = !props.timestamp ? undefined : new Date(props.timestamp.t_ms);
-  const dateStr = date?.toLocaleString([], {
-    dateStyle: "medium",
-    timeStyle: "short",
-  } as any);
-
-  return (
-    <RowBorderGray>
-      <div style={{ color: !props.active ? "grey" : undefined }}>
-        <a
-          href={Pages.provider_detail.replace(
-            ":pid",
-            encodeURIComponent(props.id),
-          )}
-        >
-          <span>{props.title}</span>
-        </a>
-
-        {dateStr && (
-          <SmallText style={{ marginTop: 5 }}>Last synced: 
{dateStr}</SmallText>
-        )}
-        {!dateStr && (
-          <SmallLightText style={{ marginTop: 5 }}>Not synced</SmallLightText>
-        )}
-      </div>
-      <div>
-        {props.status?.type === "paid" ? (
-          <ExpirationText until={props.status.paidUntil} />
-        ) : (
-          <div>{props.status.type}</div>
-        )}
-      </div>
-    </RowBorderGray>
-  );
-}
-
-function ExpirationText({ until }: { until: Timestamp }) {
-  return (
-    <Fragment>
-      <CenteredText> Expires in </CenteredText>
-      <CenteredBoldText {...{ color: colorByTimeToExpire(until) }}>
-        {" "}
-        {daysUntil(until)}{" "}
-      </CenteredBoldText>
-    </Fragment>
-  );
-}
-
-function colorByTimeToExpire(d: Timestamp) {
-  if (d.t_ms === "never") return "rgb(28, 184, 65)";
-  const months = differenceInMonths(d.t_ms, new Date());
-  return months > 1 ? "rgb(28, 184, 65)" : "rgb(223, 117, 20)";
-}
-
-function daysUntil(d: Timestamp) {
-  if (d.t_ms === "never") return undefined;
-  const duration = intervalToDuration({
-    start: d.t_ms,
-    end: new Date(),
-  });
-  const str = formatDuration(duration, {
-    delimiter: ", ",
-    format: [
-      duration?.years
-        ? "years"
-        : duration?.months
-        ? "months"
-        : duration?.days
-        ? "days"
-        : duration.hours
-        ? "hours"
-        : "minutes",
-    ],
-  });
-  return `${str}`;
-}
diff --git a/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx 
b/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx
index 80203f6d..a4988cf2 100644
--- a/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx
+++ b/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx
@@ -158,7 +158,7 @@ export const SomeCoinsInTreeCurrencies = 
createExample(TestedComponent, {
           requiresUserInput: false,
         },
         {
-          available: "COL:2000",
+          available: "TESTKUDOS:2000",
           hasPendingTransactions: false,
           pendingIncoming: "USD:0",
           pendingOutgoing: "USD:0",
diff --git a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx 
b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx
index a23c81cd..008f30cb 100644
--- a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx
+++ b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx
@@ -14,194 +14,77 @@
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import {
-  amountFractionalBase,
-  Amounts,
-  Balance,
-  i18n,
-} from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
-import {
-  ButtonPrimary,
-  ErrorBox,
-  Middle,
-  PopupBox,
-} from "../components/styled/index";
-import { BalancesHook, useBalances } from "../hooks/useBalances";
-import { PageLink, renderAmount } from "../renderHtml";
+import { BalancesResponse, i18n } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
+import { BalanceTable } from "../components/BalanceTable";
+import { ButtonPrimary, ErrorBox } from "../components/styled/index";
+import { HookResponse, useAsyncAsHook } from "../hooks/useAsyncAsHook";
+import { PageLink } from "../renderHtml";
+import * as wxApi from "../wxApi";
 
 export function BalancePage({
   goToWalletManualWithdraw,
 }: {
   goToWalletManualWithdraw: () => void;
 }): VNode {
-  const balance = useBalances();
+  const state = useAsyncAsHook(wxApi.getBalance);
   return (
     <BalanceView
-      balance={balance}
+      balance={state}
       Linker={PageLink}
       goToWalletManualWithdraw={goToWalletManualWithdraw}
     />
   );
 }
 export interface BalanceViewProps {
-  balance: BalancesHook;
+  balance: HookResponse<BalancesResponse>;
   Linker: typeof PageLink;
   goToWalletManualWithdraw: () => void;
 }
 
-function formatPending(entry: Balance): VNode {
-  let incoming: VNode | undefined;
-  let payment: VNode | undefined;
-
-  // const available = Amounts.parseOrThrow(entry.available);
-  const pendingIncoming = Amounts.parseOrThrow(entry.pendingIncoming);
-  const pendingOutgoing = Amounts.parseOrThrow(entry.pendingOutgoing);
-
-  if (!Amounts.isZero(pendingIncoming)) {
-    incoming = (
-      <span>
-        <i18n.Translate>
-          <span style={{ color: "darkgreen" }} title="incoming amount">
-            {"+"}
-            {renderAmount(entry.pendingIncoming)}
-          </span>{" "}
-        </i18n.Translate>
-      </span>
-    );
-  }
-  if (!Amounts.isZero(pendingOutgoing)) {
-    payment = (
-      <span>
-        <i18n.Translate>
-          <span style={{ color: "darkred" }} title="outgoing amount">
-            {"-"}
-            {renderAmount(entry.pendingOutgoing)}
-          </span>{" "}
-        </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>
-  );
-}
-
 export function BalanceView({
   balance,
   Linker,
   goToWalletManualWithdraw,
 }: BalanceViewProps): VNode {
-  function Content(): VNode {
-    if (!balance) {
-      return <span />;
-    }
+  if (!balance) {
+    return <div>Loading...</div>;
+  }
 
-    if (balance.hasError) {
-      return (
-        <section>
-          <ErrorBox>{balance.message}</ErrorBox>
-          <p>
-            Click <Linker pageName="welcome">here</Linker> for help and
-            diagnostics.
-          </p>
-        </section>
-      );
-    }
-    if (balance.response.balances.length === 0) {
-      return (
-        <section data-expanded>
-          <Middle>
-            <p>
-              <i18n.Translate>
-                You have no balance to show. Need some{" "}
-                <Linker pageName="/welcome">help</Linker> getting started?
-              </i18n.Translate>
-            </p>
-          </Middle>
-        </section>
-      );
-    }
+  if (balance.hasError) {
     return (
-      <section data-expanded data-centered>
-        <table style={{ width: "100%" }}>
-          {balance.response.balances.map((entry, idx) => {
-            const av = Amounts.parseOrThrow(entry.available);
-            // Create our number formatter.
-            let formatter;
-            try {
-              formatter = new Intl.NumberFormat("en-US", {
-                style: "currency",
-                currency: av.currency,
-                currencyDisplay: "symbol",
-                // These options are needed to round to whole numbers if 
that's what you want.
-                //minimumFractionDigits: 0, // (this suffices for whole 
numbers, but will print 2500.10 as $2,500.1)
-                //maximumFractionDigits: 0, // (causes 2500.99 to be printed 
as $2,501)
-              });
-            } catch {
-              formatter = new Intl.NumberFormat("en-US", {
-                // style: 'currency',
-                // currency: av.currency,
-                // These options are needed to round to whole numbers if 
that's what you want.
-                //minimumFractionDigits: 0, // (this suffices for whole 
numbers, but will print 2500.10 as $2,500.1)
-                //maximumFractionDigits: 0, // (causes 2500.99 to be printed 
as $2,501)
-              });
-            }
-
-            const v = formatter.format(
-              av.value + av.fraction / amountFractionalBase,
-            );
-            const fontSize =
-              v.length < 8 ? "3em" : v.length < 13 ? "2em" : "1em";
-            return (
-              <tr key={idx}>
-                <td
-                  style={{
-                    height: 50,
-                    fontSize,
-                    width: "60%",
-                    textAlign: "right",
-                    padding: 0,
-                  }}
-                >
-                  {v}
-                </td>
-                <td style={{ maxWidth: "2em", overflowX: "hidden" }}>
-                  {av.currency}
-                </td>
-                <td style={{ fontSize: "small", color: "gray" }}>
-                  {formatPending(entry)}
-                </td>
-              </tr>
-            );
-          })}
-        </table>
-      </section>
+      <Fragment>
+        <ErrorBox>{balance.message}</ErrorBox>
+        <p>
+          Click <Linker pageName="welcome">here</Linker> for help and
+          diagnostics.
+        </p>
+      </Fragment>
+    );
+  }
+  if (balance.response.balances.length === 0) {
+    return (
+      <Fragment>
+        <p>
+          <i18n.Translate>
+            You have no balance to show. Need some{" "}
+            <Linker pageName="/welcome">help</Linker> getting started?
+          </i18n.Translate>
+        </p>
+      </Fragment>
     );
   }
 
   return (
-    <PopupBox>
-      {/* <section> */}
-      <Content />
-      {/* </section> */}
-      <footer>
-        <div />
+    <Fragment>
+      <section>
+        <BalanceTable balances={balance.response.balances} />
+      </section>
+      <footer style={{ justifyContent: "space-around" }}>
         <ButtonPrimary onClick={goToWalletManualWithdraw}>
           Withdraw
         </ButtonPrimary>
       </footer>
-    </PopupBox>
+    </Fragment>
   );
 }
diff --git a/packages/taler-wallet-webextension/src/popup/Debug.tsx 
b/packages/taler-wallet-webextension/src/popup/Debug.tsx
index b0e8543f..8b5d4165 100644
--- a/packages/taler-wallet-webextension/src/popup/Debug.tsx
+++ b/packages/taler-wallet-webextension/src/popup/Debug.tsx
@@ -16,7 +16,7 @@
 
 import { h, VNode } from "preact";
 import { Diagnostics } from "../components/Diagnostics";
-import { useDiagnostics } from "../hooks/useDiagnostics.js";
+import { useDiagnostics } from "../hooks/useDiagnostics";
 import * as wxApi from "../wxApi";
 
 export function DeveloperPage(): VNode {
diff --git a/packages/taler-wallet-webextension/src/popup/History.stories.tsx 
b/packages/taler-wallet-webextension/src/popup/History.stories.tsx
index 95f4a547..43d39da8 100644
--- a/packages/taler-wallet-webextension/src/popup/History.stories.tsx
+++ b/packages/taler-wallet-webextension/src/popup/History.stories.tsx
@@ -55,6 +55,7 @@ const exampleData = {
     type: TransactionType.Withdrawal,
     exchangeBaseUrl: "http://exchange.demo.taler.net";,
     withdrawalDetails: {
+      reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
       confirmed: false,
       exchangePaytoUris: ["payto://x-taler-bank/bank/account"],
       type: WithdrawalType.ManualTransfer,
diff --git a/packages/taler-wallet-webextension/src/popup/History.tsx 
b/packages/taler-wallet-webextension/src/popup/History.tsx
index 2228271d..b23b4781 100644
--- a/packages/taler-wallet-webextension/src/popup/History.tsx
+++ b/packages/taler-wallet-webextension/src/popup/History.tsx
@@ -21,18 +21,18 @@ import {
   Transaction,
   TransactionsResponse,
 } from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
+import { Fragment, h, VNode } from "preact";
 import { useEffect, useState } from "preact/hooks";
 import { PopupBox } from "../components/styled";
 import { TransactionItem } from "../components/TransactionItem";
-import { useBalances } from "../hooks/useBalances";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
 import * as wxApi from "../wxApi";
 
 export function HistoryPage(): VNode {
   const [transactions, setTransactions] = useState<
     TransactionsResponse | undefined
   >(undefined);
-  const balance = useBalances();
+  const balance = useAsyncAsHook(wxApi.getBalance);
   const balanceWithoutError = balance?.hasError
     ? []
     : balance?.response.balances || [];
@@ -57,7 +57,7 @@ export function HistoryPage(): VNode {
   );
 }
 
-function amountToString(c: AmountString) {
+function amountToString(c: AmountString): string {
   const idx = c.indexOf(":");
   return `${c.substring(idx + 1)} ${c.substring(0, idx)}`;
 }
@@ -68,18 +68,18 @@ export function HistoryView({
 }: {
   list: Transaction[];
   balances: Balance[];
-}) {
+}): VNode {
   const multiCurrency = balances.length > 1;
   return (
-    <PopupBox noPadding>
+    <Fragment>
       {balances.length > 0 && (
         <header>
           {multiCurrency ? (
             <div class="title">
               Balance:{" "}
               <ul style={{ margin: 0 }}>
-                {balances.map((b) => (
-                  <li>{b.available}</li>
+                {balances.map((b, i) => (
+                  <li key={i}>{b.available}</li>
                 ))}
               </ul>
             </div>
@@ -113,8 +113,10 @@ export function HistoryView({
             rel="noopener noreferrer"
             style={{ color: "darkgreen", textDecoration: "none" }}
             href={
+              // eslint-disable-next-line no-undef
               chrome.extension
-                ? chrome.extension.getURL(`/static/wallet.html#/history`)
+                ? // eslint-disable-next-line no-undef
+                  chrome.extension.getURL(`/static/wallet.html#/history`)
                 : "#"
             }
           >
@@ -122,6 +124,6 @@ export function HistoryView({
           </a>
         )}
       </footer>
-    </PopupBox>
+    </Fragment>
   );
 }
diff --git 
a/packages/taler-wallet-webextension/src/popup/ProviderAddConfirmProvider.stories.tsx
 
b/packages/taler-wallet-webextension/src/popup/ProviderAddConfirmProvider.stories.tsx
deleted file mode 100644
index 0cff7f75..00000000
--- 
a/packages/taler-wallet-webextension/src/popup/ProviderAddConfirmProvider.stories.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { createExample } from "../test-utils";
-import { ConfirmProviderView as TestedComponent } from "./ProviderAddPage";
-
-export default {
-  title: "popup/backup/confirm",
-  component: TestedComponent,
-  argTypes: {
-    onRetry: { action: "onRetry" },
-    onDelete: { action: "onDelete" },
-    onBack: { action: "onBack" },
-  },
-};
-
-export const DemoService = createExample(TestedComponent, {
-  url: "https://sync.demo.taler.net/";,
-  provider: {
-    annual_fee: "KUDOS:0.1",
-    storage_limit_in_megabytes: 20,
-    supported_protocol_version: "1",
-  },
-});
-
-export const FreeService = createExample(TestedComponent, {
-  url: "https://sync.taler:9667/";,
-  provider: {
-    annual_fee: "ARS:0",
-    storage_limit_in_megabytes: 20,
-    supported_protocol_version: "1",
-  },
-});
diff --git a/packages/taler-wallet-webextension/src/popup/ProviderAddPage.tsx 
b/packages/taler-wallet-webextension/src/popup/ProviderAddPage.tsx
deleted file mode 100644
index 55686ee9..00000000
--- a/packages/taler-wallet-webextension/src/popup/ProviderAddPage.tsx
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-import {
-  Amounts,
-  BackupBackupProviderTerms,
-  canonicalizeBaseUrl,
-  i18n,
-} from "@gnu-taler/taler-util";
-import { VNode, h } from "preact";
-import { useEffect, useState } from "preact/hooks";
-import { Checkbox } from "../components/Checkbox";
-import { ErrorMessage } from "../components/ErrorMessage";
-import {
-  Button,
-  ButtonPrimary,
-  Input,
-  LightText,
-  PopupBox,
-  SmallLightText,
-} from "../components/styled/index";
-import * as wxApi from "../wxApi";
-
-interface Props {
-  currency: string;
-  onBack: () => void;
-}
-
-function getJsonIfOk(r: Response) {
-  if (r.ok) {
-    return r.json();
-  } else {
-    if (r.status >= 400 && r.status < 500) {
-      throw new Error(`URL may not be right: (${r.status}) ${r.statusText}`);
-    } else {
-      throw new Error(
-        `Try another server: (${r.status}) ${
-          r.statusText || "internal server error"
-        }`,
-      );
-    }
-  }
-}
-
-export function ProviderAddPage({ onBack }: Props): VNode {
-  const [verifying, setVerifying] = useState<
-    | { url: string; name: string; provider: BackupBackupProviderTerms }
-    | undefined
-  >(undefined);
-
-  async function getProviderInfo(
-    url: string,
-  ): Promise<BackupBackupProviderTerms> {
-    return fetch(`${url}config`)
-      .catch((e) => {
-        throw new Error(`Network error`);
-      })
-      .then(getJsonIfOk);
-  }
-
-  if (!verifying) {
-    return (
-      <SetUrlView
-        onCancel={onBack}
-        onVerify={(url) => getProviderInfo(url)}
-        onConfirm={(url, name) =>
-          getProviderInfo(url)
-            .then((provider) => {
-              setVerifying({ url, name, provider });
-            })
-            .catch((e) => e.message)
-        }
-      />
-    );
-  }
-  return (
-    <ConfirmProviderView
-      provider={verifying.provider}
-      url={verifying.url}
-      onCancel={() => {
-        setVerifying(undefined);
-      }}
-      onConfirm={() => {
-        wxApi.addBackupProvider(verifying.url, verifying.name).then(onBack);
-      }}
-    />
-  );
-}
-
-export interface SetUrlViewProps {
-  initialValue?: string;
-  onCancel: () => void;
-  onVerify: (s: string) => Promise<BackupBackupProviderTerms | undefined>;
-  onConfirm: (url: string, name: string) => Promise<string | undefined>;
-  withError?: string;
-}
-
-export function SetUrlView({
-  initialValue,
-  onCancel,
-  onVerify,
-  onConfirm,
-  withError,
-}: SetUrlViewProps) {
-  const [value, setValue] = useState<string>(initialValue || "");
-  const [urlError, setUrlError] = useState(false);
-  const [name, setName] = useState<string | undefined>(undefined);
-  const [error, setError] = useState<string | undefined>(withError);
-  useEffect(() => {
-    try {
-      const url = canonicalizeBaseUrl(value);
-      onVerify(url)
-        .then((r) => {
-          setUrlError(false);
-          setName(new URL(url).hostname);
-        })
-        .catch(() => {
-          setUrlError(true);
-          setName(undefined);
-        });
-    } catch {
-      setUrlError(true);
-      setName(undefined);
-    }
-  }, [value]);
-  return (
-    <PopupBox>
-      <section>
-        <h1> Add backup provider</h1>
-        <ErrorMessage
-          title={error && "Could not get provider information"}
-          description={error}
-        />
-        <LightText> Backup providers may charge for their service</LightText>
-        <p>
-          <Input invalid={urlError}>
-            <label>URL</label>
-            <input
-              type="text"
-              placeholder="https://";
-              value={value}
-              onChange={(e) => setValue(e.currentTarget.value)}
-            />
-          </Input>
-          <Input>
-            <label>Name</label>
-            <input
-              type="text"
-              disabled={name === undefined}
-              value={name}
-              onChange={(e) => setName(e.currentTarget.value)}
-            />
-          </Input>
-        </p>
-      </section>
-      <footer>
-        <Button onClick={onCancel}>
-          <i18n.Translate> &lt; Back</i18n.Translate>
-        </Button>
-        <ButtonPrimary
-          disabled={!value && !urlError}
-          onClick={() => {
-            const url = canonicalizeBaseUrl(value);
-            return onConfirm(url, name!).then((r) =>
-              r ? setError(r) : undefined,
-            );
-          }}
-        >
-          <i18n.Translate>Next</i18n.Translate>
-        </ButtonPrimary>
-      </footer>
-    </PopupBox>
-  );
-}
-
-export interface ConfirmProviderViewProps {
-  provider: BackupBackupProviderTerms;
-  url: string;
-  onCancel: () => void;
-  onConfirm: () => void;
-}
-export function ConfirmProviderView({
-  url,
-  provider,
-  onCancel,
-  onConfirm,
-}: ConfirmProviderViewProps) {
-  const [accepted, setAccepted] = useState(false);
-
-  return (
-    <PopupBox>
-      <section>
-        <h1>Review terms of service</h1>
-        <div>
-          Provider URL:{" "}
-          <a href={url} target="_blank">
-            {url}
-          </a>
-        </div>
-        <SmallLightText>
-          Please review and accept this provider's terms of service
-        </SmallLightText>
-        <h2>1. Pricing</h2>
-        <p>
-          {Amounts.isZero(provider.annual_fee)
-            ? "free of charge"
-            : `${provider.annual_fee} per year of service`}
-        </p>
-        <h2>2. Storage</h2>
-        <p>
-          {provider.storage_limit_in_megabytes} megabytes of storage per year 
of
-          service
-        </p>
-        <Checkbox
-          label="Accept terms of service"
-          name="terms"
-          onToggle={() => setAccepted((old) => !old)}
-          enabled={accepted}
-        />
-      </section>
-      <footer>
-        <Button onClick={onCancel}>
-          <i18n.Translate> &lt; Back</i18n.Translate>
-        </Button>
-        <ButtonPrimary disabled={!accepted} onClick={onConfirm}>
-          <i18n.Translate>Add provider</i18n.Translate>
-        </ButtonPrimary>
-      </footer>
-    </PopupBox>
-  );
-}
diff --git 
a/packages/taler-wallet-webextension/src/popup/ProviderAddSetUrl.stories.tsx 
b/packages/taler-wallet-webextension/src/popup/ProviderAddSetUrl.stories.tsx
deleted file mode 100644
index 9a2f9705..00000000
--- a/packages/taler-wallet-webextension/src/popup/ProviderAddSetUrl.stories.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { createExample } from "../test-utils";
-import { SetUrlView as TestedComponent } from "./ProviderAddPage";
-
-export default {
-  title: "popup/backup/add",
-  component: TestedComponent,
-  argTypes: {
-    onRetry: { action: "onRetry" },
-    onDelete: { action: "onDelete" },
-    onBack: { action: "onBack" },
-  },
-};
-
-export const Initial = createExample(TestedComponent, {});
-
-export const WithValue = createExample(TestedComponent, {
-  initialValue: "sync.demo.taler.net",
-});
-
-export const WithConnectionError = createExample(TestedComponent, {
-  withError: "Network error",
-});
-
-export const WithClientError = createExample(TestedComponent, {
-  withError: "URL may not be right: (404) Not Found",
-});
-
-export const WithServerError = createExample(TestedComponent, {
-  withError: "Try another server: (500) Internal Server Error",
-});
diff --git 
a/packages/taler-wallet-webextension/src/popup/ProviderDetail.stories.tsx 
b/packages/taler-wallet-webextension/src/popup/ProviderDetail.stories.tsx
deleted file mode 100644
index fab21398..00000000
--- a/packages/taler-wallet-webextension/src/popup/ProviderDetail.stories.tsx
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021 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 { ProviderPaymentType } from "@gnu-taler/taler-wallet-core";
-import { createExample } from "../test-utils";
-import { ProviderView as TestedComponent } from "./ProviderDetailPage";
-
-export default {
-  title: "popup/backup/details",
-  component: TestedComponent,
-  argTypes: {
-    onRetry: { action: "onRetry" },
-    onDelete: { action: "onDelete" },
-    onBack: { action: "onBack" },
-  },
-};
-
-export const Active = createExample(TestedComponent, {
-  info: {
-    active: true,
-    name: "sync.demo",
-    syncProviderBaseUrl: "http://sync.taler:9967/";,
-    lastSuccessfulBackupTimestamp: {
-      t_ms: 1625063925078,
-    },
-    paymentProposalIds: [
-      "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
-    ],
-    paymentStatus: {
-      type: ProviderPaymentType.Paid,
-      paidUntil: {
-        t_ms: 1656599921000,
-      },
-    },
-    terms: {
-      annualFee: "EUR:1",
-      storageLimitInMegabytes: 16,
-      supportedProtocolVersion: "0.0",
-    },
-  },
-});
-
-export const ActiveErrorSync = createExample(TestedComponent, {
-  info: {
-    active: true,
-    name: "sync.demo",
-    syncProviderBaseUrl: "http://sync.taler:9967/";,
-    lastSuccessfulBackupTimestamp: {
-      t_ms: 1625063925078,
-    },
-    lastAttemptedBackupTimestamp: {
-      t_ms: 1625063925078,
-    },
-    paymentProposalIds: [
-      "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
-    ],
-    paymentStatus: {
-      type: ProviderPaymentType.Paid,
-      paidUntil: {
-        t_ms: 1656599921000,
-      },
-    },
-    lastError: {
-      code: 2002,
-      details: "details",
-      hint: "error hint from the server",
-      message: "message",
-    },
-    terms: {
-      annualFee: "EUR:1",
-      storageLimitInMegabytes: 16,
-      supportedProtocolVersion: "0.0",
-    },
-  },
-});
-
-export const ActiveBackupProblemUnreadable = createExample(TestedComponent, {
-  info: {
-    active: true,
-    name: "sync.demo",
-    syncProviderBaseUrl: "http://sync.taler:9967/";,
-    lastSuccessfulBackupTimestamp: {
-      t_ms: 1625063925078,
-    },
-    paymentProposalIds: [
-      "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
-    ],
-    paymentStatus: {
-      type: ProviderPaymentType.Paid,
-      paidUntil: {
-        t_ms: 1656599921000,
-      },
-    },
-    backupProblem: {
-      type: "backup-unreadable",
-    },
-    terms: {
-      annualFee: "EUR:1",
-      storageLimitInMegabytes: 16,
-      supportedProtocolVersion: "0.0",
-    },
-  },
-});
-
-export const ActiveBackupProblemDevice = createExample(TestedComponent, {
-  info: {
-    active: true,
-    name: "sync.demo",
-    syncProviderBaseUrl: "http://sync.taler:9967/";,
-    lastSuccessfulBackupTimestamp: {
-      t_ms: 1625063925078,
-    },
-    paymentProposalIds: [
-      "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
-    ],
-    paymentStatus: {
-      type: ProviderPaymentType.Paid,
-      paidUntil: {
-        t_ms: 1656599921000,
-      },
-    },
-    backupProblem: {
-      type: "backup-conflicting-device",
-      myDeviceId: "my-device-id",
-      otherDeviceId: "other-device-id",
-      backupTimestamp: {
-        t_ms: 1656599921000,
-      },
-    },
-    terms: {
-      annualFee: "EUR:1",
-      storageLimitInMegabytes: 16,
-      supportedProtocolVersion: "0.0",
-    },
-  },
-});
-
-export const InactiveUnpaid = createExample(TestedComponent, {
-  info: {
-    active: false,
-    name: "sync.demo",
-    syncProviderBaseUrl: "http://sync.demo.taler.net/";,
-    paymentProposalIds: [],
-    paymentStatus: {
-      type: ProviderPaymentType.Unpaid,
-    },
-    terms: {
-      annualFee: "EUR:0.1",
-      storageLimitInMegabytes: 16,
-      supportedProtocolVersion: "0.0",
-    },
-  },
-});
-
-export const InactiveInsufficientBalance = createExample(TestedComponent, {
-  info: {
-    active: false,
-    name: "sync.demo",
-    syncProviderBaseUrl: "http://sync.demo.taler.net/";,
-    paymentProposalIds: [],
-    paymentStatus: {
-      type: ProviderPaymentType.InsufficientBalance,
-    },
-    terms: {
-      annualFee: "EUR:0.1",
-      storageLimitInMegabytes: 16,
-      supportedProtocolVersion: "0.0",
-    },
-  },
-});
-
-export const InactivePending = createExample(TestedComponent, {
-  info: {
-    active: false,
-    name: "sync.demo",
-    syncProviderBaseUrl: "http://sync.demo.taler.net/";,
-    paymentProposalIds: [],
-    paymentStatus: {
-      type: ProviderPaymentType.Pending,
-    },
-    terms: {
-      annualFee: "EUR:0.1",
-      storageLimitInMegabytes: 16,
-      supportedProtocolVersion: "0.0",
-    },
-  },
-});
-
-export const ActiveTermsChanged = createExample(TestedComponent, {
-  info: {
-    active: true,
-    name: "sync.demo",
-    syncProviderBaseUrl: "http://sync.demo.taler.net/";,
-    paymentProposalIds: [],
-    paymentStatus: {
-      type: ProviderPaymentType.TermsChanged,
-      paidUntil: {
-        t_ms: 1656599921000,
-      },
-      newTerms: {
-        annualFee: "EUR:10",
-        storageLimitInMegabytes: 8,
-        supportedProtocolVersion: "0.0",
-      },
-      oldTerms: {
-        annualFee: "EUR:0.1",
-        storageLimitInMegabytes: 16,
-        supportedProtocolVersion: "0.0",
-      },
-    },
-    terms: {
-      annualFee: "EUR:0.1",
-      storageLimitInMegabytes: 16,
-      supportedProtocolVersion: "0.0",
-    },
-  },
-});
diff --git 
a/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx 
b/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx
deleted file mode 100644
index 9617c9a4..00000000
--- a/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- 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 { i18n, Timestamp } from "@gnu-taler/taler-util";
-import {
-  ProviderInfo,
-  ProviderPaymentStatus,
-  ProviderPaymentType,
-} from "@gnu-taler/taler-wallet-core";
-import { format, formatDuration, intervalToDuration } from "date-fns";
-import { Fragment, VNode, h } from "preact";
-import { ErrorMessage } from "../components/ErrorMessage";
-import {
-  Button,
-  ButtonDestructive,
-  ButtonPrimary,
-  PaymentStatus,
-  PopupBox,
-  SmallLightText,
-} from "../components/styled";
-import { useProviderStatus } from "../hooks/useProviderStatus";
-
-interface Props {
-  pid: string;
-  onBack: () => void;
-}
-
-export function ProviderDetailPage({ pid, onBack }: Props): VNode {
-  const status = useProviderStatus(pid);
-  if (!status) {
-    return (
-      <div>
-        <i18n.Translate>Loading...</i18n.Translate>
-      </div>
-    );
-  }
-  if (!status.info) {
-    onBack();
-    return <div />;
-  }
-  return (
-    <ProviderView
-      info={status.info}
-      onSync={status.sync}
-      onDelete={() => status.remove().then(onBack)}
-      onBack={onBack}
-      onExtend={() => {
-        null;
-      }}
-    />
-  );
-}
-
-export interface ViewProps {
-  info: ProviderInfo;
-  onDelete: () => void;
-  onSync: () => void;
-  onBack: () => void;
-  onExtend: () => void;
-}
-
-export function ProviderView({
-  info,
-  onDelete,
-  onSync,
-  onBack,
-  onExtend,
-}: ViewProps): VNode {
-  const lb = info?.lastSuccessfulBackupTimestamp;
-  const isPaid =
-    info.paymentStatus.type === ProviderPaymentType.Paid ||
-    info.paymentStatus.type === ProviderPaymentType.TermsChanged;
-  return (
-    <PopupBox>
-      <Error info={info} />
-      <header>
-        <h3>
-          {info.name}{" "}
-          <SmallLightText>{info.syncProviderBaseUrl}</SmallLightText>
-        </h3>
-        <PaymentStatus color={isPaid ? "rgb(28, 184, 65)" : "rgb(202, 60, 
60)"}>
-          {isPaid ? "Paid" : "Unpaid"}
-        </PaymentStatus>
-      </header>
-      <section>
-        <p>
-          <b>Last backup:</b>{" "}
-          {lb == null || lb.t_ms == "never"
-            ? "never"
-            : format(lb.t_ms, "dd MMM yyyy")}{" "}
-        </p>
-        <ButtonPrimary onClick={onSync}>
-          <i18n.Translate>Back up</i18n.Translate>
-        </ButtonPrimary>
-        {info.terms && (
-          <Fragment>
-            <p>
-              <b>Provider fee:</b> {info.terms && info.terms.annualFee} per 
year
-            </p>
-          </Fragment>
-        )}
-        <p>{descriptionByStatus(info.paymentStatus)}</p>
-        <ButtonPrimary disabled onClick={onExtend}>
-          <i18n.Translate>Extend</i18n.Translate>
-        </ButtonPrimary>
-
-        {info.paymentStatus.type === ProviderPaymentType.TermsChanged && (
-          <div>
-            <p>
-              <i18n.Translate>
-                terms has changed, extending the service will imply accepting
-                the new terms of service
-              </i18n.Translate>
-            </p>
-            <table>
-              <thead>
-                <tr>
-                  <td></td>
-                  <td>
-                    <i18n.Translate>old</i18n.Translate>
-                  </td>
-                  <td> -&gt;</td>
-                  <td>
-                    <i18n.Translate>new</i18n.Translate>
-                  </td>
-                </tr>
-              </thead>
-              <tbody>
-                <tr>
-                  <td>
-                    <i18n.Translate>fee</i18n.Translate>
-                  </td>
-                  <td>{info.paymentStatus.oldTerms.annualFee}</td>
-                  <td>-&gt;</td>
-                  <td>{info.paymentStatus.newTerms.annualFee}</td>
-                </tr>
-                <tr>
-                  <td>
-                    <i18n.Translate>storage</i18n.Translate>
-                  </td>
-                  
<td>{info.paymentStatus.oldTerms.storageLimitInMegabytes}</td>
-                  <td>-&gt;</td>
-                  
<td>{info.paymentStatus.newTerms.storageLimitInMegabytes}</td>
-                </tr>
-              </tbody>
-            </table>
-          </div>
-        )}
-      </section>
-      <footer>
-        <Button onClick={onBack}>
-          <i18n.Translate> &lt; back</i18n.Translate>
-        </Button>
-        <div>
-          <ButtonDestructive onClick={onDelete}>
-            <i18n.Translate>remove provider</i18n.Translate>
-          </ButtonDestructive>
-        </div>
-      </footer>
-    </PopupBox>
-  );
-}
-
-function daysSince(d?: Timestamp) {
-  if (!d || d.t_ms === "never") return "never synced";
-  const duration = intervalToDuration({
-    start: d.t_ms,
-    end: new Date(),
-  });
-  const str = formatDuration(duration, {
-    delimiter: ", ",
-    format: [
-      duration?.years
-        ? i18n.str`years`
-        : duration?.months
-        ? i18n.str`months`
-        : duration?.days
-        ? i18n.str`days`
-        : duration?.hours
-        ? i18n.str`hours`
-        : duration?.minutes
-        ? i18n.str`minutes`
-        : i18n.str`seconds`,
-    ],
-  });
-  return `synced ${str} ago`;
-}
-
-function Error({ info }: { info: ProviderInfo }) {
-  if (info.lastError) {
-    return <ErrorMessage title={info.lastError.hint} />;
-  }
-  if (info.backupProblem) {
-    switch (info.backupProblem.type) {
-      case "backup-conflicting-device":
-        return (
-          <ErrorMessage
-            title={
-              <Fragment>
-                <i18n.Translate>
-                  There is conflict with another backup from{" "}
-                  <b>{info.backupProblem.otherDeviceId}</b>
-                </i18n.Translate>
-              </Fragment>
-            }
-          />
-        );
-      case "backup-unreadable":
-        return <ErrorMessage title="Backup is not readable" />;
-      default:
-        return (
-          <ErrorMessage
-            title={
-              <Fragment>
-                <i18n.Translate>
-                  Unknown backup problem: {JSON.stringify(info.backupProblem)}
-                </i18n.Translate>
-              </Fragment>
-            }
-          />
-        );
-    }
-  }
-  return null;
-}
-
-function colorByStatus(status: ProviderPaymentType) {
-  switch (status) {
-    case ProviderPaymentType.InsufficientBalance:
-      return "rgb(223, 117, 20)";
-    case ProviderPaymentType.Unpaid:
-      return "rgb(202, 60, 60)";
-    case ProviderPaymentType.Paid:
-      return "rgb(28, 184, 65)";
-    case ProviderPaymentType.Pending:
-      return "gray";
-    case ProviderPaymentType.InsufficientBalance:
-      return "rgb(202, 60, 60)";
-    case ProviderPaymentType.TermsChanged:
-      return "rgb(202, 60, 60)";
-  }
-}
-
-function descriptionByStatus(status: ProviderPaymentStatus) {
-  switch (status.type) {
-    // return i18n.str`no enough balance to make the payment`
-    // return i18n.str`not paid yet`
-    case ProviderPaymentType.Paid:
-    case ProviderPaymentType.TermsChanged:
-      if (status.paidUntil.t_ms === "never") {
-        return i18n.str`service paid`;
-      } else {
-        return (
-          <Fragment>
-            <b>Backup valid until:</b>{" "}
-            {format(status.paidUntil.t_ms, "dd MMM yyyy")}
-          </Fragment>
-        );
-      }
-    case ProviderPaymentType.Unpaid:
-    case ProviderPaymentType.InsufficientBalance:
-    case ProviderPaymentType.Pending:
-      return "";
-  }
-}
diff --git a/packages/taler-wallet-webextension/src/popup/Settings.tsx 
b/packages/taler-wallet-webextension/src/popup/Settings.tsx
index 3b83f076..0a3f777d 100644
--- a/packages/taler-wallet-webextension/src/popup/Settings.tsx
+++ b/packages/taler-wallet-webextension/src/popup/Settings.tsx
@@ -14,26 +14,34 @@
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 
-import { i18n } from "@gnu-taler/taler-util";
-import { VNode, h } from "preact";
+import { ExchangeListItem, i18n } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
 import { Checkbox } from "../components/Checkbox";
-import { EditableText } from "../components/EditableText";
-import { SelectList } from "../components/SelectList";
-import { PopupBox } from "../components/styled";
+import { ButtonPrimary } from "../components/styled";
 import { useDevContext } from "../context/devContext";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
 import { useBackupDeviceName } from "../hooks/useBackupDeviceName";
 import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
 import { useLang } from "../hooks/useLang";
+// import { strings as messages } from "../i18n/strings";
+import * as wxApi from "../wxApi";
 
 export function SettingsPage(): VNode {
   const [permissionsEnabled, togglePermissions] = useExtendedPermissions();
   const { devMode, toggleDevMode } = useDevContext();
   const { name, update } = useBackupDeviceName();
   const [lang, changeLang] = useLang();
+  const exchangesHook = useAsyncAsHook(wxApi.listExchanges);
+
   return (
     <SettingsView
       lang={lang}
       changeLang={changeLang}
+      knownExchanges={
+        !exchangesHook || exchangesHook.hasError
+          ? []
+          : exchangesHook.response.exchanges
+      }
       deviceName={name}
       setDeviceName={update}
       permissionsEnabled={permissionsEnabled}
@@ -53,36 +61,59 @@ export interface ViewProps {
   togglePermissions: () => void;
   developerMode: boolean;
   toggleDeveloperMode: () => void;
+  knownExchanges: Array<ExchangeListItem>;
 }
 
-import { strings as messages } from "../i18n/strings";
-
-type LangsNames = {
-  [P in keyof typeof messages]: string;
-};
+// type LangsNames = {
+//   [P in keyof typeof messages]: string;
+// };
 
-const names: LangsNames = {
-  es: "Español [es]",
-  en: "English [en]",
-  fr: "Français [fr]",
-  de: "Deutsch [de]",
-  sv: "Svenska [sv]",
-  it: "Italiano [it]",
-};
+// const names: LangsNames = {
+//   es: "Español [es]",
+//   en: "English [en]",
+//   fr: "Français [fr]",
+//   de: "Deutsch [de]",
+//   sv: "Svenska [sv]",
+//   it: "Italiano [it]",
+// };
 
 export function SettingsView({
-  lang,
-  changeLang,
-  deviceName,
-  setDeviceName,
+  knownExchanges,
+  // lang,
+  // changeLang,
+  // deviceName,
+  // setDeviceName,
   permissionsEnabled,
   togglePermissions,
   developerMode,
   toggleDeveloperMode,
 }: ViewProps): VNode {
   return (
-    <PopupBox>
+    <Fragment>
       <section>
+        <h2>
+          <i18n.Translate>Known exchanges</i18n.Translate>
+        </h2>
+        {!knownExchanges || !knownExchanges.length ? (
+          <div>No exchange yet!</div>
+        ) : (
+          <Fragment>
+            <table>
+              {knownExchanges.map((e, idx) => (
+                <tr key={idx}>
+                  <td>{e.currency}</td>
+                  <td>
+                    <a href={e.exchangeBaseUrl}>{e.exchangeBaseUrl}</a>
+                  </td>
+                </tr>
+              ))}
+            </table>
+          </Fragment>
+        )}
+        <div style={{ display: "flex", justifyContent: "space-between" }}>
+          <div />
+          <ButtonPrimary>Manage exchange</ButtonPrimary>
+        </div>
         {/* <h2><i18n.Translate>Wallet</i18n.Translate></h2> */}
         {/* <SelectList
           value={lang}
@@ -124,14 +155,16 @@ export function SettingsView({
           rel="noopener noreferrer"
           style={{ color: "darkgreen", textDecoration: "none" }}
           href={
+            // eslint-disable-next-line no-undef
             chrome.extension
-              ? chrome.extension.getURL(`/static/wallet.html#/settings`)
+              ? // eslint-disable-next-line no-undef
+                chrome.extension.getURL(`/static/wallet.html#/settings`)
               : "#"
           }
         >
           VIEW MORE SETTINGS
         </a>
       </footer>
-    </PopupBox>
+    </Fragment>
   );
 }
diff --git a/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx 
b/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx
index cbdcbeb1..b2220e37 100644
--- a/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx
+++ b/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx
@@ -20,12 +20,8 @@
  */
 
 import { classifyTalerUri, TalerUriType } from "@gnu-taler/taler-util";
-import {
-  ButtonPrimary,
-  ButtonSuccess,
-  PopupBox,
-} from "../components/styled/index";
-import { h } from "preact";
+import { Fragment, h } from "preact";
+import { ButtonPrimary, ButtonSuccess } from "../components/styled/index";
 
 export interface Props {
   url: string;
@@ -35,7 +31,7 @@ export interface Props {
 export function TalerActionFound({ url, onDismiss }: Props) {
   const uriType = classifyTalerUri(url);
   return (
-    <PopupBox>
+    <Fragment>
       <section>
         <h1>Taler Action </h1>
         {uriType === TalerUriType.TalerPay && (
@@ -109,7 +105,7 @@ export function TalerActionFound({ url, onDismiss }: Props) 
{
         <div />
         <ButtonPrimary onClick={() => onDismiss()}> Dismiss </ButtonPrimary>
       </footer>
-    </PopupBox>
+    </Fragment>
   );
 }
 
diff --git a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx 
b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
index a5723ccb..d0c79f6d 100644
--- a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
+++ b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
@@ -25,16 +25,17 @@ import { createHashHistory } from "history";
 import { render, h } from "preact";
 import Router, { route, Route } from "preact-router";
 import { useEffect } from "preact/hooks";
+import { PopupBox } from "./components/styled";
 import { DevContextProvider } from "./context/devContext";
 import { useTalerActionURL } from "./hooks/useTalerActionURL";
 import { strings } from "./i18n/strings";
 import { Pages, WalletNavBar } from "./NavigationBar";
-import { BackupPage } from "./popup/BackupPage";
+import { BackupPage } from "./wallet/BackupPage";
 import { BalancePage } from "./popup/BalancePage";
 import { DeveloperPage } from "./popup/Debug";
 import { HistoryPage } from "./popup/History";
-import { ProviderAddPage } from "./popup/ProviderAddPage";
-import { ProviderDetailPage } from "./popup/ProviderDetailPage";
+import { ProviderAddPage } from "./wallet/ProviderAddPage";
+import { ProviderDetailPage } from "./wallet/ProviderDetailPage";
 import { SettingsPage } from "./popup/Settings";
 import { TalerActionFound } from "./popup/TalerActionFound";
 
@@ -72,7 +73,7 @@ function Application() {
     <div>
       <DevContextProvider>
         <WalletNavBar />
-        <div style={{ width: 400, height: 290 }}>
+        <PopupBox>
           <Router history={createHashHistory()}>
             <Route path={Pages.dev} component={DeveloperPage} />
 
@@ -128,15 +129,17 @@ function Application() {
             />
             <Route default component={Redirect} to={Pages.balance} />
           </Router>
-        </div>
+        </PopupBox>
       </DevContextProvider>
     </div>
   );
 }
 
 function goToWalletPage(page: Pages | string): null {
+  // eslint-disable-next-line no-undef
   chrome.tabs.create({
     active: true,
+    // eslint-disable-next-line no-undef
     url: chrome.extension.getURL(`/static/wallet.html#${page}`),
   });
   return null;
diff --git a/packages/taler-wallet-webextension/src/svg/index.tsx 
b/packages/taler-wallet-webextension/src/svg/index.tsx
new file mode 100644
index 00000000..34ff7767
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/svg/index.tsx
@@ -0,0 +1,40 @@
+import { h, VNode } from "preact";
+
+export const CopyIcon = (): VNode => (
+  <svg
+    aria-hidden="true"
+    height="10"
+    viewBox="0 0 16 16"
+    version="1.1"
+    width="10"
+    data-view-component="true"
+    class="octicon octicon-copy"
+    style="display: inline-block;"
+  >
+    <path
+      fill-rule="evenodd"
+      d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 
00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 
0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z"
+    />
+    <path
+      fill-rule="evenodd"
+      d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 
1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25v-7.5zm1.75-.25a.25.25 0 
00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 
00-.25-.25h-7.5z"
+    />
+  </svg>
+);
+
+export const CopiedIcon = (): VNode => (
+  <svg
+    aria-hidden="true"
+    height="8"
+    viewBox="0 0 16 16"
+    version="1.1"
+    width="8"
+    data-view-component="true"
+    class="octicon octicon-check color-fg-success"
+  >
+    <path
+      fill-rule="evenodd"
+      d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 
9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"
+    />
+  </svg>
+);
diff --git a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx 
b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx
index f0ae38e0..0b0af25a 100644
--- a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx
@@ -35,7 +35,6 @@ import {
   RowBorderGray,
   SmallLightText,
   SmallText,
-  WalletBox,
 } from "../components/styled";
 import { useBackupStatus } from "../hooks/useBackupStatus";
 import { Pages } from "../NavigationBar";
@@ -70,7 +69,7 @@ export function BackupView({
   onSyncAll,
 }: ViewProps): VNode {
   return (
-    <WalletBox>
+    <Fragment>
       <section>
         {providers.map((provider, idx) => (
           <BackupLayout
@@ -106,7 +105,7 @@ export function BackupView({
           </div>
         </footer>
       )}
-    </WalletBox>
+    </Fragment>
   );
 }
 
@@ -155,7 +154,7 @@ function BackupLayout(props: TransactionLayoutProps): VNode 
{
   );
 }
 
-function ExpirationText({ until }: { until: Timestamp }) {
+function ExpirationText({ until }: { until: Timestamp }): VNode {
   return (
     <Fragment>
       <CenteredText> Expires in </CenteredText>
@@ -167,14 +166,14 @@ function ExpirationText({ until }: { until: Timestamp }) {
   );
 }
 
-function colorByTimeToExpire(d: Timestamp) {
+function colorByTimeToExpire(d: Timestamp): string {
   if (d.t_ms === "never") return "rgb(28, 184, 65)";
   const months = differenceInMonths(d.t_ms, new Date());
   return months > 1 ? "rgb(28, 184, 65)" : "rgb(223, 117, 20)";
 }
 
-function daysUntil(d: Timestamp) {
-  if (d.t_ms === "never") return undefined;
+function daysUntil(d: Timestamp): string {
+  if (d.t_ms === "never") return "";
   const duration = intervalToDuration({
     start: d.t_ms,
     end: new Date(),
diff --git a/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx 
b/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx
index 9a284767..04d79a5e 100644
--- a/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx
@@ -14,27 +14,23 @@
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import {
-  amountFractionalBase,
-  Amounts,
-  Balance,
-  BalancesResponse,
-  i18n,
-} from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
-import { ButtonPrimary, Centered, WalletBox } from 
"../components/styled/index";
-import { BalancesHook, useBalances } from "../hooks/useBalances";
-import { PageLink, renderAmount } from "../renderHtml";
+import { BalancesResponse, i18n } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
+import { BalanceTable } from "../components/BalanceTable";
+import { ButtonPrimary, ErrorBox } from "../components/styled/index";
+import { HookResponse, useAsyncAsHook } from "../hooks/useAsyncAsHook";
+import { PageLink } from "../renderHtml";
+import * as wxApi from "../wxApi";
 
 export function BalancePage({
   goToWalletManualWithdraw,
 }: {
   goToWalletManualWithdraw: () => void;
 }): VNode {
-  const balance = useBalances();
+  const state = useAsyncAsHook(wxApi.getBalance);
   return (
     <BalanceView
-      balance={balance}
+      balance={state}
       Linker={PageLink}
       goToWalletManualWithdraw={goToWalletManualWithdraw}
     />
@@ -42,7 +38,7 @@ export function BalancePage({
 }
 
 export interface BalanceViewProps {
-  balance: BalancesHook;
+  balance: HookResponse<BalancesResponse>;
   Linker: typeof PageLink;
   goToWalletManualWithdraw: () => void;
 }
@@ -53,18 +49,18 @@ export function BalanceView({
   goToWalletManualWithdraw,
 }: BalanceViewProps): VNode {
   if (!balance) {
-    return <span />;
+    return <div>Loading...</div>;
   }
 
   if (balance.hasError) {
     return (
-      <div>
-        <p>{i18n.str`Error: could not retrieve balance information.`}</p>
+      <Fragment>
+        <ErrorBox>{balance.message}</ErrorBox>
         <p>
           Click <Linker pageName="welcome">here</Linker> for help and
           diagnostics.
         </p>
-      </div>
+      </Fragment>
     );
   }
   if (balance.response.balances.length === 0) {
@@ -77,81 +73,17 @@ export function BalanceView({
       </p>
     );
   }
-  return (
-    <ShowBalances
-      wallet={balance.response}
-      onWithdraw={goToWalletManualWithdraw}
-    />
-  );
-}
-
-function formatPending(entry: Balance): VNode {
-  let incoming: VNode | undefined;
-  let payment: VNode | undefined;
-
-  // const available = Amounts.parseOrThrow(entry.available);
-  const pendingIncoming = Amounts.parseOrThrow(entry.pendingIncoming);
-  // const pendingOutgoing = Amounts.parseOrThrow(entry.pendingOutgoing);
-
-  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>
-  );
-}
 
-function ShowBalances({
-  wallet,
-  onWithdraw,
-}: {
-  wallet: BalancesResponse;
-  onWithdraw: () => void;
-}): VNode {
   return (
-    <WalletBox>
+    <Fragment>
       <section>
-        <Centered>
-          {wallet.balances.map((entry) => {
-            const av = Amounts.parseOrThrow(entry.available);
-            const v = av.value + av.fraction / amountFractionalBase;
-            return (
-              <p key={av.currency}>
-                <span>
-                  <span style={{ fontSize: "5em", display: "block" 
}}>{v}</span>{" "}
-                  <span>{av.currency}</span>
-                </span>
-                {formatPending(entry)}
-              </p>
-            );
-          })}
-        </Centered>
+        <BalanceTable balances={balance.response.balances} />
       </section>
-      <footer>
-        <div />
-        <ButtonPrimary onClick={onWithdraw}>Withdraw</ButtonPrimary>
+      <footer style={{ justifyContent: "space-around" }}>
+        <ButtonPrimary onClick={goToWalletManualWithdraw}>
+          Withdraw
+        </ButtonPrimary>
       </footer>
-    </WalletBox>
+    </Fragment>
   );
 }
diff --git 
a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx
 
b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx
index 300e9cd5..e4955e37 100644
--- 
a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx
+++ 
b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx
@@ -34,6 +34,10 @@ const exchangeList = {
   "http://exchange.tal": "EUR",
 };
 
+export const WithoutAnyExchangeKnown = createExample(TestedComponent, {
+  exchangeList: {},
+});
+
 export const InitialState = createExample(TestedComponent, {
   exchangeList,
 });
diff --git 
a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx 
b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx
index 140ac2d4..1bceabd2 100644
--- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx
@@ -19,17 +19,19 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { AmountJson, Amounts } from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
+import { AmountJson, Amounts, i18n } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { ErrorMessage } from "../components/ErrorMessage";
 import { SelectList } from "../components/SelectList";
 import {
+  BoldLight,
   ButtonPrimary,
+  ButtonSuccess,
+  Centered,
   Input,
   InputWithLabel,
   LightText,
-  WalletBox,
 } from "../components/styled";
 
 export interface Props {
@@ -82,11 +84,23 @@ export function CreateManualWithdraw({
   }
 
   if (!initialExchange) {
-    return <div>There is no known exchange where to withdraw, add one</div>;
+    return (
+      <Centered style={{ marginTop: 100 }}>
+        <BoldLight>No exchange configured</BoldLight>
+        <ButtonSuccess
+          //FIXME: add exchange feature
+          onClick={() => {
+            null;
+          }}
+        >
+          <i18n.Translate>Add exchange</i18n.Translate>
+        </ButtonSuccess>
+      </Centered>
+    );
   }
 
   return (
-    <WalletBox>
+    <Fragment>
       <section>
         <ErrorMessage
           title={error && "Can't create the reserve"}
@@ -145,6 +159,6 @@ export function CreateManualWithdraw({
           Start withdrawal
         </ButtonPrimary>
       </footer>
-    </WalletBox>
+    </Fragment>
   );
 }
diff --git a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx
index 9ae3ac3b..0f471ac3 100644
--- a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx
@@ -57,6 +57,7 @@ const exampleData = {
     type: TransactionType.Withdrawal,
     exchangeBaseUrl: "http://exchange.demo.taler.net";,
     withdrawalDetails: {
+      reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
       confirmed: false,
       exchangePaytoUris: ["payto://x-taler-bank/bank/account"],
       type: WithdrawalType.ManualTransfer,
diff --git a/packages/taler-wallet-webextension/src/wallet/History.tsx 
b/packages/taler-wallet-webextension/src/wallet/History.tsx
index 6b1a2185..bc8ef734 100644
--- a/packages/taler-wallet-webextension/src/wallet/History.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/History.tsx
@@ -17,42 +17,37 @@
 import {
   AmountString,
   Balance,
+  NotificationType,
   Transaction,
-  TransactionsResponse,
 } from "@gnu-taler/taler-util";
 import { Fragment, h, VNode } from "preact";
-import { useEffect, useState } from "preact/hooks";
-import { DateSeparator, WalletBox } from "../components/styled";
+import { DateSeparator } from "../components/styled";
 import { Time } from "../components/Time";
 import { TransactionItem } from "../components/TransactionItem";
-import { useBalances } from "../hooks/useBalances";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
 import * as wxApi from "../wxApi";
 
 export function HistoryPage(): VNode {
-  const [transactions, setTransactions] = useState<
-    TransactionsResponse | undefined
-  >(undefined);
-  const balance = useBalances();
+  const balance = useAsyncAsHook(wxApi.getBalance);
   const balanceWithoutError = balance?.hasError
     ? []
     : balance?.response.balances || [];
 
-  useEffect(() => {
-    const fetchData = async (): Promise<void> => {
-      const res = await wxApi.getTransactions();
-      setTransactions(res);
-    };
-    fetchData();
-  }, []);
+  const transactionQuery = useAsyncAsHook(wxApi.getTransactions, [
+    NotificationType.WithdrawGroupFinished,
+  ]);
 
-  if (!transactions) {
+  if (!transactionQuery) {
     return <div>Loading ...</div>;
   }
+  if (transactionQuery.hasError) {
+    return <div>There was an error loading the transactions.</div>;
+  }
 
   return (
     <HistoryView
       balances={balanceWithoutError}
-      list={[...transactions.transactions].reverse()}
+      list={[...transactionQuery.response.transactions].reverse()}
     />
   );
 }
@@ -87,7 +82,7 @@ export function HistoryView({
   const multiCurrency = balances.length > 1;
 
   return (
-    <WalletBox noPadding>
+    <Fragment>
       {balances.length > 0 && (
         <header>
           {balances.length === 1 && (
@@ -128,6 +123,6 @@ export function HistoryView({
           );
         })}
       </section>
-    </WalletBox>
+    </Fragment>
   );
 }
diff --git 
a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx 
b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx
index 1af4e8d8..88d5f172 100644
--- a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx
@@ -23,9 +23,9 @@ import {
   AmountJson,
   Amounts,
 } from "@gnu-taler/taler-util";
-import { ReserveCreated } from "./ReserveCreated.js";
+import { ReserveCreated } from "./ReserveCreated";
 import { route } from "preact-router";
-import { Pages } from "../NavigationBar.js";
+import { Pages } from "../NavigationBar";
 import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
 
 export function ManualWithdrawPage(): VNode {
@@ -39,7 +39,7 @@ export function ManualWithdrawPage(): VNode {
   >(undefined);
   const [error, setError] = useState<string | undefined>(undefined);
 
-  const knownExchangesHook = useAsyncAsHook(() => wxApi.listExchanges());
+  const state = useAsyncAsHook(() => wxApi.listExchanges());
 
   async function doCreate(
     exchangeBaseUrl: string,
@@ -75,10 +75,13 @@ export function ManualWithdrawPage(): VNode {
     );
   }
 
-  if (!knownExchangesHook || knownExchangesHook.hasError) {
-    return <div>No Known exchanges</div>;
+  if (!state) {
+    return <div>loading...</div>;
   }
-  const exchangeList = knownExchangesHook.response.exchanges.reduce(
+  if (state.hasError) {
+    return <div>There was an error getting the known exchanges</div>;
+  }
+  const exchangeList = state.response.exchanges.reduce(
     (p, c) => ({
       ...p,
       [c.exchangeBaseUrl]: c.currency,
diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx 
b/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx
index 1c7fdc82..41852e38 100644
--- a/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx
@@ -20,7 +20,7 @@ import {
   canonicalizeBaseUrl,
   i18n,
 } from "@gnu-taler/taler-util";
-import { VNode, h } from "preact";
+import { Fragment, h, VNode } from "preact";
 import { useEffect, useState } from "preact/hooks";
 import { Checkbox } from "../components/Checkbox";
 import { ErrorMessage } from "../components/ErrorMessage";
@@ -29,7 +29,6 @@ import {
   ButtonPrimary,
   Input,
   LightText,
-  WalletBox,
   SmallLightText,
 } from "../components/styled/index";
 import * as wxApi from "../wxApi";
@@ -64,7 +63,7 @@ export function ProviderAddPage({ onBack }: Props): VNode {
   async function getProviderInfo(
     url: string,
   ): Promise<BackupBackupProviderTerms> {
-    return fetch(`${url}config`)
+    return fetch(new URL("config", url).href)
       .catch((e) => {
         throw new Error(`Network error`);
       })
@@ -137,7 +136,7 @@ export function SetUrlView({
     }
   }, [value]);
   return (
-    <WalletBox>
+    <Fragment>
       <section>
         <h1> Add backup provider</h1>
         <ErrorMessage
@@ -182,7 +181,7 @@ export function SetUrlView({
           <i18n.Translate>Next</i18n.Translate>
         </ButtonPrimary>
       </footer>
-    </WalletBox>
+    </Fragment>
   );
 }
 
@@ -201,7 +200,7 @@ export function ConfirmProviderView({
   const [accepted, setAccepted] = useState(false);
 
   return (
-    <WalletBox>
+    <Fragment>
       <section>
         <h1>Review terms of service</h1>
         <div>
@@ -239,6 +238,6 @@ export function ConfirmProviderView({
           <i18n.Translate>Add provider</i18n.Translate>
         </ButtonPrimary>
       </footer>
-    </WalletBox>
+    </Fragment>
   );
 }
diff --git 
a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx 
b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx
index 1c14c6e0..d14429ee 100644
--- a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx
@@ -28,34 +28,62 @@ import {
   ButtonPrimary,
   PaymentStatus,
   SmallLightText,
-  WalletBox,
 } from "../components/styled";
 import { Time } from "../components/Time";
-import { useProviderStatus } from "../hooks/useProviderStatus";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
+import * as wxApi from "../wxApi";
 
 interface Props {
   pid: string;
   onBack: () => void;
 }
 
-export function ProviderDetailPage({ pid, onBack }: Props): VNode {
-  const status = useProviderStatus(pid);
-  if (!status) {
+export function ProviderDetailPage({ pid: providerURL, onBack }: Props): VNode 
{
+  async function getProviderInfo(): Promise<ProviderInfo | null> {
+    //create a first list of backup info by currency
+    const status = await wxApi.getBackupInfo();
+
+    const providers = status.providers.filter(
+      (p) => p.syncProviderBaseUrl === providerURL,
+    );
+    return providers.length ? providers[0] : null;
+  }
+
+  const state = useAsyncAsHook(getProviderInfo);
+
+  if (!state) {
     return (
       <div>
         <i18n.Translate>Loading...</i18n.Translate>
       </div>
     );
   }
-  if (!status.info) {
+  if (state.hasError) {
+    return (
+      <div>
+        <i18n.Translate>
+          There was an error loading the provider detail for "{providerURL}"
+        </i18n.Translate>
+      </div>
+    );
+  }
+
+  if (state.response === null) {
     onBack();
-    return <div />;
+    return (
+      <div>
+        <i18n.Translate>
+          There is not known provider with url "{providerURL}". Redirecting
+          back...
+        </i18n.Translate>
+      </div>
+    );
   }
   return (
     <ProviderView
-      info={status.info}
-      onSync={status.sync}
-      onDelete={() => status.remove().then(onBack)}
+      info={state.response}
+      onSync={async () => wxApi.syncOneProvider(providerURL)}
+      onDelete={async () => wxApi.syncOneProvider(providerURL).then(onBack)}
       onBack={onBack}
       onExtend={() => {
         null;
@@ -84,7 +112,7 @@ export function ProviderView({
     info.paymentStatus.type === ProviderPaymentType.Paid ||
     info.paymentStatus.type === ProviderPaymentType.TermsChanged;
   return (
-    <WalletBox>
+    <Fragment>
       <Error info={info} />
       <header>
         <h3>
@@ -167,35 +195,10 @@ export function ProviderView({
           </ButtonDestructive>
         </div>
       </footer>
-    </WalletBox>
+    </Fragment>
   );
 }
 
-// function daysSince(d?: Timestamp): string {
-//   if (!d || d.t_ms === "never") return "never synced";
-//   const duration = intervalToDuration({
-//     start: d.t_ms,
-//     end: new Date(),
-//   });
-//   const str = formatDuration(duration, {
-//     delimiter: ", ",
-//     format: [
-//       duration?.years
-//         ? i18n.str`years`
-//         : duration?.months
-//         ? i18n.str`months`
-//         : duration?.days
-//         ? i18n.str`days`
-//         : duration?.hours
-//         ? i18n.str`hours`
-//         : duration?.minutes
-//         ? i18n.str`minutes`
-//         : i18n.str`seconds`,
-//     ],
-//   });
-//   return `synced ${str} ago`;
-// }
-
 function Error({ info }: { info: ProviderInfo }): VNode {
   if (info.lastError) {
     return <ErrorMessage title={info.lastError.hint} />;
@@ -234,23 +237,6 @@ function Error({ info }: { info: ProviderInfo }): VNode {
   return <Fragment />;
 }
 
-// function colorByStatus(status: ProviderPaymentType): string {
-//   switch (status) {
-//     case ProviderPaymentType.InsufficientBalance:
-//       return "rgb(223, 117, 20)";
-//     case ProviderPaymentType.Unpaid:
-//       return "rgb(202, 60, 60)";
-//     case ProviderPaymentType.Paid:
-//       return "rgb(28, 184, 65)";
-//     case ProviderPaymentType.Pending:
-//       return "gray";
-//     // case ProviderPaymentType.InsufficientBalance:
-//     //   return "rgb(202, 60, 60)";
-//     case ProviderPaymentType.TermsChanged:
-//       return "rgb(202, 60, 60)";
-//   }
-// }
-
 function descriptionByStatus(status: ProviderPaymentStatus): VNode {
   switch (status.type) {
     // return i18n.str`no enough balance to make the payment`
diff --git a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx 
b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx
index a72026ab..075126dc 100644
--- a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx
@@ -1,18 +1,8 @@
-import {
-  AmountJson,
-  Amounts,
-  parsePaytoUri,
-  PaytoUri,
-} from "@gnu-taler/taler-util";
+import { AmountJson, Amounts, parsePaytoUri } from "@gnu-taler/taler-util";
 import { Fragment, h, VNode } from "preact";
-import { useEffect, useState } from "preact/hooks";
+import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType";
 import { QR } from "../components/QR";
-import {
-  ButtonDestructive,
-  ButtonPrimary,
-  WalletBox,
-  WarningBox,
-} from "../components/styled";
+import { ButtonDestructive, WarningBox } from "../components/styled";
 export interface Props {
   reservePub: string;
   payto: string;
@@ -21,92 +11,6 @@ export interface Props {
   onBack: () => void;
 }
 
-interface BankDetailsProps {
-  payto: PaytoUri;
-  exchangeBaseUrl: string;
-  subject: string;
-  amount: string;
-}
-
-function Row({
-  name,
-  value,
-  literal,
-}: {
-  name: string;
-  value: string;
-  literal?: boolean;
-}): VNode {
-  const [copied, setCopied] = useState(false);
-  function copyText(): void {
-    navigator.clipboard.writeText(value);
-    setCopied(true);
-  }
-  useEffect(() => {
-    setTimeout(() => {
-      setCopied(false);
-    }, 1000);
-  }, [copied]);
-  return (
-    <tr>
-      <td>
-        {!copied ? (
-          <ButtonPrimary small onClick={copyText}>
-            &nbsp; Copy &nbsp;
-          </ButtonPrimary>
-        ) : (
-          <ButtonPrimary small disabled>
-            Copied
-          </ButtonPrimary>
-        )}
-      </td>
-      <td>
-        <b>{name}</b>
-      </td>
-      {literal ? (
-        <td>
-          <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
-            {value}
-          </pre>
-        </td>
-      ) : (
-        <td>{value}</td>
-      )}
-    </tr>
-  );
-}
-
-function BankDetailsByPaytoType({
-  payto,
-  subject,
-  exchangeBaseUrl,
-  amount,
-}: BankDetailsProps): VNode {
-  const firstPart = !payto.isKnown ? (
-    <Fragment>
-      <Row name="Account" value={payto.targetPath} />
-      <Row name="Exchange" value={exchangeBaseUrl} />
-    </Fragment>
-  ) : payto.targetType === "x-taler-bank" ? (
-    <Fragment>
-      <Row name="Bank host" value={payto.host} />
-      <Row name="Bank account" value={payto.account} />
-      <Row name="Exchange" value={exchangeBaseUrl} />
-    </Fragment>
-  ) : payto.targetType === "iban" ? (
-    <Fragment>
-      <Row name="IBAN" value={payto.iban} />
-      <Row name="Exchange" value={exchangeBaseUrl} />
-    </Fragment>
-  ) : undefined;
-  return (
-    <table>
-      {firstPart}
-      <Row name="Amount" value={amount} />
-      <Row name="Subject" value={subject} literal />
-    </table>
-  );
-}
 export function ReserveCreated({
   reservePub,
   payto,
@@ -120,11 +24,12 @@ export function ReserveCreated({
     return <div>could not parse payto uri from exchange {payto}</div>;
   }
   return (
-    <WalletBox>
+    <Fragment>
       <section>
-        <h1>Bank transfer details</h1>
+        <h1>Exchange is ready for withdrawal!</h1>
         <p>
-          Please wire <b>{Amounts.stringify(amount)}</b> to:
+          To complete the process you need to wire{" "}
+          <b>{Amounts.stringify(amount)}</b> to the exchange bank account
         </p>
         <BankDetailsByPaytoType
           amount={Amounts.stringify(amount)}
@@ -132,14 +37,14 @@ export function ReserveCreated({
           payto={paytoURI}
           subject={reservePub}
         />
-      </section>
-      <section>
         <p>
           <WarningBox>
             Make sure to use the correct subject, otherwise the money will not
             arrive in this wallet.
           </WarningBox>
         </p>
+      </section>
+      <section>
         <p>
           Alternative, you can also scan this QR code or open{" "}
           <a href={payto}>this link</a> if you have a banking app installed 
that
@@ -149,8 +54,10 @@ export function ReserveCreated({
       </section>
       <footer>
         <div />
-        <ButtonDestructive onClick={onBack}>Cancel withdraw</ButtonDestructive>
+        <ButtonDestructive onClick={onBack}>
+          Cancel withdrawal
+        </ButtonDestructive>
       </footer>
-    </WalletBox>
+    </Fragment>
   );
 }
diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.tsx 
b/packages/taler-wallet-webextension/src/wallet/Settings.tsx
index 8d8f3cdb..586d7b53 100644
--- a/packages/taler-wallet-webextension/src/wallet/Settings.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Settings.tsx
@@ -15,16 +15,15 @@
 */
 
 import { ExchangeListItem, i18n } from "@gnu-taler/taler-util";
-import { VNode, h, Fragment } from "preact";
+import { Fragment, h, VNode } from "preact";
 import { Checkbox } from "../components/Checkbox";
-import { EditableText } from "../components/EditableText";
-import { SelectList } from "../components/SelectList";
-import { ButtonPrimary, ButtonSuccess, WalletBox } from "../components/styled";
+import { ButtonPrimary } from "../components/styled";
 import { useDevContext } from "../context/devContext";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
 import { useBackupDeviceName } from "../hooks/useBackupDeviceName";
 import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
-import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
 import { useLang } from "../hooks/useLang";
+// import { strings as messages } from "../i18n/strings";
 import * as wxApi from "../wxApi";
 
 export function SettingsPage(): VNode {
@@ -32,7 +31,7 @@ export function SettingsPage(): VNode {
   const { devMode, toggleDevMode } = useDevContext();
   const { name, update } = useBackupDeviceName();
   const [lang, changeLang] = useLang();
-  const exchangesHook = useAsyncAsHook(() => wxApi.listExchanges());
+  const exchangesHook = useAsyncAsHook(wxApi.listExchanges);
 
   return (
     <SettingsView
@@ -65,34 +64,32 @@ export interface ViewProps {
   knownExchanges: Array<ExchangeListItem>;
 }
 
-import { strings as messages } from "../i18n/strings";
-
-type LangsNames = {
-  [P in keyof typeof messages]: string;
-};
+// type LangsNames = {
+//   [P in keyof typeof messages]: string;
+// };
 
-const names: LangsNames = {
-  es: "Español [es]",
-  en: "English [en]",
-  fr: "Français [fr]",
-  de: "Deutsch [de]",
-  sv: "Svenska [sv]",
-  it: "Italiano [it]",
-};
+// const names: LangsNames = {
+//   es: "Español [es]",
+//   en: "English [en]",
+//   fr: "Français [fr]",
+//   de: "Deutsch [de]",
+//   sv: "Svenska [sv]",
+//   it: "Italiano [it]",
+// };
 
 export function SettingsView({
   knownExchanges,
-  lang,
-  changeLang,
-  deviceName,
-  setDeviceName,
+  // lang,
+  // changeLang,
+  // deviceName,
+  // setDeviceName,
   permissionsEnabled,
   togglePermissions,
   developerMode,
   toggleDeveloperMode,
 }: ViewProps): VNode {
   return (
-    <WalletBox>
+    <Fragment>
       <section>
         <h2>
           <i18n.Translate>Known exchanges</i18n.Translate>
@@ -100,17 +97,23 @@ export function SettingsView({
         {!knownExchanges || !knownExchanges.length ? (
           <div>No exchange yet!</div>
         ) : (
-          <table>
-            {knownExchanges.map((e) => (
-              <tr>
-                <td>{e.currency}</td>
-                <td>
-                  <a href={e.exchangeBaseUrl}>{e.exchangeBaseUrl}</a>
-                </td>
-              </tr>
-            ))}
-          </table>
+          <Fragment>
+            <table>
+              {knownExchanges.map((e, idx) => (
+                <tr key={idx}>
+                  <td>{e.currency}</td>
+                  <td>
+                    <a href={e.exchangeBaseUrl}>{e.exchangeBaseUrl}</a>
+                  </td>
+                </tr>
+              ))}
+            </table>
+          </Fragment>
         )}
+        <div style={{ display: "flex", justifyContent: "space-between" }}>
+          <div />
+          <ButtonPrimary>Manage exchange</ButtonPrimary>
+        </div>
 
         <h2>
           <i18n.Translate>Permissions</i18n.Translate>
@@ -131,6 +134,6 @@ export function SettingsView({
           onToggle={toggleDeveloperMode}
         />
       </section>
-    </WalletBox>
+    </Fragment>
   );
 }
diff --git 
a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx
index c9a3f47c..a25e2ca8 100644
--- a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx
@@ -61,6 +61,7 @@ const exampleData = {
     exchangeBaseUrl: "http://exchange.taler";,
     withdrawalDetails: {
       confirmed: false,
+      reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
       exchangePaytoUris: ["payto://x-taler-bank/bank/account"],
       type: WithdrawalType.ManualTransfer,
     },
@@ -134,10 +135,49 @@ export const WithdrawError = 
createExample(TestedComponent, {
   },
 });
 
-export const WithdrawPending = createExample(TestedComponent, {
-  transaction: { ...exampleData.withdraw, pending: true },
+export const WithdrawPendingManual = createExample(TestedComponent, {
+  transaction: {
+    ...exampleData.withdraw,
+    withdrawalDetails: {
+      type: WithdrawalType.ManualTransfer,
+      exchangePaytoUris: ["payto://iban/asdasdasd"],
+      reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
+    },
+    pending: true,
+  },
 });
 
+export const WithdrawPendingTalerBankUnconfirmed = createExample(
+  TestedComponent,
+  {
+    transaction: {
+      ...exampleData.withdraw,
+      withdrawalDetails: {
+        type: WithdrawalType.TalerBankIntegrationApi,
+        confirmed: false,
+        reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
+        bankConfirmationUrl: "http://bank.demo.taler.net";,
+      },
+      pending: true,
+    },
+  },
+);
+
+export const WithdrawPendingTalerBankConfirmed = createExample(
+  TestedComponent,
+  {
+    transaction: {
+      ...exampleData.withdraw,
+      withdrawalDetails: {
+        type: WithdrawalType.TalerBankIntegrationApi,
+        confirmed: true,
+        reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
+      },
+      pending: true,
+    },
+  },
+);
+
 export const Payment = createExample(TestedComponent, {
   transaction: exampleData.payment,
 });
diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx 
b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
index 1472efb4..02c78320 100644
--- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
@@ -18,62 +18,80 @@ import {
   AmountLike,
   Amounts,
   i18n,
+  NotificationType,
+  parsePaytoUri,
   Transaction,
   TransactionType,
+  WithdrawalType,
 } from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
+import { ComponentChildren, Fragment, h, VNode } from "preact";
 import { route } from "preact-router";
-import { useEffect, useState } from "preact/hooks";
+import { useState } from "preact/hooks";
 import emptyImg from "../../static/img/empty.png";
+import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType";
 import { ErrorMessage } from "../components/ErrorMessage";
 import { Part } from "../components/Part";
 import {
   Button,
   ButtonDestructive,
   ButtonPrimary,
+  CenteredDialog,
+  InfoBox,
   ListOfProducts,
+  Overlay,
   RowBorderGray,
   SmallLightText,
-  WalletBox,
   WarningBox,
 } from "../components/styled";
 import { Time } from "../components/Time";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
 import { Pages } from "../NavigationBar";
 import * as wxApi from "../wxApi";
 
 export function TransactionPage({ tid }: { tid: string }): VNode {
-  const [transaction, setTransaction] = useState<Transaction | undefined>(
-    undefined,
-  );
+  async function getTransaction(): Promise<Transaction> {
+    const res = await wxApi.getTransactions();
+    const ts = res.transactions.filter((t) => t.transactionId === tid);
+    if (ts.length > 1) throw Error("more than one transaction with this id");
+    if (ts.length === 1) {
+      return ts[0];
+    }
+    throw Error("no transaction found");
+  }
 
-  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();
-  }, [tid]);
+  const state = useAsyncAsHook(getTransaction, [
+    NotificationType.WithdrawGroupFinished,
+  ]);
 
-  if (!transaction) {
+  if (!state) {
     return (
       <div>
         <i18n.Translate>Loading ...</i18n.Translate>
       </div>
     );
   }
+
+  if (state.hasError) {
+    route(Pages.history);
+    return (
+      <div>
+        <i18n.Translate>
+          There was an error. Redirecting into the history page
+        </i18n.Translate>
+      </div>
+    );
+  }
+
+  function goToHistory(): void {
+    route(Pages.history);
+  }
+
   return (
     <TransactionView
-      transaction={transaction}
-      onDelete={() => wxApi.deleteTransaction(tid).then(() => history.go(-1))}
-      onRetry={() => wxApi.retryTransaction(tid).then(() => history.go(-1))}
-      onBack={() => {
-        route(Pages.history);
-      }}
+      transaction={state.response}
+      onDelete={() => wxApi.deleteTransaction(tid).then(goToHistory)}
+      onRetry={() => wxApi.retryTransaction(tid).then(goToHistory)}
+      onBack={goToHistory}
     />
   );
 }
@@ -91,16 +109,28 @@ export function TransactionView({
   onRetry,
   onBack,
 }: WalletTransactionProps): VNode {
-  function TransactionTemplate({ children }: { children: VNode[] }): VNode {
+  const [confirmBeforeForget, setConfirmBeforeForget] = useState(false);
+  function doCheckBeforeForget(): void {
+    if (
+      transaction.pending &&
+      transaction.type === TransactionType.Withdrawal
+    ) {
+      setConfirmBeforeForget(true);
+    } else {
+      onDelete();
+    }
+  }
+  function TransactionTemplate({
+    children,
+  }: {
+    children: ComponentChildren;
+  }): VNode {
     return (
-      <WalletBox>
+      <Fragment>
         <section style={{ padding: 8, textAlign: "center" }}>
           <ErrorMessage title={transaction?.error?.hint} />
           {transaction.pending && (
-            <WarningBox>
-              This transaction is not completed
-              <a href="">more info...</a>
-            </WarningBox>
+            <WarningBox>This transaction is not completed</WarningBox>
           )}
         </section>
         <section>
@@ -116,12 +146,12 @@ export function TransactionView({
                 <i18n.Translate>retry</i18n.Translate>
               </ButtonPrimary>
             ) : null}
-            <ButtonDestructive onClick={onDelete}>
+            <ButtonDestructive onClick={doCheckBeforeForget}>
               <i18n.Translate> Forget </i18n.Translate>
             </ButtonDestructive>
           </div>
         </footer>
-      </WalletBox>
+      </Fragment>
     );
   }
 
@@ -138,27 +168,119 @@ export function TransactionView({
     ).amount;
     return (
       <TransactionTemplate>
+        {confirmBeforeForget ? (
+          <Overlay>
+            <CenteredDialog>
+              <header>Caution!</header>
+              <section>
+                If you have already wired money to the exchange you will loose
+                the chance to get the coins form it.
+              </section>
+              <footer>
+                <Button onClick={() => setConfirmBeforeForget(false)}>
+                  <i18n.Translate> Cancel </i18n.Translate>
+                </Button>
+
+                <ButtonDestructive onClick={onDelete}>
+                  <i18n.Translate> Confirm </i18n.Translate>
+                </ButtonDestructive>
+              </footer>
+            </CenteredDialog>
+          </Overlay>
+        ) : undefined}
         <h2>Withdrawal</h2>
         <Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
-        <br />
-        <Part
-          big
-          title="Total withdrawn"
-          text={amountToString(transaction.amountEffective)}
-          kind="positive"
-        />
-        <Part
-          big
-          title="Chosen amount"
-          text={amountToString(transaction.amountRaw)}
-          kind="neutral"
-        />
-        <Part
-          big
-          title="Exchange fee"
-          text={amountToString(fee)}
-          kind="negative"
-        />
+        {transaction.pending ? (
+          transaction.withdrawalDetails.type ===
+          WithdrawalType.ManualTransfer ? (
+            <Fragment>
+              <BankDetailsByPaytoType
+                amount={amountToString(transaction.amountRaw)}
+                exchangeBaseUrl={transaction.exchangeBaseUrl}
+                payto={parsePaytoUri(
+                  transaction.withdrawalDetails.exchangePaytoUris[0],
+                )}
+                subject={transaction.withdrawalDetails.reservePub}
+              />
+              <p>
+                <WarningBox>
+                  Make sure to use the correct subject, otherwise the money 
will
+                  not arrive in this wallet.
+                </WarningBox>
+              </p>
+              <Part
+                big
+                title="Total withdrawn"
+                text={amountToString(transaction.amountEffective)}
+                kind="positive"
+              />
+              <Part
+                big
+                title="Exchange fee"
+                text={amountToString(fee)}
+                kind="negative"
+              />
+            </Fragment>
+          ) : (
+            <Fragment>
+              {!transaction.withdrawalDetails.confirmed &&
+              transaction.withdrawalDetails.bankConfirmationUrl ? (
+                <InfoBox>
+                  The bank is waiting for confirmation. Go to the
+                  <a
+                    href={transaction.withdrawalDetails.bankConfirmationUrl}
+                    target="_blank"
+                    rel="noreferrer"
+                  >
+                    bank site
+                  </a>
+                </InfoBox>
+              ) : undefined}
+              {transaction.withdrawalDetails.confirmed && (
+                <InfoBox>Waiting for the coins to arrive</InfoBox>
+              )}
+              <Part
+                big
+                title="Total withdrawn"
+                text={amountToString(transaction.amountEffective)}
+                kind="positive"
+              />
+              <Part
+                big
+                title="Chosen amount"
+                text={amountToString(transaction.amountRaw)}
+                kind="neutral"
+              />
+              <Part
+                big
+                title="Exchange fee"
+                text={amountToString(fee)}
+                kind="negative"
+              />
+            </Fragment>
+          )
+        ) : (
+          <Fragment>
+            <Part
+              big
+              title="Total withdrawn"
+              text={amountToString(transaction.amountEffective)}
+              kind="positive"
+            />
+            <Part
+              big
+              title="Chosen amount"
+              text={amountToString(transaction.amountRaw)}
+              kind="neutral"
+            />
+            <Part
+              big
+              title="Exchange fee"
+              text={amountToString(fee)}
+              kind="negative"
+            />
+          </Fragment>
+        )}
         <Part
           title="Exchange"
           text={new URL(transaction.exchangeBaseUrl).hostname}
diff --git a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx 
b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx
index a6dd040e..b180fdd0 100644
--- a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx
@@ -20,13 +20,12 @@
  * @author Florian Dold
  */
 
+import { WalletDiagnostics } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
 import { Checkbox } from "../components/Checkbox";
-import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
 import { Diagnostics } from "../components/Diagnostics";
-import { WalletBox } from "../components/styled";
 import { useDiagnostics } from "../hooks/useDiagnostics";
-import { WalletDiagnostics } from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
+import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
 
 export function WelcomePage(): VNode {
   const [permissionsEnabled, togglePermissions] = useExtendedPermissions();
@@ -54,7 +53,7 @@ export function View({
   timedOut,
 }: ViewProps): VNode {
   return (
-    <WalletBox>
+    <Fragment>
       <h1>Browser Extension Installed!</h1>
       <div>
         <p>Thank you for installing the wallet.</p>
@@ -75,6 +74,6 @@ export function View({
           Learn how to top up your wallet balance »
         </a>
       </div>
-    </WalletBox>
+    </Fragment>
   );
 }
diff --git a/packages/taler-wallet-webextension/src/walletEntryPoint.tsx 
b/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
index f097d58b..a17550ff 100644
--- a/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
+++ b/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
@@ -22,7 +22,7 @@
 
 import { setupI18n } from "@gnu-taler/taler-util";
 import { createHashHistory } from "history";
-import { Fragment, h, render } from "preact";
+import { Fragment, h, render, VNode } from "preact";
 import Router, { route, Route } from "preact-router";
 import { useEffect } from "preact/hooks";
 import { LogoHeader } from "./components/LogoHeader";
@@ -39,8 +39,11 @@ import { SettingsPage } from "./wallet/Settings";
 import { TransactionPage } from "./wallet/Transaction";
 import { WelcomePage } from "./wallet/Welcome";
 import { BackupPage } from "./wallet/BackupPage";
-import { DeveloperPage } from "./popup/Debug.js";
-import { ManualWithdrawPage } from "./wallet/ManualWithdrawPage.js";
+import { DeveloperPage } from "./popup/Debug";
+import { ManualWithdrawPage } from "./wallet/ManualWithdrawPage";
+import { WalletBox } from "./components/styled";
+import { ProviderDetailPage } from "./wallet/ProviderDetailPage";
+import { ProviderAddPage } from "./wallet/ProviderAddPage";
 
 function main(): void {
   try {
@@ -66,16 +69,20 @@ if (document.readyState === "loading") {
 }
 
 function withLogoAndNavBar(Component: any) {
-  return (props: any) => (
-    <Fragment>
-      <LogoHeader />
-      <WalletNavBar />
-      <Component {...props} />
-    </Fragment>
-  );
+  return function withLogoAndNavBarComponent(props: any): VNode {
+    return (
+      <Fragment>
+        <LogoHeader />
+        <WalletNavBar />
+        <WalletBox>
+          <Component {...props} />
+        </WalletBox>
+      </Fragment>
+    );
+  };
 }
 
-function Application() {
+function Application(): VNode {
   return (
     <div>
       <DevContextProvider>
@@ -105,6 +112,23 @@ function Application() {
           <Route
             path={Pages.backup}
             component={withLogoAndNavBar(BackupPage)}
+            onAddProvider={() => {
+              route(Pages.provider_add);
+            }}
+          />
+          <Route
+            path={Pages.provider_detail}
+            component={withLogoAndNavBar(ProviderDetailPage)}
+            onBack={() => {
+              route(Pages.backup);
+            }}
+          />
+          <Route
+            path={Pages.provider_add}
+            component={withLogoAndNavBar(ProviderAddPage)}
+            onBack={() => {
+              route(Pages.backup);
+            }}
           />
 
           <Route
diff --git a/packages/taler-wallet-webextension/src/wxApi.ts 
b/packages/taler-wallet-webextension/src/wxApi.ts
index 90cfd3ed..be0fed88 100644
--- a/packages/taler-wallet-webextension/src/wxApi.ts
+++ b/packages/taler-wallet-webextension/src/wxApi.ts
@@ -22,39 +22,21 @@
  * Imports.
  */
 import {
-  CoreApiResponse,
-  ConfirmPayResult,
-  BalancesResponse,
-  TransactionsResponse,
-  ApplyRefundResponse,
-  PreparePayResult,
-  AcceptWithdrawalResponse,
-  WalletDiagnostics,
-  GetWithdrawalDetailsForUriRequest,
-  WithdrawUriInfoResponse,
-  PrepareTipRequest,
-  PrepareTipResult,
-  AcceptTipRequest,
-  DeleteTransactionRequest,
-  RetryTransactionRequest,
-  SetWalletDeviceIdRequest,
-  GetExchangeWithdrawalInfo,
   AcceptExchangeTosRequest,
-  AcceptManualWithdrawalResult,
-  AcceptManualWithdrawalRequest,
-  AmountJson,
-  ExchangesListRespose,
-  AddExchangeRequest,
-  GetExchangeTosResult,
+  AcceptManualWithdrawalResult, AcceptTipRequest, AcceptWithdrawalResponse,
+  AddExchangeRequest, ApplyRefundResponse, BalancesResponse, ConfirmPayResult,
+  CoreApiResponse, DeleteTransactionRequest, ExchangesListRespose,
+  GetExchangeTosResult, GetExchangeWithdrawalInfo,
+  GetWithdrawalDetailsForUriRequest, NotificationType, PreparePayResult, 
PrepareTipRequest,
+  PrepareTipResult, RetryTransactionRequest,
+  SetWalletDeviceIdRequest, TransactionsResponse, WalletDiagnostics, 
WithdrawUriInfoResponse
 } from "@gnu-taler/taler-util";
 import {
-  AddBackupProviderRequest,
-  BackupProviderState,
-  OperationFailedError,
-  RemoveBackupProviderRequest,
+  AddBackupProviderRequest, BackupInfo, OperationFailedError,
+  RemoveBackupProviderRequest
 } from "@gnu-taler/taler-wallet-core";
-import { BackupInfo } from "@gnu-taler/taler-wallet-core";
 import { ExchangeWithdrawDetails } from 
"@gnu-taler/taler-wallet-core/src/operations/withdraw";
+import { MessageFromBackend } from "./wxBackend.js";
 
 export interface ExtendedPermissionsResponse {
   newValue: boolean;
@@ -83,7 +65,9 @@ export interface UpgradeResponse {
 
 async function callBackend(operation: string, payload: any): Promise<any> {
   return new Promise<any>((resolve, reject) => {
+    // eslint-disable-next-line no-undef
     chrome.runtime.sendMessage({ operation, payload, id: "(none)" }, (resp) => 
{
+      // eslint-disable-next-line no-undef
       if (chrome.runtime.lastError) {
         console.log("Error calling backend");
         reject(
@@ -366,10 +350,13 @@ export function acceptTip(req: AcceptTipRequest): 
Promise<void> {
   return callBackend("acceptTip", req);
 }
 
-export function onUpdateNotification(f: () => void): () => void {
+export function onUpdateNotification(messageType: Array<NotificationType>, 
doCallback: () => void): () => void {
+  // eslint-disable-next-line no-undef
   const port = chrome.runtime.connect({ name: "notifications" });
-  const listener = (): void => {
-    f();
+  const listener = (message: MessageFromBackend): void => {
+    if (messageType.includes(message.type)) {
+      doCallback();
+    }
   };
   port.onMessage.addListener(listener);
   return () => {
diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts 
b/packages/taler-wallet-webextension/src/wxBackend.ts
index 4004f04f..df311524 100644
--- a/packages/taler-wallet-webextension/src/wxBackend.ts
+++ b/packages/taler-wallet-webextension/src/wxBackend.ts
@@ -39,6 +39,7 @@ import {
   classifyTalerUri,
   CoreApiResponse,
   CoreApiResponseSuccess,
+  NotificationType,
   TalerErrorCode,
   TalerUriType,
   WalletDiagnostics,
@@ -237,6 +238,10 @@ function makeSyncWalletRedirect(
   return { redirectUrl: innerUrl.href };
 }
 
+export type MessageFromBackend = {
+  type: NotificationType
+}
+
 async function reinitWallet(): Promise<void> {
   if (currentWallet) {
     currentWallet.stop();
@@ -266,9 +271,10 @@ async function reinitWallet(): Promise<void> {
     return;
   }
   wallet.addNotificationListener((x) => {
-    for (const x of notificationPorts) {
+    for (const notif of notificationPorts) {
+      const message: MessageFromBackend = { type: x.type };
       try {
-        x.postMessage({ type: "notification" });
+        notif.postMessage(message);
       } catch (e) {
         console.error(e);
       }

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