Wallet progress
This commit is contained in:
parent
cee2f2a626
commit
089572880f
21 changed files with 629 additions and 394 deletions
|
@ -52,15 +52,6 @@ export function doCloseModal() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doUpdateBalance(balance) {
|
|
||||||
return {
|
|
||||||
type: types.UPDATE_BALANCE,
|
|
||||||
data: {
|
|
||||||
balance: balance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function doUpdateDownloadProgress(percent) {
|
export function doUpdateDownloadProgress(percent) {
|
||||||
return {
|
return {
|
||||||
type: types.UPGRADE_DOWNLOAD_PROGRESSED,
|
type: types.UPGRADE_DOWNLOAD_PROGRESSED,
|
||||||
|
@ -204,3 +195,9 @@ export function doSearch(term) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function doDaemonReady() {
|
||||||
|
return {
|
||||||
|
type: types.DAEMON_READY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
60
ui/js/actions/wallet.js
Normal file
60
ui/js/actions/wallet.js
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import * as types from 'constants/action_types'
|
||||||
|
import lbry from 'lbry'
|
||||||
|
|
||||||
|
export function doUpdateBalance(balance) {
|
||||||
|
return {
|
||||||
|
type: types.UPDATE_BALANCE,
|
||||||
|
data: {
|
||||||
|
balance: balance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doFetchTransactions() {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
dispatch({
|
||||||
|
type: types.FETCH_TRANSACTIONS_STARTED
|
||||||
|
})
|
||||||
|
|
||||||
|
lbry.call('get_transaction_history', {}, (results) => {
|
||||||
|
dispatch({
|
||||||
|
type: types.FETCH_TRANSACTIONS_COMPLETED,
|
||||||
|
data: {
|
||||||
|
transactions: results
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doGetNewAddress() {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
dispatch({
|
||||||
|
type: types.GET_NEW_ADDRESS_STARTED
|
||||||
|
})
|
||||||
|
|
||||||
|
lbry.wallet_new_address().then(function(address) {
|
||||||
|
localStorage.setItem('wallet_address', address);
|
||||||
|
dispatch({
|
||||||
|
type: types.GET_NEW_ADDRESS_COMPLETED,
|
||||||
|
data: { address }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doCheckAddressIsMine(address) {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
dispatch({
|
||||||
|
type: types.CHECK_ADDRESS_IS_MINE_STARTED
|
||||||
|
})
|
||||||
|
|
||||||
|
lbry.checkAddressIsMine(address, (isMine) => {
|
||||||
|
if (!isMine) dispatch(doGetNewAddress())
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: types.CHECK_ADDRESS_IS_MINE_COMPLETED
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,8 +2,7 @@ import React from 'react'
|
||||||
|
|
||||||
import lbry from 'lbry.js';
|
import lbry from 'lbry.js';
|
||||||
import Router from 'component/router'
|
import Router from 'component/router'
|
||||||
import Drawer from 'component/drawer';
|
import Header from 'component/header';
|
||||||
import Header from 'component/header.js';
|
|
||||||
import {Modal, ExpandableModal} from 'component/modal.js';
|
import {Modal, ExpandableModal} from 'component/modal.js';
|
||||||
import ErrorModal from 'component/errorModal'
|
import ErrorModal from 'component/errorModal'
|
||||||
import DownloadingModal from 'component/downloadingModal'
|
import DownloadingModal from 'component/downloadingModal'
|
||||||
|
|
|
@ -7,12 +7,16 @@ import {
|
||||||
doNavigate,
|
doNavigate,
|
||||||
doCloseDrawer,
|
doCloseDrawer,
|
||||||
doLogoClick,
|
doLogoClick,
|
||||||
doUpdateBalance,
|
|
||||||
} from 'actions/app'
|
} from 'actions/app'
|
||||||
|
import {
|
||||||
|
doUpdateBalance,
|
||||||
|
} from 'actions/wallet'
|
||||||
import {
|
import {
|
||||||
selectCurrentPage,
|
selectCurrentPage,
|
||||||
selectBalance,
|
|
||||||
} from 'selectors/app'
|
} from 'selectors/app'
|
||||||
|
import {
|
||||||
|
selectBalance,
|
||||||
|
} from 'selectors/wallet'
|
||||||
|
|
||||||
const select = (state) => ({
|
const select = (state) => ({
|
||||||
currentPage: selectCurrentPage(state),
|
currentPage: selectCurrentPage(state),
|
||||||
|
|
23
ui/js/component/header/index.js
Normal file
23
ui/js/component/header/index.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import React from 'react'
|
||||||
|
import {
|
||||||
|
connect
|
||||||
|
} from 'react-redux'
|
||||||
|
import {
|
||||||
|
selectCurrentPage,
|
||||||
|
selectHeaderLinks,
|
||||||
|
} from 'selectors/app'
|
||||||
|
import {
|
||||||
|
doNavigate,
|
||||||
|
} from 'actions/app'
|
||||||
|
import Header from './view'
|
||||||
|
|
||||||
|
const select = (state) => ({
|
||||||
|
currentPage: selectCurrentPage(state),
|
||||||
|
subLinks: selectHeaderLinks(state),
|
||||||
|
})
|
||||||
|
|
||||||
|
const perform = (dispatch) => ({
|
||||||
|
navigate: (path) => dispatch(doNavigate(path)),
|
||||||
|
})
|
||||||
|
|
||||||
|
export default connect(select, perform)(Header)
|
|
@ -3,7 +3,7 @@ import lbryuri from '../lbryuri.js';
|
||||||
import {Icon, CreditAmount} from './common.js';
|
import {Icon, CreditAmount} from './common.js';
|
||||||
import Link from 'component/link';
|
import Link from 'component/link';
|
||||||
|
|
||||||
var Header = React.createClass({
|
let Header = React.createClass({
|
||||||
_balanceSubscribeId: null,
|
_balanceSubscribeId: null,
|
||||||
_isMounted: false,
|
_isMounted: false,
|
||||||
|
|
||||||
|
@ -190,24 +190,4 @@ class WunderBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export let SubHeader = React.createClass({
|
|
||||||
render: function() {
|
|
||||||
let links = [],
|
|
||||||
viewingUrl = '?' + this.props.viewingPage;
|
|
||||||
|
|
||||||
for (let link of Object.keys(this.props.links)) {
|
|
||||||
links.push(
|
|
||||||
<a href={link} key={link} className={ viewingUrl == link ? 'sub-header-selected' : 'sub-header-unselected' }>
|
|
||||||
{this.props.links[link]}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<nav className={'sub-header' + (this.props.modifier ? ' sub-header--' + this.props.modifier : '')}>
|
|
||||||
{links}
|
|
||||||
</nav>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Header;
|
export default Header;
|
|
@ -4,9 +4,7 @@ import HelpPage from 'page/help';
|
||||||
import WatchPage from 'page/watch.js';
|
import WatchPage from 'page/watch.js';
|
||||||
import ReportPage from 'page/report.js';
|
import ReportPage from 'page/report.js';
|
||||||
import StartPage from 'page/start.js';
|
import StartPage from 'page/start.js';
|
||||||
import ClaimCodePage from 'page/claim_code.js';
|
import WalletPage from 'page/wallet';
|
||||||
import ReferralPage from 'page/referral.js';
|
|
||||||
import WalletPage from 'page/wallet.js';
|
|
||||||
import DetailPage from 'page/show.js';
|
import DetailPage from 'page/show.js';
|
||||||
import PublishPage from 'page/publish.js';
|
import PublishPage from 'page/publish.js';
|
||||||
import DiscoverPage from 'page/discover.js';
|
import DiscoverPage from 'page/discover.js';
|
||||||
|
|
22
ui/js/component/sub-header.js
Normal file
22
ui/js/component/sub-header.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
const SubHeader = (props) => {
|
||||||
|
const {
|
||||||
|
subLinks,
|
||||||
|
currentPage,
|
||||||
|
navigate,
|
||||||
|
} = props
|
||||||
|
|
||||||
|
const links = [],
|
||||||
|
viewingUrl = '?' + this.props.viewingPage;
|
||||||
|
|
||||||
|
for(let link of Object.keys(subLinks)) {
|
||||||
|
links.push(
|
||||||
|
<a href="#" onClick={() => navigate(link)} key={link} className={link == currentPage ? 'sub-header-selected' : 'sub-header-unselected' }>
|
||||||
|
{subLinks[link]}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className={"sub-header" + (this.props.modifier ? ' sub-header--' + this.props.modifier : '')}>{links}</nav>
|
||||||
|
)
|
||||||
|
}
|
12
ui/js/component/wallet-nav.js
Normal file
12
ui/js/component/wallet-nav.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import {SubHeader} from '../component/sub-header.js';
|
||||||
|
|
||||||
|
export let WalletNav = React.createClass({
|
||||||
|
render: function () {
|
||||||
|
return <SubHeader modifier="constrained" viewingPage={this.props.viewingPage} links={{
|
||||||
|
'?wallet': 'Overview',
|
||||||
|
'?send': 'Send',
|
||||||
|
'?receive': 'Receive',
|
||||||
|
'?rewards': 'Rewards'
|
||||||
|
}} />;
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,5 +1,13 @@
|
||||||
export const UPDATE_BALANCE = 'UPDATE_BALANCE'
|
|
||||||
export const NAVIGATE = 'NAVIGATE'
|
export const NAVIGATE = 'NAVIGATE'
|
||||||
|
export const OPEN_MODAL = 'OPEN_MODAL'
|
||||||
|
export const CLOSE_MODAL = 'CLOSE_MODAL'
|
||||||
|
|
||||||
|
export const OPEN_DRAWER = 'OPEN_DRAWER'
|
||||||
|
export const CLOSE_DRAWER = 'CLOSE_DRAWER'
|
||||||
|
|
||||||
|
export const START_SEARCH = 'START_SEARCH'
|
||||||
|
|
||||||
|
export const DAEMON_READY = 'DAEMON_READY'
|
||||||
|
|
||||||
// Upgrades
|
// Upgrades
|
||||||
export const UPGRADE_CANCELLED = 'UPGRADE_CANCELLED'
|
export const UPGRADE_CANCELLED = 'UPGRADE_CANCELLED'
|
||||||
|
@ -12,10 +20,11 @@ export const UPDATE_VERSION = 'UPDATE_VERSION'
|
||||||
export const SKIP_UPGRADE = 'SKIP_UPGRADE'
|
export const SKIP_UPGRADE = 'SKIP_UPGRADE'
|
||||||
export const START_UPGRADE = 'START_UPGRADE'
|
export const START_UPGRADE = 'START_UPGRADE'
|
||||||
|
|
||||||
export const OPEN_MODAL = 'OPEN_MODAL'
|
// Wallet
|
||||||
export const CLOSE_MODAL = 'CLOSE_MODAL'
|
export const GET_NEW_ADDRESS_STARTED = 'GET_NEW_ADDRESS_STARTED'
|
||||||
|
export const GET_NEW_ADDRESS_COMPLETED = 'GET_NEW_ADDRESS_COMPLETED'
|
||||||
export const OPEN_DRAWER = 'OPEN_DRAWER'
|
export const FETCH_TRANSACTIONS_STARTED = 'FETCH_TRANSACTIONS_STARTED'
|
||||||
export const CLOSE_DRAWER = 'CLOSE_DRAWER'
|
export const FETCH_TRANSACTIONS_COMPLETED = 'FETCH_TRANSACTIONS_COMPLETED'
|
||||||
|
export const UPDATE_BALANCE = 'UPDATE_BALANCE'
|
||||||
export const START_SEARCH = 'START_SEARCH'
|
export const CHECK_ADDRESS_IS_MINE_STARTED = 'CHECK_ADDRESS_IS_MINE_STARTED'
|
||||||
|
export const CHECK_ADDRESS_IS_MINE_COMPLETED = 'CHECK_ADDRESS_IS_MINE_COMPLETED'
|
||||||
|
|
|
@ -9,6 +9,7 @@ import SnackBar from './component/snack-bar.js';
|
||||||
import {AuthOverlay} from './component/auth.js';
|
import {AuthOverlay} from './component/auth.js';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import store from 'store.js';
|
import store from 'store.js';
|
||||||
|
import { runTriggers } from 'triggers'
|
||||||
|
|
||||||
const {remote} = require('electron');
|
const {remote} = require('electron');
|
||||||
const contextMenu = remote.require('./menu/context-menu');
|
const contextMenu = remote.require('./menu/context-menu');
|
||||||
|
@ -23,6 +24,8 @@ window.addEventListener('contextmenu', (event) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const initialState = app.store.getState();
|
const initialState = app.store.getState();
|
||||||
|
app.store.subscribe(runTriggers);
|
||||||
|
runTriggers();
|
||||||
|
|
||||||
var init = function() {
|
var init = function() {
|
||||||
window.lbry = lbry;
|
window.lbry = lbry;
|
||||||
|
|
|
@ -48,7 +48,7 @@ var HelpPage = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="page">
|
<main className="main--single-column">
|
||||||
<section className="card">
|
<section className="card">
|
||||||
<h3>Read the FAQ</h3>
|
<h3>Read the FAQ</h3>
|
||||||
<p>Our FAQ answers many common questions.</p>
|
<p>Our FAQ answers many common questions.</p>
|
||||||
|
|
|
@ -1,326 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import lbry from '../lbry.js';
|
|
||||||
import Link from 'component/link';
|
|
||||||
import Modal from '../component/modal.js';
|
|
||||||
import {SubHeader} from '../component/header.js';
|
|
||||||
import {FormField, FormRow} from '../component/form.js';
|
|
||||||
import {Address, BusyMessage, CreditAmount} from '../component/common.js';
|
|
||||||
|
|
||||||
var AddressSection = React.createClass({
|
|
||||||
_refreshAddress: function(event) {
|
|
||||||
if (typeof event !== 'undefined') {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
lbry.getUnusedAddress((address) => {
|
|
||||||
window.localStorage.setItem('wallet_address', address);
|
|
||||||
this.setState({
|
|
||||||
address: address,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_getNewAddress: function(event) {
|
|
||||||
if (typeof event !== 'undefined') {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
lbry.getNewAddress((address) => {
|
|
||||||
window.localStorage.setItem('wallet_address', address);
|
|
||||||
this.setState({
|
|
||||||
address: address,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
address: null,
|
|
||||||
modal: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
componentWillMount: function() {
|
|
||||||
var address = window.localStorage.getItem('wallet_address');
|
|
||||||
if (address === null) {
|
|
||||||
this._refreshAddress();
|
|
||||||
} else {
|
|
||||||
lbry.checkAddressIsMine(address, (isMine) => {
|
|
||||||
if (isMine) {
|
|
||||||
this.setState({
|
|
||||||
address: address,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this._refreshAddress();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render: function() {
|
|
||||||
return (
|
|
||||||
<section className="card">
|
|
||||||
<div className="card__title-primary">
|
|
||||||
<h3>Wallet Address</h3>
|
|
||||||
</div>
|
|
||||||
<div className="card__content">
|
|
||||||
<Address address={this.state.address} />
|
|
||||||
</div>
|
|
||||||
<div className="card__actions">
|
|
||||||
<Link label="Get New Address" button="primary" icon='icon-refresh' onClick={this._getNewAddress} />
|
|
||||||
</div>
|
|
||||||
<div className="card__content">
|
|
||||||
<div className="help">
|
|
||||||
<p>Other LBRY users may send credits to you by entering this address on the "Send" page.</p>
|
|
||||||
<p>You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var SendToAddressSection = React.createClass({
|
|
||||||
handleSubmit: function(event) {
|
|
||||||
if (typeof event !== 'undefined') {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((this.state.balance - this.state.amount) < 1)
|
|
||||||
{
|
|
||||||
this.setState({
|
|
||||||
modal: 'insufficientBalance',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
results: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
lbry.sendToAddress(this.state.amount, this.state.address, (results) => {
|
|
||||||
if(results === true)
|
|
||||||
{
|
|
||||||
this.setState({
|
|
||||||
results: "Your transaction was successfully placed in the queue.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.setState({
|
|
||||||
results: "Something went wrong: " + results
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, (error) => {
|
|
||||||
this.setState({
|
|
||||||
results: "Something went wrong: " + error.message
|
|
||||||
})
|
|
||||||
});
|
|
||||||
},
|
|
||||||
closeModal: function() {
|
|
||||||
this.setState({
|
|
||||||
modal: null,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
address: "",
|
|
||||||
amount: 0.0,
|
|
||||||
balance: <BusyMessage message="Checking balance" />,
|
|
||||||
results: "",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
componentWillMount: function() {
|
|
||||||
lbry.getBalance((results) => {
|
|
||||||
this.setState({
|
|
||||||
balance: results,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
setAmount: function(event) {
|
|
||||||
this.setState({
|
|
||||||
amount: parseFloat(event.target.value),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
setAddress: function(event) {
|
|
||||||
this.setState({
|
|
||||||
address: event.target.value,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
render: function() {
|
|
||||||
return (
|
|
||||||
<section className="card">
|
|
||||||
<form onSubmit={this.handleSubmit}>
|
|
||||||
<div className="card__title-primary">
|
|
||||||
<h3>Send Credits</h3>
|
|
||||||
</div>
|
|
||||||
<div className="card__content">
|
|
||||||
<FormRow label="Amount" postfix="LBC" step="0.01" type="number" placeholder="1.23" size="10" onChange={this.setAmount} />
|
|
||||||
</div>
|
|
||||||
<div className="card__content">
|
|
||||||
<FormRow label="Recipient Address" placeholder="bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs" type="text" size="60" onChange={this.setAddress} />
|
|
||||||
</div>
|
|
||||||
<div className="card__actions card__actions--form-submit">
|
|
||||||
<Link button="primary" label="Send" onClick={this.handleSubmit} disabled={!(parseFloat(this.state.amount) > 0.0) || this.state.address == ""} />
|
|
||||||
<input type='submit' className='hidden' />
|
|
||||||
</div>
|
|
||||||
{
|
|
||||||
this.state.results ?
|
|
||||||
<div className="card__content">
|
|
||||||
<h4>Results</h4>
|
|
||||||
{this.state.results}
|
|
||||||
</div> : ''
|
|
||||||
}
|
|
||||||
</form>
|
|
||||||
<Modal isOpen={this.state.modal === 'insufficientBalance'} contentLabel="Insufficient balance"
|
|
||||||
onConfirmed={this.closeModal}>
|
|
||||||
Insufficient balance: after this transaction you would have less than 1 LBC in your wallet.
|
|
||||||
</Modal>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
var TransactionList = React.createClass({
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
transactionItems: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
componentWillMount: function() {
|
|
||||||
lbry.call('get_transaction_history', {}, (results) => {
|
|
||||||
if (results.length == 0) {
|
|
||||||
this.setState({ transactionItems: [] })
|
|
||||||
} else {
|
|
||||||
var transactionItems = [],
|
|
||||||
condensedTransactions = {};
|
|
||||||
results.forEach(function(tx) {
|
|
||||||
var txid = tx["txid"];
|
|
||||||
if (!(txid in condensedTransactions)) {
|
|
||||||
condensedTransactions[txid] = 0;
|
|
||||||
}
|
|
||||||
condensedTransactions[txid] += parseFloat(tx["value"]);
|
|
||||||
});
|
|
||||||
results.reverse().forEach(function(tx) {
|
|
||||||
var txid = tx["txid"];
|
|
||||||
if (condensedTransactions[txid] && condensedTransactions[txid] != 0)
|
|
||||||
{
|
|
||||||
transactionItems.push({
|
|
||||||
id: txid,
|
|
||||||
date: tx["timestamp"] ? (new Date(parseInt(tx["timestamp"]) * 1000)) : null,
|
|
||||||
amount: condensedTransactions[txid]
|
|
||||||
});
|
|
||||||
delete condensedTransactions[txid];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setState({ transactionItems: transactionItems });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
render: function() {
|
|
||||||
var rows = [];
|
|
||||||
if (this.state.transactionItems && this.state.transactionItems.length > 0)
|
|
||||||
{
|
|
||||||
this.state.transactionItems.forEach(function(item) {
|
|
||||||
rows.push(
|
|
||||||
<tr key={item.id}>
|
|
||||||
<td>{ (item.amount > 0 ? '+' : '' ) + item.amount }</td>
|
|
||||||
<td>{ item.date ? item.date.toLocaleDateString() : <span className="empty">(Transaction pending)</span> }</td>
|
|
||||||
<td>{ item.date ? item.date.toLocaleTimeString() : <span className="empty">(Transaction pending)</span> }</td>
|
|
||||||
<td>
|
|
||||||
<a className="button-text" href={"https://explorer.lbry.io/tx/"+item.id} target="_blank">{item.id.substr(0, 7)}</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<section className="card">
|
|
||||||
<div className="card__title-primary">
|
|
||||||
<h3>Transaction History</h3>
|
|
||||||
</div>
|
|
||||||
<div className="card__content">
|
|
||||||
{ this.state.transactionItems === null ? <BusyMessage message="Loading transactions" /> : '' }
|
|
||||||
{ this.state.transactionItems && rows.length === 0 ? <div className="empty">You have no transactions.</div> : '' }
|
|
||||||
{ this.state.transactionItems && rows.length > 0 ?
|
|
||||||
<table className="table-standard table-stretch">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Amount</th>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Time</th>
|
|
||||||
<th>Transaction</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{rows}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export let WalletNav = React.createClass({
|
|
||||||
render: function() {
|
|
||||||
return <SubHeader modifier="constrained" viewingPage={this.props.viewingPage} links={{
|
|
||||||
'?wallet': 'Overview',
|
|
||||||
'?send': 'Send',
|
|
||||||
'?receive': 'Receive',
|
|
||||||
'?rewards': 'Rewards'
|
|
||||||
}} />;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var WalletPage = React.createClass({
|
|
||||||
_balanceSubscribeId: null,
|
|
||||||
|
|
||||||
propTypes: {
|
|
||||||
viewingPage: React.PropTypes.string,
|
|
||||||
},
|
|
||||||
/*
|
|
||||||
Below should be refactored so that balance is shared all of wallet page. Or even broader?
|
|
||||||
What is the proper React pattern for sharing a global state like balance?
|
|
||||||
*/
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
balance: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
componentWillMount: function() {
|
|
||||||
this._balanceSubscribeId = lbry.balanceSubscribe((results) => {
|
|
||||||
this.setState({
|
|
||||||
balance: results,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
},
|
|
||||||
componentWillUnmount: function() {
|
|
||||||
if (this._balanceSubscribeId) {
|
|
||||||
lbry.balanceUnsubscribe(this._balanceSubscribeId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render: function() {
|
|
||||||
return (
|
|
||||||
<main className="main--single-column">
|
|
||||||
<WalletNav viewingPage={this.props.viewingPage} />
|
|
||||||
<section className="card">
|
|
||||||
<div className="card__title-primary">
|
|
||||||
<h3>Balance</h3>
|
|
||||||
</div>
|
|
||||||
<div className="card__content">
|
|
||||||
{ this.state.balance === null ? <BusyMessage message="Checking balance" /> : ''}
|
|
||||||
{ this.state.balance !== null ? <CreditAmount amount={this.state.balance} precision={8} /> : '' }
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{ this.props.viewingPage === 'wallet' ? <TransactionList /> : '' }
|
|
||||||
{ this.props.viewingPage === 'send' ? <SendToAddressSection /> : '' }
|
|
||||||
{ this.props.viewingPage === 'receive' ? <AddressSection /> : '' }
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default WalletPage;
|
|
39
ui/js/page/wallet/index.js
Normal file
39
ui/js/page/wallet/index.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import React from 'react'
|
||||||
|
import {
|
||||||
|
connect
|
||||||
|
} from 'react-redux'
|
||||||
|
import {
|
||||||
|
doCloseModal,
|
||||||
|
} from 'actions/app'
|
||||||
|
import {
|
||||||
|
doGetNewAddress,
|
||||||
|
} from 'actions/wallet'
|
||||||
|
import {
|
||||||
|
selectCurrentPage,
|
||||||
|
} from 'selectors/app'
|
||||||
|
import {
|
||||||
|
selectBalance,
|
||||||
|
selectTransactions,
|
||||||
|
selectTransactionItems,
|
||||||
|
selectIsFetchingTransactions,
|
||||||
|
selectReceiveAddress,
|
||||||
|
selectGettingNewAddress,
|
||||||
|
} from 'selectors/wallet'
|
||||||
|
import WalletPage from './view'
|
||||||
|
|
||||||
|
const select = (state) => ({
|
||||||
|
currentPage: selectCurrentPage(state),
|
||||||
|
balance: selectBalance(state),
|
||||||
|
transactions: selectTransactions(state),
|
||||||
|
fetchingTransactions: selectIsFetchingTransactions(state),
|
||||||
|
transactionItems: selectTransactionItems(state),
|
||||||
|
receiveAddress: selectReceiveAddress(state),
|
||||||
|
gettingNewAddress: selectGettingNewAddress(state),
|
||||||
|
})
|
||||||
|
|
||||||
|
const perform = (dispatch) => ({
|
||||||
|
closeModal: () => dispatch(doCloseModal()),
|
||||||
|
getNewAddress: () => dispatch(doGetNewAddress()),
|
||||||
|
})
|
||||||
|
|
||||||
|
export default connect(select, perform)(WalletPage)
|
219
ui/js/page/wallet/view.jsx
Normal file
219
ui/js/page/wallet/view.jsx
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
import React from 'react';
|
||||||
|
import lbry from 'lbry.js';
|
||||||
|
import Link from 'component/link';
|
||||||
|
import Modal from 'component/modal';
|
||||||
|
import {
|
||||||
|
FormField,
|
||||||
|
FormRow
|
||||||
|
} from 'component/form';
|
||||||
|
import {
|
||||||
|
Address,
|
||||||
|
BusyMessage,
|
||||||
|
CreditAmount
|
||||||
|
} from 'component/common';
|
||||||
|
|
||||||
|
const AddressSection = (props) => {
|
||||||
|
const {
|
||||||
|
receiveAddress,
|
||||||
|
getNewAddress,
|
||||||
|
gettingNewAddress,
|
||||||
|
} = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="card">
|
||||||
|
<div className="card__title-primary">
|
||||||
|
<h3>Wallet Address</h3>
|
||||||
|
</div>
|
||||||
|
<div className="card__content">
|
||||||
|
<Address address={receiveAddress} />
|
||||||
|
</div>
|
||||||
|
<div className="card__actions">
|
||||||
|
<Link label="Get New Address" button="primary" icon='icon-refresh' onClick={getNewAddress} disabled={gettingNewAddress}/>
|
||||||
|
</div>
|
||||||
|
<div className="card__content">
|
||||||
|
<div className="help">
|
||||||
|
<p>Other LBRY users may send credits to you by entering this address on the "Send" page.</p>
|
||||||
|
<p>You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var SendToAddressSection = React.createClass({
|
||||||
|
handleSubmit: function(event) {
|
||||||
|
if (typeof event !== 'undefined') {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((this.state.balance - this.state.amount) < 1)
|
||||||
|
{
|
||||||
|
this.setState({
|
||||||
|
modal: 'insufficientBalance',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
results: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
lbry.sendToAddress(this.state.amount, this.state.address, (results) => {
|
||||||
|
if(results === true)
|
||||||
|
{
|
||||||
|
this.setState({
|
||||||
|
results: "Your transaction was successfully placed in the queue.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.setState({
|
||||||
|
results: "Something went wrong: " + results
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, (error) => {
|
||||||
|
this.setState({
|
||||||
|
results: "Something went wrong: " + error.message
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
closeModal: function() {
|
||||||
|
this.setState({
|
||||||
|
modal: null,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
address: "",
|
||||||
|
amount: 0.0,
|
||||||
|
balance: <BusyMessage message="Checking balance" />,
|
||||||
|
results: "",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
componentWillMount: function() {
|
||||||
|
lbry.getBalance((results) => {
|
||||||
|
this.setState({
|
||||||
|
balance: results,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setAmount: function(event) {
|
||||||
|
this.setState({
|
||||||
|
amount: parseFloat(event.target.value),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setAddress: function(event) {
|
||||||
|
this.setState({
|
||||||
|
address: event.target.value,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<section className="card">
|
||||||
|
<form onSubmit={this.handleSubmit}>
|
||||||
|
<div className="card__title-primary">
|
||||||
|
<h3>Send Credits</h3>
|
||||||
|
</div>
|
||||||
|
<div className="card__content">
|
||||||
|
<FormRow label="Amount" postfix="LBC" step="0.01" type="number" placeholder="1.23" size="10" onChange={this.setAmount} />
|
||||||
|
</div>
|
||||||
|
<div className="card__content">
|
||||||
|
<FormRow label="Recipient Address" placeholder="bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs" type="text" size="60" onChange={this.setAddress} />
|
||||||
|
</div>
|
||||||
|
<div className="card__actions card__actions--form-submit">
|
||||||
|
<Link button="primary" label="Send" onClick={this.handleSubmit} disabled={!(parseFloat(this.state.amount) > 0.0) || this.state.address == ""} />
|
||||||
|
<input type='submit' className='hidden' />
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
this.state.results ?
|
||||||
|
<div className="card__content">
|
||||||
|
<h4>Results</h4>
|
||||||
|
{this.state.results}
|
||||||
|
</div> : ''
|
||||||
|
}
|
||||||
|
</form>
|
||||||
|
<Modal isOpen={this.state.modal === 'insufficientBalance'} contentLabel="Insufficient balance"
|
||||||
|
onConfirmed={this.closeModal}>
|
||||||
|
Insufficient balance: after this transaction you would have less than 1 LBC in your wallet.
|
||||||
|
</Modal>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const TransactionList = (props) => {
|
||||||
|
const {
|
||||||
|
transactions,
|
||||||
|
fetchingTransactions,
|
||||||
|
transactionItems,
|
||||||
|
} = props
|
||||||
|
|
||||||
|
const rows = []
|
||||||
|
if (transactions.length > 0) {
|
||||||
|
transactionItems.forEach(function(item) {
|
||||||
|
rows.push(
|
||||||
|
<tr key={item.id}>
|
||||||
|
<td>{ (item.amount > 0 ? '+' : '' ) + item.amount }</td>
|
||||||
|
<td>{ item.date ? item.date.toLocaleDateString() : <span className="empty">(Transaction pending)</span> }</td>
|
||||||
|
<td>{ item.date ? item.date.toLocaleTimeString() : <span className="empty">(Transaction pending)</span> }</td>
|
||||||
|
<td>
|
||||||
|
<a className="button-text" href={"https://explorer.lbry.io/tx/"+item.id} target="_blank">{item.id.substr(0, 7)}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="card">
|
||||||
|
<div className="card__title-primary">
|
||||||
|
<h3>Transaction History</h3>
|
||||||
|
</div>
|
||||||
|
<div className="card__content">
|
||||||
|
{ fetchingTransactions ? <BusyMessage message="Loading transactions" /> : '' }
|
||||||
|
{ !fetchingTransactions && rows.length === 0 ? <div className="empty">You have no transactions.</div> : '' }
|
||||||
|
{ rows.length > 0 ?
|
||||||
|
<table className="table-standard table-stretch">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Amount</th>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Time</th>
|
||||||
|
<th>Transaction</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{rows}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const WalletPage = (props) => {
|
||||||
|
const {
|
||||||
|
balance,
|
||||||
|
currentPage
|
||||||
|
} = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="main--single-column">
|
||||||
|
<section className="card">
|
||||||
|
<div className="card__title-primary">
|
||||||
|
<h3>Balance</h3>
|
||||||
|
</div>
|
||||||
|
<div className="card__content">
|
||||||
|
<CreditAmount amount={balance} precision={8} />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{ currentPage === 'wallet' ? <TransactionList {...props} /> : '' }
|
||||||
|
{ currentPage === 'send' ? <SendToAddressSection {...props} /> : '' }
|
||||||
|
{ currentPage === 'receive' ? <AddressSection {...props} /> : '' }
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WalletPage;
|
|
@ -6,13 +6,8 @@ const defaultState = {
|
||||||
currentPage: 'discover',
|
currentPage: 'discover',
|
||||||
platform: process.platform,
|
platform: process.platform,
|
||||||
drawerOpen: sessionStorage.getItem('drawerOpen') || true,
|
drawerOpen: sessionStorage.getItem('drawerOpen') || true,
|
||||||
upgradeSkipped: sessionStorage.getItem('upgradeSkipped')
|
upgradeSkipped: sessionStorage.getItem('upgradeSkipped'),
|
||||||
}
|
daemonReady: false,
|
||||||
|
|
||||||
reducers[types.UPDATE_BALANCE] = function(state, action) {
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
balance: action.data.balance
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reducers[types.NAVIGATE] = function(state, action) {
|
reducers[types.NAVIGATE] = function(state, action) {
|
||||||
|
@ -98,6 +93,13 @@ reducers[types.UPGRADE_DOWNLOAD_PROGRESSED] = function(state, action) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reducers[types.DAEMON_READY] = function(state, action) {
|
||||||
|
// sessionStorage.setItem('loaded', 'y');
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
daemonReady: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export default function reducer(state = defaultState, action) {
|
export default function reducer(state = defaultState, action) {
|
||||||
const handler = reducers[action.type];
|
const handler = reducers[action.type];
|
||||||
if (handler) return handler(state, action);
|
if (handler) return handler(state, action);
|
||||||
|
|
52
ui/js/reducers/wallet.js
Normal file
52
ui/js/reducers/wallet.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import * as types from 'constants/action_types'
|
||||||
|
|
||||||
|
const reducers = {}
|
||||||
|
const address = sessionStorage.getItem('receiveAddress')
|
||||||
|
const defaultState = {
|
||||||
|
balance: 0,
|
||||||
|
transactions: [],
|
||||||
|
fetchingTransactions: false,
|
||||||
|
receiveAddress: address,
|
||||||
|
gettingNewAddress: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
reducers[types.FETCH_TRANSACTIONS_STARTED] = function(state, action) {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
fetchingTransactions: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
reducers[types.FETCH_TRANSACTIONS_COMPLETED] = function(state, action) {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
transactions: action.data.transactions,
|
||||||
|
fetchingTransactions: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
reducers[types.GET_NEW_ADDRESS_STARTED] = function(state, action) {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
gettingNewAddress: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
reducers[types.GET_NEW_ADDRESS_COMPLETED] = function(state, action) {
|
||||||
|
const { address } = action.data
|
||||||
|
|
||||||
|
sessionStorage.setItem('receiveAddress', address)
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
gettingNewAddress: false,
|
||||||
|
receiveAddress: address
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
reducers[types.UPDATE_BALANCE] = function(state, action) {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
balance: action.data.balance
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function reducer(state = defaultState, action) {
|
||||||
|
const handler = reducers[action.type];
|
||||||
|
if (handler) return handler(state, action);
|
||||||
|
return state;
|
||||||
|
}
|
|
@ -16,13 +16,6 @@ export const selectCurrentPage = createSelector(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
export const selectBalance = createSelector(
|
|
||||||
_selectState,
|
|
||||||
(state) => {
|
|
||||||
return state.balance || 0
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
export const selectPlatform = createSelector(
|
export const selectPlatform = createSelector(
|
||||||
_selectState,
|
_selectState,
|
||||||
(state) => {
|
(state) => {
|
||||||
|
@ -101,17 +94,17 @@ export const selectHeaderLinks = createSelector(
|
||||||
case 'claim':
|
case 'claim':
|
||||||
case 'referral':
|
case 'referral':
|
||||||
return {
|
return {
|
||||||
'?wallet' : 'Overview',
|
'wallet' : 'Overview',
|
||||||
'?send' : 'Send',
|
'send' : 'Send',
|
||||||
'?receive' : 'Receive',
|
'receive' : 'Receive',
|
||||||
'?claim' : 'Claim Beta Code',
|
'claim' : 'Claim Beta Code',
|
||||||
'?referral' : 'Check Referral Credit',
|
'referral' : 'Check Referral Credit',
|
||||||
};
|
};
|
||||||
case 'downloaded':
|
case 'downloaded':
|
||||||
case 'published':
|
case 'published':
|
||||||
return {
|
return {
|
||||||
'?downloaded': 'Downloaded',
|
'downloaded': 'Downloaded',
|
||||||
'?published': 'Published',
|
'published': 'Published',
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
|
@ -143,3 +136,8 @@ export const selectError = createSelector(
|
||||||
_selectState,
|
_selectState,
|
||||||
(state) => state.error
|
(state) => state.error
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const selectDaemonReady = createSelector(
|
||||||
|
_selectState,
|
||||||
|
(state) => state.daemonReady
|
||||||
|
)
|
||||||
|
|
109
ui/js/selectors/wallet.js
Normal file
109
ui/js/selectors/wallet.js
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
import { createSelector } from 'reselect'
|
||||||
|
import {
|
||||||
|
selectCurrentPage,
|
||||||
|
} from 'selectors/app'
|
||||||
|
|
||||||
|
export const _selectState = state => state.wallet || {}
|
||||||
|
|
||||||
|
export const selectBalance = createSelector(
|
||||||
|
_selectState,
|
||||||
|
(state) => {
|
||||||
|
return state.balance || 0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export const selectTransactions = createSelector(
|
||||||
|
_selectState,
|
||||||
|
(state) => state.transactions
|
||||||
|
)
|
||||||
|
|
||||||
|
export const selectTransactionItems = createSelector(
|
||||||
|
selectTransactions,
|
||||||
|
(transactions) => {
|
||||||
|
if (transactions.length == 0) return transactions
|
||||||
|
|
||||||
|
const transactionItems = []
|
||||||
|
const condensedTransactions = {}
|
||||||
|
|
||||||
|
transactions.forEach(function(tx) {
|
||||||
|
const txid = tx["txid"];
|
||||||
|
if (!(txid in condensedTransactions)) {
|
||||||
|
condensedTransactions[txid] = 0;
|
||||||
|
}
|
||||||
|
condensedTransactions[txid] += parseFloat(tx["value"]);
|
||||||
|
});
|
||||||
|
transactions.reverse().forEach(function(tx) {
|
||||||
|
const txid = tx["txid"];
|
||||||
|
if (condensedTransactions[txid] && condensedTransactions[txid] != 0)
|
||||||
|
{
|
||||||
|
transactionItems.push({
|
||||||
|
id: txid,
|
||||||
|
date: tx["timestamp"] ? (new Date(parseInt(tx["timestamp"]) * 1000)) : null,
|
||||||
|
amount: condensedTransactions[txid]
|
||||||
|
});
|
||||||
|
delete condensedTransactions[txid];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return transactionItems
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export const selectIsFetchingTransactions = createSelector(
|
||||||
|
_selectState,
|
||||||
|
(state) => state.fetchingTransactions
|
||||||
|
)
|
||||||
|
|
||||||
|
export const shouldFetchTransactions = createSelector(
|
||||||
|
selectCurrentPage,
|
||||||
|
selectTransactions,
|
||||||
|
selectIsFetchingTransactions,
|
||||||
|
(page, transactions, fetching) => {
|
||||||
|
if (page != 'wallet') return false
|
||||||
|
if (fetching) return false
|
||||||
|
if (transactions.length != 0) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export const selectReceiveAddress = createSelector(
|
||||||
|
_selectState,
|
||||||
|
(state) => state.receiveAddress
|
||||||
|
)
|
||||||
|
|
||||||
|
export const selectGettingNewAddress = createSelector(
|
||||||
|
_selectState,
|
||||||
|
(state) => state.gettingNewAddress
|
||||||
|
)
|
||||||
|
|
||||||
|
export const selectDaemonReady = createSelector(
|
||||||
|
() => sessionStorage.getItem('loaded') == 'y'
|
||||||
|
)
|
||||||
|
|
||||||
|
export const shouldGetReceiveAddress = createSelector(
|
||||||
|
selectReceiveAddress,
|
||||||
|
selectGettingNewAddress,
|
||||||
|
selectDaemonReady,
|
||||||
|
(address, fetching, daemonReady) => {
|
||||||
|
if (!daemonReady) return false
|
||||||
|
if (fetching) return false
|
||||||
|
if (address !== undefined) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export const shouldCheckAddressIsMine = createSelector(
|
||||||
|
_selectState,
|
||||||
|
selectCurrentPage,
|
||||||
|
selectReceiveAddress,
|
||||||
|
selectDaemonReady,
|
||||||
|
(state, page, address, daemonReady) => {
|
||||||
|
if (!daemonReady) return false
|
||||||
|
if (address === undefined) return false
|
||||||
|
if (state.addressOwnershipChecked) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
)
|
|
@ -6,6 +6,7 @@ import {
|
||||||
createLogger
|
createLogger
|
||||||
} from 'redux-logger'
|
} from 'redux-logger'
|
||||||
import appReducer from 'reducers/app';
|
import appReducer from 'reducers/app';
|
||||||
|
import walletReducer from 'reducers/wallet'
|
||||||
|
|
||||||
function isFunction(object) {
|
function isFunction(object) {
|
||||||
return typeof object === 'function';
|
return typeof object === 'function';
|
||||||
|
@ -17,6 +18,7 @@ function isNotFunction(object) {
|
||||||
|
|
||||||
const reducers = redux.combineReducers({
|
const reducers = redux.combineReducers({
|
||||||
app: appReducer,
|
app: appReducer,
|
||||||
|
wallet: walletReducer,
|
||||||
});
|
});
|
||||||
|
|
||||||
var middleware = [thunk]
|
var middleware = [thunk]
|
||||||
|
|
33
ui/js/triggers.js
Normal file
33
ui/js/triggers.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import {
|
||||||
|
shouldFetchTransactions,
|
||||||
|
shouldGetReceiveAddress,
|
||||||
|
} from 'selectors/wallet'
|
||||||
|
import {
|
||||||
|
doFetchTransactions,
|
||||||
|
doGetNewAddress,
|
||||||
|
} from 'actions/wallet'
|
||||||
|
|
||||||
|
const triggers = []
|
||||||
|
|
||||||
|
triggers.push({
|
||||||
|
selector: shouldFetchTransactions,
|
||||||
|
action: doFetchTransactions,
|
||||||
|
})
|
||||||
|
|
||||||
|
triggers.push({
|
||||||
|
selector: shouldGetReceiveAddress,
|
||||||
|
action: doGetNewAddress
|
||||||
|
})
|
||||||
|
|
||||||
|
const runTriggers = function() {
|
||||||
|
triggers.forEach(function(trigger) {
|
||||||
|
const state = app.store.getState();
|
||||||
|
const should = trigger.selector(state)
|
||||||
|
if (trigger.selector(state)) app.store.dispatch(trigger.action())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
triggers: triggers,
|
||||||
|
runTriggers: runTriggers
|
||||||
|
}
|
Loading…
Reference in a new issue