Browse Source

feature(balance + currency): dynamic balance in Nav plus add currency in reducer

renovate/lint-staged-8.x
Jack Mallers 7 years ago
parent
commit
35fc8b8a1c
  1. 10
      app/api/index.js
  2. 6
      app/reducers/activity.js
  3. 52
      app/reducers/balance.js
  4. 6
      app/reducers/index.js
  5. 50
      app/reducers/info.js
  6. 54
      app/reducers/ticker.js
  7. 25
      app/routes/activity/components/Activity.js
  8. 2
      app/routes/activity/components/Activity.scss
  9. 2
      app/routes/activity/components/components/Invoices.js
  10. 1
      app/routes/activity/components/components/Invoices.scss
  11. 13
      app/routes/app/components/App.js
  12. 32
      app/routes/app/components/components/Nav.js
  13. 31
      app/routes/app/components/components/Nav.scss
  14. 14
      app/routes/app/containers/AppContainer.js
  15. 30
      package-lock.json
  16. 1
      package.json

10
app/api/index.js

@ -31,4 +31,14 @@ export function callApi(endpoint, method = 'get', data = null) {
export function callApis(endpoints) {
const BASE_URL = 'http://localhost:3000/api/'
return axios.all(endpoints.map(endpoint => callApi(endpoint)))
}
export function requestTicker() {
const BASE_URL = 'https://api.coinmarketcap.com/v1/ticker/bitcoin/'
return axios({
method: 'get',
url: BASE_URL
})
.then(response => response.data)
.catch(error => error)
}

6
app/reducers/activity.js

@ -32,15 +32,15 @@ export const fetchActivity = () => async (dispatch) => {
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
[GET_ACTIVITY]: (state) => ({ ...state, isLoading: true }),
[RECEIVE_ACTIVITY]: (state, { payments, invoices }) => ({ ...state, isLoading: false, payments, invoices })
[GET_ACTIVITY]: (state) => ({ ...state, activityLoading: true }),
[RECEIVE_ACTIVITY]: (state, { payments, invoices }) => ({ ...state, activityLoading: false, payments, invoices })
}
// ------------------------------------
// Reducer
// ------------------------------------
const initialState = {
isLoading: false,
activityLoading: false,
payments: [],
invoices: []
}

52
app/reducers/balance.js

@ -0,0 +1,52 @@
import { callApis } from '../api'
// ------------------------------------
// Constants
// ------------------------------------
export const GET_BALANCE = 'GET_BALANCE'
export const RECEIVE_BALANCE = 'RECEIVE_BALANCE'
// ------------------------------------
// Actions
// ------------------------------------
export function getBalance() {
return {
type: GET_BALANCE
}
}
export function receiveBalance(data) {
return {
type: RECEIVE_BALANCE,
walletBalance: data[0].data.balance,
channelBalance: data[1].data.balance
}
}
export const fetchBalance = () => async (dispatch) => {
dispatch(getBalance())
const balance = await callApis(['wallet_balance', 'channel_balance'])
dispatch(receiveBalance(balance))
}
// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
[GET_BALANCE]: (state) => ({ ...state, balanceLoading: true }),
[RECEIVE_BALANCE]: (state, { walletBalance, channelBalance }) => ({ ...state, balanceLoading: false, walletBalance, channelBalance })
}
// ------------------------------------
// Reducer
// ------------------------------------
const initialState = {
balanceLoading: false,
walletBalance: null,
channelBalance: null
}
export default function balanceReducer(state = initialState, action) {
const handler = ACTION_HANDLERS[action.type]
return handler ? handler(state, action) : state
}

6
app/reducers/index.js

@ -1,10 +1,16 @@
// @flow
import { combineReducers } from 'redux'
import { routerReducer as router } from 'react-router-redux'
import ticker from './ticker'
import info from './info'
import balance from './balance'
import activity from './activity'
const rootReducer = combineReducers({
router,
ticker,
info,
balance,
activity
})

50
app/reducers/info.js

@ -0,0 +1,50 @@
import { callApi } from '../api'
// ------------------------------------
// Constants
// ------------------------------------
export const GET_INFO = 'GET_INFO'
export const RECEIVE_INFO = 'RECEIVE_INFO'
// ------------------------------------
// Actions
// ------------------------------------
export function getInfo() {
return {
type: GET_INFO
}
}
export function receiveInfo(data) {
return {
type: RECEIVE_INFO,
data
}
}
export const fetchInfo = () => async (dispatch) => {
dispatch(getInfo())
const info = await callApi('info')
dispatch(receiveInfo(info.data))
}
// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
[GET_INFO]: (state) => ({ ...state, infoLoading: true }),
[RECEIVE_INFO]: (state, { data }) => ({ ...state, infoLoading: false, data })
}
// ------------------------------------
// Reducer
// ------------------------------------
const initialState = {
infoLoading: false,
data: {}
}
export default function infoReducer(state = initialState, action) {
const handler = ACTION_HANDLERS[action.type]
return handler ? handler(state, action) : state
}

