Skip to content

Commit

Permalink
feat(Watch Only): add watch only balance to separate section of balan…
Browse files Browse the repository at this point in the history
…ce dropdown
  • Loading branch information
plondon committed May 31, 2018
1 parent 93da3bd commit 65e72a9
Show file tree
Hide file tree
Showing 15 changed files with 201 additions and 9 deletions.
@@ -0,0 +1,53 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'

import { Remote } from 'blockchain-wallet-v4/src'
import { actions } from 'data'
import { getData } from './selectors'
import Error from './template.error'
import Loading from './template.loading'
import Success from './template.success'

class BtcBalance extends React.PureComponent {
constructor (props) {
super(props)
this.handleRefresh = this.handleRefresh.bind(this)
}

componentWillMount () {
if (Remote.NotAsked.is(this.props.data)) {
this.props.actions.fetchUnspendableBalance(this.props.context)
}
}

handleRefresh () {
this.props.actions.fetchUnspendableBalance(this.props.context)
}

render () {
const { data, large } = this.props

return data.cata({
Success: (value) => <Success balance={value} large={large} />,
Failure: (message) => <Error onRefresh={this.handleRefresh} />,
Loading: () => <Loading />,
NotAsked: () => <Loading />
})
}
}

BtcBalance.propTypes = {
context: PropTypes.string.isRequired
}

const mapStateToProps = (state) => ({
data: getData(state)
})

const mapDispatchToProps = (dispatch) => ({
actions: bindActionCreators(actions.core.data.bitcoin, dispatch)
})

export default connect(mapStateToProps, mapDispatchToProps)(BtcBalance)
@@ -0,0 +1,3 @@
import { selectors } from 'data'

export const getData = selectors.core.data.bitcoin.getUnspendableBalance
@@ -0,0 +1,22 @@
import React from 'react'
import styled from 'styled-components'
import { FormattedMessage } from 'react-intl'
import { Link } from 'blockchain-info-components'

const Wrapper = styled.div`
display: flex;
box-sizing: border-box;
justify-content: flex-end;
padding-right: 25px;
`
const ErrorLink = styled(Link)`
text-decoration: underline;
`

export default (props) => (
<Wrapper>
<ErrorLink size='12px' weight={300} onClick={() => props.onRefresh()}>
<FormattedMessage id='wallet.menutop.balance.refresh' defaultMessage='Refresh {curr} data' values={{curr: 'Watch Only Bitcoin'}} />
</ErrorLink>
</Wrapper>
)
@@ -0,0 +1,21 @@
import React from 'react'
import styled from 'styled-components'

import { FlatLoader } from 'blockchain-info-components'

const Wrapper = styled.div`
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 5px;
box-sizing: border-box;
`

export default (props) => {
return (
<Wrapper>
<FlatLoader width='50px' height='14px' />
</Wrapper>
)
}
@@ -0,0 +1,46 @@
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import FiatDisplay from 'components/Display/FiatDisplay'
import { LinkContainer } from 'react-router-bootstrap'
import { FormattedMessage } from 'react-intl'
import { Banner, Text } from 'blockchain-info-components'

const Wrapper = styled.div`
display: inline-flex;
flex-direction: row;
align-items: center;
padding-left: 5px;
margin-bottom: 10px;
padding-right: ${props => props.large ? '15px' : '25px'};
> div:last-child {
margin-left: 10px;
> div {
color: ${props => props.theme['gray-3']}
}
}
`

const Success = props => {
const { balance } = props

return (
<LinkContainer to='/btc/transactions'>
<Wrapper>
<Text size='10px' weight={300}>BTC</Text>
<Banner inline type='informational'>
<FiatDisplay coin='BTC' cursor='pointer' size='10px' weight={300}>{balance}</FiatDisplay>
<Text size='10px' weight={300}>
<FormattedMessage id='scenes.wallet.menutop.balance.watchonlybtcbalance' defaultMessage='&nbsp;Non-Spendable' />
</Text>
</Banner>
</Wrapper>
</LinkContainer>
)
}

Success.propTypes = {
balance: PropTypes.number.isRequired
}

