Browse Source

feature(network): network tab all hooked up to backend

renovate/lint-staged-8.x
Jack Mallers 7 years ago
parent
commit
9320584009
  1. 2
      app/components/Contacts/ContactModal.js
  2. 2
      app/components/Contacts/ContactsForm.scss
  3. 195
      app/components/Contacts/Network.js
  4. 62
      app/components/Contacts/Network.scss
  5. 2
      app/components/Wallet/ReceiveModal.js
  6. 12
      app/reducers/channels.js
  7. 4
      app/routes/activity/components/components/Activity.scss
  8. 2
      app/routes/activity/components/components/Modal/Modal.js
  9. 17
      app/routes/app/components/App.js
  10. 99
      app/routes/app/containers/AppContainer.js

2
app/components/Contacts/ContactModal.js

@ -26,7 +26,7 @@ const ContactModal = ({
},
content: {
top: 'auto',
left: '20%',
left: '0',
right: '0',
bottom: 'auto',
width: '40%',

2
app/components/Contacts/ContactsForm.scss

@ -6,7 +6,7 @@
margin: 50px auto;
position: absolute;
top: auto;
left: 20%;
left: 0;
right: 0;
bottom: auto;
background: $white;

195
app/components/Contacts/Network.js

@ -1,56 +1,165 @@
import React from 'react'
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import find from 'lodash/find'
import Isvg from 'react-inlinesvg'
import { FaAngleDown, FaCircle } from 'react-icons/lib/fa'
import { FaAngleDown, FaCircle, FaRepeat } from 'react-icons/lib/fa'
import { btc } from 'utils'
import plus from 'icons/plus.svg'
import search from 'icons/search.svg'
import styles from './Network.scss'
const Network = ({ channels, balance, currentTicker }) => {
console.log('channels: ', channels)
return (
<div className={styles.network}>
<header className={styles.header}>
<section>
<h2>My Network</h2>
<span className={styles.channelAmount}>
{btc.satoshisToBtc(balance.channelBalance)}BTC ${btc.satoshisToUsd(balance.channelBalance, currentTicker.price_usd).toLocaleString()}
</span>
</section>
<section className={styles.addChannel}>
<Isvg src={plus} />
</section>
</header>
<div className={styles.channels}>
<header className={styles.listHeader}>
<div>
<span>All <FaAngleDown /></span>
</div>
<div>
<span>Refresh</span>
</div>
class Network extends Component {
constructor(props) {
super(props)
this.state = {
refreshing: false
}
}
render() {
const {
channels: {
searchQuery,
filterPulldown,
filter,
loadingChannelPubkeys,
closingChannelIds
},
currentChannels,
balance,
currentTicker,
nodes,
fetchChannels,
openContactsForm,
nonActiveFilters,
toggleFilterPulldown,
changeFilter,
updateChannelSearchQuery
} = this.props
const refreshClicked = () => {
// turn the spinner on
this.setState({ refreshing: true })
// store event in icon so we dont get an error when react clears it
const icon = this.repeat.childNodes
// fetch channels
fetchChannels()
// wait for the svg to appear as child
const svgTimeout = setTimeout(() => {
if (icon[0].tagName === 'svg') {
// spin icon for 1 sec
icon[0].style.animation = 'spin 1000ms linear 1'
clearTimeout(svgTimeout)
}
}, 1)
// clear animation after the second so we can reuse it
const refreshTimeout = setTimeout(() => {
icon[0].style.animation = ''
this.setState({ refreshing: false })
clearTimeout(refreshTimeout)
}, 1000)
}
const displayNodeName = (channel) => {
const node = find(nodes, node => channel.remote_pubkey === node.pub_key)
if (node && node.alias.length) { return node.alias }
return channel.remote_pubkey ? channel.remote_pubkey.substring(0, 10) : channel.remote_node_pub.substring(0, 10)
}
const channelStatus = (channel) => {
if (Object.prototype.hasOwnProperty.call(channel, 'confirmation_height')) { return 'pending' }
if (Object.prototype.hasOwnProperty.call(channel, 'closing_txid')) { return 'closing' }
if (!channel.active) { return 'offline' }
return 'online'
}
return (
<div className={styles.network}>
<header className={styles.header}>
<section>
<h2>My Network</h2>
<span className={styles.channelAmount}>
{btc.satoshisToBtc(balance.channelBalance)}BTC ${btc.satoshisToUsd(balance.channelBalance, currentTicker.price_usd).toLocaleString()}
</span>
</section>
<section className={styles.addChannel} onClick={openContactsForm}>
<Isvg src={plus} />
</section>
</header>
<ul>
{
channels.channels.map(channel => {
console.log('channel: ', channel)
})
}
</ul>
</div>
<div className={styles.channels}>
<header className={styles.listHeader}>
<section>
<h2 onClick={toggleFilterPulldown} className={styles.filterTitle}>
{filter.name} <span className={filterPulldown && styles.pulldown}><FaAngleDown /></span>
</h2>
<ul className={`${styles.filters} ${filterPulldown && styles.active}`}>
{
nonActiveFilters.map(f => (
<li key={f.key} onClick={() => changeFilter(f)}>
{f.name}
</li>
))
}
</ul>
</section>
<section className={styles.refreshContainer}>
<span className={styles.refresh} onClick={refreshClicked} ref={(ref) => { this.repeat = ref }}>
{
this.state.refreshing ?
<FaRepeat />
:
'Refresh'
}
</span>
</section>
</header>
<footer className={styles.search}>
<label htmlFor='search' className={`${styles.label} ${styles.input}`}>
<Isvg src={search} />
</label>
<input id='search' type='text' className={`${styles.text} ${styles.input}`} placeholder='search by alias or pubkey' />
</footer>
</div>
)
<ul className={filterPulldown && styles.fade}>
{
currentChannels.length > 0 && currentChannels.map((channelObj, index) => {
const channel = Object.prototype.hasOwnProperty.call(channelObj, 'channel') ? channelObj.channel : channelObj
return (
<li key={index} className={styles.channel}>
<span>{displayNodeName(channel)}</span>
<span className={styles[channelStatus(channelObj)]}><FaCircle /></span>
</li>
)
})
}
</ul>
</div>
<footer className={styles.search}>
<label htmlFor='search' className={`${styles.label} ${styles.input}`}>
<Isvg src={search} />
</label>
<input
id='search'
type='text'
className={`${styles.text} ${styles.input}`}
placeholder='search by alias or pubkey'
value={searchQuery}
onChange={event => updateChannelSearchQuery(event.target.value)}
/>
</footer>
</div>
)
}
}
Network.propTypes = {}

62
app/components/Contacts/Network.scss

@ -21,30 +21,78 @@
font-size: 14px;
font-weight: bold;
letter-spacing: 1.2px;
margin-bottom: 5px;
}
.channelAmount {
font-size: 10px;
opacity: 0.5;
}
.addChannel {
cursor: pointer;
transition: all 0.25s;
&:hover {
color: $darkestgrey;
}
}
}
.channels {
padding: 20px;
.listHeader {
position: relative;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: baseline;
h2, h2 span {
color: $white;
cursor: pointer;
transition: color 0.25s;
&:hover {
color: $darkestgrey;
}
}
h2, .filters li {
font-size: 12px;
}
.filters {
display: none;
&.active {
display: block;
position: absolute;
bottom: -80px;
z-index: 10;
li {
margin: 5px 0;
cursor: pointer;
color: $white;
&:hover {
color: $darkestgrey;
}
}
}
}
span {
color: white;
opacity: 0.5;
color: $white;
opacity: 1;
font-size: 10px;
cursor: pointer;
&:nth-child(1) {
opacity: 1;
transition: all 0.25s;
&:hover {
opacity: 0.5;
}
}
}
@ -53,6 +101,10 @@
margin-top: 20px;
}
.fade {
opacity: 0.15;
}
.channel {
display: flex;
flex-direction: row;

2
app/components/Wallet/ReceiveModal.js

@ -17,7 +17,7 @@ const ReceiveModal = ({
},
content: {
top: 'auto',
left: '20%',
left: '0',
right: '0',
bottom: 'auto',
width: '40%',

12
app/reducers/channels.js

@ -512,13 +512,13 @@ const initialState = {
viewType: 0,
filterPulldown: false,
filter: { key: 'ALL_CHANNELS', name: 'All Contacts' },
filter: { key: 'ALL_CHANNELS', name: 'All' },
filters: [
{ key: 'ALL_CHANNELS', name: 'All Contacts' },
{ key: 'ACTIVE_CHANNELS', name: 'Online Contacts' },
{ key: 'NON_ACTIVE_CHANNELS', name: 'Offline Contacts' },
{ key: 'OPEN_PENDING_CHANNELS', name: 'Pending Contacts' },
{ key: 'CLOSING_PENDING_CHANNELS', name: 'Closing Contacts' }
{ key: 'ALL_CHANNELS', name: 'All' },
{ key: 'ACTIVE_CHANNELS', name: 'Online' },
{ key: 'NON_ACTIVE_CHANNELS', name: 'Offline' },
{ key: 'OPEN_PENDING_CHANNELS', name: 'Pending' },
{ key: 'CLOSING_PENDING_CHANNELS', name: 'Closing' }
],
loadingChannelPubkeys: [],

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

@ -80,12 +80,12 @@
position: relative;
width: 20px;
height: 20px;
background: $main;
background: #31343f;
border-radius: 50%;
margin-right: 5px;
svg {
color: $spaceblue;
color: $white;
font-size: 10px;
vertical-align: middle;
display: flex;

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

@ -24,7 +24,7 @@ const Modal = ({
},
content: {
top: 'auto',
left: '20%',
left: '0',
right: '0',
bottom: 'auto',
width: '40%',

17
app/routes/app/components/App.js

@ -5,17 +5,19 @@ import LoadingBolt from 'components/LoadingBolt'
import Form from 'components/Form'
import ModalRoot from 'components/ModalRoot'
import Network from 'components/Contacts/Network'
import ContactsForm from 'components/Contacts/ContactsForm'
import styles from './App.scss'
class App extends Component {
componentWillMount() {
const { fetchTicker, fetchInfo, newAddress, fetchChannels, fetchBalance } = this.props
const { fetchTicker, fetchInfo, newAddress, fetchChannels, fetchBalance, fetchDescribeNetwork } = this.props
fetchTicker()
fetchInfo()
newAddress('np2wkh')
fetchChannels()
fetchBalance()
fetchDescribeNetwork()
}
render() {
@ -35,6 +37,10 @@ class App extends Component {
error: { error },
clearError,
contactsFormProps,
networkTabProps,
children
} = this.props
@ -52,16 +58,15 @@ class App extends Component {
currency={ticker.currency}
/>
<ContactsForm {...contactsFormProps} />
<Form formType={form.formType} formProps={formProps} closeForm={closeForm} />
<div className={styles.content}>
{children}
</div>
<Network
channels={channels}
balance={balance}
currentTicker={currentTicker}
/>
<Network {...networkTabProps} />
</div>
)
}

99
app/routes/app/containers/AppContainer.js

@ -17,9 +17,32 @@ import { createInvoice, fetchInvoice } from 'reducers/invoice'
import { fetchBlockHeight, lndSelectors } from 'reducers/lnd'
import { fetchChannels } from 'reducers/channels'
import {
fetchChannels,
openChannel,
channelsSelectors,
currentChannels,
toggleFilterPulldown,
changeFilter,
updateChannelSearchQuery
} from 'reducers/channels'
import {
openContactsForm,
closeContactsForm,
updateContactFormSearchQuery,
updateManualFormSearchQuery,
updateContactCapacity,
contactFormSelectors,
updateManualFormErrors
} from 'reducers/contactsform'
import { fetchBalance } from 'reducers/balance'
import { fetchDescribeNetwork } from 'reducers/network'
import { clearError } from 'reducers/error'
@ -53,8 +76,23 @@ const mapDispatchToProps = {
fetchBlockHeight,
clearError,
fetchBalance,
fetchChannels,
fetchBalance
openChannel,
toggleFilterPulldown,
changeFilter,
updateChannelSearchQuery,
openContactsForm,
closeContactsForm,
updateContactFormSearchQuery,
updateManualFormSearchQuery,
updateContactCapacity,
contactFormSelectors,
updateManualFormErrors,
fetchDescribeNetwork
}
const mapStateToProps = state => ({
@ -65,7 +103,9 @@ const mapStateToProps = state => ({
info: state.info,
payment: state.payment,
transaction: state.transaction,
peers: state.peers,
channels: state.channels,
contactsform: state.contactsform,
balance: state.balance,
form: state.form,
@ -77,6 +117,8 @@ const mapStateToProps = state => ({
error: state.error,
network: state.network,
currentTicker: tickerSelectors.currentTicker(state),
isOnchain: payFormSelectors.isOnchain(state),
isLn: payFormSelectors.isLn(state),
@ -84,7 +126,17 @@ const mapStateToProps = state => ({
inputCaption: payFormSelectors.inputCaption(state),
showPayLoadingScreen: payFormSelectors.showPayLoadingScreen(state),
payFormIsValid: payFormSelectors.payFormIsValid(state),
syncPercentage: lndSelectors.syncPercentage(state)
syncPercentage: lndSelectors.syncPercentage(state),
filteredNetworkNodes: contactFormSelectors.filteredNetworkNodes(state),
showManualForm: contactFormSelectors.showManualForm(state),
manualFormIsValid: contactFormSelectors.manualFormIsValid(state),
currentChannels: currentChannels(state),
activeChannelPubkeys: channelsSelectors.activeChannelPubkeys(state),
nonActiveChannelPubkeys: channelsSelectors.nonActiveChannelPubkeys(state),
pendingOpenChannelPubkeys: channelsSelectors.pendingOpenChannelPubkeys(state),
nonActiveFilters: channelsSelectors.nonActiveFilters(state)
})
const mergeProps = (stateProps, dispatchProps, ownProps) => {
@ -180,16 +232,57 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
return {}
}
const networkTabProps = {
currentChannels: stateProps.currentChannels,
channels: stateProps.channels,
balance: stateProps.balance,
currentTicker: stateProps.currentTicker,
contactsform: stateProps.contactsform,
nodes: stateProps.network.nodes,
nonActiveFilters: stateProps.nonActiveFilters,
fetchChannels: dispatchProps.fetchChannels,
openContactsForm: dispatchProps.openContactsForm,
contactFormSelectors: dispatchProps.contactFormSelectors,
updateManualFormError: dispatchProps.updateManualFormErrors,
toggleFilterPulldown: dispatchProps.toggleFilterPulldown,
changeFilter: dispatchProps.changeFilter,
updateChannelSearchQuery: dispatchProps.updateChannelSearchQuery
}
const contactsFormProps = {
closeContactsForm: dispatchProps.closeContactsForm,
updateContactFormSearchQuery: dispatchProps.updateContactFormSearchQuery,
updateManualFormSearchQuery: dispatchProps.updateManualFormSearchQuery,
updateContactCapacity: dispatchProps.updateContactCapacity,
openChannel: dispatchProps.openChannel,
updateManualFormErrors: dispatchProps.updateManualFormErrors,
contactsform: stateProps.contactsform,
filteredNetworkNodes: stateProps.filteredNetworkNodes,
loadingChannelPubkeys: stateProps.channels.loadingChannelPubkeys,
showManualForm: stateProps.showManualForm,
manualFormIsValid: stateProps.manualFormIsValid,
activeChannelPubkeys: stateProps.activeChannelPubkeys,
nonActiveChannelPubkeys: stateProps.nonActiveChannelPubkeys,
pendingOpenChannelPubkeys: stateProps.pendingOpenChannelPubkeys
}
return {
...stateProps,
...dispatchProps,
...ownProps,
// props for the network sidebar
networkTabProps,
// props for the contacts form
contactsFormProps,
// Props to pass to the pay form
formProps: formProps(stateProps.form.formType),
// action to close form
closeForm: () => dispatchProps.setFormType(null)
}
}

Loading…
Cancel
Save