54
app/reducers/ticker.js

@ -0,0 +1,54 @@
import { requestTicker } from '../api'
// ------------------------------------
// Constants
// ------------------------------------
export const GET_TICKER = 'GET_TICKER'
export const RECIEVE_TICKER = 'RECIEVE_TICKER'
// ------------------------------------
// Actions
// ------------------------------------
export function getTicker() {
return {
type: GET_TICKER
}
}
export function recieveTicker(ticker) {
return {
type: RECIEVE_TICKER,
ticker
}
}
export const fetchTicker = () => async (dispatch) => {
dispatch(getTicker())
const ticker = await requestTicker()
dispatch(recieveTicker(ticker))
return ticker
}
// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
[GET_TICKER]: (state) => ({ ...state, tickerLoading: true }),
[RECIEVE_TICKER]: (state, { ticker }) => ({...state, btcTicker: ticker[0] })
}
// ------------------------------------
// Reducer
// ------------------------------------
const initialState = {
tickerLoading: false,
current: 'btc',
crypto: 'btc',
btcTicker: null
}
export default function tickerReducer(state = initialState, action) {
const handler = ACTION_HANDLERS[action.type]
return handler ? handler(state, action) : state
}

25
app/routes/activity/components/Activity.js

@ -1,5 +1,6 @@
// @flow
import React, { Component } from 'react'
import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup'
import { MdSearch } from 'react-icons/lib/md'
import Payments from './components/Payments'
import Invoices from './components/Invoices'
@ -9,7 +10,7 @@ class Activity extends Component {
constructor(props, context) {
super(props, context)
this.state = {
tab: 2
tab: 1
}
}
@ -19,9 +20,9 @@ class Activity extends Component {
render() {
const { tab } = this.state
const { activity: { isLoading, payments, invoices } } = this.props
const { activity: { activityLoading, payments, invoices } } = this.props
if (isLoading) { return <div>Loading...</div> }
if (activityLoading) { return <div>Loading...</div> }
return (
<div>
<div className={styles.search}>
@ -47,12 +48,18 @@ class Activity extends Component {
</span>
</header>
<div className={styles.activityContainer}>
{
tab === 1 ?
<Payments payments={payments} />
:
<Invoices invoices={invoices} />
}
<CSSTransitionGroup
transitionName='activity'
transitionEnterTimeout={500}
transitionLeaveTimeout={500}
>
{
tab === 1 ?
<Payments key={1} payments={payments} />
:
<Invoices key={2} invoices={invoices} />
}
</CSSTransitionGroup>
</div>
</div>
</div>

2
app/routes/activity/components/Activity.scss

@ -98,4 +98,4 @@
.right {
text-align: right;
}
}
}

2
app/routes/activity/components/components/Invoices.js

@ -25,7 +25,7 @@ class Invoices extends Component {
invoices.map((invoice, index) => (
<li key={index} className={styles.invoice}>
<div className={styles.left}>
<div className={styles.path}>{invoice.payment_request}</div>
<div className={styles.path}>{`${invoice.payment_request.substring(0, 75)}...`}</div>
</div>
<div className={styles.center}>
<div>{invoice.memo}</div>

1
app/routes/activity/components/components/Invoices.scss

@ -13,7 +13,6 @@
.left {
flex: 7;
font-size: 12px;
}
.center {

13
app/routes/app/components/App.js

@ -4,12 +4,20 @@ import Nav from './components/Nav.js'
import styles from './App.scss'
class App extends Component {
componentWillMount() {
const { fetchTicker, fetchBalance } = this.props
fetchTicker()
fetchBalance()
}
render() {
const { ticker, balance, children } = this.props
return (
<div>
<Nav />
<Nav ticker={ticker} balance={balance} />
<div className={styles.content}>
{this.props.children}
{children}
</div>
</div>
)
@ -17,6 +25,7 @@ class App extends Component {
}
App.propTypes = {
ticker: React.PropTypes.object,
children: React.PropTypes.object
}

32
app/routes/app/components/components/Nav.js

@ -4,31 +4,40 @@ import { Link } from 'react-router-dom'
import ReactSVG from 'react-svg'
import { MdAccountBalanceWallet, MdSettings } from 'react-icons/lib/md'
import { FaClockO, FaBitcoin, FaDollar } from 'react-icons/lib/fa'
import { satoshisToBtc } from '../../../../utils/bitcoin'
import styles from './Nav.scss'
class Nav extends Component {
render() {
console.log('props: ', this.props)
const { ticker, balance } = this.props
return (
<nav className={styles.nav}>
<ul className={styles.info}>
<li className={`${styles.currencies} ${styles.link}`}>
<span className={`${styles.currency} ${styles.btc}`}>
<span className={`${styles.currency} ${ticker.current === 'btc' ? styles.active : ''}`}>
<FaBitcoin />
</span>
<span className={`${styles.currency} ${styles.usd}`}>
<span className={`${styles.currency} ${ticker.current === 'usd' ? styles.active : ''}`}>
<FaDollar />
</span>
</li>
<li className={`${styles.logo} ${styles.link}`}>
<ReactSVG path='../resources/zap_2.svg' />
</li>
<li className={`${styles.wallet} ${styles.link}`}>
<span>
$56.13
</span>
<li className={`${styles.balance} ${styles.link}`}>
<p>
<span>{ticker.current === 'btc' ? <FaBitcoin /> : <FaDollar />}</span>
<span>{satoshisToBtc(balance.walletBalance)}</span>
</p>
<p>
<span>{ticker.current === 'btc' ? <FaBitcoin /> : <FaDollar />}</span>
<span>{satoshisToBtc(balance.channelBalance)}</span>
</p>
</li>
</ul>
<div className={styles.logo}>
<ReactSVG path='../resources/zap_2.svg' />
</div>
<ul className={styles.links}>
<li className={styles.link}>
<FaClockO />
@ -56,4 +65,9 @@ class Nav extends Component {
}
}
Nav.propTypes = {
ticker: React.PropTypes.object.isRequired,
balance: React.PropTypes.object.isRequired
}
export default Nav

31
app/routes/app/components/components/Nav.scss

@ -11,31 +11,29 @@
}
.info {
margin-bottom: 50%;
padding: 10px;
padding: 25px 10px 10px 10px;
.link {
display: inline-block;
vertical-align: top;
list-style-type: none;
width: 33.3%;
width: 50%;
cursor: pointer;
}
}
.currency {
margin: 0 5px;
margin: 0 1px;
&.usd {
&.active {
color: #ebb864;
font-weight: bold;
}
}
.logo {
text-align: center;
margin-top: 50px;
margin-bottom: 50%;
svg {
width: 100px;
@ -43,9 +41,26 @@
}
}
.wallet {
.balance {
text-align: right;
color: #ebb864;
p {
margin: 2px 0;
&:first-child {
font-size: 14px;
}
&:nth-child(2) {
font-size: 12px;
}
span {
display: inline-block;
vertical-align: top;
}
}
}
.links {

14
app/routes/app/containers/AppContainer.js

@ -1,8 +1,18 @@
import { connect } from 'react-redux'
import App from '../components/App'
import { fetchTicker } from '../../../reducers/ticker'
import { fetchBalance } from '../../../reducers/balance'
import { fetchInfo } from '../../../reducers/info'
const mapDispatchToProps = {}
const mapDispatchToProps = {
fetchTicker,
fetchBalance,
fetchInfo
}
const mapStateToProps = (state) => ({})
const mapStateToProps = (state) => ({
ticker: state.ticker,
balance: state.balance
})
export default connect(mapStateToProps, mapDispatchToProps)(App)

30
package-lock.json

@ -2613,6 +2613,11 @@
"lazy-cache": "1.0.4"
}
},
"chain-function": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/chain-function/-/chain-function-1.0.0.tgz",
"integrity": "sha1-DUqzfn4Y6tC9xHuSB2QRjOWHM9w="
},
"chainsaw": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz",
@ -4150,6 +4155,11 @@
}
}
},
"dom-helpers": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.2.1.tgz",
"integrity": "sha1-MgPgf+0he9H0JLAZc1WC/Deyglo="
},
"dom-serializer": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
@ -12242,6 +12252,14 @@
"prop-types": "15.5.10"
}
},
"react-addons-css-transition-group": {
"version": "15.6.0",
"resolved": "https://registry.npmjs.org/react-addons-css-transition-group/-/react-addons-css-transition-group-15.6.0.tgz",
"integrity": "sha1-aYh89uSHTSXNZuIqaZ4p8NZIq6A=",
"requires": {
"react-transition-group": "1.2.0"
}
},
"react-addons-test-utils": {
"version": "15.6.0",
"resolved": "https://registry.npmjs.org/react-addons-test-utils/-/react-addons-test-utils-15.6.0.tgz",
@ -12422,6 +12440,18 @@
}
}
},
"react-transition-group": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.0.tgz",
"integrity": "sha1-tR/JIbDDg1p+98Vxx5/ILHPpIE8=",
"requires": {
"chain-function": "1.0.0",
"dom-helpers": "3.2.1",
"loose-envify": "1.3.1",
"prop-types": "15.5.10",
"warning": "3.0.0"
}
},
"read-config-file": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-0.1.3.tgz",

1
package.json

@ -190,6 +190,7 @@
"history": "^4.6.3",
"moment-timezone": "^0.5.13",
"react": "^15.6.1",
"react-addons-css-transition-group": "^15.6.0",
"react-dom": "^15.6.1",
"react-hot-loader": "3.0.0-beta.6",
"react-icons": "^2.2.5",

Loading…
Cancel
Save