diff options
Diffstat (limited to 'src/webex')
-rw-r--r-- | src/webex/messages.ts | 8 | ||||
-rw-r--r-- | src/webex/pages/confirm-create-reserve.html | 30 | ||||
-rw-r--r-- | src/webex/pages/popup.tsx | 44 | ||||
-rw-r--r-- | src/webex/pages/return-coins.html | 19 | ||||
-rw-r--r-- | src/webex/pages/return-coins.tsx | 271 | ||||
-rw-r--r-- | src/webex/style/wallet.css | 16 | ||||
-rw-r--r-- | src/webex/wxApi.ts | 27 | ||||
-rw-r--r-- | src/webex/wxBackend.ts | 17 |
8 files changed, 381 insertions, 51 deletions
diff --git a/src/webex/messages.ts b/src/webex/messages.ts index bf9ca00b0..d7ecd06a1 100644 --- a/src/webex/messages.ts +++ b/src/webex/messages.ts @@ -168,6 +168,14 @@ export interface MessageMap { request: { }; response: void; }; + "get-sender-wire-infos": { + request: { }; + response: void; + }; + "return-coins": { + request: { }; + response: void; + }; } /** diff --git a/src/webex/pages/confirm-create-reserve.html b/src/webex/pages/confirm-create-reserve.html index 493a6fb5f..17daf4dde 100644 --- a/src/webex/pages/confirm-create-reserve.html +++ b/src/webex/pages/confirm-create-reserve.html @@ -6,40 +6,12 @@ <title>Taler Wallet: Select Taler Provider</title> <link rel="icon" href="/img/icon.png"> - <link rel="stylesheet" type="text/css" href="../style/wallet.css"> <link rel="stylesheet" type="text/css" href="../style/pure.css"> + <link rel="stylesheet" type="text/css" href="../style/wallet.css"> <script src="/dist/page-common-bundle.js"></script> <script src="/dist/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> diff --git a/src/webex/pages/popup.tsx b/src/webex/pages/popup.tsx index f1f0353ad..7d12d365e 100644 --- a/src/webex/pages/popup.tsx +++ b/src/webex/pages/popup.tsx @@ -219,22 +219,26 @@ class WalletBalanceView extends React.Component<any, any> { this.unmount = true; } - updateBalance() { - chrome.runtime.sendMessage({type: "balances"}, (resp) => { + async updateBalance() { + let balance: WalletBalance; + try { + balance = await wxApi.getBalance(); + } catch (e) { 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.gotError = true; + console.error("could not retrieve balances", e); this.setState({}); - }); + return; + } + if (this.unmount) { + return; + } + this.gotError = false; + console.log("got balance", balance); + this.balance = balance; + this.setState({}); } renderEmpty(): JSX.Element { @@ -308,8 +312,8 @@ class WalletBalanceView extends React.Component<any, any> { } console.log(wallet); let paybackAvailable = false; - const listing = Object.keys(wallet).map((key) => { - const entry: WalletBalanceEntry = wallet[key]; + const listing = Object.keys(wallet.byCurrency).map((key) => { + const entry: WalletBalanceEntry = wallet.byCurrency[key]; if (entry.paybackAmount.value !== 0 || entry.paybackAmount.fraction !== 0) { paybackAvailable = true; } @@ -321,14 +325,16 @@ class WalletBalanceView extends React.Component<any, any> { </p> ); }); - 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>; + const makeLink = (page: string, name: string) => { + const url = chrome.extension.getURL(`/src/webex/pages/${page}`); + return <div><a className="actionLink" href={url} target="_blank">{name}</a></div>; + }; return ( <div> {listing.length > 0 ? listing : this.renderEmpty()} - {paybackAvailable && paybackLinkElem} - {linkElem} + {paybackAvailable && makeLink("payback", i18n.str`Payback`)} + {makeLink("return-coins.html#dissolve", i18n.str`Return Electronic Cash to Bank Account`)} + {makeLink("auditors.html", i18n.str`Manage Trusted Auditors and Exchanges`)} </div> ); } diff --git a/src/webex/pages/return-coins.html b/src/webex/pages/return-coins.html new file mode 100644 index 000000000..c0ab218d2 --- /dev/null +++ b/src/webex/pages/return-coins.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> + +<head> + <meta charset="UTF-8"> + <title>Taler Wallet: Return Coins to Bank Account</title> + + <link rel="stylesheet" type="text/css" href="../style/pure.css"> + <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/return-coins-bundle.js"></script> + + <body> + <div id="container"></div> + </body> +</html> diff --git a/src/webex/pages/return-coins.tsx b/src/webex/pages/return-coins.tsx new file mode 100644 index 000000000..1fdadd2e9 --- /dev/null +++ b/src/webex/pages/return-coins.tsx @@ -0,0 +1,271 @@ +/* + 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 + */ + + +/** + * Imports. + */ + +import { + AmountJson, + Amounts, + SenderWireInfos, + WalletBalance, +} from "../../types"; + +import * as i18n from "../../i18n"; + +import * as wire from "../../wire"; + +import { + getBalance, + getSenderWireInfos, + returnCoins, +} from "../wxApi"; + +import { renderAmount } from "../renderHtml"; + +import * as React from "react"; +import * as ReactDOM from "react-dom"; + +interface ReturnSelectionItemProps extends ReturnSelectionListProps { + exchangeUrl: string; + senderWireInfos: SenderWireInfos; +} + +interface ReturnSelectionItemState { + selectedValue: string; + supportedWires: object[]; + selectedWire: string; + currency: string; +} + +class ReturnSelectionItem extends React.Component<ReturnSelectionItemProps, ReturnSelectionItemState> { + constructor(props: ReturnSelectionItemProps) { + super(props); + const exchange = this.props.exchangeUrl; + const wireTypes = this.props.senderWireInfos.exchangeWireTypes; + const supportedWires = this.props.senderWireInfos.senderWires.filter((x) => { + return wireTypes[exchange] && wireTypes[exchange].indexOf((x as any).type) >= 0; + }); + this.state = { + currency: props.balance.byExchange[props.exchangeUrl].available.currency, + selectedValue: Amounts.toFloat(props.balance.byExchange[props.exchangeUrl].available).toString(), + selectedWire: "", + supportedWires, + }; + } + render(): JSX.Element { + const exchange = this.props.exchangeUrl; + const byExchange = this.props.balance.byExchange; + const wireTypes = this.props.senderWireInfos.exchangeWireTypes; + return ( + <div key={exchange}> + <h2>Exchange {exchange}</h2> + <p>Available amount: {renderAmount(byExchange[exchange].available)}</p> + <p>Supported wire methods: {wireTypes[exchange].length ? wireTypes[exchange].join(", ") : "none"}</p> + <p>Wire {""} + <input + type="text" + size={this.state.selectedValue.length || 1} + value={this.state.selectedValue} + onChange={(evt) => this.setState({selectedValue: evt.target.value})} + style={{textAlign: "center"}} + /> {this.props.balance.byExchange[exchange].available.currency} {""} + to account {""} + <select value={this.state.selectedWire} onChange={(evt) => this.setState({selectedWire: evt.target.value})}> + <option style={{display: "none"}}>Select account</option> + {this.state.supportedWires.map((w, n) => + <option value={n.toString()} key={JSON.stringify(w)}>{n+1}: {wire.summarizeWire(w)}</option> + )} + </select>. + </p> + {this.state.selectedWire + ? <button className="pure-button button-success" onClick={() => this.select()}> + {i18n.str`Wire to bank account`} + </button> + : null} + </div> + ); + } + + select() { + let val: number; + let selectedWire: number; + try { + val = Number.parseFloat(this.state.selectedValue); + selectedWire = Number.parseInt(this.state.selectedWire); + } catch (e) { + console.error(e); + return; + } + this.props.selectDetail({ + amount: Amounts.fromFloat(val, this.state.currency), + exchange: this.props.exchangeUrl, + senderWire: this.state.supportedWires[selectedWire], + }); + } +} + +interface ReturnSelectionListProps { + balance: WalletBalance; + senderWireInfos: SenderWireInfos; + selectDetail(d: SelectedDetail): void; +} + +class ReturnSelectionList extends React.Component<ReturnSelectionListProps, {}> { + render(): JSX.Element { + const byExchange = this.props.balance.byExchange; + const exchanges = Object.keys(byExchange); + if (!exchanges.length) { + return <p className="errorbox">Currently no funds available to transfer.</p>; + } + return ( + <div> + {exchanges.map((e) => <ReturnSelectionItem key={e} exchangeUrl={e} {...this.props} />)} + </div> + ); + } +} + +interface SelectedDetail { + amount: AmountJson; + senderWire: any; + exchange: string; +} + + +interface ReturnConfirmationProps { + detail: SelectedDetail; + cancel(): void; + confirm(): void; +} + +class ReturnConfirmation extends React.Component<ReturnConfirmationProps, {}> { + render() { + return ( + <div> + <p>Please confirm if you want to transmit <strong>{renderAmount(this.props.detail.amount)}</strong> at {""} + {this.props.detail.exchange} to account {""} + <strong style={{whiteSpace: "nowrap"}}>{wire.summarizeWire(this.props.detail.senderWire)}</strong>. + </p> + <button className="pure-button button-success" onClick={() => this.props.confirm()}> + {i18n.str`Confirm`} + </button> + <button className="pure-button" onClick={() => this.props.cancel()}> + {i18n.str`Cancel`} + </button> + </div> + ); + } +} + +interface ReturnCoinsState { + balance: WalletBalance | undefined; + senderWireInfos: SenderWireInfos | undefined; + selectedReturn: SelectedDetail | undefined; + /** + * Last confirmed detail, so we can show a nice box. + */ + lastConfirmedDetail: SelectedDetail | undefined; +} + +class ReturnCoins extends React.Component<any, ReturnCoinsState> { + constructor() { + super(); + const port = chrome.runtime.connect(); + port.onMessage.addListener((msg: any) => { + if (msg.notify) { + console.log("got notified"); + this.update(); + } + }); + this.update(); + this.state = {} as any; + } + + async update() { + const balance = await getBalance(); + const senderWireInfos = await getSenderWireInfos(); + console.log("got swi", senderWireInfos); + console.log("got bal", balance); + this.setState({ balance, senderWireInfos }); + } + + selectDetail(d: SelectedDetail) { + this.setState({selectedReturn: d}); + } + + async confirm() { + const selectedReturn = this.state.selectedReturn; + if (!selectedReturn) { + return; + } + await returnCoins(selectedReturn); + await this.update(); + this.setState({selectedReturn: undefined, lastConfirmedDetail: selectedReturn}); + } + + async cancel() { + this.setState({selectedReturn: undefined, lastConfirmedDetail: undefined}); + } + + render() { + const balance = this.state.balance; + const senderWireInfos = this.state.senderWireInfos; + if (!balance || !senderWireInfos) { + return <span>...</span>; + } + if (this.state.selectedReturn) { + return ( + <div id="main"> + <ReturnConfirmation + detail={this.state.selectedReturn} + cancel={() => this.cancel()} + confirm={() => this.confirm()} + /> + </div> + ); + } + return ( + <div id="main"> + <h1>Wire electronic cash back to own bank account</h1> + <p>You can send coins back into your own bank account. Note that + you're acting as a merchant when doing this, and thus the same fees apply.</p> + {this.state.lastConfirmedDetail + ? <p className="okaybox">Transfer of {renderAmount(this.state.lastConfirmedDetail.amount)} successfully initiated.</p> + : null} + <ReturnSelectionList + selectDetail={(d) => this.selectDetail(d)} + balance={balance} + senderWireInfos={senderWireInfos} /> + </div> + ); + } +} + + +function main() { + ReactDOM.render(<ReturnCoins />, document.getElementById("container")!); +} + +document.addEventListener("DOMContentLoaded", main); diff --git a/src/webex/style/wallet.css b/src/webex/style/wallet.css index 5773eb396..61dd611e9 100644 --- a/src/webex/style/wallet.css +++ b/src/webex/style/wallet.css @@ -1,3 +1,8 @@ +body { + font-size: 100%; + overflow-y: scroll; +} + #main { border: solid 1px black; border-radius: 10px; @@ -235,3 +240,14 @@ a.actionLink { font-weight: bold; background: #00FA9A; } + + +a.opener { + color: black; +} +.opener-open::before { + content: "\25bc" +} +.opener-collapsed::before { + content: "\25b6 " +} diff --git a/src/webex/wxApi.ts b/src/webex/wxApi.ts index 9d8ba4d1d..1371e27e4 100644 --- a/src/webex/wxApi.ts +++ b/src/webex/wxApi.ts @@ -31,9 +31,11 @@ import { DenominationRecord, ExchangeRecord, PreCoinRecord, + QueryPaymentResult, ReserveCreationInfo, ReserveRecord, - QueryPaymentResult, + SenderWireInfos, + WalletBalance, } from "../types"; import { MessageType, MessageMap } from "./messages"; @@ -296,3 +298,26 @@ export function createReserve(args: { amount: AmountJson, exchange: string, send export function resetDb(): Promise<void> { return callBackend("reset-db", { }); } + +/** + * Get balances for all currencies/exchanges. + */ +export function getBalance(): Promise<WalletBalance> { + return callBackend("balances", { }); +} + + +/** + * Get possible sender wire infos for getting money + * wired from an exchange. + */ +export function getSenderWireInfos(): Promise<SenderWireInfos> { + return callBackend("get-sender-wire-infos", { }); +} + +/** + * Return coins to a bank account. + */ +export function returnCoins(args: { amount: AmountJson, exchange: string, senderWire: object }): Promise<void> { + return callBackend("return-coins", args); +} diff --git a/src/webex/wxBackend.ts b/src/webex/wxBackend.ts index 261477386..974bcb3c2 100644 --- a/src/webex/wxBackend.ts +++ b/src/webex/wxBackend.ts @@ -32,12 +32,13 @@ import { } from "../query"; import { AmountJson, + ConfirmReserveRequest, + CreateReserveRequest, Notifier, ProposalRecord, + ReturnCoinsRequest, } from "../types"; import { - ConfirmReserveRequest, - CreateReserveRequest, Stores, WALLET_DB_VERSION, Wallet, @@ -278,6 +279,18 @@ function handleMessage(sender: MessageSender, } return needsWallet().paymentSucceeded(contractTermsHash, merchantSig); } + case "get-sender-wire-infos": { + return needsWallet().getSenderWireInfos(); + } + case "return-coins": { + const d = { + amount: detail.amount, + exchange: detail.exchange, + senderWire: detail.senderWire, + }; + const req = ReturnCoinsRequest.checked(d); + return needsWallet().returnCoins(req); + } case "check-upgrade": { let dbResetRequired = false; if (!currentWallet) { |