2016-01-24 02:29:13 +01:00
|
|
|
/*
|
|
|
|
This file is part of TALER
|
|
|
|
(C) 2016 GNUnet e.V.
|
|
|
|
|
|
|
|
TALER is free software; you can redistribute it and/or modify it under the
|
|
|
|
terms of the GNU General Public License as published by the Free Software
|
|
|
|
Foundation; either version 3, or (at your option) any later version.
|
|
|
|
|
|
|
|
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
|
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License along with
|
2016-07-07 17:59:29 +02:00
|
|
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
2016-01-24 02:29:13 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
2016-03-01 19:46:20 +01:00
|
|
|
/**
|
|
|
|
* Popup shown to the user when they click
|
|
|
|
* the Taler browser action button.
|
|
|
|
*
|
|
|
|
* @author Florian Dold
|
|
|
|
*/
|
|
|
|
|
2017-05-29 15:18:48 +02:00
|
|
|
/**
|
|
|
|
* Imports.
|
|
|
|
*/
|
|
|
|
import { amountToPretty } from "../../helpers";
|
|
|
|
import * as i18n from "../../i18n";
|
2016-10-19 18:40:29 +02:00
|
|
|
import {
|
2017-05-28 23:15:41 +02:00
|
|
|
AmountJson,
|
|
|
|
Amounts,
|
2017-05-30 18:33:28 +02:00
|
|
|
HistoryLevel,
|
|
|
|
HistoryRecord,
|
2017-05-28 23:15:41 +02:00
|
|
|
WalletBalance,
|
2017-05-29 15:18:48 +02:00
|
|
|
WalletBalanceEntry,
|
2017-05-28 23:15:41 +02:00
|
|
|
} from "../../types";
|
|
|
|
|
2017-05-24 16:45:57 +02:00
|
|
|
import { abbrev } from "../renderHtml";
|
2017-05-28 23:15:41 +02:00
|
|
|
|
2017-04-20 03:09:25 +02:00
|
|
|
import * as React from "react";
|
|
|
|
import * as ReactDOM from "react-dom";
|
|
|
|
import URI = require("urijs");
|
2016-01-24 02:29:13 +01:00
|
|
|
|
2016-11-13 08:16:12 +01:00
|
|
|
function onUpdateNotification(f: () => void): () => void {
|
2017-05-29 15:18:48 +02:00
|
|
|
const port = chrome.runtime.connect({name: "notifications"});
|
|
|
|
const listener = () => {
|
2016-02-18 23:41:29 +01:00
|
|
|
f();
|
2016-11-13 08:16:12 +01:00
|
|
|
};
|
|
|
|
port.onMessage.addListener(listener);
|
|
|
|
return () => {
|
|
|
|
port.onMessage.removeListener(listener);
|
2017-05-29 15:18:48 +02:00
|
|
|
};
|
2016-02-18 23:41:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-05-29 15:18:48 +02:00
|
|
|
class Router extends React.Component<any, any> {
|
2016-10-10 00:37:08 +02:00
|
|
|
static setRoute(s: string): void {
|
|
|
|
window.location.hash = s;
|
|
|
|
}
|
|
|
|
|
|
|
|
static getRoute(): string {
|
|
|
|
// Omit the '#' at the beginning
|
|
|
|
return window.location.hash.substring(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static onRoute(f: any): () => void {
|
2016-10-13 02:36:33 +02:00
|
|
|
Router.routeHandlers.push(f);
|
2016-10-10 00:37:08 +02:00
|
|
|
return () => {
|
2017-05-29 15:18:48 +02:00
|
|
|
const i = Router.routeHandlers.indexOf(f);
|
2016-10-10 00:37:08 +02:00
|
|
|
this.routeHandlers = this.routeHandlers.splice(i, 1);
|
2017-05-29 15:18:48 +02:00
|
|
|
};
|
2016-10-10 00:37:08 +02:00
|
|
|
}
|
|
|
|
|
2017-05-29 15:18:48 +02:00
|
|
|
private static routeHandlers: any[] = [];
|
2016-10-10 00:37:08 +02:00
|
|
|
|
|
|
|
componentWillMount() {
|
|
|
|
console.log("router mounted");
|
|
|
|
window.onhashchange = () => {
|
2016-10-10 02:36:12 +02:00
|
|
|
this.setState({});
|
2017-05-29 15:18:48 +02:00
|
|
|
for (const f of Router.routeHandlers) {
|
2016-10-10 00:37:08 +02:00
|
|
|
f();
|
|
|
|
}
|
2017-05-29 15:18:48 +02:00
|
|
|
};
|
2016-10-10 00:37:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
console.log("router unmounted");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-11-13 08:16:12 +01:00
|
|
|
render(): JSX.Element {
|
2017-05-29 15:18:48 +02:00
|
|
|
const route = window.location.hash.substring(1);
|
2016-10-10 00:37:08 +02:00
|
|
|
console.log("rendering route", route);
|
2016-11-13 08:16:12 +01:00
|
|
|
let defaultChild: React.ReactChild|null = null;
|
|
|
|
let foundChild: React.ReactChild|null = null;
|
|
|
|
React.Children.forEach(this.props.children, (child) => {
|
2017-05-29 15:18:48 +02:00
|
|
|
const childProps: any = (child as any).props;
|
2016-11-13 08:16:12 +01:00
|
|
|
if (!childProps) {
|
|
|
|
return;
|
|
|
|
}
|
2017-05-29 15:18:48 +02:00
|
|
|
if (childProps.default) {
|
2016-10-10 00:37:08 +02:00
|
|
|
defaultChild = child;
|
|
|
|
}
|
2017-05-29 15:18:48 +02:00
|
|
|
if (childProps.route === route) {
|
2016-11-13 08:16:12 +01:00
|
|
|
foundChild = child;
|
2016-10-10 00:37:08 +02:00
|
|
|
}
|
2017-05-29 15:18:48 +02:00
|
|
|
});
|
|
|
|
const child: React.ReactChild | null = foundChild || defaultChild;
|
2016-11-13 08:16:12 +01:00
|
|
|
if (!child) {
|
2016-10-10 00:37:08 +02:00
|
|
|
throw Error("unknown route");
|
|
|
|
}
|
2017-05-29 15:18:48 +02:00
|
|
|
Router.setRoute((child as any).props.route);
|
2016-11-13 08:16:12 +01:00
|
|
|
return <div>{child}</div>;
|
2016-10-10 00:37:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-24 02:29:13 +01:00
|
|
|
|
2016-11-13 08:16:12 +01:00
|
|
|
interface TabProps {
|
2016-10-10 00:37:08 +02:00
|
|
|
target: string;
|
2016-11-13 08:16:12 +01:00
|
|
|
children?: React.ReactNode;
|
2016-10-10 00:37:08 +02:00
|
|
|
}
|
2016-01-24 02:29:13 +01:00
|
|
|
|
2016-10-10 00:37:08 +02:00
|
|
|
function Tab(props: TabProps) {
|
2016-01-24 02:29:13 +01:00
|
|
|
let cssClass = "";
|
2017-05-29 15:18:48 +02:00
|
|
|
if (props.target === Router.getRoute()) {
|
2016-01-24 02:29:13 +01:00
|
|
|
cssClass = "active";
|
|
|
|
}
|
2017-05-29 15:18:48 +02:00
|
|
|
const onClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
2016-10-10 00:37:08 +02:00
|
|
|
Router.setRoute(props.target);
|
|
|
|
e.preventDefault();
|
|
|
|
};
|
|
|
|
return (
|
|
|
|
<a onClick={onClick} href={props.target} className={cssClass}>
|
|
|
|
{props.children}
|
|
|
|
</a>
|
|
|
|
);
|
2016-01-24 02:29:13 +01:00
|
|
|
}
|
|
|
|
|
2016-10-10 00:37:08 +02:00
|
|
|
|
2017-05-29 15:18:48 +02:00
|
|
|
class WalletNavBar extends React.Component<any, any> {
|
|
|
|
private cancelSubscription: any;
|
2016-10-10 00:37:08 +02:00
|
|
|
|
|
|
|
componentWillMount() {
|
|
|
|
this.cancelSubscription = Router.onRoute(() => {
|
|
|
|
this.setState({});
|
|
|
|
});
|
2016-01-24 02:29:13 +01:00
|
|
|
}
|
2016-02-18 23:41:29 +01:00
|
|
|
|
2016-10-10 00:37:08 +02:00
|
|
|
componentWillUnmount() {
|
|
|
|
if (this.cancelSubscription) {
|
|
|
|
this.cancelSubscription();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
console.log("rendering nav bar");
|
|
|
|
return (
|
2016-11-13 08:16:12 +01:00
|
|
|
<div className="nav" id="header">
|
2016-10-10 00:37:08 +02:00
|
|
|
<Tab target="/balance">
|
2016-11-27 22:13:24 +01:00
|
|
|
{i18n.str`Balance`}
|
2016-10-10 00:37:08 +02:00
|
|
|
</Tab>
|
|
|
|
<Tab target="/history">
|
2016-11-27 22:13:24 +01:00
|
|
|
{i18n.str`History`}
|
2016-10-10 00:37:08 +02:00
|
|
|
</Tab>
|
|
|
|
<Tab target="/debug">
|
2016-11-27 22:13:24 +01:00
|
|
|
{i18n.str`Debug`}
|
2016-10-10 00:37:08 +02:00
|
|
|
</Tab>
|
|
|
|
</div>);
|
2016-02-18 23:41:29 +01:00
|
|
|
}
|
|
|
|
}
|
2016-01-24 02:29:13 +01:00
|
|
|
|
|
|
|
|
2016-10-10 00:37:08 +02:00
|
|
|
function ExtensionLink(props: any) {
|
2017-05-29 15:18:48 +02:00
|
|
|
const onClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
2016-01-24 02:29:13 +01:00
|
|
|
chrome.tabs.create({
|
2017-05-29 15:18:48 +02:00
|
|
|
url: chrome.extension.getURL(props.target),
|
|
|
|
});
|
2016-01-24 02:29:13 +01:00
|
|
|
e.preventDefault();
|
2016-10-10 00:37:08 +02:00
|
|
|
};
|
|
|
|
return (
|
2016-10-10 03:16:12 +02:00
|
|
|
<a onClick={onClick} href={props.target}>
|
2016-10-10 00:37:08 +02:00
|
|
|
{props.children}
|
2017-05-29 15:18:48 +02:00
|
|
|
</a>
|
|
|
|
);
|
2016-01-24 02:29:13 +01:00
|
|
|
}
|
|
|
|
|
2016-11-28 08:19:06 +01:00
|
|
|
|
2017-05-29 15:18:48 +02:00
|
|
|
/**
|
|
|
|
* Render an amount as a large number with a small currency symbol.
|
|
|
|
*/
|
|
|
|
function bigAmount(amount: AmountJson): JSX.Element {
|
|
|
|
const v = amount.value + amount.fraction / Amounts.fractionalBase;
|
2016-11-28 08:19:06 +01:00
|
|
|
return (
|
|
|
|
<span>
|
|
|
|
<span style={{fontSize: "300%"}}>{v}</span>
|
|
|
|
{" "}
|
|
|
|
<span>{amount.currency}</span>
|
|
|
|
</span>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-11-13 08:16:12 +01:00
|
|
|
class WalletBalanceView extends React.Component<any, any> {
|
2017-05-29 15:18:48 +02:00
|
|
|
private balance: WalletBalance;
|
|
|
|
private gotError = false;
|
|
|
|
private canceler: (() => void) | undefined = undefined;
|
|
|
|
private unmount = false;
|
2016-09-14 15:20:18 +02:00
|
|
|
|
2016-10-10 00:37:08 +02:00
|
|
|
componentWillMount() {
|
2016-11-13 08:16:12 +01:00
|
|
|
this.canceler = onUpdateNotification(() => this.updateBalance());
|
2016-10-10 00:37:08 +02:00
|
|
|
this.updateBalance();
|
2016-11-13 08:16:12 +01:00
|
|
|
}
|
2016-02-18 23:41:29 +01:00
|
|
|
|
2016-11-13 08:16:12 +01:00
|
|
|
componentWillUnmount() {
|
|
|
|
console.log("component WalletBalanceView will unmount");
|
|
|
|
if (this.canceler) {
|
|
|
|
this.canceler();
|
|
|
|
}
|
|
|
|
this.unmount = true;
|
2016-10-10 00:37:08 +02:00
|
|
|
}
|
2016-02-18 23:41:29 +01:00
|
|
|
|
2016-10-10 00:37:08 +02:00
|
|
|
updateBalance() {
|
|
|
|
chrome.runtime.sendMessage({type: "balances"}, (resp) => {
|
2016-11-13 08:16:12 +01:00
|
|
|
if (this.unmount) {
|
|
|
|
return;
|
|
|
|
}
|
2016-10-10 00:37:08 +02:00
|
|
|
if (resp.error) {
|
|
|
|
this.gotError = true;
|
|
|
|
console.error("could not retrieve balances", resp);
|
2016-10-18 02:40:46 +02:00
|
|
|
this.setState({});
|
2016-10-10 00:37:08 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.gotError = false;
|
|
|
|
console.log("got wallet", resp);
|
2016-10-19 18:40:29 +02:00
|
|
|
this.balance = resp;
|
2016-10-18 02:40:46 +02:00
|
|
|
this.setState({});
|
2016-10-10 00:37:08 +02:00
|
|
|
});
|
2016-02-18 23:41:29 +01:00
|
|
|
}
|
|
|
|
|
2016-10-19 18:40:29 +02:00
|
|
|
renderEmpty(): JSX.Element {
|
2017-05-29 15:18:48 +02:00
|
|
|
const helpLink = (
|
2017-05-29 01:48:46 +02:00
|
|
|
<ExtensionLink target="/src/webex/pages/help/empty-wallet.html">
|
2016-11-27 22:13:24 +01:00
|
|
|
{i18n.str`help`}
|
2016-10-10 03:16:12 +02:00
|
|
|
</ExtensionLink>
|
|
|
|
);
|
2016-11-17 02:58:27 +01:00
|
|
|
return (
|
|
|
|
<div>
|
2017-04-29 00:05:39 +02:00
|
|
|
<i18n.Translate wrap="p">
|
2016-11-17 02:58:27 +01:00
|
|
|
You have no balance to show. Need some
|
2016-11-23 01:14:45 +01:00
|
|
|
{" "}<span>{helpLink}</span>{" "}
|
2016-11-17 02:58:27 +01:00
|
|
|
getting started?
|
|
|
|
</i18n.Translate>
|
|
|
|
</div>
|
|
|
|
);
|
2016-10-19 18:40:29 +02:00
|
|
|
}
|
|
|
|
|
2016-10-19 22:49:03 +02:00
|
|
|
formatPending(entry: WalletBalanceEntry): JSX.Element {
|
|
|
|
let incoming: JSX.Element | undefined;
|
|
|
|
let payment: JSX.Element | undefined;
|
|
|
|
|
2017-05-24 16:45:57 +02:00
|
|
|
console.log("available: ", entry.pendingIncoming ? amountToPretty(entry.available) : null);
|
|
|
|
console.log("incoming: ", entry.pendingIncoming ? amountToPretty(entry.pendingIncoming) : null);
|
2016-10-19 22:49:03 +02:00
|
|
|
|
|
|
|
if (Amounts.isNonZero(entry.pendingIncoming)) {
|
|
|
|
incoming = (
|
2016-11-23 01:14:45 +01:00
|
|
|
<i18n.Translate wrap="span">
|
2016-11-13 08:16:12 +01:00
|
|
|
<span style={{color: "darkgreen"}}>
|
2016-10-19 22:49:03 +02:00
|
|
|
{"+"}
|
2017-05-24 16:45:57 +02:00
|
|
|
{amountToPretty(entry.pendingIncoming)}
|
2016-10-19 22:49:03 +02:00
|
|
|
</span>
|
|
|
|
{" "}
|
|
|
|
incoming
|
2016-11-23 01:14:45 +01:00
|
|
|
</i18n.Translate>
|
|
|
|
);
|
2016-10-19 22:49:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (Amounts.isNonZero(entry.pendingPayment)) {
|
|
|
|
payment = (
|
2016-11-23 01:14:45 +01:00
|
|
|
<i18n.Translate wrap="span">
|
2016-11-13 08:16:12 +01:00
|
|
|
<span style={{color: "darkblue"}}>
|
2017-05-24 16:45:57 +02:00
|
|
|
{amountToPretty(entry.pendingPayment)}
|
2016-10-19 22:49:03 +02:00
|
|
|
</span>
|
|
|
|
{" "}
|
|
|
|
being spent
|
2016-11-23 01:14:45 +01:00
|
|
|
</i18n.Translate>
|
|
|
|
);
|
2016-10-19 22:49:03 +02:00
|
|
|
}
|
|
|
|
|
2017-05-29 15:18:48 +02:00
|
|
|
const l = [incoming, payment].filter((x) => x !== undefined);
|
|
|
|
if (l.length === 0) {
|
2016-10-19 22:49:03 +02:00
|
|
|
return <span />;
|
|
|
|
}
|
|
|
|
|
2017-05-29 15:18:48 +02:00
|
|
|
if (l.length === 1) {
|
|
|
|
return <span>({l})</span>;
|
2016-10-19 22:49:03 +02:00
|
|
|
}
|
|
|
|
return <span>({l[0]}, {l[1]})</span>;
|
|
|
|
|
2016-10-10 03:16:12 +02:00
|
|
|
}
|
|
|
|
|
2016-10-10 00:37:08 +02:00
|
|
|
render(): JSX.Element {
|
2017-05-29 15:18:48 +02:00
|
|
|
const wallet = this.balance;
|
2016-10-10 00:37:08 +02:00
|
|
|
if (this.gotError) {
|
2016-11-27 22:13:24 +01:00
|
|
|
return i18n.str`Error: could not retrieve balance information.`;
|
2016-03-02 00:47:00 +01:00
|
|
|
}
|
2016-01-24 02:29:13 +01:00
|
|
|
if (!wallet) {
|
2016-10-20 01:37:00 +02:00
|
|
|
return <span></span>;
|
2016-01-24 02:29:13 +01:00
|
|
|
}
|
2016-10-10 00:37:08 +02:00
|
|
|
console.log(wallet);
|
2017-05-01 04:05:16 +02:00
|
|
|
let paybackAvailable = false;
|
2017-05-29 15:18:48 +02:00
|
|
|
const listing = Object.keys(wallet).map((key) => {
|
|
|
|
const entry: WalletBalanceEntry = wallet[key];
|
|
|
|
if (entry.paybackAmount.value !== 0 || entry.paybackAmount.fraction !== 0) {
|
2017-05-01 04:05:16 +02:00
|
|
|
paybackAvailable = true;
|
|
|
|
}
|
2016-10-19 18:40:29 +02:00
|
|
|
return (
|
|
|
|
<p>
|
2016-11-28 08:19:06 +01:00
|
|
|
{bigAmount(entry.available)}
|
2016-10-19 22:49:03 +02:00
|
|
|
{" "}
|
|
|
|
{this.formatPending(entry)}
|
2016-10-19 18:40:29 +02:00
|
|
|
</p>
|
|
|
|
);
|
2016-10-10 00:37:08 +02:00
|
|
|
});
|
2017-05-29 15:18:48 +02:00
|
|
|
const link = chrome.extension.getURL("/src/webex/pages/auditors.html");
|
|
|
|
const linkElem = <a className="actionLink" href={link} target="_blank">Trusted Auditors and Exchanges</a>;
|
|
|
|
const paybackLinkElem = <a className="actionLink" href={link} target="_blank">Trusted Auditors and Exchanges</a>;
|
2017-04-28 23:28:27 +02:00
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
{listing.length > 0 ? listing : this.renderEmpty()}
|
2017-05-01 04:05:16 +02:00
|
|
|
{paybackAvailable && paybackLinkElem}
|
2017-04-28 23:28:27 +02:00
|
|
|
{linkElem}
|
|
|
|
</div>
|
|
|
|
);
|
2016-01-24 02:29:13 +01:00
|
|
|
}
|
2016-02-18 23:41:29 +01:00
|
|
|
}
|
2016-01-24 02:29:13 +01:00
|
|
|
|
|
|
|
|
2016-09-28 23:41:34 +02:00
|
|
|
function formatHistoryItem(historyItem: HistoryRecord) {
|
2016-01-24 02:29:13 +01:00
|
|
|
const d = historyItem.detail;
|
2016-02-22 23:21:41 +01:00
|
|
|
console.log("hist item", historyItem);
|
2016-01-24 02:29:13 +01:00
|
|
|
switch (historyItem.type) {
|
|
|
|
case "create-reserve":
|
2016-10-10 00:37:08 +02:00
|
|
|
return (
|
2016-11-26 17:27:33 +01:00
|
|
|
<i18n.Translate wrap="p">
|
2017-05-29 15:18:48 +02:00
|
|
|
Bank requested reserve (<span>{abbrev(d.reservePub)}</span>) for
|
|
|
|
{" "}
|
|
|
|
<span>{amountToPretty(d.requestedAmount)}</span>.
|
2016-11-26 17:27:33 +01:00
|
|
|
</i18n.Translate>
|
2016-10-10 00:37:08 +02:00
|
|
|
);
|
2016-10-10 03:16:12 +02:00
|
|
|
case "confirm-reserve": {
|
|
|
|
// FIXME: eventually remove compat fix
|
2017-05-29 15:18:48 +02:00
|
|
|
const exchange = d.exchangeBaseUrl ? (new URI(d.exchangeBaseUrl)).host() : "??";
|
|
|
|
const pub = abbrev(d.reservePub);
|
2016-10-10 00:37:08 +02:00
|
|
|
return (
|
2016-11-26 17:27:33 +01:00
|
|
|
<i18n.Translate wrap="p">
|
|
|
|
Started to withdraw
|
2017-05-24 16:45:57 +02:00
|
|
|
{" "}{amountToPretty(d.requestedAmount)}{" "}
|
2016-11-26 17:27:33 +01:00
|
|
|
from <span>{exchange}</span> (<span>{pub}</span>).
|
|
|
|
</i18n.Translate>
|
2016-10-10 00:37:08 +02:00
|
|
|
);
|
2016-10-10 03:16:12 +02:00
|
|
|
}
|
2016-09-29 01:40:29 +02:00
|
|
|
case "offer-contract": {
|
2017-05-29 15:18:48 +02:00
|
|
|
const link = chrome.extension.getURL("view-contract.html");
|
2016-10-10 00:37:08 +02:00
|
|
|
return (
|
2016-11-26 17:27:33 +01:00
|
|
|
<i18n.Translate wrap="p">
|
|
|
|
Merchant <em>{abbrev(d.merchantName, 15)}</em> offered contract <a href={link}>{abbrev(d.contractHash)}</a>;
|
|
|
|
</i18n.Translate>
|
2016-10-10 00:37:08 +02:00
|
|
|
);
|
2016-09-29 01:40:29 +02:00
|
|
|
}
|
2016-10-10 03:16:12 +02:00
|
|
|
case "depleted-reserve": {
|
2017-05-29 15:18:48 +02:00
|
|
|
const exchange = d.exchangeBaseUrl ? (new URI(d.exchangeBaseUrl)).host() : "??";
|
|
|
|
const amount = amountToPretty(d.requestedAmount);
|
|
|
|
const pub = abbrev(d.reservePub);
|
2016-11-23 01:14:45 +01:00
|
|
|
return (
|
2016-11-26 17:27:33 +01:00
|
|
|
<i18n.Translate wrap="p">
|
|
|
|
Withdrew <span>{amount}</span> from <span>{exchange}</span> (<span>{pub}</span>).
|
|
|
|
</i18n.Translate>
|
2016-11-23 01:14:45 +01:00
|
|
|
);
|
2016-10-10 03:16:12 +02:00
|
|
|
}
|
2016-09-29 01:40:29 +02:00
|
|
|
case "pay": {
|
2017-05-29 15:18:48 +02:00
|
|
|
const url = d.fulfillmentUrl;
|
|
|
|
const merchantElem = <em>{abbrev(d.merchantName, 15)}</em>;
|
|
|
|
const fulfillmentLinkElem = <a href={url} onClick={openTab(url)}>view product</a>;
|
2016-10-10 00:37:08 +02:00
|
|
|
return (
|
2016-11-26 17:27:33 +01:00
|
|
|
<i18n.Translate wrap="p">
|
2017-05-29 15:18:48 +02:00
|
|
|
Paid <span>{amountToPretty(d.amount)}</span> to merchant <span>{merchantElem}</span>.
|
|
|
|
{" "}
|
|
|
|
(<span>{fulfillmentLinkElem}</span>)
|
2016-11-26 17:27:33 +01:00
|
|
|
</i18n.Translate>
|
|
|
|
);
|
2016-09-29 01:40:29 +02:00
|
|
|
}
|
2016-01-24 02:29:13 +01:00
|
|
|
default:
|
2016-11-27 22:13:24 +01:00
|
|
|
return (<p>{i18n.str`Unknown event (${historyItem.type})`}</p>);
|
2016-01-24 02:29:13 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-11-13 08:16:12 +01:00
|
|
|
class WalletHistory extends React.Component<any, any> {
|
2017-05-29 15:18:48 +02:00
|
|
|
private myHistory: any[];
|
|
|
|
private gotError = false;
|
|
|
|
private unmounted = false;
|
2016-02-18 23:41:29 +01:00
|
|
|
|
2016-10-10 00:37:08 +02:00
|
|
|
componentWillMount() {
|
|
|
|
this.update();
|
|
|
|
onUpdateNotification(() => this.update());
|
|
|
|
}
|
2016-02-18 23:41:29 +01:00
|
|
|
|
2016-11-13 08:16:12 +01:00
|
|
|
componentWillUnmount() {
|
|
|
|
console.log("history component unmounted");
|
|
|
|
this.unmounted = true;
|
|
|
|
}
|
|
|
|
|
2016-10-10 00:37:08 +02:00
|
|
|
update() {
|
|
|
|
chrome.runtime.sendMessage({type: "get-history"}, (resp) => {
|
2016-11-13 08:16:12 +01:00
|
|
|
if (this.unmounted) {
|
|
|
|
return;
|
|
|
|
}
|
2016-10-10 00:37:08 +02:00
|
|
|
console.log("got history response");
|
|
|
|
if (resp.error) {
|
|
|
|
this.gotError = true;
|
|
|
|
console.error("could not retrieve history", resp);
|
2016-10-18 02:58:46 +02:00
|
|
|
this.setState({});
|
2016-10-10 00:37:08 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.gotError = false;
|
|
|
|
console.log("got history", resp.history);
|
|
|
|
this.myHistory = resp.history;
|
2016-10-18 02:58:46 +02:00
|
|
|
this.setState({});
|
2016-10-10 00:37:08 +02:00
|
|
|
});
|
2016-02-18 23:41:29 +01:00
|
|
|
}
|
|
|
|
|
2016-10-10 00:37:08 +02:00
|
|
|
render(): JSX.Element {
|
|
|
|
console.log("rendering history");
|
2017-05-29 15:18:48 +02:00
|
|
|
const history: HistoryRecord[] = this.myHistory;
|
2016-10-10 00:37:08 +02:00
|
|
|
if (this.gotError) {
|
2016-11-27 22:13:24 +01:00
|
|
|
return i18n.str`Error: could not retrieve event history`;
|
2016-03-02 00:47:00 +01:00
|
|
|
}
|
2016-10-10 00:37:08 +02:00
|
|
|
|
2016-01-24 02:29:13 +01:00
|
|
|
if (!history) {
|
2016-10-10 00:37:08 +02:00
|
|
|
// We're not ready yet
|
|
|
|
return <span />;
|
2016-01-24 02:29:13 +01:00
|
|
|
}
|
2016-09-28 23:41:34 +02:00
|
|
|
|
2017-05-29 15:18:48 +02:00
|
|
|
const subjectMemo: {[s: string]: boolean} = {};
|
|
|
|
const listing: any[] = [];
|
|
|
|
for (const record of history.reverse()) {
|
2016-09-29 01:40:29 +02:00
|
|
|
if (record.subjectId && subjectMemo[record.subjectId]) {
|
|
|
|
continue;
|
|
|
|
}
|
2017-05-29 15:18:48 +02:00
|
|
|
if (record.level !== undefined && record.level < HistoryLevel.User) {
|
2016-09-29 01:40:29 +02:00
|
|
|
continue;
|
|
|
|
}
|
2016-09-28 23:41:34 +02:00
|
|
|
subjectMemo[record.subjectId as string] = true;
|
2016-09-29 01:40:29 +02:00
|
|
|
|
2017-05-29 15:18:48 +02:00
|
|
|
const item = (
|
2016-10-10 00:37:08 +02:00
|
|
|
<div className="historyItem">
|
|
|
|
<div className="historyDate">
|
|
|
|
{(new Date(record.timestamp)).toString()}
|
|
|
|
</div>
|
|
|
|
{formatHistoryItem(record)}
|
|
|
|
</div>
|
|
|
|
);
|
2016-09-29 01:40:29 +02:00
|
|
|
|
|
|
|
listing.push(item);
|
2016-09-28 23:41:34 +02:00
|
|
|
}
|
|
|
|
|
2016-01-24 02:29:13 +01:00
|
|
|
if (listing.length > 0) {
|
2016-10-10 00:37:08 +02:00
|
|
|
return <div className="container">{listing}</div>;
|
2016-01-24 02:29:13 +01:00
|
|
|
}
|
2017-05-29 15:18:48 +02:00
|
|
|
return <p>{i18n.str`Your wallet has no events recorded.`}</p>;
|
2016-01-24 02:29:13 +01:00
|
|
|
}
|
2016-10-10 00:37:08 +02:00
|
|
|
|
2016-02-18 23:41:29 +01:00
|
|
|
}
|
2016-01-24 02:29:13 +01:00
|
|
|
|
|
|
|
|
2016-01-26 17:21:17 +01:00
|
|
|
function reload() {
|
|
|
|
try {
|
|
|
|
chrome.runtime.reload();
|
|
|
|
window.close();
|
|
|
|
} catch (e) {
|
|
|
|
// Functionality missing in firefox, ignore!
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-24 02:29:13 +01:00
|
|
|
function confirmReset() {
|
|
|
|
if (confirm("Do you want to IRREVOCABLY DESTROY everything inside your" +
|
|
|
|
" wallet and LOSE ALL YOUR COINS?")) {
|
|
|
|
chrome.runtime.sendMessage({type: "reset"});
|
|
|
|
window.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-10-10 00:37:08 +02:00
|
|
|
function WalletDebug(props: any) {
|
|
|
|
return (<div>
|
|
|
|
<p>Debug tools:</p>
|
2017-05-29 01:48:46 +02:00
|
|
|
<button onClick={openExtensionPage("/src/webex/pages/popup.html")}>
|
2016-10-10 00:37:08 +02:00
|
|
|
wallet tab
|
|
|
|
</button>
|
2017-05-29 01:48:46 +02:00
|
|
|
<button onClick={openExtensionPage("/src/webex/pages/show-db.html")}>
|
2016-10-10 00:37:08 +02:00
|
|
|
show db
|
|
|
|
</button>
|
2017-05-29 01:48:46 +02:00
|
|
|
<button onClick={openExtensionPage("/src/webex/pages/tree.html")}>
|
2016-10-13 02:36:33 +02:00
|
|
|
show tree
|
|
|
|
</button>
|
2017-05-29 01:48:46 +02:00
|
|
|
<button onClick={openExtensionPage("/src/webex/pages/logs.html")}>
|
2016-11-18 04:09:04 +01:00
|
|
|
show logs
|
|
|
|
</button>
|
2016-10-10 00:37:08 +02:00
|
|
|
<br />
|
|
|
|
<button onClick={confirmReset}>
|
|
|
|
reset
|
|
|
|
</button>
|
|
|
|
<button onClick={reload}>
|
|
|
|
reload chrome extension
|
|
|
|
</button>
|
|
|
|
</div>);
|
|
|
|
}
|
2016-01-24 02:29:13 +01:00
|
|
|
|
|
|
|
|
2016-09-12 20:25:56 +02:00
|
|
|
function openExtensionPage(page: string) {
|
2017-05-29 15:18:48 +02:00
|
|
|
return () => {
|
2016-01-26 17:21:17 +01:00
|
|
|
chrome.tabs.create({
|
2017-05-29 15:18:48 +02:00
|
|
|
url: chrome.extension.getURL(page),
|
2016-11-26 17:27:33 +01:00
|
|
|
});
|
2017-05-29 15:18:48 +02:00
|
|
|
};
|
2016-01-24 02:29:13 +01:00
|
|
|
}
|
2016-01-26 17:21:17 +01:00
|
|
|
|
2016-02-01 15:10:20 +01:00
|
|
|
|
2016-09-12 20:25:56 +02:00
|
|
|
function openTab(page: string) {
|
2017-05-29 15:18:48 +02:00
|
|
|
return () => {
|
2016-02-01 15:10:20 +01:00
|
|
|
chrome.tabs.create({
|
2017-05-29 15:18:48 +02:00
|
|
|
url: page,
|
2016-11-26 17:27:33 +01:00
|
|
|
});
|
2017-05-29 15:18:48 +02:00
|
|
|
};
|
2016-02-01 15:10:20 +01:00
|
|
|
}
|
2017-04-20 03:09:25 +02:00
|
|
|
|
|
|
|
|
2017-05-29 15:18:48 +02:00
|
|
|
const el = (
|
2017-04-20 03:09:25 +02:00
|
|
|
<div>
|
|
|
|
<WalletNavBar />
|
|
|
|
<div style={{margin: "1em"}}>
|
|
|
|
<Router>
|
|
|
|
<WalletBalanceView route="/balance" default/>
|
|
|
|
<WalletHistory route="/history"/>
|
|
|
|
<WalletDebug route="/debug"/>
|
|
|
|
</Router>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
|
|
ReactDOM.render(el, document.getElementById("content")!);
|
2017-05-29 15:18:48 +02:00
|
|
|
});
|