diff options
author | Florian Dold <florian.dold@gmail.com> | 2017-05-28 23:15:41 +0200 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2017-05-28 23:15:41 +0200 |
commit | b6e774585d32017e5f1ceeeb2b2e2a5e350354d3 (patch) | |
tree | 080cb5afe3b48c0428abd2d7de1ff7fe34d9b9b1 /src/pages | |
parent | 38a74188d759444d7e1abac856f78ae710e2a4c5 (diff) |
move webex specific things in their own directory
Diffstat (limited to 'src/pages')
-rw-r--r-- | src/pages/add-auditor.html | 34 | ||||
-rw-r--r-- | src/pages/add-auditor.tsx | 125 | ||||
-rw-r--r-- | src/pages/auditors.html | 36 | ||||
-rw-r--r-- | src/pages/auditors.tsx | 146 | ||||
-rw-r--r-- | src/pages/confirm-contract.html | 69 | ||||
-rw-r--r-- | src/pages/confirm-contract.tsx | 240 | ||||
-rw-r--r-- | src/pages/confirm-create-reserve.html | 52 | ||||
-rw-r--r-- | src/pages/confirm-create-reserve.tsx | 639 | ||||
-rw-r--r-- | src/pages/error.html | 18 | ||||
-rw-r--r-- | src/pages/error.tsx | 63 | ||||
-rw-r--r-- | src/pages/help/empty-wallet.html | 30 | ||||
-rw-r--r-- | src/pages/logs.html | 27 | ||||
-rw-r--r-- | src/pages/logs.tsx | 82 | ||||
-rw-r--r-- | src/pages/payback.html | 36 | ||||
-rw-r--r-- | src/pages/payback.tsx | 98 | ||||
-rw-r--r-- | src/pages/popup.css | 84 | ||||
-rw-r--r-- | src/pages/popup.html | 18 | ||||
-rw-r--r-- | src/pages/popup.tsx | 545 | ||||
-rw-r--r-- | src/pages/show-db.html | 18 | ||||
-rw-r--r-- | src/pages/show-db.ts | 94 | ||||
-rw-r--r-- | src/pages/tree.html | 27 | ||||
-rw-r--r-- | src/pages/tree.tsx | 436 |
22 files changed, 0 insertions, 2917 deletions
diff --git a/src/pages/add-auditor.html b/src/pages/add-auditor.html deleted file mode 100644 index b7a9d041d..000000000 --- a/src/pages/add-auditor.html +++ /dev/null @@ -1,34 +0,0 @@ -<!DOCTYPE html> -<html> - -<head> - <meta charset="UTF-8"> - - <title>Taler Wallet: Add Auditor</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/add-auditor-bundle.js"></script> - - <style> - .tree-item { - margin: 2em; - border-radius: 5px; - border: 1px solid gray; - padding: 1em; - } - .button-linky { - background: none; - color: black; - text-decoration: underline; - border: none; - } - </style> - - <body> - <div id="container"></div> - </body> -</html> diff --git a/src/pages/add-auditor.tsx b/src/pages/add-auditor.tsx deleted file mode 100644 index 8bef557d9..000000000 --- a/src/pages/add-auditor.tsx +++ /dev/null @@ -1,125 +0,0 @@ -/* - This file is part of TALER - (C) 2017 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/> - */ - -/** - * View and edit auditors. - * - * @author Florian Dold - */ - - -import { - ExchangeRecord, - DenominationRecord, - AuditorRecord, - CurrencyRecord, - ReserveRecord, - CoinRecord, - PreCoinRecord, - Denomination -} from "../types"; -import { ImplicitStateComponent, StateHolder } from "../components"; -import { - getCurrencies, - updateCurrency, -} from "../wxApi"; -import { getTalerStampDate } from "../helpers"; - -import * as React from "react"; -import * as ReactDOM from "react-dom"; -import URI = require("urijs"); - -interface ConfirmAuditorProps { - url: string; - currency: string; - auditorPub: string; - expirationStamp: number; -} - -class ConfirmAuditor extends ImplicitStateComponent<ConfirmAuditorProps> { - addDone: StateHolder<boolean> = this.makeState(false); - constructor() { - super(); - } - - async add() { - let currencies = await getCurrencies(); - let currency: CurrencyRecord|undefined = undefined; - - for (let c of currencies) { - if (c.name == this.props.currency) { - currency = c; - } - } - - if (!currency) { - currency = { name: this.props.currency, auditors: [], fractionalDigits: 2, exchanges: [] }; - } - - let newAuditor = { auditorPub: this.props.auditorPub, baseUrl: this.props.url, expirationStamp: this.props.expirationStamp }; - - let auditorFound = false; - for (let idx in currency.auditors) { - let a = currency.auditors[idx]; - if (a.baseUrl == this.props.url) { - auditorFound = true; - // Update auditor if already found by URL. - currency.auditors[idx] = newAuditor; - } - } - - if (!auditorFound) { - currency.auditors.push(newAuditor); - } - - await updateCurrency(currency); - - this.addDone(true); - } - - 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/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> - ); - } -} - -export function main() { - const walletPageUrl = new URI(document.location.href); - 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); diff --git a/src/pages/auditors.html b/src/pages/auditors.html deleted file mode 100644 index cbfc3b4b5..000000000 --- a/src/pages/auditors.html +++ /dev/null @@ -1,36 +0,0 @@ -<!DOCTYPE html> -<html> - -<head> - <meta charset="UTF-8"> - <title>Taler Wallet: Auditors</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/auditors-bundle.js"></script> - - <style> - body { - font-size: 100%; - } - .tree-item { - margin: 2em; - border-radius: 5px; - border: 1px solid gray; - padding: 1em; - } - .button-linky { - background: none; - color: black; - text-decoration: underline; - border: none; - } - </style> - - <body> - <div id="container"></div> - </body> -</html> diff --git a/src/pages/auditors.tsx b/src/pages/auditors.tsx deleted file mode 100644 index f263d2ec9..000000000 --- a/src/pages/auditors.tsx +++ /dev/null @@ -1,146 +0,0 @@ -/* - This file is part of TALER - (C) 2017 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/> - */ - -/** - * View and edit auditors. - * - * @author Florian Dold - */ - - -import { - ExchangeRecord, - ExchangeForCurrencyRecord, - DenominationRecord, - AuditorRecord, - CurrencyRecord, - ReserveRecord, - CoinRecord, - PreCoinRecord, - Denomination -} from "../types"; -import { ImplicitStateComponent, StateHolder } from "../components"; -import { - getCurrencies, - updateCurrency, -} from "../wxApi"; -import { getTalerStampDate } from "../helpers"; -import * as React from "react"; -import * as ReactDOM from "react-dom"; - -interface CurrencyListState { - currencies?: CurrencyRecord[]; -} - -class CurrencyList extends React.Component<any, CurrencyListState> { - constructor() { - super(); - let 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() { - let currencies = await getCurrencies(); - console.log("currencies: ", currencies); - this.setState({ currencies }); - } - - async confirmRemoveAuditor(c: CurrencyRecord, a: AuditorRecord) { - if (window.confirm(`Do you really want to remove auditor ${a.baseUrl} for currency ${c.name}?`)) { - c.auditors = c.auditors.filter((x) => x.auditorPub != a.auditorPub); - await updateCurrency(c); - } - } - - async confirmRemoveExchange(c: CurrencyRecord, e: ExchangeForCurrencyRecord) { - if (window.confirm(`Do you really want to remove exchange ${e.baseUrl} for currency ${c.name}?`)) { - c.exchanges = c.exchanges.filter((x) => x.baseUrl != e.baseUrl); - await updateCurrency(c); - } - } - - renderAuditors(c: CurrencyRecord): any { - if (c.auditors.length == 0) { - return <p>No trusted auditors for this currency.</p> - } - return ( - <div> - <p>Trusted Auditors:</p> - <ul> - {c.auditors.map(a => ( - <li>{a.baseUrl} <button className="pure-button button-destructive" onClick={() => this.confirmRemoveAuditor(c, a)}>Remove</button> - <ul> - <li>valid until {new Date(a.expirationStamp).toString()}</li> - <li>public key {a.auditorPub}</li> - </ul> - </li> - ))} - </ul> - </div> - ); - } - - renderExchanges(c: CurrencyRecord): any { - if (c.exchanges.length == 0) { - return <p>No trusted exchanges for this currency.</p> - } - return ( - <div> - <p>Trusted Exchanges:</p> - <ul> - {c.exchanges.map(e => ( - <li>{e.baseUrl} <button className="pure-button button-destructive" onClick={() => this.confirmRemoveExchange(c, e)}>Remove</button> - </li> - ))} - </ul> - </div> - ); - } - - render(): JSX.Element { - let currencies = this.state.currencies; - if (!currencies) { - return <span>...</span>; - } - return ( - <div id="main"> - {currencies.map(c => ( - <div> - <h1>Currency {c.name}</h1> - <p>Displayed with {c.fractionalDigits} fractional digits.</p> - <h2>Auditors</h2> - <div>{this.renderAuditors(c)}</div> - <h2>Exchanges</h2> - <div>{this.renderExchanges(c)}</div> - </div> - ))} - </div> - ); - } -} - -export function main() { - ReactDOM.render(<CurrencyList />, document.getElementById("container")!); -} - -document.addEventListener("DOMContentLoaded", main); diff --git a/src/pages/confirm-contract.html b/src/pages/confirm-contract.html deleted file mode 100644 index 6713b2e2c..000000000 --- a/src/pages/confirm-contract.html +++ /dev/null @@ -1,69 +0,0 @@ -<!DOCTYPE html> -<html> - -<head> - <meta charset="UTF-8"> - <title>Taler Wallet: Confirm Reserve Creation</title> - - <link rel="stylesheet" type="text/css" href="/src/style/wallet.css"> - - <link rel="icon" href="/img/icon.png"> - - <script src="/dist/page-common-bundle.js"></script> - <script src="/dist/confirm-contract-bundle.js"></script> - - <style> - button.accept { - background-color: #5757D2; - border: 1px solid black; - border-radius: 5px; - margin: 1em 0; - padding: 0.5em; - font-weight: bold; - color: white; - } - button.linky { - background:none!important; - border:none; - padding:0!important; - - font-family:arial,sans-serif; - color:#069; - text-decoration:underline; - cursor:pointer; - } - - input.url { - width: 25em; - } - - - button.accept:disabled { - background-color: #dedbe8; - border: 1px solid white; - border-radius: 5px; - margin: 1em 0; - padding: 0.5em; - font-weight: bold; - color: #2C2C2C; - } - - .errorbox { - border: 1px solid; - display: inline-block; - margin: 1em; - padding: 1em; - font-weight: bold; - background: #FF8A8A; - } - </style> -</head> - -<body> - <section id="main"> - <h1>GNU Taler Wallet</h1> - <article id="contract" class="fade"></article> - </section> -</body> - -</html> diff --git a/src/pages/confirm-contract.tsx b/src/pages/confirm-contract.tsx deleted file mode 100644 index 47db94ee8..000000000 --- a/src/pages/confirm-contract.tsx +++ /dev/null @@ -1,240 +0,0 @@ -/* - This file is part of TALER - (C) 2015 GNUnet e.V. - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Page shown to the user to confirm entering - * a contract. - */ - - -/** - * Imports. - */ -import { Contract, AmountJson, ExchangeRecord } from "../types"; -import { OfferRecord } from "../wallet"; -import { renderContract } from "../renderHtml"; -import { getExchanges } from "../wxApi"; -import * as i18n from "../i18n"; -import * as React from "react"; -import * as ReactDOM from "react-dom"; -import URI = require("urijs"); - - -interface DetailState { - collapsed: boolean; -} - -interface DetailProps { - contract: Contract - collapsed: boolean - exchanges: null|ExchangeRecord[]; -} - - -class Details extends React.Component<DetailProps, DetailState> { - constructor(props: DetailProps) { - super(props); - console.log("new Details component created"); - this.state = { - collapsed: props.collapsed, - }; - - console.log("initial state:", this.state); - } - - render() { - if (this.state.collapsed) { - return ( - <div> - <button className="linky" - onClick={() => { this.setState({collapsed: false} as any)}}> - <i18n.Translate wrap="span"> - show more details - </i18n.Translate> - </button> - </div> - ); - } else { - return ( - <div> - <button className="linky" - onClick={() => this.setState({collapsed: true} as any)}> - show less details - </button> - <div> - {i18n.str`Accepted exchanges:`} - <ul> - {this.props.contract.exchanges.map( - e => <li>{`${e.url}: ${e.master_pub}`}</li>)} - </ul> - {i18n.str`Exchanges in the wallet:`} - <ul> - {(this.props.exchanges || []).map( - (e: ExchangeRecord) => - <li>{`${e.baseUrl}: ${e.masterPublicKey}`}</li>)} - </ul> - </div> - </div>); - } - } -} - -interface ContractPromptProps { - offerId: number; -} - -interface ContractPromptState { - offer: OfferRecord|null; - error: string|null; - payDisabled: boolean; - exchanges: null|ExchangeRecord[]; -} - -class ContractPrompt extends React.Component<ContractPromptProps, ContractPromptState> { - constructor() { - super(); - this.state = { - offer: null, - error: null, - payDisabled: true, - exchanges: null - } - } - - componentWillMount() { - this.update(); - } - - componentWillUnmount() { - // FIXME: abort running ops - } - - async update() { - let offer = await this.getOffer(); - this.setState({offer} as any); - this.checkPayment(); - let exchanges = await getExchanges(); - this.setState({exchanges} as any); - } - - getOffer(): Promise<OfferRecord> { - return new Promise<OfferRecord>((resolve, reject) => { - let msg = { - type: 'get-offer', - detail: { - offerId: this.props.offerId - } - }; - chrome.runtime.sendMessage(msg, (resp) => { - resolve(resp); - }); - }) - } - - checkPayment() { - let msg = { - type: 'check-pay', - detail: { - offer: this.state.offer - } - }; - chrome.runtime.sendMessage(msg, (resp) => { - if (resp.error) { - console.log("check-pay error", JSON.stringify(resp)); - switch (resp.error) { - case "coins-insufficient": - let msgInsufficient = i18n.str`You have insufficient funds of the requested currency in your wallet.`; - let msgNoMatch = i18n.str`You do not have any funds from an exchange that is accepted by this merchant. None of the exchanges accepted by the merchant is known to your wallet.`; - if (this.state.exchanges && this.state.offer) { - let acceptedExchangePubs = this.state.offer.contract.exchanges.map((e) => e.master_pub); - let ex = this.state.exchanges.find((e) => acceptedExchangePubs.indexOf(e.masterPublicKey) >= 0); - if (ex) { - this.setState({error: msgInsufficient}); - } else { - this.setState({error: msgNoMatch}); - } - } else { - this.setState({error: msgInsufficient}); - } - break; - default: - this.setState({error: `Error: ${resp.error}`}); - break; - } - this.setState({payDisabled: true}); - } else { - this.setState({payDisabled: false, error: null}); - } - this.setState({} as any); - window.setTimeout(() => this.checkPayment(), 500); - }); - } - - doPayment() { - let d = {offer: this.state.offer}; - chrome.runtime.sendMessage({type: 'confirm-pay', detail: d}, (resp) => { - if (resp.error) { - console.log("confirm-pay error", JSON.stringify(resp)); - switch (resp.error) { - case "coins-insufficient": - this.setState({error: "You do not have enough coins of the requested currency."}); - break; - default: - this.setState({error: `Error: ${resp.error}`}); - break; - } - return; - } - let c = d.offer!.contract; - console.log("contract", c); - document.location.href = c.fulfillment_url; - }); - } - - - render() { - if (!this.state.offer) { - return <span>...</span>; - } - let c = this.state.offer.contract; - return ( - <div> - <div> - {renderContract(c)} - </div> - <button onClick={() => this.doPayment()} - disabled={this.state.payDisabled} - className="accept"> - Confirm payment - </button> - <div> - {(this.state.error ? <p className="errorbox">{this.state.error}</p> : <p />)} - </div> - <Details exchanges={this.state.exchanges} contract={c} collapsed={!this.state.error}/> - </div> - ); - } -} - - -document.addEventListener("DOMContentLoaded", () => { - let url = new URI(document.location.href); - let query: any = URI.parseQuery(url.query()); - let offerId = JSON.parse(query.offerId); - - ReactDOM.render(<ContractPrompt offerId={offerId}/>, document.getElementById( - "contract")!); -}); diff --git a/src/pages/confirm-create-reserve.html b/src/pages/confirm-create-reserve.html deleted file mode 100644 index 16ab12a30..000000000 --- a/src/pages/confirm-create-reserve.html +++ /dev/null @@ -1,52 +0,0 @@ -<!DOCTYPE html> -<html> - -<head> - <meta charset="UTF-8"> - <title>Taler Wallet: Select Taler Provider</title> - - <link rel="icon" href="/img/icon.png"> - <link rel="stylesheet" type="text/css" href="/src/style/wallet.css"> - <link rel="stylesheet" type="text/css" href="/src/style/pure.css"> - - <script src="/dist/page-common-bundle.js"></script> - <script src="/dist/confirm-create-reserve-bundle.js"></script> - - <style> - body { - font-size: 100%; - overflow-y: scroll; - } - .button-success { - background: rgb(28, 184, 65); /* this is a green */ - color: white; - border-radius: 4px; - text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); - } - .button-secondary { - background: rgb(66, 184, 221); /* this is a light blue */ - color: white; - border-radius: 4px; - text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); - } - a.opener { - color: black; - } - .opener-open::before { - content: "\25bc" - } - .opener-collapsed::before { - content: "\25b6 " - } - </style> - -</head> - -<body> - <section id="main"> - <h1>GNU Taler Wallet</h1> - <div class="fade" id="exchange-selection"></div> - </section> -</body> - -</html> diff --git a/src/pages/confirm-create-reserve.tsx b/src/pages/confirm-create-reserve.tsx deleted file mode 100644 index 2f341bb4e..000000000 --- a/src/pages/confirm-create-reserve.tsx +++ /dev/null @@ -1,639 +0,0 @@ -/* - This file is part of TALER - (C) 2015-2016 GNUnet e.V. - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - - -/** - * Page shown to the user to confirm creation - * of a reserve, usually requested by the bank. - * - * @author Florian Dold - */ - -import {amountToPretty, canonicalizeBaseUrl} from "../helpers"; -import { - AmountJson, CreateReserveResponse, - ReserveCreationInfo, Amounts, - Denomination, DenominationRecord, CurrencyRecord -} from "../types"; -import {getReserveCreationInfo, getCurrency, getExchangeInfo} from "../wxApi"; -import {ImplicitStateComponent, StateHolder} from "../components"; -import * as i18n from "../i18n"; -import * as React from "react"; -import * as ReactDOM from "react-dom"; -import URI = require("urijs"); -import * as moment from "moment"; - - -function delay<T>(delayMs: number, value: T): Promise<T> { - return new Promise<T>((resolve, reject) => { - setTimeout(() => resolve(value), delayMs); - }); -} - -class EventTrigger { - triggerResolve: any; - triggerPromise: Promise<boolean>; - - constructor() { - this.reset(); - } - - private reset() { - this.triggerPromise = new Promise<boolean>((resolve, reject) => { - this.triggerResolve = resolve; - }); - } - - trigger() { - this.triggerResolve(false); - this.reset(); - } - - async wait(delayMs: number): Promise<boolean> { - return await Promise.race([this.triggerPromise, delay(delayMs, true)]); - } -} - - -interface CollapsibleState { - collapsed: boolean; -} - -interface CollapsibleProps { - initiallyCollapsed: boolean; - title: string; -} - -class Collapsible extends React.Component<CollapsibleProps, CollapsibleState> { - constructor(props: CollapsibleProps) { - super(props); - this.state = { collapsed: props.initiallyCollapsed }; - } - render() { - const doOpen = (e: any) => { - this.setState({collapsed: false}) - e.preventDefault() - }; - const doClose = (e: any) => { - this.setState({collapsed: true}) - e.preventDefault(); - }; - if (this.state.collapsed) { - return <h2><a className="opener opener-collapsed" href="#" onClick={doOpen}>{this.props.title}</a></h2>; - } - return ( - <div> - <h2><a className="opener opener-open" href="#" onClick={doClose}>{this.props.title}</a></h2> - {this.props.children} - </div> - ); - } -} - -function renderAuditorDetails(rci: ReserveCreationInfo|null) { - if (!rci) { - return ( - <p> - Details will be displayed when a valid exchange provider URL is entered. - </p> - ); - } - if (rci.exchangeInfo.auditors.length == 0) { - return ( - <p> - The exchange is not audited by any auditors. - </p> - ); - } - return ( - <div> - {rci.exchangeInfo.auditors.map(a => ( - <h3>Auditor {a.url}</h3> - ))} - </div> - ); -} - -function renderReserveCreationDetails(rci: ReserveCreationInfo|null) { - if (!rci) { - return ( - <p> - Details will be displayed when a valid exchange provider URL is entered. - </p> - ); - } - - let denoms = rci.selectedDenoms; - - let countByPub: {[s: string]: number} = {}; - let uniq: DenominationRecord[] = []; - - denoms.forEach((x: DenominationRecord) => { - let c = countByPub[x.denomPub] || 0; - if (c == 0) { - uniq.push(x); - } - c += 1; - countByPub[x.denomPub] = c; - }); - - function row(denom: DenominationRecord) { - return ( - <tr> - <td>{countByPub[denom.denomPub] + "x"}</td> - <td>{amountToPretty(denom.value)}</td> - <td>{amountToPretty(denom.feeWithdraw)}</td> - <td>{amountToPretty(denom.feeRefresh)}</td> - <td>{amountToPretty(denom.feeDeposit)}</td> - </tr> - ); - } - - function wireFee(s: string) { - return [ - <thead> - <tr> - <th colSpan={3}>Wire Method {s}</th> - </tr> - <tr> - <th>Applies Until</th> - <th>Wire Fee</th> - <th>Closing Fee</th> - </tr> - </thead>, - <tbody> - {rci!.wireFees.feesForType[s].map(f => ( - <tr> - <td>{moment.unix(f.endStamp).format("llll")}</td> - <td>{amountToPretty(f.wireFee)}</td> - <td>{amountToPretty(f.closingFee)}</td> - </tr> - ))} - </tbody> - ]; - } - - let withdrawFeeStr = amountToPretty(rci.withdrawFee); - let overheadStr = amountToPretty(rci.overhead); - - return ( - <div> - <h3>Overview</h3> - <p>{i18n.str`Withdrawal fees: ${withdrawFeeStr}`}</p> - <p>{i18n.str`Rounding loss: ${overheadStr}`}</p> - <p>{i18n.str`Earliest expiration (for deposit): ${moment.unix(rci.earliestDepositExpiration).fromNow()}`}</p> - <h3>Coin Fees</h3> - <table className="pure-table"> - <thead> - <tr> - <th>{i18n.str`# Coins`}</th> - <th>{i18n.str`Value`}</th> - <th>{i18n.str`Withdraw Fee`}</th> - <th>{i18n.str`Refresh Fee`}</th> - <th>{i18n.str`Deposit Fee`}</th> - </tr> - </thead> - <tbody> - {uniq.map(row)} - </tbody> - </table> - <h3>Wire Fees</h3> - <table className="pure-table"> - {Object.keys(rci.wireFees.feesForType).map(wireFee)} - </table> - </div> - ); -} - - -function getSuggestedExchange(currency: string): Promise<string> { - // TODO: make this request go to the wallet backend - // Right now, this is a stub. - const defaultExchange: {[s: string]: string} = { - "KUDOS": "https://exchange.demo.taler.net", - "PUDOS": "https://exchange.test.taler.net", - }; - - let exchange = defaultExchange[currency]; - - if (!exchange) { - exchange = "" - } - - return Promise.resolve(exchange); -} - - -function WithdrawFee(props: {reserveCreationInfo: ReserveCreationInfo|null}): JSX.Element { - if (props.reserveCreationInfo) { - let {overhead, withdrawFee} = props.reserveCreationInfo; - let totalCost = Amounts.add(overhead, withdrawFee).amount; - return <p>{i18n.str`Withdraw fees:`} {amountToPretty(totalCost)}</p>; - } - return <p />; -} - - -interface ExchangeSelectionProps { - suggestedExchangeUrl: string; - amount: AmountJson; - callback_url: string; - wt_types: string[]; - currencyRecord: CurrencyRecord|null; -} - -interface ManualSelectionProps { - onSelect(url: string): void; - initialUrl: string; -} - -class ManualSelection extends ImplicitStateComponent<ManualSelectionProps> { - url: StateHolder<string> = this.makeState(""); - errorMessage: StateHolder<string|null> = this.makeState(null); - isOkay: StateHolder<boolean> = this.makeState(false); - updateEvent = new EventTrigger(); - constructor(p: ManualSelectionProps) { - super(p); - this.url(p.initialUrl); - this.update(); - } - render() { - return ( - <div className="pure-g pure-form pure-form-stacked"> - <div className="pure-u-1"> - <label>URL</label> - <input className="url" type="text" spellCheck={false} - value={this.url()} - key="exchange-url-input" - onInput={(e) => this.onUrlChanged((e.target as HTMLInputElement).value)} /> - </div> - <div className="pure-u-1"> - <button className="pure-button button-success" - disabled={!this.isOkay()} - onClick={() => this.props.onSelect(this.url())}> - {i18n.str`Select`} - </button> - {this.errorMessage()} - </div> - </div> - ); - } - - async update() { - this.errorMessage(null); - this.isOkay(false); - if (!this.url()) { - return; - } - let parsedUrl = new URI(this.url()!); - if (parsedUrl.is("relative")) { - this.errorMessage(i18n.str`Error: URL may not be relative`); - this.isOkay(false); - return; - } - try { - let url = canonicalizeBaseUrl(this.url()!); - let r = await getExchangeInfo(url) - console.log("getExchangeInfo returned") - this.isOkay(true); - } catch (e) { - console.log("got error", e); - if (e.hasOwnProperty("httpStatus")) { - this.errorMessage(`Error: request failed with status ${e.httpStatus}`); - } else if (e.hasOwnProperty("errorResponse")) { - let resp = e.errorResponse; - this.errorMessage(`Error: ${resp.error} (${resp.hint})`); - } else { - this.errorMessage("invalid exchange URL"); - } - } - } - - async onUrlChanged(s: string) { - this.url(s); - this.errorMessage(null); - this.isOkay(false); - this.updateEvent.trigger(); - let waited = await this.updateEvent.wait(200); - if (waited) { - // Run the actual update if nobody else preempted us. - this.update(); - } - } -} - - -class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> { - statusString: StateHolder<string|null> = this.makeState(null); - reserveCreationInfo: StateHolder<ReserveCreationInfo|null> = this.makeState( - null); - url: StateHolder<string|null> = this.makeState(null); - - selectingExchange: StateHolder<boolean> = this.makeState(false); - - constructor(props: ExchangeSelectionProps) { - super(props); - let prefilledExchangesUrls = []; - if (props.currencyRecord) { - let exchanges = props.currencyRecord.exchanges.map((x) => x.baseUrl); - prefilledExchangesUrls.push(...exchanges); - } - if (props.suggestedExchangeUrl) { - prefilledExchangesUrls.push(props.suggestedExchangeUrl); - } - if (prefilledExchangesUrls.length != 0) { - this.url(prefilledExchangesUrls[0]); - this.forceReserveUpdate(); - } else { - this.selectingExchange(true); - } - } - - renderFeeStatus() { - let rci = this.reserveCreationInfo(); - if (rci) { - let totalCost = Amounts.add(rci.overhead, rci.withdrawFee).amount; - let trustMessage; - if (rci.isTrusted) { - trustMessage = ( - <i18n.Translate wrap="p"> - The exchange is trusted by the wallet. - </i18n.Translate> - ); - } else if (rci.isAudited) { - trustMessage = ( - <i18n.Translate wrap="p"> - The exchange is audited by a trusted auditor. - </i18n.Translate> - ); - } else { - trustMessage = ( - <i18n.Translate wrap="p"> - Warning: The exchange is neither directly trusted nor audited by a trusted auditor. - If you withdraw from this exchange, it will be trusted in the future. - </i18n.Translate> - ); - } - return ( - <div> - <i18n.Translate wrap="p"> - Using exchange provider <strong>{this.url()}</strong>. - The exchange provider will charge - {" "} - <span>{amountToPretty(totalCost)}</span> - {" "} - in fees. - </i18n.Translate> - {trustMessage} - </div> - ); - } - if (this.url() && !this.statusString()) { - let shortName = new URI(this.url()!).host(); - return ( - <i18n.Translate wrap="p"> - Waiting for a response from - {" "} - <em>{shortName}</em> - </i18n.Translate> - ); - } - if (this.statusString()) { - return ( - <p> - <strong style={{color: "red"}}>{i18n.str`A problem occured, see below. ${this.statusString()}`}</strong> - </p> - ); - } - return ( - <p> - {i18n.str`Information about fees will be available when an exchange provider is selected.`} - </p> - ); - } - - renderConfirm() { - return ( - <div> - {this.renderFeeStatus()} - <button className="pure-button button-success" - disabled={this.reserveCreationInfo() == null} - onClick={() => this.confirmReserve()}> - {i18n.str`Accept fees and withdraw`} - </button> - { " " } - <button className="pure-button button-secondary" - onClick={() => this.selectingExchange(true)}> - {i18n.str`Change Exchange Provider`} - </button> - <br/> - <Collapsible initiallyCollapsed={true} title="Fee and Spending Details"> - {renderReserveCreationDetails(this.reserveCreationInfo())} - </Collapsible> - <Collapsible initiallyCollapsed={true} title="Auditor Details"> - {renderAuditorDetails(this.reserveCreationInfo())} - </Collapsible> - </div> - ); - } - - select(url: string) { - this.reserveCreationInfo(null); - this.url(url); - this.selectingExchange(false); - this.forceReserveUpdate(); - } - - renderSelect() { - let exchanges = (this.props.currencyRecord && this.props.currencyRecord.exchanges) || []; - console.log(exchanges); - return ( - <div> - Please select an exchange. You can review the details before after your selection. - - {this.props.suggestedExchangeUrl && ( - <div> - <h2>Bank Suggestion</h2> - <button className="pure-button button-success" onClick={() => this.select(this.props.suggestedExchangeUrl)}> - Select <strong>{this.props.suggestedExchangeUrl}</strong> - </button> - </div> - )} - - {exchanges.length > 0 && ( - <div> - <h2>Known Exchanges</h2> - {exchanges.map(e => ( - <button className="pure-button button-success" onClick={() => this.select(e.baseUrl)}> - Select <strong>{e.baseUrl}</strong> - </button> - ))} - </div> - )} - - <h2>Manual Selection</h2> - <ManualSelection initialUrl={this.url() || ""} onSelect={(url: string) => this.select(url)} /> - </div> - ); - } - - render(): JSX.Element { - return ( - <div> - <i18n.Translate wrap="p"> - {"You are about to withdraw "} - <strong>{amountToPretty(this.props.amount)}</strong> - {" from your bank account into your wallet."} - </i18n.Translate> - {this.selectingExchange() ? this.renderSelect() : this.renderConfirm()} - </div> - ); - } - - - confirmReserve() { - this.confirmReserveImpl(this.reserveCreationInfo()!, - this.url()!, - this.props.amount, - this.props.callback_url); - } - - /** - * Do an update of the reserve creation info, without any debouncing. - */ - async forceReserveUpdate() { - this.reserveCreationInfo(null); - try { - let url = canonicalizeBaseUrl(this.url()!); - let r = await getReserveCreationInfo(url, - this.props.amount); - console.log("get exchange info resolved"); - this.reserveCreationInfo(r); - console.dir(r); - } catch (e) { - console.log("get exchange info rejected", e); - if (e.hasOwnProperty("httpStatus")) { - this.statusString(`Error: request failed with status ${e.httpStatus}`); - } else if (e.hasOwnProperty("errorResponse")) { - let resp = e.errorResponse; - this.statusString(`Error: ${resp.error} (${resp.hint})`); - } - } - } - - confirmReserveImpl(rci: ReserveCreationInfo, - exchange: string, - amount: AmountJson, - callback_url: string) { - const d = {exchange: canonicalizeBaseUrl(exchange), amount}; - const cb = (rawResp: any) => { - if (!rawResp) { - throw Error("empty response"); - } - // FIXME: filter out types that bank/exchange don't have in common - let wireDetails = rci.wireInfo; - let filteredWireDetails: any = {}; - for (let wireType in wireDetails) { - if (this.props.wt_types.findIndex((x) => x.toLowerCase() == wireType.toLowerCase()) < 0) { - continue; - } - let obj = Object.assign({}, wireDetails[wireType]); - // The bank doesn't need to know about fees - delete obj.fees; - // Consequently the bank can't verify signatures anyway, so - // we delete this extra data, to make the request URL shorter. - delete obj.salt; - delete obj.sig; - filteredWireDetails[wireType] = obj; - } - if (!rawResp.error) { - const resp = CreateReserveResponse.checked(rawResp); - let q: {[name: string]: string|number} = { - wire_details: JSON.stringify(filteredWireDetails), - exchange: resp.exchange, - reserve_pub: resp.reservePub, - amount_value: amount.value, - amount_fraction: amount.fraction, - amount_currency: amount.currency, - }; - let url = new URI(callback_url).addQuery(q); - if (!url.is("absolute")) { - throw Error("callback url is not absolute"); - } - console.log("going to", url.href()); - document.location.href = url.href(); - } else { - this.statusString( - i18n.str`Oops, something went wrong. The wallet responded with error status (${rawResp.error}).`); - } - }; - chrome.runtime.sendMessage({type: 'create-reserve', detail: d}, cb); - } - - renderStatus(): any { - if (this.statusString()) { - return <p><strong style={{color: "red"}}>{this.statusString()}</strong></p>; - } else if (!this.reserveCreationInfo()) { - return <p>{i18n.str`Checking URL, please wait ...`}</p>; - } - return ""; - } -} - -export async function main() { - try { - const url = new URI(document.location.href); - const query: any = URI.parseQuery(url.query()); - let amount; - try { - amount = AmountJson.checked(JSON.parse(query.amount)); - } catch (e) { - throw Error(i18n.str`Can't parse amount: ${e.message}`); - } - const callback_url = query.callback_url; - const bank_url = query.bank_url; - let wt_types; - try { - wt_types = JSON.parse(query.wt_types); - } catch (e) { - throw Error(i18n.str`Can't parse wire_types: ${e.message}`); - } - - let suggestedExchangeUrl = query.suggested_exchange_url; - let currencyRecord = await getCurrency(amount.currency); - - let args = { - wt_types, - suggestedExchangeUrl, - callback_url, - amount, - currencyRecord, - }; - - ReactDOM.render(<ExchangeSelection {...args} />, document.getElementById( - "exchange-selection")!); - - } catch (e) { - // TODO: provide more context information, maybe factor it out into a - // TODO:generic error reporting function or component. - document.body.innerText = i18n.str`Fatal error: "${e.message}".`; - console.error(`got error "${e.message}"`, e); - } -} - -document.addEventListener("DOMContentLoaded", () => { - main(); -}); diff --git a/src/pages/error.html b/src/pages/error.html deleted file mode 100644 index 51a8fd73a..000000000 --- a/src/pages/error.html +++ /dev/null @@ -1,18 +0,0 @@ -<!DOCTYPE html> -<html> - -<head> - <meta charset="UTF-8"> - <title>Taler Wallet: Error Occured</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/error-bundle.js"></script> - - <body> - <div id="container"></div> - </body> -</html> diff --git a/src/pages/error.tsx b/src/pages/error.tsx deleted file mode 100644 index f278bd224..000000000 --- a/src/pages/error.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* - This file is part of TALER - (C) 2015-2016 GNUnet e.V. - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - - -/** - * Page shown to the user to confirm creation - * of a reserve, usually requested by the bank. - * - * @author Florian Dold - */ - -import {ImplicitStateComponent, StateHolder} from "../components"; - -import * as React from "react"; -import * as ReactDOM from "react-dom"; -import URI = require("urijs"); - -"use strict"; - -interface ErrorProps { - message: string; -} - -class ErrorView extends React.Component<ErrorProps, void> { - render(): JSX.Element { - return ( - <div> - An error occurred: {this.props.message} - </div> - ); - } -} - -export async function main() { - try { - const url = new URI(document.location.href); - const query: any = URI.parseQuery(url.query()); - - const message: string = query.message || "unknown error"; - - ReactDOM.render(<ErrorView message={message} />, document.getElementById( - "container")!); - - } catch (e) { - // TODO: provide more context information, maybe factor it out into a - // TODO:generic error reporting function or component. - document.body.innerText = `Fatal error: "${e.message}".`; - console.error(`got error "${e.message}"`, e); - } -} diff --git a/src/pages/help/empty-wallet.html b/src/pages/help/empty-wallet.html deleted file mode 100644 index dd29d9689..000000000 --- a/src/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/pages/logs.html b/src/pages/logs.html deleted file mode 100644 index 9545269e3..000000000 --- a/src/pages/logs.html +++ /dev/null @@ -1,27 +0,0 @@ -<!DOCTYPE html> -<html> - -<head> - <meta charset="UTF-8"> - <title>Taler Wallet: Logs</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/logs-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/pages/logs.tsx b/src/pages/logs.tsx deleted file mode 100644 index a1e5161ec..000000000 --- a/src/pages/logs.tsx +++ /dev/null @@ -1,82 +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 wallet logs. - * - * @author Florian Dold - */ - -import * as React from "react"; -import * as ReactDOM from "react-dom"; -import {LogEntry, getLogs} from "../logging"; - -interface LogViewProps { - log: LogEntry; -} - -class LogView extends React.Component<LogViewProps, void> { - render(): JSX.Element { - let e = this.props.log; - return ( - <div className="tree-item"> - <ul> - <li>level: {e.level}</li> - <li>msg: {e.msg}</li> - <li>id: {e.id || "unknown"}</li> - <li>file: {e.source || "(unknown)"}</li> - <li>line: {e.line || "(unknown)"}</li> - <li>col: {e.col || "(unknown)"}</li> - {(e.detail ? <li> detail: <pre>{e.detail}</pre></li> : [])} - </ul> - </div> - ); - } -} - -interface LogsState { - logs: LogEntry[]|undefined; -} - -class Logs extends React.Component<any, LogsState> { - constructor() { - super(); - this.update(); - this.state = {} as any; - } - - async update() { - let logs = await getLogs(); - this.setState({logs}); - } - - render(): JSX.Element { - let logs = this.state.logs; - if (!logs) { - return <span>...</span>; - } - return ( - <div className="tree-item"> - Logs: - {logs.map(e => <LogView log={e} />)} - </div> - ); - } -} - -document.addEventListener("DOMContentLoaded", () => { - ReactDOM.render(<Logs />, document.getElementById("container")!); -}); diff --git a/src/pages/payback.html b/src/pages/payback.html deleted file mode 100644 index d6fe334c8..000000000 --- a/src/pages/payback.html +++ /dev/null @@ -1,36 +0,0 @@ -<!DOCTYPE html> -<html> - -<head> - <meta charset="UTF-8"> - <title>Taler Wallet: Payback</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/payback-bundle.js"></script> - - <style> - body { - font-size: 100%; - } - .tree-item { - margin: 2em; - border-radius: 5px; - border: 1px solid gray; - padding: 1em; - } - .button-linky { - background: none; - color: black; - text-decoration: underline; - border: none; - } - </style> - - <body> - <div id="container"></div> - </body> -</html> diff --git a/src/pages/payback.tsx b/src/pages/payback.tsx deleted file mode 100644 index 01f5a64e4..000000000 --- a/src/pages/payback.tsx +++ /dev/null @@ -1,98 +0,0 @@ -/* - This file is part of TALER - (C) 2017 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/> - */ - -/** - * View and edit auditors. - * - * @author Florian Dold - */ - - -import { - ExchangeRecord, - ExchangeForCurrencyRecord, - DenominationRecord, - AuditorRecord, - CurrencyRecord, - ReserveRecord, - CoinRecord, - PreCoinRecord, - Denomination, - WalletBalance, -} from "../types"; -import { ImplicitStateComponent, StateHolder } from "../components"; -import { - getCurrencies, - updateCurrency, - getPaybackReserves, - withdrawPaybackReserve, -} from "../wxApi"; -import { amountToPretty, getTalerStampDate } from "../helpers"; -import * as React from "react"; -import * as ReactDOM from "react-dom"; - -class Payback extends ImplicitStateComponent<any> { - reserves: StateHolder<ReserveRecord[]|null> = this.makeState(null); - constructor() { - super(); - let port = chrome.runtime.connect(); - port.onMessage.addListener((msg: any) => { - if (msg.notify) { - console.log("got notified"); - this.update(); - } - }); - this.update(); - } - - async update() { - let reserves = await getPaybackReserves(); - this.reserves(reserves); - } - - withdrawPayback(pub: string) { - withdrawPaybackReserve(pub); - } - - render(): JSX.Element { - let 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 ${amountToPretty(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> - ); - } -} - -export function main() { - ReactDOM.render(<Payback />, document.getElementById("container")!); -} - -document.addEventListener("DOMContentLoaded", main); diff --git a/src/pages/popup.css b/src/pages/popup.css deleted file mode 100644 index 675412c11..000000000 --- a/src/pages/popup.css +++ /dev/null @@ -1,84 +0,0 @@ - -/** - * @author Gabor X. Toth - * @author Marcello Stanisci - * @author Florian Dold - */ - -body { - min-height: 20em; - width: 30em; - margin: 0; - padding: 0; - max-height: 800px; - overflow: hidden; -} - -.nav { - background-color: #ddd; - padding: 0.5em 0; -} - -.nav a { - color: black; - padding: 0.5em; - text-decoration: none; -} - -.nav a.active { - background-color: white; - font-weight: bold; -} - - -.container { - overflow-y: scroll; - max-height: 400px; -} - -.abbrev { - text-decoration-style: dotted; -} - -#content { - padding: 1em; -} - - -#wallet-table .amount { - text-align: right; -} - -.hidden { - display: none; -} - -#transactions-table th, -#transactions-table td { - padding: 0.2em 0.5em; -} - -#reserve-create table { - width: 100%; -} - -#reserve-create table td.label { - width: 5em; -} - -#reserve-create table .input input[type="text"] { - width: 100%; -} - -.historyItem { - border: 1px solid black; - border-radius: 10px; - padding-left: 0.5em; - margin: 0.5em; -} - -.historyDate { - font-size: 90%; - margin: 0.3em; - color: slategray; -} diff --git a/src/pages/popup.html b/src/pages/popup.html deleted file mode 100644 index 98f24bccc..000000000 --- a/src/pages/popup.html +++ /dev/null @@ -1,18 +0,0 @@ -<!DOCTYPE html> -<html> - -<head> - <meta charset="utf-8"> - - <link rel="stylesheet" type="text/css" href="../style/wallet.css"> - <link rel="stylesheet" type="text/css" href="popup.css"> - - <script src="/dist/page-common-bundle.js"></script> - <script src="/dist/popup-bundle.js"></script> -</head> - -<body> - <div id="content" style="margin:0;padding:0"></div> -</body> - -</html> diff --git a/src/pages/popup.tsx b/src/pages/popup.tsx deleted file mode 100644 index aef5a3df8..000000000 --- a/src/pages/popup.tsx +++ /dev/null @@ -1,545 +0,0 @@ -/* - This file is part of TALER - (C) 2016 GNUnet e.V. - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - - -/** - * Popup shown to the user when they click - * the Taler browser action button. - * - * @author Florian Dold - */ - - -"use strict"; - -import BrowserClickedEvent = chrome.browserAction.BrowserClickedEvent; -import { HistoryRecord, HistoryLevel } from "../wallet"; -import { - AmountJson, WalletBalance, Amounts, - WalletBalanceEntry -} from "../types"; -import { amountToPretty } from "../helpers"; -import { abbrev } from "../renderHtml"; -import * as i18n from "../i18n"; -import * as React from "react"; -import * as ReactDOM from "react-dom"; -import URI = require("urijs"); - -function onUpdateNotification(f: () => void): () => void { - let port = chrome.runtime.connect({name: "notifications"}); - let listener = (msg: any, port: any) => { - f(); - }; - port.onMessage.addListener(listener); - return () => { - port.onMessage.removeListener(listener); - } -} - - -class Router extends React.Component<any,any> { - 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 { - Router.routeHandlers.push(f); - return () => { - let i = Router.routeHandlers.indexOf(f); - this.routeHandlers = this.routeHandlers.splice(i, 1); - } - } - - static routeHandlers: any[] = []; - - componentWillMount() { - console.log("router mounted"); - window.onhashchange = () => { - this.setState({}); - for (let f of Router.routeHandlers) { - f(); - } - } - } - - componentWillUnmount() { - console.log("router unmounted"); - } - - - render(): JSX.Element { - let 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 childProps: any = (child as any).props; - if (!childProps) { - return; - } - if (childProps["default"]) { - defaultChild = child; - } - if (childProps["route"] == route) { - foundChild = child; - } - }) - let child: React.ReactChild | null = foundChild || defaultChild; - if (!child) { - throw Error("unknown route"); - } - Router.setRoute((child as any).props["route"]); - return <div>{child}</div>; - } -} - - -interface TabProps { - target: string; - children?: React.ReactNode; -} - -function Tab(props: TabProps) { - let cssClass = ""; - if (props.target == Router.getRoute()) { - cssClass = "active"; - } - let onClick = (e: React.MouseEvent<HTMLAnchorElement>) => { - Router.setRoute(props.target); - e.preventDefault(); - }; - return ( - <a onClick={onClick} href={props.target} className={cssClass}> - {props.children} - </a> - ); -} - - -class WalletNavBar extends React.Component<any,any> { - cancelSubscription: any; - - componentWillMount() { - this.cancelSubscription = Router.onRoute(() => { - this.setState({}); - }); - } - - componentWillUnmount() { - if (this.cancelSubscription) { - this.cancelSubscription(); - } - } - - render() { - 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>); - } -} - - -function ExtensionLink(props: any) { - let onClick = (e: React.MouseEvent<HTMLAnchorElement>) => { - chrome.tabs.create({ - "url": chrome.extension.getURL(props.target) - }); - e.preventDefault(); - }; - return ( - <a onClick={onClick} href={props.target}> - {props.children} - </a>) -} - - -export function bigAmount(amount: AmountJson): JSX.Element { - let v = amount.value + amount.fraction / Amounts.fractionalBase; - return ( - <span> - <span style={{fontSize: "300%"}}>{v}</span> - {" "} - <span>{amount.currency}</span> - </span> - ); -} - -class WalletBalanceView extends React.Component<any, any> { - balance: WalletBalance; - gotError = false; - canceler: (() => void) | undefined = undefined; - unmount = false; - - componentWillMount() { - this.canceler = onUpdateNotification(() => this.updateBalance()); - this.updateBalance(); - } - - componentWillUnmount() { - console.log("component WalletBalanceView will unmount"); - if (this.canceler) { - this.canceler(); - } - this.unmount = true; - } - - updateBalance() { - chrome.runtime.sendMessage({type: "balances"}, (resp) => { - if (this.unmount) { - return; - } - if (resp.error) { - this.gotError = true; - console.error("could not retrieve balances", resp); - this.setState({}); - return; - } - this.gotError = false; - console.log("got wallet", resp); - this.balance = resp; - this.setState({}); - }); - } - - renderEmpty(): JSX.Element { - let helpLink = ( - <ExtensionLink target="/src/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 ? amountToPretty(entry.available) : null); - console.log("incoming: ", entry.pendingIncoming ? amountToPretty(entry.pendingIncoming) : null); - - if (Amounts.isNonZero(entry.pendingIncoming)) { - incoming = ( - <i18n.Translate wrap="span"> - <span style={{color: "darkgreen"}}> - {"+"} - {amountToPretty(entry.pendingIncoming)} - </span> - {" "} - incoming - </i18n.Translate> - ); - } - - if (Amounts.isNonZero(entry.pendingPayment)) { - payment = ( - <i18n.Translate wrap="span"> - <span style={{color: "darkblue"}}> - {amountToPretty(entry.pendingPayment)} - </span> - {" "} - being spent - </i18n.Translate> - ); - } - - let l = [incoming, payment].filter((x) => x !== undefined); - if (l.length == 0) { - return <span />; - } - - if (l.length == 1) { - return <span>({l})</span> - } - return <span>({l[0]}, {l[1]})</span>; - - } - - render(): JSX.Element { - let wallet = this.balance; - if (this.gotError) { - return i18n.str`Error: could not retrieve balance information.`; - } - if (!wallet) { - return <span></span>; - } - console.log(wallet); - let paybackAvailable = false; - let listing = Object.keys(wallet).map((key) => { - let entry: WalletBalanceEntry = wallet[key]; - if (entry.paybackAmount.value != 0 || entry.paybackAmount.fraction != 0) { - paybackAvailable = true; - } - return ( - <p> - {bigAmount(entry.available)} - {" "} - {this.formatPending(entry)} - </p> - ); - }); - let link = chrome.extension.getURL("/src/pages/auditors.html"); - let linkElem = <a className="actionLink" href={link} target="_blank">Trusted Auditors and Exchanges</a>; - let paybackLink = chrome.extension.getURL("/src/pages/payback.html"); - let paybackLinkElem = <a className="actionLink" href={link} target="_blank">Trusted Auditors and Exchanges</a>; - return ( - <div> - {listing.length > 0 ? listing : this.renderEmpty()} - {paybackAvailable && paybackLinkElem} - {linkElem} - </div> - ); - } -} - - -function formatHistoryItem(historyItem: HistoryRecord) { - const d = historyItem.detail; - const t = historyItem.timestamp; - console.log("hist item", historyItem); - switch (historyItem.type) { - case "create-reserve": - return ( - <i18n.Translate wrap="p"> - Bank requested reserve (<span>{abbrev(d.reservePub)}</span>) for <span>{amountToPretty(d.requestedAmount)}</span>. - </i18n.Translate> - ); - case "confirm-reserve": { - // FIXME: eventually remove compat fix - let exchange = d.exchangeBaseUrl ? (new URI(d.exchangeBaseUrl)).host() : "??"; - let pub = abbrev(d.reservePub); - return ( - <i18n.Translate wrap="p"> - Started to withdraw - {" "}{amountToPretty(d.requestedAmount)}{" "} - from <span>{exchange}</span> (<span>{pub}</span>). - </i18n.Translate> - ); - } - case "offer-contract": { - let link = chrome.extension.getURL("view-contract.html"); - let linkElem = <a href={link}>{abbrev(d.contractHash)}</a>; - let merchantElem = <em>{abbrev(d.merchantName, 15)}</em>; - return ( - <i18n.Translate wrap="p"> - Merchant <em>{abbrev(d.merchantName, 15)}</em> offered contract <a href={link}>{abbrev(d.contractHash)}</a>; - </i18n.Translate> - ); - } - case "depleted-reserve": { - let exchange = d.exchangeBaseUrl ? (new URI(d.exchangeBaseUrl)).host() : "??"; - let amount = amountToPretty(d.requestedAmount); - let pub = abbrev(d.reservePub); - return ( - <i18n.Translate wrap="p"> - Withdrew <span>{amount}</span> from <span>{exchange}</span> (<span>{pub}</span>). - </i18n.Translate> - ); - } - case "pay": { - let url = d.fulfillmentUrl; - let merchantElem = <em>{abbrev(d.merchantName, 15)}</em>; - let fulfillmentLinkElem = <a href={url} onClick={openTab(url)}>view product</a>; - return ( - <i18n.Translate wrap="p"> - Paid <span>{amountToPretty(d.amount)}</span> to merchant <span>{merchantElem}</span>. (<span>{fulfillmentLinkElem}</span>) - </i18n.Translate> - ); - } - default: - return (<p>{i18n.str`Unknown event (${historyItem.type})`}</p>); - } -} - - -class WalletHistory extends React.Component<any, any> { - myHistory: any[]; - gotError = false; - unmounted = false; - - componentWillMount() { - this.update(); - onUpdateNotification(() => this.update()); - } - - componentWillUnmount() { - console.log("history component unmounted"); - this.unmounted = true; - } - - update() { - chrome.runtime.sendMessage({type: "get-history"}, (resp) => { - if (this.unmounted) { - return; - } - console.log("got history response"); - if (resp.error) { - this.gotError = true; - console.error("could not retrieve history", resp); - this.setState({}); - return; - } - this.gotError = false; - console.log("got history", resp.history); - this.myHistory = resp.history; - this.setState({}); - }); - } - - render(): JSX.Element { - console.log("rendering history"); - let history: HistoryRecord[] = this.myHistory; - if (this.gotError) { - return i18n.str`Error: could not retrieve event history`; - } - - if (!history) { - // We're not ready yet - return <span />; - } - - let subjectMemo: {[s: string]: boolean} = {}; - let listing: any[] = []; - for (let record of history.reverse()) { - if (record.subjectId && subjectMemo[record.subjectId]) { - continue; - } - if (record.level != undefined && record.level < HistoryLevel.User) { - continue; - } - subjectMemo[record.subjectId as string] = true; - - let item = ( - <div className="historyItem"> - <div className="historyDate"> - {(new Date(record.timestamp)).toString()} - </div> - {formatHistoryItem(record)} - </div> - ); - - listing.push(item); - } - - if (listing.length > 0) { - return <div className="container">{listing}</div>; - } - return <p>{i18n.str`Your wallet has no events recorded.`}</p> - } - -} - - -function reload() { - try { - chrome.runtime.reload(); - window.close(); - } catch (e) { - // Functionality missing in firefox, ignore! - } -} - -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(); - } -} - - -function WalletDebug(props: any) { - return (<div> - <p>Debug tools:</p> - <button onClick={openExtensionPage("/src/pages/popup.html")}> - wallet tab - </button> - <button onClick={openExtensionPage("/src/pages/show-db.html")}> - show db - </button> - <button onClick={openExtensionPage("/src/pages/tree.html")}> - show tree - </button> - <button onClick={openExtensionPage("/src/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 function() { - chrome.tabs.create({ - "url": chrome.extension.getURL(page) - }); - } -} - - -function openTab(page: string) { - return function() { - chrome.tabs.create({ - "url": page - }); - } -} - - -let el = ( - <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")!); -}) diff --git a/src/pages/show-db.html b/src/pages/show-db.html deleted file mode 100644 index 215c726d9..000000000 --- a/src/pages/show-db.html +++ /dev/null @@ -1,18 +0,0 @@ -<!doctype html> -<html> - <head> - <meta charset="UTF-8"> - <title>Taler Wallet: Reserve Created</title> - <link rel="stylesheet" type="text/css" href="../style/wallet.css"> - <link rel="icon" href="/img/icon.png"> - <script src="/dist/page-common.js"></script> - <script src="/dist/show-db-bundle.js"></script> - </head> - <body> - <h1>DB Dump</h1> - <input type="file" id="fileInput" style="display:none"> - <button id="import">Import Dump</button> - <button id="download">Download Dump</button> - <pre id="dump"></pre> - </body> -</html> diff --git a/src/pages/show-db.ts b/src/pages/show-db.ts deleted file mode 100644 index d95951385..000000000 --- a/src/pages/show-db.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* - This file is part of TALER - (C) 2015 GNUnet e.V. - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - - -/** - * Wallet database dump for debugging. - * - * @author Florian Dold - */ - -function replacer(match: string, pIndent: string, pKey: string, pVal: string, - pEnd: string) { - const key = "<span class=json-key>"; - const val = "<span class=json-value>"; - const str = "<span class=json-string>"; - let r = pIndent || ""; - if (pKey) { - r = r + key + '"' + pKey.replace(/[": ]/g, "") + '":</span> '; - } - if (pVal) { - r = r + (pVal[0] === '"' ? str : val) + pVal + "</span>"; - } - return r + (pEnd || ""); -} - - -function prettyPrint(obj: any) { - const jsonLine = /^( *)("[\w]+": )?("[^"]*"|[\w.+-]*)?([,[{])?$/mg; - return JSON.stringify(obj, null as any, 3) - .replace(/&/g, "&").replace(/\\"/g, """) - .replace(/</g, "<").replace(/>/g, ">") - .replace(jsonLine, replacer); -} - - -document.addEventListener("DOMContentLoaded", () => { - chrome.runtime.sendMessage({type: "dump-db"}, (resp) => { - const el = document.getElementById("dump"); - if (!el) { - throw Error(); - } - el.innerHTML = prettyPrint(resp); - - document.getElementById("download")!.addEventListener("click", (evt) => { - console.log("creating download"); - const element = document.createElement("a"); - element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(JSON.stringify(resp))); - element.setAttribute("download", "wallet-dump.txt"); - element.style.display = "none"; - document.body.appendChild(element); - element.click(); - }); - - }); - - - const fileInput = document.getElementById("fileInput")! as HTMLInputElement; - fileInput.onchange = (evt) => { - if (!fileInput.files || fileInput.files.length !== 1) { - alert("please select exactly one file to import"); - return; - } - const file = fileInput.files[0]; - const fr = new FileReader(); - fr.onload = (e: any) => { - console.log("got file"); - const dump = JSON.parse(e.target.result); - console.log("parsed contents", dump); - chrome.runtime.sendMessage({ type: "import-db", detail: { dump } }, (resp) => { - alert("loaded"); - }); - }; - console.log("reading file", file); - fr.readAsText(file); - }; - - document.getElementById("import")!.addEventListener("click", (evt) => { - fileInput.click(); - evt.preventDefault(); - }); -}); diff --git a/src/pages/tree.html b/src/pages/tree.html deleted file mode 100644 index 0c0a368b3..000000000 --- a/src/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/pages/tree.tsx b/src/pages/tree.tsx deleted file mode 100644 index 8d1258c51..000000000 --- a/src/pages/tree.tsx +++ /dev/null @@ -1,436 +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 { - ExchangeRecord, - DenominationRecord, - CoinStatus, - ReserveRecord, - CoinRecord, - PreCoinRecord, - Denomination, -} from "../types"; -import { ImplicitStateComponent, StateHolder } from "../components"; -import { - getReserves, getExchanges, getCoins, getPreCoins, - refresh, getDenoms, payback, -} from "../wxApi"; -import { amountToPretty, getTalerStampDate } from "../helpers"; -import * as React from "react"; -import * as ReactDOM from "react-dom"; - -interface ReserveViewProps { - reserve: ReserveRecord; -} - -class ReserveView extends React.Component<ReserveViewProps, void> { - render(): JSX.Element { - let 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 ? amountToPretty(r.current_amount!) : "null"}</li> - <li>Requested: {amountToPretty(r.requested_amount)}</li> - <li>Confirmed: {r.confirmed}</li> - </ul> - </div> - ); - } -} - -interface ReserveListProps { - exchangeBaseUrl: string; -} - -interface ToggleProps { - expanded: StateHolder<boolean>; -} - -class Toggle extends ImplicitStateComponent<ToggleProps> { - renderButton() { - let show = () => { - this.props.expanded(true); - this.setState({}); - }; - let 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> { - 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, void> { - render() { - let c = this.props.coin; - return ( - <div className="tree-item"> - <ul> - <li>Key: {c.coinPub}</li> - <li>Current amount: {amountToPretty(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, void> { - render() { - let 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> { - coins = this.makeState<CoinRecord[] | null>(null); - expanded = this.makeState<boolean>(false); - - constructor(props: CoinListProps) { - super(props); - this.update(props); - } - - async update(props: CoinListProps) { - let 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> { - precoins = this.makeState<PreCoinRecord[] | null>(null); - expanded = this.makeState<boolean>(false); - - constructor(props: PreCoinListProps) { - super(props); - this.update(); - } - - async update() { - let 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; -} - -interface ExpanderTextProps { - text: string; -} - -class ExpanderText extends ImplicitStateComponent<ExpanderTextProps> { - expanded = this.makeState<boolean>(false); - textArea: any = undefined; - - componentDidUpdate() { - if (this.expanded() && this.textArea) { - this.textArea.focus(); - this.textArea.scrollTop = 0; - } - } - - render(): JSX.Element { - if (!this.expanded()) { - return ( - <span onClick={() => { this.expanded(true); }}> - {(this.props.text.length <= 10) - ? this.props.text - : ( - <span> - {this.props.text.substring(0,10)} - <span style={{textDecoration: "underline"}}>...</span> - </span> - ) - } - </span> - ); - } - return ( - <textarea - readOnly - style={{display: "block"}} - onBlur={() => this.expanded(false)} - ref={(e) => this.textArea = e}> - {this.props.text} - </textarea> - ); - } -} - -class DenominationList extends ImplicitStateComponent<DenominationListProps> { - expanded = this.makeState<boolean>(false); - denoms = this.makeState<undefined|DenominationRecord[]>(undefined); - - constructor(props: DenominationListProps) { - super(props); - this.update(); - } - - async update() { - let 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: {amountToPretty(d.value)}</li> - <li>Withdraw fee: {amountToPretty(d.feeWithdraw)}</li> - <li>Refresh fee: {amountToPretty(d.feeRefresh)}</li> - <li>Deposit fee: {amountToPretty(d.feeDeposit)}</li> - <li>Refund fee: {amountToPretty(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 { - let 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> { - reserves = this.makeState<ReserveRecord[] | null>(null); - expanded = this.makeState<boolean>(false); - - constructor(props: ReserveListProps) { - super(props); - this.update(); - } - - async update() { - let 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, void> { - render(): JSX.Element { - let 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<any, ExchangesListState> { - constructor() { - super(); - let 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() { - let exchanges = await getExchanges(); - console.log("exchanges: ", exchanges); - this.setState({ exchanges }); - } - - render(): JSX.Element { - let 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> - ); - } -} - -export function main() { - ReactDOM.render(<ExchangesList />, document.getElementById("container")!); -} - -document.addEventListener("DOMContentLoaded", main); |