summaryrefslogtreecommitdiff
path: root/src/webex/pages
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2019-09-05 16:10:53 +0200
committerFlorian Dold <florian.dold@gmail.com>2019-09-05 16:10:53 +0200
commit8144b0f5535c3d00c1e508cddce3cd88a153a581 (patch)
treefadefd8febe8574a7e46bf6ffd2b1b89b3a58b55 /src/webex/pages
parentfab4e338968b619710e1652f78534a98de2d68d3 (diff)
downloadwallet-core-8144b0f5535c3d00c1e508cddce3cd88a153a581.tar.gz
wallet-core-8144b0f5535c3d00c1e508cddce3cd88a153a581.tar.bz2
wallet-core-8144b0f5535c3d00c1e508cddce3cd88a153a581.zip
welcome page with error diagnostics / react refactoring
Diffstat (limited to 'src/webex/pages')
-rw-r--r--src/webex/pages/add-auditor.tsx119
-rw-r--r--src/webex/pages/help/empty-wallet.html30
-rw-r--r--src/webex/pages/payback.tsx87
-rw-r--r--src/webex/pages/popup.tsx255
-rw-r--r--src/webex/pages/tree.html27
-rw-r--r--src/webex/pages/tree.tsx402
-rw-r--r--src/webex/pages/welcome.html24
-rw-r--r--src/webex/pages/welcome.tsx113
-rw-r--r--src/webex/pages/withdraw.html2
-rw-r--r--src/webex/pages/withdraw.tsx10
10 files changed, 354 insertions, 715 deletions
diff --git a/src/webex/pages/add-auditor.tsx b/src/webex/pages/add-auditor.tsx
index 1ab6fdf9c..7e3e06322 100644
--- a/src/webex/pages/add-auditor.tsx
+++ b/src/webex/pages/add-auditor.tsx
@@ -20,20 +20,11 @@
* @author Florian Dold
*/
-
-import {
- CurrencyRecord,
-} from "../../dbTypes";
-
-import { ImplicitStateComponent, StateHolder } from "../components";
-import {
- getCurrencies,
- updateCurrency,
-} from "../wxApi";
-
-import * as React from "react";
-import * as ReactDOM from "react-dom";
+import { CurrencyRecord } from "../../dbTypes";
+import { getCurrencies, updateCurrency } from "../wxApi";
+import React, { useState } from "react";
import URI = require("urijs");
+import { registerMountPage } from "../renderHtml";
interface ConfirmAuditorProps {
url: string;
@@ -42,36 +33,39 @@ interface ConfirmAuditorProps {
expirationStamp: number;
}
-class ConfirmAuditor extends ImplicitStateComponent<ConfirmAuditorProps> {
- private addDone: StateHolder<boolean> = this.makeState(false);
- constructor(props: ConfirmAuditorProps) {
- super(props);
- }
+function ConfirmAuditor(props: ConfirmAuditorProps) {
+ const [addDone, setAddDone] = useState(false);
+
- async add() {
+ const add = async() => {
const currencies = await getCurrencies();
- let currency: CurrencyRecord|undefined;
+ let currency: CurrencyRecord | undefined;
for (const c of currencies) {
- if (c.name === this.props.currency) {
+ if (c.name === props.currency) {
currency = c;
}
}
if (!currency) {
- currency = { name: this.props.currency, auditors: [], fractionalDigits: 2, exchanges: [] };
+ currency = {
+ name: props.currency,
+ auditors: [],
+ fractionalDigits: 2,
+ exchanges: [],
+ };
}
const newAuditor = {
- auditorPub: this.props.auditorPub,
- baseUrl: this.props.url,
- expirationStamp: this.props.expirationStamp,
+ auditorPub: props.auditorPub,
+ baseUrl: props.url,
+ expirationStamp: props.expirationStamp,
};
let auditorFound = false;
for (const idx in currency.auditors) {
const a = currency.auditors[idx];
- if (a.baseUrl === this.props.url) {
+ if (a.baseUrl === props.url) {
auditorFound = true;
// Update auditor if already found by URL.
currency.auditors[idx] = newAuditor;
@@ -84,47 +78,54 @@ class ConfirmAuditor extends ImplicitStateComponent<ConfirmAuditorProps> {
await updateCurrency(currency);
- this.addDone(true);
+ setAddDone(true);
}
- back() {
+ const back = () => {
window.history.back();
- }
-
- render(): JSX.Element {
- return (
- <div id="main">
- <p>Do you want to let <strong>{this.props.auditorPub}</strong> audit the currency "{this.props.currency}"?</p>
- {this.addDone() ?
- (
- <div>
- Auditor was added! You can also{" "}
- <a href={chrome.extension.getURL("/src/webex/pages/auditors.html")}>view and edit</a>{" "}
- auditors.
- </div>
- )
- :
- (
- <div>
- <button onClick={() => this.add()} className="pure-button pure-button-primary">Yes</button>
- <button onClick={() => this.back()} className="pure-button">No</button>
- </div>
- )
- }
- </div>
- );
- }
+ };
+
+ return (
+ <div id="main">
+ <p>
+ Do you want to let <strong>{props.auditorPub}</strong> audit the
+ currency "{props.currency}"?
+ </p>
+ {addDone ? (
+ <div>
+ Auditor was added! You can also{" "}
+ <a href={chrome.extension.getURL("/src/webex/pages/auditors.html")}>
+ view and edit
+ </a>{" "}
+ auditors.
+ </div>
+ ) : (
+ <div>
+ <button
+ onClick={() => add()}
+ className="pure-button pure-button-primary"
+ >
+ Yes
+ </button>
+ <button onClick={() => back()} className="pure-button">
+ No
+ </button>
+ </div>
+ )}
+ </div>
+ );
}
-function main() {
+
+registerMountPage(() => {
const walletPageUrl = new URI(document.location.href);
- const query: any = JSON.parse((URI.parseQuery(walletPageUrl.query()) as any).req);
+ const query: any = JSON.parse(
+ (URI.parseQuery(walletPageUrl.query()) as any).req,
+ );
const url = query.url;
const currency: string = query.currency;
const auditorPub: string = query.auditorPub;
const expirationStamp = Number.parseInt(query.expirationStamp);
const args = { url, currency, auditorPub, expirationStamp };
- ReactDOM.render(<ConfirmAuditor {...args} />, document.getElementById("container")!);
-}
-
-document.addEventListener("DOMContentLoaded", main);
+ return <ConfirmAuditor {...args}/>;
+});
diff --git a/src/webex/pages/help/empty-wallet.html b/src/webex/pages/help/empty-wallet.html
deleted file mode 100644
index dd29d9689..000000000
--- a/src/webex/pages/help/empty-wallet.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!DOCTYPE html>
-<html>
- <head>
- <meta charset="utf-8">
- <title>GNU Taler Help - Empty Wallet</title>
- <link rel="icon" href="/img/icon.png">
- <meta name="description" content="">
- <link rel="stylesheet" type="text/css" href="/src/style/wallet.css">
- </head>
- <body>
- <div class="container" id="main">
- <div class="row">
- <div class="col-lg-12">
- <h2 lang="en">Your wallet is empty!</h2>
- <p lang="en">You have succeeded with installing the Taler wallet. However, before
- you can buy articles using the Taler wallet, you must withdraw electronic coins.
- This is typically done by visiting your bank's online banking Web site. There,
- you instruct your bank to transfer the funds to a Taler exchange operator. In
- return, your wallet will be allowed to withdraw electronic coins.</p>
- <p lang="en">At this stage, we are not aware of any regular exchange operators issuing
- coins in well-known currencies. However, to see how Taler would work, you
- can visit our "fake" bank at
- <a href="https://bank.demo.taler.net/">bank.demo.taler.net</a> to
- withdraw coins in the "KUDOS" currency that we created just for
- demonstrating the system.</p>
- </div>
- </div>
- </div>
- </body>
-</html>
diff --git a/src/webex/pages/payback.tsx b/src/webex/pages/payback.tsx
index f69a33493..934c28c0a 100644
--- a/src/webex/pages/payback.tsx
+++ b/src/webex/pages/payback.tsx
@@ -20,73 +20,54 @@
* @author Florian Dold
*/
-
/**
* Imports.
*/
-import {
- ReserveRecord,
-} from "../../dbTypes";
+import { ReserveRecord } from "../../dbTypes";
+import { renderAmount, registerMountPage } from "../renderHtml";
+import { getPaybackReserves, withdrawPaybackReserve } from "../wxApi";
+import * as React from "react";
+import { useState } from "react";
-import { ImplicitStateComponent, StateHolder } from "../components";
-import { renderAmount } from "../renderHtml";
-import {
- getPaybackReserves,
- withdrawPaybackReserve,
-} from "../wxApi";
+function Payback() {
+ const [reserves, setReserves] = useState<ReserveRecord[] | null>(null);
-import * as React from "react";
-import * as ReactDOM from "react-dom";
+ useState(() => {
+ const update = async () => {
+ const r = await getPaybackReserves();
+ setReserves(r);
+ };
-class Payback extends ImplicitStateComponent<{}> {
- private reserves: StateHolder<ReserveRecord[]|null> = this.makeState(null);
- constructor(props: {}) {
- super(props);
const port = chrome.runtime.connect();
port.onMessage.addListener((msg: any) => {
if (msg.notify) {
console.log("got notified");
- this.update();
+ update();
}
});
- this.update();
- }
+ });
- async update() {
- const reserves = await getPaybackReserves();
- this.reserves(reserves);
+ if (!reserves) {
+ return <span>loading ...</span>;
}
-
- withdrawPayback(pub: string) {
- withdrawPaybackReserve(pub);
+ if (reserves.length === 0) {
+ return <span>No reserves with payback available.</span>;
}
-
- render(): JSX.Element {
- const reserves = this.reserves();
- if (!reserves) {
- return <span>loading ...</span>;
- }
- if (reserves.length === 0) {
- return <span>No reserves with payback available.</span>;
- }
- return (
- <div>
- {reserves.map((r) => (
- <div>
- <h2>Reserve for ${renderAmount(r.current_amount!)}</h2>
- <ul>
- <li>Exchange: ${r.exchange_base_url}</li>
- </ul>
- <button onClick={() => this.withdrawPayback(r.reserve_pub)}>Withdraw again</button>
- </div>
- ))}
- </div>
- );
- }
-}
-
-function main() {
- ReactDOM.render(<Payback />, document.getElementById("container")!);
+ return (
+ <div>
+ {reserves.map(r => (
+ <div>
+ <h2>Reserve for ${renderAmount(r.current_amount!)}</h2>
+ <ul>
+ <li>Exchange: ${r.exchange_base_url}</li>
+ </ul>
+ <button onClick={() => withdrawPaybackReserve(r.reserve_pub)}>
+ Withdraw again
+ </button>
+ </div>
+ ))}
+ </div>
+ );
}
-document.addEventListener("DOMContentLoaded", main);
+registerMountPage(() => <Payback />);
diff --git a/src/webex/pages/popup.tsx b/src/webex/pages/popup.tsx
index 2cdfd8235..91ab515e4 100644
--- a/src/webex/pages/popup.tsx
+++ b/src/webex/pages/popup.tsx
@@ -14,7 +14,6 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-
/**
* Popup shown to the user when they click
* the Taler browser action button.
@@ -38,7 +37,7 @@ import {
WalletBalanceEntry,
} from "../../walletTypes";
-import { abbrev, renderAmount } from "../renderHtml";
+import { abbrev, renderAmount, PageLink } from "../renderHtml";
import * as wxApi from "../wxApi";
import * as React from "react";
@@ -47,7 +46,7 @@ import * as ReactDOM from "react-dom";
import URI = require("urijs");
function onUpdateNotification(f: () => void): () => void {
- const port = chrome.runtime.connect({name: "notifications"});
+ const port = chrome.runtime.connect({ name: "notifications" });
const listener = () => {
f();
};
@@ -57,7 +56,6 @@ function onUpdateNotification(f: () => void): () => void {
};
}
-
class Router extends React.Component<any, any> {
static setRoute(s: string): void {
window.location.hash = s;
@@ -92,13 +90,12 @@ class Router extends React.Component<any, any> {
console.log("router unmounted");
}
-
render(): JSX.Element {
const route = window.location.hash.substring(1);
console.log("rendering route", route);
- let defaultChild: React.ReactChild|null = null;
- let foundChild: React.ReactChild|null = null;
- React.Children.forEach(this.props.children, (child) => {
+ let defaultChild: React.ReactChild | null = null;
+ let foundChild: React.ReactChild | null = null;
+ React.Children.forEach(this.props.children, child => {
const childProps: any = (child as any).props;
if (!childProps) {
return;
@@ -119,7 +116,6 @@ class Router extends React.Component<any, any> {
}
}
-
interface TabProps {
target: string;
children?: React.ReactNode;
@@ -141,7 +137,6 @@ function Tab(props: TabProps) {
);
}
-
class WalletNavBar extends React.Component<any, any> {
private cancelSubscription: any;
@@ -161,20 +156,14 @@ class WalletNavBar extends React.Component<any, any> {
console.log("rendering nav bar");
return (
<div className="nav" id="header">
- <Tab target="/balance">
- {i18n.str`Balance`}
- </Tab>
- <Tab target="/history">
- {i18n.str`History`}
- </Tab>
- <Tab target="/debug">
- {i18n.str`Debug`}
- </Tab>
- </div>);
+ <Tab target="/balance">{i18n.str`Balance`}</Tab>
+ <Tab target="/history">{i18n.str`History`}</Tab>
+ <Tab target="/debug">{i18n.str`Debug`}</Tab>
+ </div>
+ );
}
}
-
function ExtensionLink(props: any) {
const onClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
chrome.tabs.create({
@@ -189,7 +178,6 @@ function ExtensionLink(props: any) {
);
}
-
/**
* Render an amount as a large number with a small currency symbol.
*/
@@ -197,10 +185,21 @@ function bigAmount(amount: AmountJson): JSX.Element {
const v = amount.value + amount.fraction / Amounts.fractionalBase;
return (
<span>
- <span style={{fontSize: "300%"}}>{v}</span>
- {" "}
+ <span style={{ fontSize: "300%" }}>{v}</span>{" "}
<span>{amount.currency}</span>
- </span>
+ </span>
+ );
+}
+
+function EmptyBalanceView() {
+ return (
+ <div>
+ <i18n.Translate wrap="p">
+ You have no balance to show. Need some{" "}
+ <PageLink pageName="welcome.html">help</PageLink> getting
+ started?
+ </i18n.Translate>
+ </div>
);
}
@@ -245,57 +244,44 @@ class WalletBalanceView extends React.Component<any, any> {
this.setState({});
}
- renderEmpty(): JSX.Element {
- const helpLink = (
- <ExtensionLink target="/src/webex/pages/help/empty-wallet.html">
- {i18n.str`help`}
- </ExtensionLink>
- );
- return (
- <div>
- <i18n.Translate wrap="p">
- You have no balance to show. Need some
- {" "}<span>{helpLink}</span>{" "}
- getting started?
- </i18n.Translate>
- </div>
- );
- }
-
formatPending(entry: WalletBalanceEntry): JSX.Element {
let incoming: JSX.Element | undefined;
let payment: JSX.Element | undefined;
- console.log("available: ", entry.pendingIncoming ? renderAmount(entry.available) : null);
- console.log("incoming: ", entry.pendingIncoming ? renderAmount(entry.pendingIncoming) : null);
+ console.log(
+ "available: ",
+ entry.pendingIncoming ? renderAmount(entry.available) : null,
+ );
+ console.log(
+ "incoming: ",
+ entry.pendingIncoming ? renderAmount(entry.pendingIncoming) : null,
+ );
if (Amounts.isNonZero(entry.pendingIncoming)) {
incoming = (
<i18n.Translate wrap="span">
- <span style={{color: "darkgreen"}}>
+ <span style={{ color: "darkgreen" }}>
{"+"}
{renderAmount(entry.pendingIncoming)}
- </span>
- {" "}
+ </span>{" "}
incoming
- </i18n.Translate>
+ </i18n.Translate>
);
}
if (Amounts.isNonZero(entry.pendingPayment)) {
payment = (
<i18n.Translate wrap="span">
- <span style={{color: "red"}}>
+ <span style={{ color: "red" }}>
{"-"}
{renderAmount(entry.pendingPayment)}
- </span>
- {" "}
+ </span>{" "}
being spent
</i18n.Translate>
);
}
- const l = [incoming, payment].filter((x) => x !== undefined);
+ const l = [incoming, payment].filter(x => x !== undefined);
if (l.length === 0) {
return <span />;
}
@@ -303,49 +289,41 @@ class WalletBalanceView extends React.Component<any, any> {
if (l.length === 1) {
return <span>({l})</span>;
}
- return <span>({l[0]}, {l[1]})</span>;
-
+ return (
+ <span>
+ ({l[0]}, {l[1]})
+ </span>
+ );
}
render(): JSX.Element {
const wallet = this.balance;
if (this.gotError) {
- return i18n.str`Error: could not retrieve balance information.`;
+ return (
+ <div>
+ <p>{i18n.str`Error: could not retrieve balance information.`}</p>
+ <p>
+ Click <PageLink pageName="welcome.html">here</PageLink> for help and diagnostics.
+ </p>
+ </div>
+ );
}
if (!wallet) {
return <span></span>;
}
console.log(wallet);
- let paybackAvailable = false;
- const listing = Object.keys(wallet.byCurrency).map((key) => {
+ const listing = Object.keys(wallet.byCurrency).map(key => {
const entry: WalletBalanceEntry = wallet.byCurrency[key];
- if (entry.paybackAmount.value !== 0 || entry.paybackAmount.fraction !== 0) {
- paybackAvailable = true;
- }
return (
<p>
- {bigAmount(entry.available)}
- {" "}
- {this.formatPending(entry)}
+ {bigAmount(entry.available)} {this.formatPending(entry)}
</p>
);
});
- const makeLink = (page: string, name: string) => {
- const url = chrome.extension.getURL(`/src/webex/pages/${page}`);
- return <div><a className="actionLink" href={url} target="_blank">{name}</a></div>;
- };
- return (
- <div>
- {listing.length > 0 ? listing : this.renderEmpty()}
- {paybackAvailable && makeLink("payback", i18n.str`Payback`)}
- {makeLink("return-coins.html#dissolve", i18n.str`Return Electronic Cash to Bank Account`)}
- {makeLink("auditors.html", i18n.str`Manage Trusted Auditors and Exchanges`)}
- </div>
- );
+ return <div>{listing.length > 0 ? listing : <EmptyBalanceView />}</div>;
}
}
-
function formatHistoryItem(historyItem: HistoryRecord) {
const d = historyItem.detail;
console.log("hist item", historyItem);
@@ -353,13 +331,12 @@ function formatHistoryItem(historyItem: HistoryRecord) {
case "create-reserve":
return (
<i18n.Translate wrap="p">
- Bank requested reserve (<span>{abbrev(d.reservePub)}</span>) for
- {" "}
+ Bank requested reserve (<span>{abbrev(d.reservePub)}</span>) for{" "}
<span>{renderAmount(d.requestedAmount)}</span>.
</i18n.Translate>
);
case "confirm-reserve": {
- const exchange = (new URI(d.exchangeBaseUrl)).host();
+ const exchange = new URI(d.exchangeBaseUrl).host();
const pub = abbrev(d.reservePub);
return (
<i18n.Translate wrap="p">
@@ -372,30 +349,37 @@ function formatHistoryItem(historyItem: HistoryRecord) {
case "offer-contract": {
return (
<i18n.Translate wrap="p">
- Merchant <em>{abbrev(d.merchantName, 15)}</em> offered
- contract <span>{abbrev(d.contractTermsHash)}</span>.
+ Merchant <em>{abbrev(d.merchantName, 15)}</em> offered contract{" "}
+ <span>{abbrev(d.contractTermsHash)}</span>.
</i18n.Translate>
);
}
case "depleted-reserve": {
- const exchange = d.exchangeBaseUrl ? (new URI(d.exchangeBaseUrl)).host() : "??";
+ const exchange = d.exchangeBaseUrl
+ ? new URI(d.exchangeBaseUrl).host()
+ : "??";
const amount = renderAmount(d.requestedAmount);
const pub = abbrev(d.reservePub);
return (
<i18n.Translate wrap="p">
- Withdrew <span>{amount}</span> from <span>{exchange}</span> (<span>{pub}</span>).
+ Withdrew <span>{amount}</span> from <span>{exchange}</span> (
+ <span>{pub}</span>).
</i18n.Translate>
);
}
case "pay": {
const url = d.fulfillmentUrl;
const merchantElem = <em>{abbrev(d.merchantName, 15)}</em>;
- const fulfillmentLinkElem = <a href={url} onClick={openTab(url)}>view product</a>;
+ const fulfillmentLinkElem = (
+ <a href={url} onClick={openTab(url)}>
+ view product
+ </a>
+ );
return (
<i18n.Translate wrap="p">
- Paid <span>{renderAmount(d.amount)}</span> to merchant <span>{merchantElem}</span>.
- <span> </span>
- (<span>{fulfillmentLinkElem}</span>)
+ Paid <span>{renderAmount(d.amount)}</span> to merchant{" "}
+ <span>{merchantElem}</span>.<span> </span>(
+ <span>{fulfillmentLinkElem}</span>)
</i18n.Translate>
);
}
@@ -403,12 +387,15 @@ function formatHistoryItem(historyItem: HistoryRecord) {
const merchantElem = <em>{abbrev(d.merchantName, 15)}</em>;
return (
<i18n.Translate wrap="p">
- Merchant <span>{merchantElem}</span> gave a refund over <span>{renderAmount(d.refundAmount)}</span>.
+ Merchant <span>{merchantElem}</span> gave a refund over{" "}
+ <span>{renderAmount(d.refundAmount)}</span>.
</i18n.Translate>
);
}
case "tip": {
- const tipPageUrl = new URI(chrome.extension.getURL("/src/webex/pages/tip.html"));
+ const tipPageUrl = new URI(
+ chrome.extension.getURL("/src/webex/pages/tip.html"),
+ );
const params = { tip_id: d.tipId, merchant_domain: d.merchantDomain };
const url = tipPageUrl.query(params).href();
const tipLink = <a href={url} onClick={openTab(url)}>{i18n.str`tip`}</a>;
@@ -416,19 +403,23 @@ function formatHistoryItem(historyItem: HistoryRecord) {
return (
<>
<i18n.Translate wrap="p">
- Merchant <span>{d.merchantDomain}</span> gave
- a <span>{tipLink}</span> of <span>{renderAmount(d.amount)}</span>.
+ Merchant <span>{d.merchantDomain}</span> gave a{" "}
+ <span>{tipLink}</span> of <span>{renderAmount(d.amount)}</span>.
</i18n.Translate>
- <span> { d.accepted ? null : <i18n.Translate>You did not accept the tip yet.</i18n.Translate> }</span>
+ <span>
+ {" "}
+ {d.accepted ? null : (
+ <i18n.Translate>You did not accept the tip yet.</i18n.Translate>
+ )}
+ </span>
</>
);
}
default:
- return (<p>{i18n.str`Unknown event (${historyItem.type})`}</p>);
+ return <p>{i18n.str`Unknown event (${historyItem.type})`}</p>;
}
}
-
class WalletHistory extends React.Component<any, any> {
private myHistory: any[];
private gotError = false;
@@ -445,7 +436,7 @@ class WalletHistory extends React.Component<any, any> {
}
update() {
- chrome.runtime.sendMessage({type: "get-history"}, (resp) => {
+ chrome.runtime.sendMessage({ type: "get-history" }, resp => {
if (this.unmounted) {
return;
}
@@ -480,7 +471,7 @@ class WalletHistory extends React.Component<any, any> {
const item = (
<div className="historyItem">
<div className="historyDate">
- {(new Date(record.timestamp)).toString()}
+ {new Date(record.timestamp).toString()}
</div>
{formatHistoryItem(record)}
</div>
@@ -494,10 +485,8 @@ class WalletHistory extends React.Component<any, any> {
}
return <p>{i18n.str`Your wallet has no events recorded.`}</p>;
}
-
}
-
function reload() {
try {
chrome.runtime.reload();
@@ -508,43 +497,43 @@ function reload() {
}
function confirmReset() {
- if (confirm("Do you want to IRREVOCABLY DESTROY everything inside your" +
- " wallet and LOSE ALL YOUR COINS?")) {
+ if (
+ confirm(
+ "Do you want to IRREVOCABLY DESTROY everything inside your" +
+ " wallet and LOSE ALL YOUR COINS?",
+ )
+ ) {
wxApi.resetDb();
window.close();
}
}
-
function WalletDebug(props: any) {
- return (<div>
- <p>Debug tools:</p>
- <button onClick={openExtensionPage("/src/webex/pages/popup.html")}>
- wallet tab
- </button>
- <button onClick={openExtensionPage("/src/webex/pages/benchmark.html")}>
- benchmark
- </button>
- <button onClick={openExtensionPage("/src/webex/pages/show-db.html")}>
- show db
- </button>
- <button onClick={openExtensionPage("/src/webex/pages/tree.html")}>
- show tree
- </button>
- <button onClick={openExtensionPage("/src/webex/pages/logs.html")}>
- show logs
- </button>
- <br />
- <button onClick={confirmReset}>
- reset
- </button>
- <button onClick={reload}>
- reload chrome extension
- </button>
- </div>);
+ return (
+ <div>
+ <p>Debug tools:</p>
+ <button onClick={openExtensionPage("/src/webex/pages/popup.html")}>
+ wallet tab
+ </button>
+ <button onClick={openExtensionPage("/src/webex/pages/benchmark.html")}>
+ benchmark
+ </button>
+ <button onClick={openExtensionPage("/src/webex/pages/show-db.html")}>
+ show db
+ </button>
+ <button onClick={openExtensionPage("/src/webex/pages/tree.html")}>
+ show tree
+ </button>
+ <button onClick={openExtensionPage("/src/webex/pages/logs.html")}>
+ show logs
+ </button>
+ <br />
+ <button onClick={confirmReset}>reset</button>
+ <button onClick={reload}>reload chrome extension</button>
+ </div>
+ );
}
-
function openExtensionPage(page: string) {
return () => {
chrome.tabs.create({
@@ -553,7 +542,6 @@ function openExtensionPage(page: string) {
};
}
-
function openTab(page: string) {
return (evt: React.SyntheticEvent<any>) => {
evt.preventDefault();
@@ -563,15 +551,14 @@ function openTab(page: string) {
};
}
-
const el = (
<div>
<WalletNavBar />
- <div style={{margin: "1em"}}>
+ <div style={{ margin: "1em" }}>
<Router>
- <WalletBalanceView route="/balance" default/>
- <WalletHistory route="/history"/>
- <WalletDebug route="/debug"/>
+ <WalletBalanceView route="/balance" default />
+ <WalletHistory route="/history" />
+ <WalletDebug route="/debug" />
</Router>
</div>
</div>
@@ -581,5 +568,5 @@ runOnceWhenReady(() => {
ReactDOM.render(el, document.getElementById("content")!);
// Will be used by the backend to detect when the popup gets closed,
// so we can clear notifications
- chrome.runtime.connect({name: "popup"});
+ chrome.runtime.connect({ name: "popup" });
});
diff --git a/src/webex/pages/tree.html b/src/webex/pages/tree.html
deleted file mode 100644
index 0c0a368b3..000000000
--- a/src/webex/pages/tree.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<!DOCTYPE html>
-<html>
-
-<head>
- <meta charset="UTF-8">
- <title>Taler Wallet: Tree View</title>
-
- <link rel="stylesheet" type="text/css" href="../style/wallet.css">
-
- <link rel="icon" href="/img/icon.png">
-
- <script src="/dist/page-common-bundle.js"></script>
- <script src="/dist/tree-bundle.js"></script>
-
- <style>
- .tree-item {
- margin: 2em;
- border-radius: 5px;
- border: 1px solid gray;
- padding: 1em;
- }
- </style>
-
- <body>
- <div id="container"></div>
- </body>
-</html>
diff --git a/src/webex/pages/tree.tsx b/src/webex/pages/tree.tsx
deleted file mode 100644
index 67e58a1df..000000000
--- a/src/webex/pages/tree.tsx
+++ /dev/null
@@ -1,402 +0,0 @@
-/*
- This file is part of TALER
- (C) 2016 Inria
-
- 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/>
- */
-
-/**
- * Show contents of the wallet as a tree.
- *
- * @author Florian Dold
- */
-
-
-import { getTalerStampDate } from "../../helpers";
-
-import {
- CoinRecord,
- CoinStatus,
- DenominationRecord,
- ExchangeRecord,
- PreCoinRecord,
- ReserveRecord,
-} from "../../dbTypes";
-
-import { ImplicitStateComponent, StateHolder } from "../components";
-import {
- getCoins,
- getDenoms,
- getExchanges,
- getPreCoins,
- getReserves,
- payback,
- refresh,
-} from "../wxApi";
-
-import { ExpanderText, renderAmount } from "../renderHtml";
-
-import * as React from "react";
-import * as ReactDOM from "react-dom";
-
-interface ReserveViewProps {
- reserve: ReserveRecord;
-}
-
-class ReserveView extends React.Component<ReserveViewProps, {}> {
- render(): JSX.Element {
- const r: ReserveRecord = this.props.reserve;
- return (
- <div className="tree-item">
- <ul>
- <li>Key: {r.reserve_pub}</li>
- <li>Created: {(new Date(r.created * 1000).toString())}</li>
- <li>Current: {r.current_amount ? renderAmount(r.current_amount!) : "null"}</li>
- <li>Requested: {renderAmount(r.requested_amount)}</li>
- <li>Confirmed: {r.timestamp_confirmed}</li>
- </ul>
- </div>
- );
- }
-}
-
-interface ReserveListProps {
- exchangeBaseUrl: string;
-}
-
-interface ToggleProps {
- expanded: StateHolder<boolean>;
-}
-
-class Toggle extends ImplicitStateComponent<ToggleProps> {
- renderButton() {
- const show = () => {
- this.props.expanded(true);
- this.setState({});
- };
- const hide = () => {
- this.props.expanded(false);
- this.setState({});
- };
- if (this.props.expanded()) {
- return <button onClick={hide}>hide</button>;
- }
- return <button onClick={show}>show</button>;
-
- }
- render() {
- return (
- <div style={{display: "inline"}}>
- {this.renderButton()}
- {this.props.expanded() ? this.props.children : []}
- </div>);
- }
-}
-
-
-interface CoinViewProps {
- coin: CoinRecord;
-}
-
-interface RefreshDialogProps {
- coin: CoinRecord;
-}
-
-class RefreshDialog extends ImplicitStateComponent<RefreshDialogProps> {
- private refreshRequested = this.makeState<boolean>(false);
- render(): JSX.Element {
- if (!this.refreshRequested()) {
- return (
- <div style={{display: "inline"}}>
- <button onClick={() => this.refreshRequested(true)}>refresh</button>
- </div>
- );
- }
- return (
- <div>
- Refresh amount: <input type="text" size={10} />
- <button onClick={() => refresh(this.props.coin.coinPub)}>ok</button>
- <button onClick={() => this.refreshRequested(false)}>cancel</button>
- </div>
- );
- }
-}
-
-class CoinView extends React.Component<CoinViewProps, {}> {
- render() {
- const c = this.props.coin;
- return (
- <div className="tree-item">
- <ul>
- <li>Key: {c.coinPub}</li>
- <li>Current amount: {renderAmount(c.currentAmount)}</li>
- <li>Denomination: <ExpanderText text={c.denomPub} /></li>
- <li>Suspended: {(c.suspended || false).toString()}</li>
- <li>Status: {CoinStatus[c.status]}</li>
- <li><RefreshDialog coin={c} /></li>
- <li><button onClick={() => payback(c.coinPub)}>Payback</button></li>
- </ul>
- </div>
- );
- }
-}
-
-
-interface PreCoinViewProps {
- precoin: PreCoinRecord;
-}
-
-class PreCoinView extends React.Component<PreCoinViewProps, {}> {
- render() {
- const c = this.props.precoin;
- return (
- <div className="tree-item">
- <ul>
- <li>Key: {c.coinPub}</li>
- </ul>
- </div>
- );
- }
-}
-
-interface CoinListProps {
- exchangeBaseUrl: string;
-}
-
-class CoinList extends ImplicitStateComponent<CoinListProps> {
- private coins = this.makeState<CoinRecord[] | null>(null);
- private expanded = this.makeState<boolean>(false);
-
- constructor(props: CoinListProps) {
- super(props);
- this.update(props);
- }
-
- async update(props: CoinListProps) {
- const coins = await getCoins(props.exchangeBaseUrl);
- this.coins(coins);
- }
-
- componentWillReceiveProps(newProps: CoinListProps) {
- this.update(newProps);
- }
-
- render(): JSX.Element {
- if (!this.coins()) {
- return <div>...</div>;
- }
- return (
- <div className="tree-item">
- Coins ({this.coins() !.length.toString()})
- {" "}
- <Toggle expanded={this.expanded}>
- {this.coins() !.map((c) => <CoinView coin={c} />)}
- </Toggle>
- </div>
- );
- }
-}
-
-
-interface PreCoinListProps {
- exchangeBaseUrl: string;
-}
-
-class PreCoinList extends ImplicitStateComponent<PreCoinListProps> {
- private precoins = this.makeState<PreCoinRecord[] | null>(null);
- private expanded = this.makeState<boolean>(false);
-
- constructor(props: PreCoinListProps) {
- super(props);
- this.update();
- }
-
- async update() {
- const precoins = await getPreCoins(this.props.exchangeBaseUrl);
- this.precoins(precoins);
- }
-
- render(): JSX.Element {
- if (!this.precoins()) {
- return <div>...</div>;
- }
- return (
- <div className="tree-item">
- Planchets ({this.precoins() !.length.toString()})
- {" "}
- <Toggle expanded={this.expanded}>
- {this.precoins() !.map((c) => <PreCoinView precoin={c} />)}
- </Toggle>
- </div>
- );
- }
-}
-
-interface DenominationListProps {
- exchange: ExchangeRecord;
-}
-
-class DenominationList extends ImplicitStateComponent<DenominationListProps> {
- private expanded = this.makeState<boolean>(false);
- private denoms = this.makeState<undefined|DenominationRecord[]>(undefined);
-
- constructor(props: DenominationListProps) {
- super(props);
- this.update();
- }
-
- async update() {
- const d = await getDenoms(this.props.exchange.baseUrl);
- this.denoms(d);
- }
-
- renderDenom(d: DenominationRecord) {
- return (
- <div className="tree-item">
- <ul>
- <li>Offered: {d.isOffered ? "yes" : "no"}</li>
- <li>Value: {renderAmount(d.value)}</li>
- <li>Withdraw fee: {renderAmount(d.feeWithdraw)}</li>
- <li>Refresh fee: {renderAmount(d.feeRefresh)}</li>
- <li>Deposit fee: {renderAmount(d.feeDeposit)}</li>
- <li>Refund fee: {renderAmount(d.feeRefund)}</li>
- <li>Start: {getTalerStampDate(d.stampStart)!.toString()}</li>
- <li>Withdraw expiration: {getTalerStampDate(d.stampExpireWithdraw)!.toString()}</li>
- <li>Legal expiration: {getTalerStampDate(d.stampExpireLegal)!.toString()}</li>
- <li>Deposit expiration: {getTalerStampDate(d.stampExpireDeposit)!.toString()}</li>
- <li>Denom pub: <ExpanderText text={d.denomPub} /></li>
- </ul>
- </div>
- );
- }
-
- render(): JSX.Element {
- const denoms = this.denoms();
- if (!denoms) {
- return (
- <div className="tree-item">
- Denominations (...)
- {" "}
- <Toggle expanded={this.expanded}>
- ...
- </Toggle>
- </div>
- );
- }
- return (
- <div className="tree-item">
- Denominations ({denoms.length.toString()})
- {" "}
- <Toggle expanded={this.expanded}>
- {denoms.map((d) => this.renderDenom(d))}
- </Toggle>
- </div>
- );
- }
-}
-
-
-class ReserveList extends ImplicitStateComponent<ReserveListProps> {
- private reserves = this.makeState<ReserveRecord[] | null>(null);
- private expanded = this.makeState<boolean>(false);
-
- constructor(props: ReserveListProps) {
- super(props);
- this.update();
- }
-
- async update() {
- const reserves = await getReserves(this.props.exchangeBaseUrl);
- this.reserves(reserves);
- }
-
- render(): JSX.Element {
- if (!this.reserves()) {
- return <div>...</div>;
- }
- return (
- <div className="tree-item">
- Reserves ({this.reserves() !.length.toString()})
- {" "}
- <Toggle expanded={this.expanded}>
- {this.reserves() !.map((r) => <ReserveView reserve={r} />)}
- </Toggle>
- </div>
- );
- }
-}
-
-interface ExchangeProps {
- exchange: ExchangeRecord;
-}
-
-class ExchangeView extends React.Component<ExchangeProps, {}> {
- render(): JSX.Element {
- const e = this.props.exchange;
- return (
- <div className="tree-item">
- <ul>
- <li>Exchange Base Url: {this.props.exchange.baseUrl}</li>
- <li>Master public key: <ExpanderText text={this.props.exchange.masterPublicKey} /></li>
- </ul>
- <DenominationList exchange={e} />
- <ReserveList exchangeBaseUrl={this.props.exchange.baseUrl} />
- <CoinList exchangeBaseUrl={this.props.exchange.baseUrl} />
- <PreCoinList exchangeBaseUrl={this.props.exchange.baseUrl} />
- </div>
- );
- }
-}
-
-interface ExchangesListState {
- exchanges?: ExchangeRecord[];
-}
-
-class ExchangesList extends React.Component<{}, ExchangesListState> {
- constructor(props: {}) {
- super(props);
- const port = chrome.runtime.connect();
- port.onMessage.addListener((msg: any) => {
- if (msg.notify) {
- console.log("got notified");
- this.update();
- }
- });
- this.update();
- this.state = {} as any;
- }
-
- async update() {
- const exchanges = await getExchanges();
- console.log("exchanges: ", exchanges);
- this.setState({ exchanges });
- }
-
- render(): JSX.Element {
- const exchanges = this.state.exchanges;
- if (!exchanges) {
- return <span>...</span>;
- }
- return (
- <div className="tree-item">
- Exchanges ({exchanges.length.toString()}):
- {exchanges.map((e) => <ExchangeView exchange={e} />)}
- </div>
- );
- }
-}
-
-function main() {
- ReactDOM.render(<ExchangesList />, document.getElementById("container")!);
-}
-
-document.addEventListener("DOMContentLoaded", main);
diff --git a/src/webex/pages/welcome.html b/src/webex/pages/welcome.html
new file mode 100644
index 000000000..9a96d04a7
--- /dev/null
+++ b/src/webex/pages/welcome.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="UTF-8">
+ <title>Taler Wallet: Withdraw</title>
+
+ <link rel="icon" href="/img/icon.png">
+ <link rel="stylesheet" type="text/css" href="../style/pure.css">
+ <link rel="stylesheet" type="text/css" href="../style/wallet.css">
+
+ <script src="/dist/page-common-bundle.js"></script>
+ <script src="/dist/welcome-bundle.js"></script>
+
+</head>
+
+<body>
+ <section id="main">
+ <h1>GNU Taler Wallet Installed!</h1>
+ <div id="container">Loading...</div>
+ </section>
+</body>
+
+</html>
diff --git a/src/webex/pages/welcome.tsx b/src/webex/pages/welcome.tsx
new file mode 100644
index 000000000..1026e6e6e
--- /dev/null
+++ b/src/webex/pages/welcome.tsx
@@ -0,0 +1,113 @@
+/*
+ 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/>
+ */
+
+/**
+ * Welcome page, shown on first installs.
+ *
+ * @author Florian Dold
+ */
+
+import React, { useState, useEffect } from "react";
+import { getDiagnostics } from "../wxApi";
+import { registerMountPage, PageLink } from "../renderHtml";
+import { WalletDiagnostics } from "../../walletTypes";
+
+function Diagnostics() {
+ 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 () => {
+ const d = await getDiagnostics();
+ console.log("got diagnostics", d);
+ gotDiagnostics = true;
+ setDiagnostics(d);
+ };
+ console.log("fetching diagnostics");
+ doFetch();
+ }, []);
+
+ if (timedOut) {
+ return <p>Diagnostics timed out. Could not talk to the wallet backend.</p>;
+ }
+
+ if (diagnostics) {
+ if (diagnostics.errors.length === 0) {
+ return <p>Running diagnostics ... everything looks fine.</p>;
+ } else {
+ return (
+ <div
+ style={{
+ borderLeft: "0.5em solid red",
+ paddingLeft: "1em",
+ paddingTop: "0.2em",
+ paddingBottom: "0.2em",
+ }}
+ >
+ <p>Problems detected:</p>
+ <ol>
+ {diagnostics.errors.map(errMsg => (
+ <li>{errMsg}</li>
+ ))}
+ </ol>
+ {diagnostics.firefoxIdbProblem ? (
+ <p>
+ Please check in your <code>about:config</code> settings that you
+ have IndexedDB enabled (check the preference name{" "}
+ <code>dom.indexedDB.enabled</code>).
+ </p>
+ ) : null}
+ {diagnostics.dbOutdated ? (
+ <p>
+ Your wallet database is outdated. Currently automatic migration is
+ not supported. Please go{" "}
+ <PageLink pageName="reset-required.html">here</PageLink> to reset
+ the wallet database.
+ </p>
+ ) : null}
+ </div>
+ );
+ }
+ }
+
+ return <p>Running diagnostics ...</p>;
+}
+
+function Welcome() {
+ return (
+ <>
+ <p>Thank you for installing the wallet.</p>
+ <h2>First Steps</h2>
+ <p>
+ Check out <a href="https://demo.taler.net/">demo.taler.net</a> for a
+ demo.
+ </p>
+ <h2>Troubleshooting</h2>
+ <Diagnostics />
+ </>
+ );
+}
+
+registerMountPage(() => <Welcome />);
diff --git a/src/webex/pages/withdraw.html b/src/webex/pages/withdraw.html
index 8b1e59b1d..e5c527275 100644
--- a/src/webex/pages/withdraw.html
+++ b/src/webex/pages/withdraw.html
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
- <title>Taler Wallet: Select Taler Provider</title>
+ <title>Taler Wallet: Withdraw</title>
<link rel="icon" href="/img/icon.png">
<link rel="stylesheet" type="text/css" href="../style/pure.css">
diff --git a/src/webex/pages/withdraw.tsx b/src/webex/pages/withdraw.tsx
index 66617373b..39b27f2d8 100644
--- a/src/webex/pages/withdraw.tsx
+++ b/src/webex/pages/withdraw.tsx
@@ -21,21 +21,13 @@
* @author Florian Dold
*/
-import { canonicalizeBaseUrl } from "../../helpers";
-import * as i18n from "../../i18n";
-import { AmountJson } from "../../amounts";
-import * as Amounts from "../../amounts";
+import * as i18n from "../../i18n";
-import { CurrencyRecord } from "../../dbTypes";
import {
- CreateReserveResponse,
- ReserveCreationInfo,
WithdrawDetails,
} from "../../walletTypes";
-import { ImplicitStateComponent, StateHolder } from "../components";
-
import { WithdrawDetailView, renderAmount } from "../renderHtml";
import React, { useState, useEffect } from "react";