export default Success
Expand Up @@ -12,7 +12,7 @@ class Balance extends React.PureComponent {
render () {
const { data } = this.props
return data.cata({
Success: (value) => <Success btcContext={value.btcSpendableContext} btcWatchOnlyContext={value.btcWatchOnlyContext} ethContext={value.ethContext} bchContext={value.bchContext} path={value.path} />,
Success: (value) => <Success btcContext={value.btcSpendableContext} btcUnspendableContext={value.btcUnspendableContext} ethContext={value.ethContext} bchContext={value.bchContext} path={value.path} />,
Failure: (message) => <Error>{message}</Error>,
Loading: () => <Loading />,
NotAsked: () => <Loading />
Expand Down
Expand Up @@ -12,9 +12,9 @@ export const getData = (state) => {
const transform = lift((btcActiveAddresses, btcActiveAccounts, ethContext, bchContext) => {
const spendable = (a) => a.priv
const accounts = btcActiveAccounts.map(prop('xpub'))
const btcWatchOnlyContext = reject(spendable, btcActiveAddresses).map(prop('addr'))
const btcUnspendableContext = reject(spendable, btcActiveAddresses).map(prop('addr'))
const btcSpendableContext = filter(spendable, btcActiveAddresses).map(prop('addr')).concat(accounts)
return {btcSpendableContext, btcWatchOnlyContext, ethContext, bchContext, path}
return {btcSpendableContext, btcUnspendableContext, ethContext, bchContext, path}
})
return transform(btcActiveAddressesR, btcActiveAccountsR, ethContextR, bchContextR)
}
Expand Up @@ -5,9 +5,12 @@ import TotalBalance from './TotalBalance'
import BtcBalance from './BtcBalance'
import EthBalance from './EthBalance'
import BchBalance from './BchBalance'
import BtcWatchOnlyBalance from './BtcWatchOnlyBalance'

import { FormattedMessage } from 'react-intl'
import { ComponentDropdown, Text } from 'blockchain-info-components'
import { ComponentDropdown, Separator, Text } from 'blockchain-info-components'

const Fragment = React.Fragment

const Wrapper = styled.div`
display: flex;
Expand All @@ -25,7 +28,6 @@ const BalanceText = styled(Text)`
font-size: 16px;
}
`

const BalanceDropdown = styled.div`
margin-top: 4px;
> div > ul {
Expand All @@ -34,7 +36,6 @@ const BalanceDropdown = styled.div`
padding: 0;
padding-top: 5px;
position: absolute;
background: transparent;
> li {
padding: 0px 6px;
text-align: right;
Expand Down Expand Up @@ -65,7 +66,7 @@ const BalanceDropdown = styled.div`
`

const Success = props => {
const { btcContext, ethContext, bchContext, path } = props
const { btcContext, ethContext, bchContext, btcUnspendableContext, path } = props

const getComponentOrder = () => {
switch (path) {
Expand All @@ -85,6 +86,13 @@ const Success = props => {
}
}

const getSubBalances = () => {
return btcUnspendableContext.length ? <Fragment>
<Separator margin='0' />
<BtcWatchOnlyBalance context={btcUnspendableContext} />
</Fragment> : null
}

return (
<Wrapper>
<BalanceText weight={300}>
Expand All @@ -96,7 +104,7 @@ const Success = props => {
forceSelected
color={'gray-5'}
selectedComponent={getComponentOrder()[0]}
components={getComponentOrder()}
components={getComponentOrder().concat(getSubBalances())}
callback={() => {}} />
</BalanceDropdown>
</Wrapper>
Expand Down
Expand Up @@ -4,7 +4,7 @@ import { Exchange } from 'blockchain-wallet-v4/src'
import { Color } from 'blockchain-info-components'

export const getData = (state) => {
const btcBalanceR = selectors.core.data.bitcoin.getBalance(state)
const btcBalanceR = selectors.core.data.bitcoin.getSpendableBalance(state)
const ethBalanceR = selectors.core.data.ethereum.getBalance(state)
const bchBalanceR = selectors.core.data.bch.getBalance(state)
const btcBalance = btcBalanceR.getOrElse(0)
Expand Down
Expand Up @@ -41,3 +41,9 @@ export const FETCH_BITCOIN_SPENDABLE_BALANCE = '@CORE.FETCH_BITCOIN_SPENDABLE_BA
export const FETCH_BITCOIN_SPENDABLE_BALANCE_LOADING = '@CORE.FETCH_BITCOIN_SPENDABLE_BALANCE_LOADING'
export const FETCH_BITCOIN_SPENDABLE_BALANCE_SUCCESS = '@CORE.FETCH_BITCOIN_SPENDABLE_BALANCE_SUCCESS'
export const FETCH_BITCOIN_SPENDABLE_BALANCE_FAILURE = '@CORE.FETCH_BITCOIN_SPENDABLE_BALANCE_FAILURE'

// FETCH_BITCOIN_TRANSACTION_HISTORY
export const FETCH_BITCOIN_UNSPENDABLE_BALANCE = '@CORE.FETCH_BITCOIN_UNSPENDABLE_BALANCE'
export const FETCH_BITCOIN_UNSPENDABLE_BALANCE_LOADING = '@CORE.FETCH_BITCOIN_UNSPENDABLE_BALANCE_LOADING'
export const FETCH_BITCOIN_UNSPENDABLE_BALANCE_SUCCESS = '@CORE.FETCH_BITCOIN_UNSPENDABLE_BALANCE_SUCCESS'
export const FETCH_BITCOIN_UNSPENDABLE_BALANCE_FAILURE = '@CORE.FETCH_BITCOIN_UNSPENDABLE_BALANCE_FAILURE'
Expand Up @@ -43,3 +43,9 @@ export const fetchSpendableBalance = (context) => ({ type: AT.FETCH_BITCOIN_SPEN
export const fetchSpendableBalanceLoading = () => ({ type: AT.FETCH_BITCOIN_SPENDABLE_BALANCE_LOADING })
export const fetchSpendableBalanceSuccess = (data) => ({ type: AT.FETCH_BITCOIN_SPENDABLE_BALANCE_SUCCESS, payload: data })
export const fetchSpendableBalanceFailure = (error) => ({ type: AT.FETCH_BITCOIN_SPENDABLE_BALANCE_FAILURE, payload: error })

// FETCH_BITCOIN_UNSPENDABLE_BALANCE
export const fetchUnspendableBalance = (context) => ({ type: AT.FETCH_BITCOIN_UNSPENDABLE_BALANCE, payload: { context } })
export const fetchUnspendableBalanceLoading = () => ({ type: AT.FETCH_BITCOIN_UNSPENDABLE_BALANCE_LOADING })
export const fetchUnspendableBalanceSuccess = (data) => ({ type: AT.FETCH_BITCOIN_UNSPENDABLE_BALANCE_SUCCESS, payload: data })
export const fetchUnspendableBalanceFailure = (error) => ({ type: AT.FETCH_BITCOIN_UNSPENDABLE_BALANCE_FAILURE, payload: error })
12 changes: 12 additions & 0 deletions packages/blockchain-wallet-v4/src/redux/data/bitcoin/reducers.js
Expand Up @@ -11,6 +11,7 @@ const INITIAL_STATE = {
transactions: [],
transactions_fiat: {},
spendable_balance: Remote.NotAsked,
unspendable_balance: Remote.NotAsked,
transaction_history: Remote.NotAsked
}

Expand Down Expand Up @@ -111,6 +112,17 @@ const bitcoinReducer = (state = INITIAL_STATE, action) => {
case AT.FETCH_BITCOIN_SPENDABLE_BALANCE_FAILURE: {
return assoc('spendable_balance', Remote.Failure(payload), state)
}
case AT.FETCH_BITCOIN_UNSPENDABLE_BALANCE_LOADING: {
return assoc('unspendable_balance', Remote.Loading, state)
}
case AT.FETCH_BITCOIN_UNSPENDABLE_BALANCE_SUCCESS: {
const { wallet } = payload
const balance = wallet.final_balance
return assoc('unspendable_balance', Remote.Success(balance), state)
}
case AT.FETCH_BITCOIN_UNSPENDABLE_BALANCE_FAILURE: {
return assoc('unspendable_balance', Remote.Failure(payload), state)
}
default:
return state
}
Expand Down
Expand Up @@ -12,6 +12,7 @@ export default ({ api }) => {
yield takeLatest(AT.FETCH_BITCOIN_RATES, dataBtcSagas.fetchRates)
yield fork(dataBtcSagas.watchTransactions)
yield takeLatest(AT.FETCH_BITCOIN_SPENDABLE_BALANCE, dataBtcSagas.fetchSpendableBalance)
yield takeLatest(AT.FETCH_BITCOIN_UNSPENDABLE_BALANCE, dataBtcSagas.fetchUnspendableBalance)
yield takeLatest(AT.FETCH_BITCOIN_TRANSACTION_HISTORY, dataBtcSagas.fetchTransactionHistory)
}
}
12 changes: 12 additions & 0 deletions packages/blockchain-wallet-v4/src/redux/data/bitcoin/sagas.js
Expand Up @@ -116,6 +116,17 @@ export default ({ api }) => {
}
}

const fetchUnspendableBalance = function * (action) {
try {
const { context } = action.payload
yield put(A.fetchUnspendableBalanceLoading())
const data = yield call(api.fetchBlockchainData, context)
yield put(A.fetchUnspendableBalanceSuccess(data))
} catch (e) {
yield put(A.fetchUnspendableBalanceFailure(e))
}
}

const multiaddrSaga = function * (data) {
const btcData = {
addresses: indexBy(prop('address'), prop('addresses', data)),
Expand All @@ -133,6 +144,7 @@ export default ({ api }) => {
fetchFiatAtTime,
watchTransactions,
fetchSpendableBalance,
fetchUnspendableBalance,
fetchTransactionHistory
}
}
Expand Up @@ -19,6 +19,8 @@ export const getCoins = path([dataPath, 'bitcoin', 'payment', 'coins'])

export const getSpendableBalance = path([dataPath, 'bitcoin', 'spendable_balance'])

export const getUnspendableBalance = path([dataPath, 'bitcoin', 'unspendable_balance'])

// Specific
export const getChangeIndex = curry((xpub, state) => getAddresses(state).map(path([xpub, 'change_index'])))

Expand Down

0 comments on commit 65e72a9

Please sign in to comment.