gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: new wallet UI and more tests


From: gnunet
Subject: [taler-wallet-core] branch master updated: new wallet UI and more tests
Date: Fri, 13 Aug 2021 23:04:39 +0200

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

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

The following commit(s) were added to refs/heads/master by this push:
     new e9bb85a2 new wallet UI and more tests
e9bb85a2 is described below

commit e9bb85a212dbd9b86875e89a0aca5d805e2ad61b
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Fri Aug 13 18:04:05 2021 -0300

    new wallet UI and more tests
---
 .../.storybook/preview.js                          |   2 +-
 .../src/browserWorkerEntry.ts                      |   2 +-
 .../src/components/Diagnostics.tsx                 |  28 +-
 .../src/components/styled/index.tsx                |  66 ++++-
 .../src/hooks/useDiagnostics.ts                    |  29 +++
 .../src/hooks/useTalerActionURL.ts                 |   8 +-
 .../src/popup/BalancePage.tsx                      |   2 +-
 .../taler-wallet-webextension/src/popup/Debug.tsx  |   4 +-
 .../src/popup/History.stories.tsx                  |  55 +++-
 .../src/popup/History.tsx                          |  52 +++-
 .../src/popup/ProviderDetailPage.tsx               |   7 +-
 .../src/popup/Settings.tsx                         |   2 +-
 .../src/popup/Transaction.stories.tsx              |   2 +-
 .../taler-wallet-webextension/src/popup/popup.tsx  |   2 +-
 .../src/popupEntryPoint.tsx                        |   2 +-
 .../src/wallet/Pay.stories.tsx                     | 103 ++++++++
 .../taler-wallet-webextension/src/wallet/Pay.tsx   | 287 ++++++++++++---------
 .../src/wallet/Refund.stories.tsx                  |  83 ++++++
 .../src/wallet/Refund.tsx                          |  48 ++--
 .../src/wallet/Tip.stories.tsx                     |  66 +++++
 .../taler-wallet-webextension/src/wallet/Tip.tsx   |  63 +++--
 .../src/wallet/Welcome.stories.tsx                 |  56 ++++
 .../src/wallet/Welcome.tsx                         |  34 ++-
 .../src/wallet/Withdraw.stories.tsx                |  16 --
 .../src/wallet/Withdraw.tsx                        | 155 ++++++-----
 .../src/walletEntryPoint.tsx                       |  68 +----
 .../taler-wallet-webextension/src/wxBackend.ts     |   8 +-
 .../taler-wallet-webextension/static/wallet.html   |   1 +
 28 files changed, 851 insertions(+), 400 deletions(-)

diff --git a/packages/taler-wallet-webextension/.storybook/preview.js 
b/packages/taler-wallet-webextension/.storybook/preview.js
index 169b726f..02a4e43d 100644
--- a/packages/taler-wallet-webextension/.storybook/preview.js
+++ b/packages/taler-wallet-webextension/.storybook/preview.js
@@ -56,7 +56,7 @@ export const decorators = [
           // add a fake header so it looks similar
           return <Fragment>
             <NavBar path={path} devMode={path === '/dev'} />
-            <div style={{ padding: 8, width: 'calc(400px - 16px)', height: 
'calc(320px - 34px - 16px)' }}>
+            <div style={{ width: 400, height: 290 }}>
               <Story />
             </div>
           </Fragment>
diff --git a/packages/taler-wallet-webextension/src/browserWorkerEntry.ts 
b/packages/taler-wallet-webextension/src/browserWorkerEntry.ts
index d8dff72f..b5c26a7b 100644
--- a/packages/taler-wallet-webextension/src/browserWorkerEntry.ts
+++ b/packages/taler-wallet-webextension/src/browserWorkerEntry.ts
@@ -68,6 +68,6 @@ worker.onmessage = (msg: MessageEvent) => {
   }
 
   handleRequest(operation, id, args).catch((e) => {
-    console.error("error in browsere worker", e);
+    console.error("error in browser worker", e);
   });
 };
diff --git a/packages/taler-wallet-webextension/src/components/Diagnostics.tsx 
b/packages/taler-wallet-webextension/src/components/Diagnostics.tsx
index 146b0dd3..b36525db 100644
--- a/packages/taler-wallet-webextension/src/components/Diagnostics.tsx
+++ b/packages/taler-wallet-webextension/src/components/Diagnostics.tsx
@@ -4,30 +4,12 @@ import { PageLink } from "../renderHtml";
 import { WalletDiagnostics } from "@gnu-taler/taler-util";
 import { JSX } from "preact/jsx-runtime";
 
+interface Props {
+  timedOut: boolean;
+  diagnostics: WalletDiagnostics | undefined
+}
 
-export function Diagnostics(): JSX.Element | null {
-  const [timedOut, setTimedOut] = useState(false);
-  const [diagnostics, setDiagnostics] = useState<WalletDiagnostics | 
undefined>(
-    undefined
-  );
-
-  useEffect(() => {
-    let gotDiagnostics = false;
-    setTimeout(() => {
-      if (!gotDiagnostics) {
-        console.error("timed out");
-        setTimedOut(true);
-      }
-    }, 1000);
-    const doFetch = async (): Promise<void> => {
-      const d = await getDiagnostics();
-      console.log("got diagnostics", d);
-      gotDiagnostics = true;
-      setDiagnostics(d);
-    };
-    console.log("fetching diagnostics");
-    doFetch();
-  }, []);
+export function Diagnostics({timedOut, diagnostics}: Props): JSX.Element | 
null {
 
   if (timedOut) {
     return <p>Diagnostics timed out. Could not talk to the wallet backend.</p>;
diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx 
b/packages/taler-wallet-webextension/src/components/styled/index.tsx
index cf7f3e06..7f709db4 100644
--- a/packages/taler-wallet-webextension/src/components/styled/index.tsx
+++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx
@@ -11,13 +11,32 @@ export const PaymentStatus = styled.div<{ color: string }>`
   background-color: ${p => p.color};
 `
 
-export const PopupBox = styled.div`
-  height: calc(320px - 34px - 16px);
+export const WalletPage = styled.section`
+  border: solid 5px black;
+  border-radius: 10px;
+  margin-left: auto;
+  margin-right: auto;
+  padding-top: 2em;
+  max-width: 50%;
+  padding: 2em;
+
+  margin: auto;
+  height: 100%;
+  
+  & h1:first-child {
+    margin-top: 0; 
+  }
+`
+
+export const PopupBox = styled.div<{ noPadding?: boolean }>`
+  height: 290px;
   display: flex;
   flex-direction: column;
   justify-content: space-between;
 
   & > 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
     margin-bottom: auto; 
     overflow: auto;
@@ -35,6 +54,7 @@ export const PopupBox = styled.div`
     flex-direction: row;
     justify-content: space-between;
     display: flex;
+    padding: 8px;
     margin-bottom: 5px;
 
     & > div {
@@ -44,15 +64,23 @@ export const PopupBox = styled.div`
     & > h3 {
       margin: 0px;
     }
+
+    & > .title {
+      /* margin: 1em; */
+      font-size: large;
+      color: #3c4e92;
+    }
   }
 
   & > footer {
-    padding-top: 5px;
+    padding-top: 8px;
+    padding-bottom: 8px;
     flex-direction: row;
     justify-content: space-between;
     display: flex;
     & button {
-      margin-left: 5px;
+      margin-right: 8px;
+      margin-left: 8px;
     }
   }
 
@@ -145,6 +173,13 @@ export const Row = styled.div`
   padding: 0.5em;
 `
 
+export const Row2 = styled.div`
+  display: flex;
+  /* margin: 0.5em 0; */
+  justify-content: space-between;
+  padding: 0.5em;
+`
+
 export const Column = styled.div`
   display: flex;
   flex-direction: column;
@@ -154,10 +189,15 @@ export const Column = styled.div`
 
 export const RowBorderGray = styled(Row)`
   border: 1px solid gray;
-  border-radius: 0.5em;
+  /* border-radius: 0.5em; */
 `
 
-export const HistoryRow = styled(RowBorderGray)`
+export const RowLightBorderGray = styled(Row2)`
+  border: 1px solid lightgray;
+  /* border-radius: 0.5em; */
+`
+
+export const HistoryRow = styled(RowLightBorderGray)`
   & > ${Column}:last-of-type {
     margin-left: auto;
     align-self: center;
@@ -244,24 +284,24 @@ export const ErrorBox = styled.div`
     }
   }
 `
-export const PopupNavigation = styled.div`
-  background-color: #033;
+export const PopupNavigation = styled.div<{devMode?:boolean}>`
+  background-color:#0042b2;
+  height: 35px;
   
   & > a {
     color: #f8faf7;
-    padding-top: 0.7em;
     display: inline-block;
-    width: calc(400px / 5);
-    padding-bottom: 0.7em;
+    width: calc(400px / ${({ devMode }) => !devMode ? 4 : 5});
     text-align: center;
     text-decoration: none;
+    vertical-align: middle;
+    line-height: 35px;
   }
 
   & > a.active {
     background-color: #f8faf7;
-    color: #000;
+    color: #0042b2;
     font-weight: bold;
-
   }
 `;
 
diff --git a/packages/taler-wallet-webextension/src/hooks/useDiagnostics.ts 
b/packages/taler-wallet-webextension/src/hooks/useDiagnostics.ts
new file mode 100644
index 00000000..e2c62f99
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/hooks/useDiagnostics.ts
@@ -0,0 +1,29 @@
+import { WalletDiagnostics } from "@gnu-taler/taler-util";
+import { useEffect, useState } from "preact/hooks";
+import * as wxApi from "../wxApi";
+
+export function useDiagnostics(): [WalletDiagnostics | undefined, boolean] {
+  const [timedOut, setTimedOut] = useState(false);
+  const [diagnostics, setDiagnostics] = useState<WalletDiagnostics | 
undefined>(
+    undefined
+  );
+
+  useEffect(() => {
+    let gotDiagnostics = false;
+    setTimeout(() => {
+      if (!gotDiagnostics) {
+        console.error("timed out");
+        setTimedOut(true);
+      }
+    }, 1000);
+    const doFetch = async (): Promise<void> => {
+      const d = await wxApi.getDiagnostics();
+      console.log("got diagnostics", d);
+      gotDiagnostics = true;
+      setDiagnostics(d);
+    };
+    console.log("fetching diagnostics");
+    doFetch();
+  }, []);
+  return [diagnostics, timedOut]
+}
\ No newline at end of file
diff --git a/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts 
b/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts
index b884ca94..1c8504a8 100644
--- a/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts
+++ b/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts
@@ -57,12 +57,8 @@ function makeExtensionUrlWithParams(
 ): string {
   const innerUrl = new URL(chrome.extension.getURL("/" + url));
   if (params) {
-    for (const key in params) {
-      const p = params[key];
-      if (p) {
-        innerUrl.searchParams.set(key, p);
-      }
-    }
+    const hParams = Object.keys(params).map(k => `${k}=${params[k]}`).join('&')
+    innerUrl.hash = innerUrl.hash + '?' + hParams
   }
   return innerUrl.href;
 }
diff --git a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx 
b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx
index cff17af1..5a2b9f74 100644
--- a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx
+++ b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx
@@ -43,7 +43,7 @@ export function BalanceView({ balance, Linker }: 
BalanceViewProps) {
       <div>
         <p>{i18n.str`Error: could not retrieve balance information.`}</p>
         <p>
-          Click <Linker pageName="welcome.html">here</Linker> for help and
+          Click <Linker pageName="welcome">here</Linker> for help and
           diagnostics.
         </p>
       </div>
diff --git a/packages/taler-wallet-webextension/src/popup/Debug.tsx 
b/packages/taler-wallet-webextension/src/popup/Debug.tsx
index 1f6014e8..33b82b05 100644
--- a/packages/taler-wallet-webextension/src/popup/Debug.tsx
+++ b/packages/taler-wallet-webextension/src/popup/Debug.tsx
@@ -16,10 +16,12 @@
 
 import { JSX } from "preact";
 import { Diagnostics } from "../components/Diagnostics";
+import { useDiagnostics } from "../hooks/useDiagnostics.js";
 import * as wxApi from "../wxApi";
 
 
 export function DeveloperPage(props: any): JSX.Element {
+  const [status, timedOut] = useDiagnostics();
   return (
     <div>
       <p>Debug tools:</p>
@@ -27,7 +29,7 @@ export function DeveloperPage(props: any): JSX.Element {
       <br />
       <button onClick={confirmReset}>reset</button>
       <button onClick={reload}>reload chrome extension</button>
-      <Diagnostics />
+      <Diagnostics diagnostics={status} timedOut={timedOut} />
     </div>
   );
 }
diff --git a/packages/taler-wallet-webextension/src/popup/History.stories.tsx 
b/packages/taler-wallet-webextension/src/popup/History.stories.tsx
index 8eef7dc3..5337a6c1 100644
--- a/packages/taler-wallet-webextension/src/popup/History.stories.tsx
+++ b/packages/taler-wallet-webextension/src/popup/History.stories.tsx
@@ -30,7 +30,7 @@ import { FunctionalComponent } from 'preact';
 import { HistoryView as TestedComponent } from './History';
 
 export default {
-  title: 'popup/transaction/list',
+  title: 'popup/history/list',
   component: TestedComponent,
 };
 
@@ -112,12 +112,26 @@ function createExample<Props>(Component: 
FunctionalComponent<Props>, props: Part
 }
 
 export const Empty = createExample(TestedComponent, {
-  list: []
+  list: [],
+  balances: [{
+    available: 'TESTKUDOS:10',
+    pendingIncoming: 'TESTKUDOS:0',
+    pendingOutgoing: 'TESTKUDOS:0',
+    hasPendingTransactions: false,
+    requiresUserInput: false,
+  }]
 });
 
 
 export const One = createExample(TestedComponent, {
-  list: [exampleData.withdraw]
+  list: [exampleData.withdraw],
+  balances: [{
+    available: 'USD:10',
+    pendingIncoming: 'USD:0',
+    pendingOutgoing: 'USD:0',
+    hasPendingTransactions: false,
+    requiresUserInput: false,
+  }]
 });
 
 export const Several = createExample(TestedComponent, {
@@ -130,7 +144,40 @@ export const Several = createExample(TestedComponent, {
     exampleData.refund,
     exampleData.tip,
     exampleData.deposit,
-  ]
+  ],
+  balances: [{
+    available: 'TESTKUDOS:10',
+    pendingIncoming: 'TESTKUDOS:0',
+    pendingOutgoing: 'TESTKUDOS:0',
+    hasPendingTransactions: false,
+    requiresUserInput: false,
+  }]
+});
+
+export const SeveralWithTwoCurrencies = createExample(TestedComponent, {
+  list: [
+    exampleData.withdraw,
+    exampleData.payment,
+    exampleData.withdraw,
+    exampleData.payment,
+    exampleData.refresh,
+    exampleData.refund,
+    exampleData.tip,
+    exampleData.deposit,
+  ],
+  balances: [{
+    available: 'TESTKUDOS:10',
+    pendingIncoming: 'TESTKUDOS:0',
+    pendingOutgoing: 'TESTKUDOS:0',
+    hasPendingTransactions: false,
+    requiresUserInput: false,
+  },{
+    available: 'USD:10',
+    pendingIncoming: 'USD:0',
+    pendingOutgoing: 'USD:0',
+    hasPendingTransactions: false,
+    requiresUserInput: false,
+  }]
 });
 
 // export const WithdrawPending = createExample(TestedComponent, {
diff --git a/packages/taler-wallet-webextension/src/popup/History.tsx 
b/packages/taler-wallet-webextension/src/popup/History.tsx
index 57fc10c2..b6b65314 100644
--- a/packages/taler-wallet-webextension/src/popup/History.tsx
+++ b/packages/taler-wallet-webextension/src/popup/History.tsx
@@ -14,7 +14,7 @@
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { AmountString, Timestamp, Transaction, TransactionsResponse, 
TransactionType } from "@gnu-taler/taler-util";
+import { AmountJson, Amounts, AmountString, Balance, Timestamp, Transaction, 
TransactionsResponse, TransactionType } from "@gnu-taler/taler-util";
 import { JSX } from "preact";
 import { useEffect, useState } from "preact/hooks";
 import * as wxApi from "../wxApi";
@@ -25,6 +25,8 @@ export function HistoryPage(props: any): JSX.Element {
   const [transactions, setTransactions] = useState<
     TransactionsResponse | undefined
   >(undefined);
+  const balance = useBalances()
+  const balanceWithoutError = balance?.error ? [] : 
(balance?.response.balances || [])
 
   useEffect(() => {
     const fetchData = async (): Promise<void> => {
@@ -38,16 +40,36 @@ export function HistoryPage(props: any): JSX.Element {
     return <div>Loading ...</div>;
   }
 
-  return <HistoryView list={[...transactions.transactions].reverse()} />;
+  return <HistoryView balances={balanceWithoutError} 
list={[...transactions.transactions].reverse()} />;
 }
 
-export function HistoryView({ list }: { list: Transaction[] }) {
-  return <PopupBox>
+function amountToString(c: AmountString) {
+  const idx = c.indexOf(':')
+  return `${c.substring(idx+1)} ${c.substring(0,idx)}`
+}
+
+
+
+export function HistoryView({ list, balances }: { list: Transaction[], 
balances: Balance[] }) {
+  return <PopupBox noPadding>
+    {balances.length > 0 && <header>
+      {balances.length === 1 && <div class="title">
+        Balance: <span>{amountToString(balances[0].available)}</span>
+      </div>}
+      {balances.length > 1 && <div class="title">
+        Balance: <ul style={{ margin: 0 }}>
+          {balances.map(b => <li>{b.available}</li>)}
+        </ul>
+      </div>}
+    </header>}
     <section>
-      {list.map((tx, i) => (
+      {list.slice(0, 3).map((tx, i) => (
         <TransactionItem key={i} tx={tx} />
       ))}
     </section>
+    <footer style={{ justifyContent: 'space-around' }}>
+      <a style={{ color: 'darkgreen', textDecoration:'none' }} 
href={Pages.transaction.replace(':tid', 'asd')}>VIEW MORE TRANSACTIONS</a>
+    </footer>
   </PopupBox>
 }
 
@@ -57,6 +79,8 @@ import imageRefund from 
'../../static/img/ri-refund-2-line.svg';
 import imageHandHeart from '../../static/img/ri-hand-heart-line.svg';
 import imageRefresh from '../../static/img/ri-refresh-line.svg';
 import { Column, ExtraLargeText, HistoryRow, PopupBox, Row, RowBorderGray, 
SmallTextLight } from "../components/styled";
+import { useBalances } from "../hooks/useBalances";
+import { formatDistance } from "date-fns";
 
 function TransactionItem(props: { tx: Transaction }): JSX.Element {
   const tx = props.tx;
@@ -144,23 +168,21 @@ function TransactionItem(props: { tx: Transaction }): 
JSX.Element {
 
 function TransactionLayout(props: TransactionLayoutProps): JSX.Element {
   const date = new Date(props.timestamp.t_ms);
-  const dateStr = date.toLocaleString([], {
-    dateStyle: "medium",
-    timeStyle: "short",
-  } as any);
+  const now = new Date();
+  const dateStr = formatDistance(date, now, { addSuffix: true })
   return (
     <HistoryRow>
       <img src={props.iconPath} />
       <Column>
-        <SmallTextLight>{dateStr}</SmallTextLight>
         <ExtraLargeText>
           <a href={Pages.transaction.replace(':tid', 
props.id)}><span>{props.title}</span></a>
           {props.pending ? (
             <span style={{ color: "darkblue" }}> (Pending)</span>
           ) : null}
         </ExtraLargeText>
+        <SmallTextLight>{dateStr}</SmallTextLight>
 
-        <div>{props.subtitle}</div>
+        {/* <div>{props.subtitle}</div> */}
       </Column>
       <TransactionAmount
         pending={props.pending}
@@ -202,7 +224,13 @@ function TransactionAmount(props: TransactionAmountProps): 
JSX.Element {
       sign = "";
   }
   return (
-    <Column style={{ color: props.pending ? "gray" : undefined }}>
+    <Column style={{
+      color:
+        props.pending ? "gray" :
+          (sign === '+' ? 'darkgreen' :
+            (sign === '-' ? 'darkred' :
+              undefined))
+    }}>
       <ExtraLargeText>
         {sign}
         {amount}
diff --git 
a/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx 
b/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx
index c92137ee..707e6c33 100644
--- a/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx
+++ b/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx
@@ -58,13 +58,12 @@ export function ProviderView({ info, onDelete, onSync, 
onBack, onExtend }: ViewP
   const isPaid = info.paymentStatus.type === ProviderPaymentType.Paid || 
info.paymentStatus.type === ProviderPaymentType.TermsChanged
   return (
     <PopupBox>
-      <header>
+      {info.backupProblem || info.lastError ? <header>
         <Error info={info} />
-
-      </header>
+      </header> : undefined }
       <header>
         <h3>{info.name} 
<SmallTextLight>{info.syncProviderBaseUrl}</SmallTextLight></h3>
-        <PaymentStatus color={isPaid ? 'rgb(28, 184, 65)' : 'rgb(202, 60, 
60)'}>{isPaid ? 'Paid': 'Unpaid' }</PaymentStatus>
+        <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>
diff --git a/packages/taler-wallet-webextension/src/popup/Settings.tsx 
b/packages/taler-wallet-webextension/src/popup/Settings.tsx
index 18afcd10..40ab5156 100644
--- a/packages/taler-wallet-webextension/src/popup/Settings.tsx
+++ b/packages/taler-wallet-webextension/src/popup/Settings.tsx
@@ -68,7 +68,7 @@ const names: LangsNames = {
 export function SettingsView({ lang, changeLang, deviceName, setDeviceName, 
permissionsEnabled, togglePermissions, developerMode, toggleDeveloperMode }: 
ViewProps): VNode {
   return (
     <div>
-      <section style={{ height: 'calc(320px - 34px - 16px)', overflow: 'auto' 
}}>
+      <section style={{ height: 300, overflow: 'auto' }}>
         <h2><i18n.Translate>Wallet</i18n.Translate></h2>
         <SelectList
           value={lang}
diff --git 
a/packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx 
b/packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx
index 3c0bed6c..4e63b924 100644
--- a/packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx
+++ b/packages/taler-wallet-webextension/src/popup/Transaction.stories.tsx
@@ -30,7 +30,7 @@ import { FunctionalComponent } from 'preact';
 import { TransactionView as TestedComponent } from './Transaction';
 
 export default {
-  title: 'popup/transaction/details',
+  title: 'popup/history/details',
   component: TestedComponent,
   argTypes: {
     onRetry: { action: 'onRetry' },
diff --git a/packages/taler-wallet-webextension/src/popup/popup.tsx 
b/packages/taler-wallet-webextension/src/popup/popup.tsx
index a6be4d19..4aee48fb 100644
--- a/packages/taler-wallet-webextension/src/popup/popup.tsx
+++ b/packages/taler-wallet-webextension/src/popup/popup.tsx
@@ -60,7 +60,7 @@ function Tab(props: TabProps): JSX.Element {
 }
 
 export function NavBar({devMode, path}:{path:string, devMode:boolean}) {
-  return <PopupNavigation>
+  return <PopupNavigation devMode={devMode}>
     <Tab target="/balance" current={path}>{i18n.str`Balance`}</Tab>
     <Tab target="/history" current={path}>{i18n.str`History`}</Tab>
     <Tab target="/backup" current={path}>{i18n.str`Backup`}</Tab>
diff --git a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx 
b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
index 39c25d50..faa5149a 100644
--- a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
+++ b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
@@ -93,7 +93,7 @@ function Application() {
     <div>
       <DevContextProvider>
         <WalletNavBar />
-        <div style={{ padding: 8, width: 'calc(400px - 16px)', height: 
'calc(320px - 34px - 16px)' }}>
+        <div style={{ width: 400, height: 290 }}>
           <Router history={createHashHistory()}>
             <Route path={Pages.balance} component={BalancePage} />
             <Route path={Pages.settings} component={SettingsPage} />
diff --git a/packages/taler-wallet-webextension/src/wallet/Pay.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/Pay.stories.tsx
new file mode 100644
index 00000000..0297d626
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/Pay.stories.tsx
@@ -0,0 +1,103 @@
+/*
+ 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 { ContractTerms, PreparePayResultType } from '@gnu-taler/taler-util';
+import { FunctionalComponent, h } from 'preact';
+import { PaymentRequestView as TestedComponent } from './Pay';
+
+
+export default {
+  title: 'wallet/pay',
+  component: TestedComponent,
+  argTypes: {
+  },
+};
+
+function createExample<Props>(Component: FunctionalComponent<Props>, props: 
Partial<Props>) {
+  const r = (args: any) => <Component {...args} />
+  r.args = props
+  return r
+}
+
+export const InsufficientBalance = createExample(TestedComponent, {
+  payStatus: {
+    status: PreparePayResultType.InsufficientBalance,
+    proposalId: "proposal1234",
+    contractTerms: {
+      merchant: {
+        name: 'someone'
+      },
+      amount: 'USD:10',
+    } as Partial<ContractTerms> as any,
+    amountRaw: 'USD:10',
+  }
+});
+
+export const PaymentPossible = createExample(TestedComponent, {
+  payStatus: {
+    status: PreparePayResultType.PaymentPossible,
+    amountEffective: 'USD:10',
+    amountRaw: 'USD:10',
+    contractTerms: {
+      merchant: {
+        name: 'someone'
+      },
+      amount: 'USD:10',
+    } as Partial<ContractTerms> as any,
+    contractTermsHash: '123456',
+    proposalId: 'proposal1234'
+  }
+});
+
+export const AlreadyConfirmedWithFullfilment = createExample(TestedComponent, {
+  payStatus: {
+    status: PreparePayResultType.AlreadyConfirmed,
+    amountEffective: 'USD:10',
+    amountRaw: 'USD:10',
+    contractTerms: {
+      merchant: {
+        name: 'someone'
+      },
+      fulfillment_message: 'congratulations! you are looking at the 
fulfillment message! ',
+      amount: 'USD:10',
+    } as Partial<ContractTerms> as any,
+    contractTermsHash: '123456',
+    proposalId: 'proposal1234',
+    paid: false,
+  }
+});
+
+export const AlreadyConfirmedWithoutFullfilment = 
createExample(TestedComponent, {
+  payStatus: {
+    status: PreparePayResultType.AlreadyConfirmed,
+    amountEffective: 'USD:10',
+    amountRaw: 'USD:10',
+    contractTerms: {
+      merchant: {
+        name: 'someone'
+      },
+      amount: 'USD:10',
+    } as Partial<ContractTerms> as any,
+    contractTermsHash: '123456',
+    proposalId: 'proposal1234',
+    paid: false,
+  }
+});
diff --git a/packages/taler-wallet-webextension/src/wallet/Pay.tsx 
b/packages/taler-wallet-webextension/src/wallet/Pay.tsx
index bd06656c..a5849bb2 100644
--- a/packages/taler-wallet-webextension/src/wallet/Pay.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Pay.tsx
@@ -29,7 +29,7 @@ import * as wxApi from "../wxApi";
 
 import { useState, useEffect } from "preact/hooks";
 
-import { getJsonI18n, i18n } from "@gnu-taler/taler-util";
+import { ConfirmPayResultDone, getJsonI18n, i18n } from 
"@gnu-taler/taler-util";
 import {
   PreparePayResult,
   ConfirmPayResult,
@@ -45,13 +45,54 @@ interface Props {
   talerPayUri?: string
 }
 
+export function AlreadyPaid({ payStatus }: { payStatus: PreparePayResult }) {
+  const fulfillmentUrl = payStatus.contractTerms.fulfillment_url;
+  let message;
+  if (fulfillmentUrl) {
+    message = (
+      <span>
+        You have already paid for this article. Click{" "}
+        <a href={fulfillmentUrl} target="_bank" rel="external">here</a> to 
view it again.
+      </span>
+    );
+  } else {
+    message = <span>
+      You have already paid for this article:{" "}
+      <em>
+        {payStatus.contractTerms.fulfillment_message ?? "no message given"}
+      </em>
+    </span>;
+  }
+  return <section class="main">
+    <h1>GNU Taler Wallet</h1>
+    <article class="fade">
+      {message}
+    </article>
+  </section>
+}
+
+const doPayment = async (payStatus: PreparePayResult): 
Promise<ConfirmPayResultDone> => {
+  if (payStatus.status !== "payment-possible") {
+    throw Error(`invalid state: ${payStatus.status}`);
+  }
+  const proposalId = payStatus.proposalId;
+  const res = await wxApi.confirmPay(proposalId, undefined);
+  if (res.type !== ConfirmPayResultType.Done) {
+    throw Error("payment pending");
+  }
+  const fu = res.contractTerms.fulfillment_url;
+  if (fu) {
+    document.location.href = fu;
+  }
+  return res;
+};
+
+
+
 export function PayPage({ talerPayUri }: Props): JSX.Element {
   const [payStatus, setPayStatus] = useState<PreparePayResult | 
undefined>(undefined);
   const [payResult, setPayResult] = useState<ConfirmPayResult | 
undefined>(undefined);
   const [payErrMsg, setPayErrMsg] = useState<string | undefined>("");
-  const [numTries, setNumTries] = useState(0);
-  const [loading, setLoading] = useState(false);
-  let totalFees: AmountJson | undefined = undefined;
 
   useEffect(() => {
     if (!talerPayUri) return;
@@ -60,53 +101,67 @@ export function PayPage({ talerPayUri }: Props): 
JSX.Element {
       setPayStatus(p);
     };
     doFetch();
-  }, [numTries, talerPayUri]);
+  }, [talerPayUri]);
 
   if (!talerPayUri) {
     return <span>missing pay uri</span>
   }
-  
+
   if (!payStatus) {
     return <span>Loading payment information ...</span>;
   }
 
-  let insufficientBalance = false;
-  if (payStatus.status == PreparePayResultType.InsufficientBalance) {
-    insufficientBalance = true;
-  }
-
-  if (payStatus.status === PreparePayResultType.PaymentPossible) {
-    const amountRaw = Amounts.parseOrThrow(payStatus.amountRaw);
-    const amountEffective: AmountJson = Amounts.parseOrThrow(
-      payStatus.amountEffective,
-    );
-    totalFees = Amounts.sub(amountEffective, amountRaw).amount;
-  }
-
-  if (
-    payStatus.status === PreparePayResultType.AlreadyConfirmed &&
-    numTries === 0
-  ) {
-    const fulfillmentUrl = payStatus.contractTerms.fulfillment_url;
-    if (fulfillmentUrl) {
+  if (payResult && payResult.type === ConfirmPayResultType.Done) {
+    if (payResult.contractTerms.fulfillment_message) {
+      const obj = {
+        fulfillment_message: payResult.contractTerms.fulfillment_message,
+        fulfillment_message_i18n:
+          payResult.contractTerms.fulfillment_message_i18n,
+      };
+      const msg = getJsonI18n(obj, "fulfillment_message");
       return (
-        <span>
-          You have already paid for this article. Click{" "}
-          <a href={fulfillmentUrl} target="_bank" rel="external">here</a> to 
view it again.
-        </span>
+        <div>
+          <p>Payment succeeded.</p>
+          <p>{msg}</p>
+        </div>
       );
     } else {
-      <span>
-        You have already paid for this article:{" "}
-        <em>
-          {payStatus.contractTerms.fulfillment_message ?? "no message given"}
-        </em>
-      </span>;
+      return <span>Redirecting ...</span>;
     }
   }
 
+  const onClick = async () => {
+    try {
+      const res = await doPayment(payStatus)
+      setPayResult(res);
+    } catch (e) {
+      console.error(e);
+      setPayErrMsg(e.message);
+    }
+
+  }
+
+  return <PaymentRequestView payStatus={payStatus} onClick={onClick} 
payErrMsg={payErrMsg} />;
+}
+
+export interface PaymentRequestViewProps {
+  payStatus: PreparePayResult;
+  onClick: () => void;
+  payErrMsg?: string;
+
+}
+export function PaymentRequestView({ payStatus, onClick, payErrMsg }: 
PaymentRequestViewProps) {
+  let totalFees: AmountJson | undefined = undefined;
+  let insufficientBalance = false;
+  const [loading, setLoading] = useState(false);
   const contractTerms: ContractTerms = payStatus.contractTerms;
 
+  if (
+    payStatus.status === PreparePayResultType.AlreadyConfirmed
+  ) {
+    return <AlreadyPaid payStatus={payStatus} />
+  }
+
   if (!contractTerms) {
     return (
       <span>
@@ -115,6 +170,18 @@ export function PayPage({ talerPayUri }: Props): 
JSX.Element {
     );
   }
 
+  if (payStatus.status == PreparePayResultType.InsufficientBalance) {
+    insufficientBalance = true;
+  }
+
+  if (payStatus.status === PreparePayResultType.PaymentPossible) {
+    const amountRaw = Amounts.parseOrThrow(payStatus.amountRaw);
+    const amountEffective: AmountJson = Amounts.parseOrThrow(
+      payStatus.amountEffective,
+    );
+    totalFees = Amounts.sub(amountEffective, amountRaw).amount;
+  }
+
   let merchantName: VNode;
   if (contractTerms.merchant && contractTerms.merchant.name) {
     merchantName = <strong>{contractTerms.merchant.name}</strong>;
@@ -126,99 +193,61 @@ export function PayPage({ talerPayUri }: Props): 
JSX.Element {
     <strong>{renderAmount(Amounts.parseOrThrow(contractTerms.amount))}</strong>
   );
 
-  const doPayment = async (): Promise<void> => {
-    if (payStatus.status !== "payment-possible") {
-      throw Error(`invalid state: ${payStatus.status}`);
-    }
-    const proposalId = payStatus.proposalId;
-    setNumTries(numTries + 1);
-    try {
-      setLoading(true);
-      const res = await wxApi.confirmPay(proposalId, undefined);
-      if (res.type !== ConfirmPayResultType.Done) {
-        throw Error("payment pending");
-      }
-      const fu = res.contractTerms.fulfillment_url;
-      if (fu) {
-        document.location.href = fu;
-      }
-      setPayResult(res);
-    } catch (e) {
-      console.error(e);
-      setPayErrMsg(e.message);
-    }
-  };
-
-  if (payResult && payResult.type === ConfirmPayResultType.Done) {
-    if (payResult.contractTerms.fulfillment_message) {
-      const obj = {
-        fulfillment_message: payResult.contractTerms.fulfillment_message,
-        fulfillment_message_i18n:
-          payResult.contractTerms.fulfillment_message_i18n,
-      };
-      const msg = getJsonI18n(obj, "fulfillment_message");
-      return (
-        <div>
-          <p>Payment succeeded.</p>
-          <p>{msg}</p>
-        </div>
-      );
-    } else {
-      return <span>Redirecting ...</span>;
-    }
-  }
-
-  return (
-    <div>
-      <p>
-        <i18n.Translate>
-          The merchant <span>{merchantName}</span> offers you to purchase:
-        </i18n.Translate>
-        <div style={{ textAlign: "center" }}>
-          <strong>{contractTerms.summary}</strong>
-        </div>
-        {totalFees ? (
+  return <section class="main">
+    <h1>GNU Taler Wallet</h1>
+    <article class="fade">
+      <div>
+        <p>
           <i18n.Translate>
-            The total price is <span>{amount} </span>
-            (plus <span>{renderAmount(totalFees)}</span> fees).
-          </i18n.Translate>
+            The merchant <span>{merchantName}</span> offers you to purchase:
+      </i18n.Translate>
+          <div style={{ textAlign: "center" }}>
+            <strong>{contractTerms.summary}</strong>
+          </div>
+          {totalFees ? (
+            <i18n.Translate>
+              The total price is <span>{amount} </span>
+        (plus <span>{renderAmount(totalFees)}</span> fees).
+            </i18n.Translate>
+          ) : (
+              <i18n.Translate>
+                The total price is <span>{amount}</span>.
+              </i18n.Translate>
+            )}
+        </p>
+
+        {insufficientBalance ? (
+          <div>
+            <p style={{ color: "red", fontWeight: "bold" }}>
+              Unable to pay: Your balance is insufficient.
+            </p>
+          </div>
+        ) : null}
+
+        {payErrMsg ? (
+          <div>
+            <p>Payment failed: {payErrMsg}</p>
+            <button
+              class="pure-button button-success"
+              onClick={onClick}
+            >
+              {i18n.str`Retry`}
+            </button>
+          </div>
         ) : (
-          <i18n.Translate>
-            The total price is <span>{amount}</span>.
-          </i18n.Translate>
-        )}
-      </p>
-
-      {insufficientBalance ? (
-        <div>
-          <p style={{ color: "red", fontWeight: "bold" }}>
-            Unable to pay: Your balance is insufficient.
-          </p>
-        </div>
-      ) : null}
-
-      {payErrMsg ? (
-        <div>
-          <p>Payment failed: {payErrMsg}</p>
-          <button
-            class="pure-button button-success"
-            onClick={() => doPayment()}
-          >
-            {i18n.str`Retry`}
-          </button>
-        </div>
-      ) : (
-        <div>
-          <ProgressButton
-            isLoading={loading}
-            disabled={insufficientBalance}
-            onClick={() => doPayment()}
-          >
-            {i18n.str`Confirm payment`}
-          </ProgressButton>
-        </div>
-      )}
-    </div>
-  );
-}
-
+            <div>
+              <ProgressButton
+                isLoading={loading}
+                disabled={insufficientBalance}
+                onClick={onClick}
+              >
+                {i18n.str`Confirm payment`}
+              </ProgressButton>
+            </div>
+          )}
+      </div>
+    </article>
+  </section>
+
+
+}
\ No newline at end of file
diff --git a/packages/taler-wallet-webextension/src/wallet/Refund.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/Refund.stories.tsx
new file mode 100644
index 00000000..044141f0
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/Refund.stories.tsx
@@ -0,0 +1,83 @@
+/*
+ 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 { ContractTerms, OrderShortInfo, PreparePayResultType } from 
'@gnu-taler/taler-util';
+import { FunctionalComponent, h } from 'preact';
+import { View as TestedComponent } from './Refund';
+
+
+export default {
+  title: 'wallet/refund',
+  component: TestedComponent,
+  argTypes: {
+  },
+};
+
+function createExample<Props>(Component: FunctionalComponent<Props>, props: 
Partial<Props>) {
+  const r = (args: any) => <Component {...args} />
+  r.args = props
+  return r
+}
+
+export const Complete = createExample(TestedComponent, {
+  applyResult: {
+    amountEffectivePaid: 'USD:10',
+    amountRefundGone: 'USD:0',
+    amountRefundGranted: 'USD:2',
+    contractTermsHash: 'QWEASDZXC',
+    info: {
+      summary: 'tasty cold beer',
+      contractTermsHash: 'QWEASDZXC',
+    } as Partial<OrderShortInfo> as any,
+    pendingAtExchange: false,
+    proposalId: "proposal123",
+  }
+});
+
+export const Partial = createExample(TestedComponent, {
+  applyResult: {
+    amountEffectivePaid: 'USD:10',
+    amountRefundGone: 'USD:1',
+    amountRefundGranted: 'USD:2',
+    contractTermsHash: 'QWEASDZXC',
+    info: {
+      summary: 'tasty cold beer',
+      contractTermsHash: 'QWEASDZXC',
+    } as Partial<OrderShortInfo> as any,
+    pendingAtExchange: false,
+    proposalId: "proposal123",
+  }
+});
+
+export const InProgress = createExample(TestedComponent, {
+  applyResult: {
+    amountEffectivePaid: 'USD:10',
+    amountRefundGone: 'USD:1',
+    amountRefundGranted: 'USD:2',
+    contractTermsHash: 'QWEASDZXC',
+    info: {
+      summary: 'tasty cold beer',
+      contractTermsHash: 'QWEASDZXC',
+    } as Partial<OrderShortInfo> as any,
+    pendingAtExchange: true,
+    proposalId: "proposal123",
+  }
+});
diff --git a/packages/taler-wallet-webextension/src/wallet/Refund.tsx 
b/packages/taler-wallet-webextension/src/wallet/Refund.tsx
index 70221741..bb26d933 100644
--- a/packages/taler-wallet-webextension/src/wallet/Refund.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Refund.tsx
@@ -32,7 +32,32 @@ import { JSX } from "preact/jsx-runtime";
 interface Props {
   talerRefundUri?: string
 }
-
+export interface ViewProps {
+  applyResult: ApplyRefundResponse;
+}
+export function View({ applyResult }: ViewProps) {
+  return <section class="main">
+    <h1>GNU Taler Wallet</h1>
+    <article class="fade">
+      <h2>Refund Status</h2>
+      <p>
+        The product <em>{applyResult.info.summary}</em> has received a total
+        effective refund of{" "}
+        <AmountView amount={applyResult.amountRefundGranted} />.
+      </p>
+      {applyResult.pendingAtExchange ? (
+        <p>Refund processing is still in progress.</p>
+      ) : null}
+      {!Amounts.isZero(applyResult.amountRefundGone) ? (
+        <p>
+          The refund amount of{" "}
+          <AmountView amount={applyResult.amountRefundGone} />{" "}
+          could not be applied.
+        </p>
+      ) : null}
+    </article>
+  </section>
+}
 export function RefundPage({ talerRefundUri }: Props): JSX.Element {
   const [applyResult, setApplyResult] = useState<ApplyRefundResponse | 
undefined>(undefined);
   const [errMsg, setErrMsg] = useState<string | undefined>(undefined);
@@ -66,24 +91,5 @@ export function RefundPage({ talerRefundUri }: Props): 
JSX.Element {
     return <span>Updating refund status</span>;
   }
 
-  return (
-    <>
-      <h2>Refund Status</h2>
-      <p>
-        The product <em>{applyResult.info.summary}</em> has received a total
-        effective refund of{" "}
-        <AmountView amount={applyResult.amountRefundGranted} />.
-      </p>
-      {applyResult.pendingAtExchange ? (
-        <p>Refund processing is still in progress.</p>
-      ) : null}
-      {!Amounts.isZero(applyResult.amountRefundGone) ? (
-        <p>
-          The refund amount of{" "}
-          <AmountView amount={applyResult.amountRefundGone} />
-          could not be applied.
-        </p>
-      ) : null}
-    </>
-  );
+  return <View applyResult={applyResult} />;
 }
diff --git a/packages/taler-wallet-webextension/src/wallet/Tip.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/Tip.stories.tsx
new file mode 100644
index 00000000..ffd97614
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/Tip.stories.tsx
@@ -0,0 +1,66 @@
+/*
+ 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 { ContractTerms, PreparePayResultType } from '@gnu-taler/taler-util';
+import { FunctionalComponent, h } from 'preact';
+import { View as TestedComponent } from './Tip';
+
+
+export default {
+  title: 'wallet/tip',
+  component: TestedComponent,
+  argTypes: {
+  },
+};
+
+function createExample<Props>(Component: FunctionalComponent<Props>, props: 
Partial<Props>) {
+  const r = (args: any) => <Component {...args} />
+  r.args = props
+  return r
+}
+
+export const Accepted = createExample(TestedComponent, {
+  prepareTipResult: {
+    accepted: true,
+    merchantBaseUrl: '',
+    exchangeBaseUrl: '',
+    expirationTimestamp : {
+      t_ms: 0
+    },
+    tipAmountEffective: 'USD:10',
+    tipAmountRaw: 'USD:5',
+    walletTipId: 'id'
+  }
+});
+
+export const NotYetAccepted = createExample(TestedComponent, {
+  prepareTipResult: {
+    accepted: false,
+    merchantBaseUrl: 'http://merchant.url/',
+    exchangeBaseUrl: 'http://exchange.url/',
+    expirationTimestamp : {
+      t_ms: 0
+    },
+    tipAmountEffective: 'USD:10',
+    tipAmountRaw: 'USD:5',
+    walletTipId: 'id'
+  }
+});
diff --git a/packages/taler-wallet-webextension/src/wallet/Tip.tsx 
b/packages/taler-wallet-webextension/src/wallet/Tip.tsx
index 708e8940..69886668 100644
--- a/packages/taler-wallet-webextension/src/wallet/Tip.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Tip.tsx
@@ -26,8 +26,41 @@ import { AmountView } from "../renderHtml";
 import * as wxApi from "../wxApi";
 import { JSX } from "preact/jsx-runtime";
 
-interface Props { 
-  talerTipUri?: string 
+interface Props {
+  talerTipUri?: string
+}
+export interface ViewProps {
+  prepareTipResult: PrepareTipResult;
+  onAccept: () => void;
+  onIgnore: () => void;
+
+}
+export function View({ prepareTipResult, onAccept, onIgnore }: ViewProps) {
+  return <section class="main">
+    <h1>GNU Taler Wallet</h1>
+    <article class="fade">
+      {prepareTipResult.accepted ? (
+        <span>
+          Tip from <code>{prepareTipResult.merchantBaseUrl}</code> accepted. 
Check
+        your transactions list for more details.
+        </span>
+      ) : (
+          <div>
+            <p>
+              The merchant <code>{prepareTipResult.merchantBaseUrl}</code> is
+            offering you a tip of{" "}
+              <strong>
+                <AmountView amount={prepareTipResult.tipAmountEffective} />
+              </strong>{" "}
+            via the exchange <code>{prepareTipResult.exchangeBaseUrl}</code>
+            </p>
+            <button onClick={onAccept}>Accept tip</button>
+            <button onClick={onIgnore}>Ignore</button>
+          </div>
+        )}
+    </article>
+  </section>
+
 }
 
 export function TipPage({ talerTipUri }: Props): JSX.Element {
@@ -71,27 +104,7 @@ export function TipPage({ talerTipUri }: Props): 
JSX.Element {
     return <span>Loading ...</span>;
   }
 
-  if (prepareTipResult.accepted) {
-    return (
-      <span>
-        Tip from <code>{prepareTipResult.merchantBaseUrl}</code> accepted. 
Check
-        your transactions list for more details.
-      </span>
-    );
-  } else {
-    return (
-      <div>
-        <p>
-          The merchant <code>{prepareTipResult.merchantBaseUrl}</code> is
-          offering you a tip of{" "}
-          <strong>
-            <AmountView amount={prepareTipResult.tipAmountEffective} />
-          </strong>{" "}
-          via the exchange <code>{prepareTipResult.exchangeBaseUrl}</code>
-        </p>
-        <button onClick={doAccept}>Accept tip</button>
-        <button onClick={doIgnore}>Ignore</button>
-      </div>
-    );
-  }
+  return <View prepareTipResult={prepareTipResult}
+    onAccept={doAccept} onIgnore={doIgnore}
+  />
 }
diff --git a/packages/taler-wallet-webextension/src/wallet/Welcome.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/Welcome.stories.tsx
new file mode 100644
index 00000000..4fa87a13
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/wallet/Welcome.stories.tsx
@@ -0,0 +1,56 @@
+/*
+ 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 { FunctionalComponent, h } from 'preact';
+import { View as TestedComponent } from './Welcome';
+
+
+export default {
+  title: 'wallet/welcome',
+  component: TestedComponent,
+};
+
+function createExample<Props>(Component: FunctionalComponent<Props>, props: 
Partial<Props>) {
+  const r = (args: any) => <Component {...args} />
+  r.args = props
+  return r
+}
+
+export const Normal = createExample(TestedComponent, {
+  permissionsEnabled: true,
+  diagnostics: {
+    errors: [],
+    walletManifestVersion: '1.0',
+    walletManifestDisplayVersion: '1.0',
+    firefoxIdbProblem: false,
+    dbOutdated: false,
+  }
+});
+
+export const TimedoutDiagnostics = createExample(TestedComponent, {
+  timedOut: true,
+  permissionsEnabled: false,
+});
+
+export const RunningDiagnostics = createExample(TestedComponent, {
+  permissionsEnabled: false,
+});
+
diff --git a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx 
b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx
index c7438459..4c33e1c7 100644
--- a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx
@@ -24,13 +24,36 @@ import { JSX } from "preact/jsx-runtime";
 import { Checkbox } from "../components/Checkbox";
 import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
 import { Diagnostics } from "../components/Diagnostics";
+import { WalletPage } from "../components/styled";
+import { useDiagnostics } from "../hooks/useDiagnostics";
+import { WalletDiagnostics } from "@gnu-taler/taler-util";
 
-export function WelcomePage(): JSX.Element {
+export function WelcomePage() {
   const [permissionsEnabled, togglePermissions] = useExtendedPermissions()
-  return (
-    <>
+  const [diagnostics, timedOut] = useDiagnostics()
+  return <View
+    permissionsEnabled={permissionsEnabled} 
togglePermissions={togglePermissions}
+    diagnostics={diagnostics} timedOut={timedOut}
+  />
+}
+
+export interface ViewProps {
+  permissionsEnabled: boolean,
+  togglePermissions: () => void,
+  diagnostics: WalletDiagnostics | undefined,
+  timedOut: boolean,
+}
+export function View({ permissionsEnabled, togglePermissions, diagnostics, 
timedOut }: ViewProps): JSX.Element {
+  return (<WalletPage>
+    <div style="border-bottom: 3px dashed #aa3939; margin-bottom: 2em;">
+      <h1 style="font-family: monospace; font-size: 250%;">
+        <span style="color: #aa3939;">❰</span>Taler Wallet<span style="color: 
#aa3939;">❱</span>
+      </h1>
+    </div>
+    <h1>Browser Extension Installed!</h1>
+    <div>
       <p>Thank you for installing the wallet.</p>
-      <Diagnostics />
+      <Diagnostics diagnostics={diagnostics} timedOut={timedOut} />
       <h2>Permissions</h2>
       <Checkbox label="Automatically open wallet based on page content"
         name="perm"
@@ -44,6 +67,7 @@ export function WelcomePage(): JSX.Element {
       <a href="https://demo.taler.net/"; style={{ display: "block" }}>
         Learn how to top up your wallet balance »
       </a>
-    </>
+    </div>
+  </WalletPage>
   );
 }
diff --git a/packages/taler-wallet-webextension/src/wallet/Withdraw.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/Withdraw.stories.tsx
index 24fb17df..fef36b82 100644
--- a/packages/taler-wallet-webextension/src/wallet/Withdraw.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Withdraw.stories.tsx
@@ -30,27 +30,12 @@ export default {
   },
 };
 
-export const WithoutURI = (a: any) => <View {...a} />;
-WithoutURI.args = {
-} as ViewProps
-
 export const WithoutDetails = (a: any) => <View {...a} />;
 WithoutDetails.args = {
-  talerWithdrawUri: 'http://something'
-} as ViewProps
-
-export const Cancelled = (a: any) => <View {...a} />;
-Cancelled.args = {
-  talerWithdrawUri: 'http://something',
-  details: {
-    amount: 'USD:2',
-  },
-  cancelled: true
 } as ViewProps
 
 export const CompleteWithExchange = (a: any) => <View {...a} />;
 CompleteWithExchange.args = {
-  talerWithdrawUri: 'http://something',
   details: {
     amount: 'USD:2',
   },
@@ -59,7 +44,6 @@ CompleteWithExchange.args = {
 
 export const CompleteWithoutExchange = (a: any) => <View {...a} />;
 CompleteWithoutExchange.args = {
-  talerWithdrawUri: 'http://something',
   details: {
     amount: 'USD:2',
   },
diff --git a/packages/taler-wallet-webextension/src/wallet/Withdraw.tsx 
b/packages/taler-wallet-webextension/src/wallet/Withdraw.tsx
index 4cb8ebfa..442ee7da 100644
--- a/packages/taler-wallet-webextension/src/wallet/Withdraw.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Withdraw.tsx
@@ -32,6 +32,7 @@ import {
 } from "../wxApi";
 import { WithdrawUriInfoResponse } from "@gnu-taler/taler-util";
 import { JSX } from "preact/jsx-runtime";
+import { WalletPage } from '../components/styled';
 
 interface Props {
   talerWithdrawUri?: string;
@@ -39,79 +40,72 @@ interface Props {
 
 export interface ViewProps {
   talerWithdrawUri?: string;
-  details?: WithdrawUriInfoResponse;
-  cancelled?: boolean;
+  details: WithdrawUriInfoResponse;
   selectedExchange?: string;
   accept: () => Promise<void>;
   setCancelled: (b: boolean) => void;
   setSelecting: (b: boolean) => void;
 };
 
-export function View({ talerWithdrawUri, details, cancelled, selectedExchange, 
accept, setCancelled, setSelecting }: ViewProps) {
-  const [state, setState] = useState(1)
-  setTimeout(() => {
-    setState(s => s + 1)
-  }, 1000);
-  if (!talerWithdrawUri) {
-    return <span><i18n.Translate>missing withdraw uri</i18n.Translate></span>;
-  }
-
-  if (!details) {
-    return <span><i18n.Translate>Loading...</i18n.Translate></span>;
-  }
-
-  if (cancelled) {
-    return <span><i18n.Translate>Withdraw operation has been 
cancelled.{state}</i18n.Translate></span>;
-  }
+export function View({ details, selectedExchange, accept, setCancelled, 
setSelecting }: ViewProps) {
 
   return (
-    <div>
-      <h1><i18n.Translate>Digital Cash Withdrawal</i18n.Translate></h1>
-      <p><i18n.Translate>
-        You are about to withdraw{" "}
-        <strong>{renderAmount(details.amount)}</strong> from your bank account
-        into your wallet.
-      </i18n.Translate></p>
-      {selectedExchange ? (
-        <p><i18n.Translate>
-          The exchange <strong>{selectedExchange}</strong> will be used as the
-          Taler payment service provider.
-        </i18n.Translate></p>
-      ) : null}
-
-      <div>
-        <button
-          class="pure-button button-success"
-          disabled={!selectedExchange}
-          onClick={() => accept()}
-        >
-          {i18n.str`Accept fees and withdraw`}
-        </button>
-        <p>
-          <span
-            role="button"
-            tabIndex={0}
-            style={{ textDecoration: "underline", cursor: "pointer" }}
-            onClick={() => setSelecting(true)}
-          >
-            {i18n.str`Chose different exchange provider`}
-          </span>
-          <br />
-          <span
-            role="button"
-            tabIndex={0}
-            style={{ textDecoration: "underline", cursor: "pointer" }}
-            onClick={() => setCancelled(true)}
-          >
-            {i18n.str`Cancel withdraw operation`}
-          </span>
-        </p>
+    <WalletPage>
+      <div style="border-bottom: 3px dashed #aa3939; margin-bottom: 2em;">
+        <h1 style="font-family: monospace; font-size: 250%;">
+          <span style="color: #aa3939;">❰</span>Taler Wallet<span 
style="color: #aa3939;">❱</span>
+        </h1>
+      </div>
+      <div class="fade">
+        <div>
+          <h1><i18n.Translate>Digital Cash Withdrawal</i18n.Translate></h1>
+          <p><i18n.Translate>
+            You are about to withdraw{" "}
+            <strong>{renderAmount(details.amount)}</strong> from your bank 
account
+            into your wallet.
+          </i18n.Translate></p>
+          {selectedExchange ? (
+            <p><i18n.Translate>
+              The exchange <strong>{selectedExchange}</strong> will be used as 
the
+              Taler payment service provider.
+            </i18n.Translate></p>
+          ) : null}
+
+          <div>
+            <button
+              class="pure-button button-success"
+              disabled={!selectedExchange}
+              onClick={() => accept()}
+            >
+              {i18n.str`Accept fees and withdraw`}
+            </button>
+            <p>
+              <span
+                role="button"
+                tabIndex={0}
+                style={{ textDecoration: "underline", cursor: "pointer" }}
+                onClick={() => setSelecting(true)}
+              >
+                {i18n.str`Chose different exchange provider`}
+              </span>
+              <br />
+              <span
+                role="button"
+                tabIndex={0}
+                style={{ textDecoration: "underline", cursor: "pointer" }}
+                onClick={() => setCancelled(true)}
+              >
+                {i18n.str`Cancel withdraw operation`}
+              </span>
+            </p>
+          </div>
+        </div>
       </div>
-    </div>
+    </WalletPage>
   )
 }
 
-export function WithdrawPage({ talerWithdrawUri }: Props): JSX.Element {
+export function WithdrawPage({ talerWithdrawUri, ...rest }: Props): 
JSX.Element {
   const [details, setDetails] = useState<WithdrawUriInfoResponse | 
undefined>(undefined);
   const [selectedExchange, setSelectedExchange] = useState<
     string | undefined
@@ -120,27 +114,44 @@ export function WithdrawPage({ talerWithdrawUri }: 
Props): JSX.Element {
   const [selecting, setSelecting] = useState(false);
   const [errMsg, setErrMsg] = useState<string | undefined>("");
   const [updateCounter, setUpdateCounter] = useState(1);
+  const [state, setState] = useState(1)
+
+  // setTimeout(() => {
+  //   console.log('tick...')
+  //   setState(s => s + 1)
+  // }, 1000);
 
   useEffect(() => {
     return onUpdateNotification(() => {
+      console.log('updating...')
       setUpdateCounter(updateCounter + 1);
     });
   }, []);
 
   useEffect(() => {
+    console.log('on effect yes', talerWithdrawUri)
     if (!talerWithdrawUri) return
     const fetchData = async (): Promise<void> => {
-      const res = await getWithdrawalDetailsForUri({ talerWithdrawUri });
-      setDetails(res);
-      if (res.defaultExchangeBaseUrl) {
-        setSelectedExchange(res.defaultExchangeBaseUrl);
+      console.log('que pasa')
+      try {
+        const res = await getWithdrawalDetailsForUri({ talerWithdrawUri });
+        console.log('res', res)
+        setDetails(res);
+        if (res.defaultExchangeBaseUrl) {
+          setSelectedExchange(res.defaultExchangeBaseUrl);
+        }
+      } catch (e) {
+        console.error(e)
       }
     };
     fetchData();
-  }, [selectedExchange, errMsg, selecting, talerWithdrawUri, updateCounter]);
+  }, [selectedExchange, errMsg, selecting, talerWithdrawUri, updateCounter, 
state]);
+
+  if (!talerWithdrawUri) {
+    return <span><i18n.Translate>missing withdraw uri</i18n.Translate></span>;
+  }
 
   const accept = async (): Promise<void> => {
-    if (!talerWithdrawUri) return
     if (!selectedExchange) {
       throw Error("can't accept, no exchange selected");
     }
@@ -152,10 +163,16 @@ export function WithdrawPage({ talerWithdrawUri }: 
Props): JSX.Element {
     }
   };
 
+  if (!details) {
+    return <span><i18n.Translate>Loading...</i18n.Translate></span>;
+  }
+  if (cancelled) {
+    return <span><i18n.Translate>Withdraw operation has been 
cancelled.</i18n.Translate></span>;
+  }
+
   return <View accept={accept}
     setCancelled={setCancelled} setSelecting={setSelecting}
-    cancelled={cancelled} details={details} selectedExchange={selectedExchange}
-    talerWithdrawUri={talerWithdrawUri}
+    details={details} selectedExchange={selectedExchange}
   />
 }
 
diff --git a/packages/taler-wallet-webextension/src/walletEntryPoint.tsx 
b/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
index 004fcc71..f487e54f 100644
--- a/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
+++ b/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
@@ -32,7 +32,6 @@ import { RefundPage } from "./wallet/Refund";
 import { TipPage } from './wallet/Tip';
 import Router, { route, Route } from "preact-router";
 
-
 function main(): void {
   try {
     const container = document.getElementById("container");
@@ -67,64 +66,15 @@ enum Pages {
 }
 
 function Application() {
-  const sp = new URL(document.location.href).searchParams
-  const queryParams: any = {}
-  sp.forEach((v, k) => { queryParams[k] = v; });
-
-  return <Router history={createHashHistory()} >
-
-    <Route path={Pages.welcome} component={() => {
-      return <section class="main">
-        <div style="border-bottom: 3px dashed #aa3939; margin-bottom: 2em;">
-          <h1 style="font-family: monospace; font-size: 250%;">
-            <span style="color: #aa3939;">❰</span>Taler Wallet<span 
style="color: #aa3939;">❱</span>
-          </h1>
-        </div>
-        <h1>Browser Extension Installed!</h1>
-        <div>
-          <WelcomePage />
-        </div>
-      </section>
-    }} />
-
-    <Route path={Pages.pay} component={() => {
-      return <section class="main">
-        <h1>GNU Taler Wallet</h1>
-        <article class="fade">
-          <PayPage talerPayUri={queryParams.talerPayUri} />
-        </article>
-      </section>
-    }} />
-
-    <Route path={Pages.refund} component={() => {
-      return <section class="main">
-        <h1>GNU Taler Wallet</h1>
-        <article class="fade">
-          <RefundPage talerRefundUri={queryParams.talerRefundUri} />
-        </article>
-      </section>
-    }} />
-
-    <Route path={Pages.tips} component={() => {
-      return <section class="main">
-        <h1>GNU Taler Wallet</h1>
-        <div>
-          <TipPage talerTipUri={queryParams.talerTipUri} />
-        </div>
-      </section>
-    }} />
-    <Route path={Pages.withdraw} component={() => {
-      return <section class="main">
-        <div style="border-bottom: 3px dashed #aa3939; margin-bottom: 2em;">
-          <h1 style="font-family: monospace; font-size: 250%;">
-            <span style="color: #aa3939;">❰</span>Taler Wallet<span 
style="color: #aa3939;">❱</span>
-          </h1>
-        </div>
-        <div class="fade">
-          <WithdrawPage talerWithdrawUri={queryParams.talerWithdrawUri} />
-        </div>
-      </section>
-    }} />
+  const h = createHashHistory();
+  return <Router history={h} >
+
+    <Route path={Pages.welcome} component={WelcomePage} />
+    <Route path={Pages.pay} component={PayPage} />
+    <Route path={Pages.refund} component={RefundPage} />
+
+    <Route path={Pages.tips} component={TipPage} />
+    <Route path={Pages.withdraw} component={WithdrawPage} />
 
     <Route path={Pages.reset_required} component={() => <div>no yet 
implemented</div>} />
     <Route path={Pages.payback} component={() => <div>no yet 
implemented</div>} />
diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts 
b/packages/taler-wallet-webextension/src/wxBackend.ts
index e1517c4c..c474c940 100644
--- a/packages/taler-wallet-webextension/src/wxBackend.ts
+++ b/packages/taler-wallet-webextension/src/wxBackend.ts
@@ -216,12 +216,8 @@ function makeSyncWalletRedirect(
 ): Record<string, unknown> {
   const innerUrl = new URL(chrome.extension.getURL(url));
   if (params) {
-    for (const key in params) {
-      const p = params[key];
-      if (p) {
-        innerUrl.searchParams.set(key, p);
-      }
-    }
+    const hParams = Object.keys(params).map(k => `${k}=${params[k]}`).join('&')
+    innerUrl.hash = innerUrl.hash + '?' + hParams
   }
   if (isFirefox()) {
     // Some platforms don't support the sync redirect (yet), so fall back to
diff --git a/packages/taler-wallet-webextension/static/wallet.html 
b/packages/taler-wallet-webextension/static/wallet.html
index 2b500b56..817e8bfb 100644
--- a/packages/taler-wallet-webextension/static/wallet.html
+++ b/packages/taler-wallet-webextension/static/wallet.html
@@ -4,6 +4,7 @@
     <meta charset="utf-8" />
     <link rel="stylesheet" type="text/css" href="/static/style/pure.css" />
     <link rel="stylesheet" type="text/css" href="/static/style/wallet.css" />
+    <link rel="stylesheet" type="text/css" href="/dist/styles.css" />
     <link rel="icon" href="/static/img/icon.png" />
     <script src="/dist/walletEntryPoint.js"></script>
   </head>

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