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. 139
      app/components/Contacts/Network.js
  4. 60
      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: { content: {
top: 'auto', top: 'auto',
left: '20%', left: '0',
right: '0', right: '0',
bottom: 'auto', bottom: 'auto',
width: '40%', width: '40%',

2
app/components/Contacts/ContactsForm.scss

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

139
app/components/Contacts/Network.js

@ -1,14 +1,90 @@
import React from 'react' import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import find from 'lodash/find'
import Isvg from 'react-inlinesvg' 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 { btc } from 'utils'
import plus from 'icons/plus.svg' import plus from 'icons/plus.svg'
import search from 'icons/search.svg' import search from 'icons/search.svg'
import styles from './Network.scss' import styles from './Network.scss'
const Network = ({ channels, balance, currentTicker }) => { class Network extends Component {
console.log('channels: ', channels) 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 ( return (
<div className={styles.network}> <div className={styles.network}>
@ -19,25 +95,50 @@ const Network = ({ channels, balance, currentTicker }) => {
{btc.satoshisToBtc(balance.channelBalance)}BTC ${btc.satoshisToUsd(balance.channelBalance, currentTicker.price_usd).toLocaleString()} {btc.satoshisToBtc(balance.channelBalance)}BTC ${btc.satoshisToUsd(balance.channelBalance, currentTicker.price_usd).toLocaleString()}
</span> </span>
</section> </section>
<section className={styles.addChannel}> <section className={styles.addChannel} onClick={openContactsForm}>
<Isvg src={plus} /> <Isvg src={plus} />
</section> </section>
</header> </header>
<div className={styles.channels}> <div className={styles.channels}>
<header className={styles.listHeader}> <header className={styles.listHeader}>
<div> <section>
<span>All <FaAngleDown /></span> <h2 onClick={toggleFilterPulldown} className={styles.filterTitle}>
</div> {filter.name} <span className={filterPulldown && styles.pulldown}><FaAngleDown /></span>
<div> </h2>
<span>Refresh</span> <ul className={`${styles.filters} ${filterPulldown && styles.active}`}>
</div> {
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> </header>
<ul> <ul className={filterPulldown && styles.fade}>
{ {
channels.channels.map(channel => { currentChannels.length > 0 && currentChannels.map((channelObj, index) => {
console.log('channel: ', channel) 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> </ul>
@ -47,10 +148,18 @@ const Network = ({ channels, balance, currentTicker }) => {
<label htmlFor='search' className={`${styles.label} ${styles.input}`}> <label htmlFor='search' className={`${styles.label} ${styles.input}`}>
<Isvg src={search} /> <Isvg src={search} />
</label> </label>
<input id='search' type='text' className={`${styles.text} ${styles.input}`} placeholder='search by alias or pubkey' /> <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> </footer>
</div> </div>
) )
}
} }
Network.propTypes = {} Network.propTypes = {}

60
app/components/Contacts/Network.scss

@ -21,30 +21,78 @@
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;
letter-spacing: 1.2px; letter-spacing: 1.2px;
margin-bottom: 5px;
} }
.channelAmount { .channelAmount {
font-size: 10px; font-size: 10px;
opacity: 0.5; opacity: 0.5;
} }
.addChannel {
cursor: pointer;
transition: all 0.25s;
&:hover {
color: $darkestgrey;
}
}
} }
.channels { .channels {
padding: 20px; padding: 20px;
.listHeader { .listHeader {
position: relative;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; 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 { span {
color: white; color: $white;
opacity: 0.5; opacity: 1;
font-size: 10px; font-size: 10px;
cursor: pointer; cursor: pointer;
transition: all 0.25s;
&:nth-child(1) { &:hover {
opacity: 1; opacity: 0.5;
} }
} }
} }
@ -53,6 +101,10 @@
margin-top: 20px; margin-top: 20px;
} }
.fade {
opacity: 0.15;
}
.channel { .channel {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

2
app/components/Wallet/ReceiveModal.js

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

12
app/reducers/channels.js

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

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

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

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

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

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

@ -5,17 +5,19 @@ import LoadingBolt from 'components/LoadingBolt'
import Form from 'components/Form' import Form from 'components/Form'
import ModalRoot from 'components/ModalRoot' import ModalRoot from 'components/ModalRoot'
import Network from 'components/Contacts/Network' import Network from 'components/Contacts/Network'
import ContactsForm from 'components/Contacts/ContactsForm'
import styles from './App.scss' import styles from './App.scss'
class App extends Component { class App extends Component {
componentWillMount() { componentWillMount() {
const { fetchTicker, fetchInfo, newAddress, fetchChannels, fetchBalance } = this.props const { fetchTicker, fetchInfo, newAddress, fetchChannels, fetchBalance, fetchDescribeNetwork } = this.props
fetchTicker() fetchTicker()
fetchInfo() fetchInfo()
newAddress('np2wkh') newAddress('np2wkh')
fetchChannels() fetchChannels()
fetchBalance() fetchBalance()
fetchDescribeNetwork()
} }
render() { render() {
@ -35,6 +37,10 @@ class App extends Component {
error: { error }, error: { error },
clearError, clearError,
contactsFormProps,
networkTabProps,
children children
} = this.props } = this.props
@ -52,16 +58,15 @@ class App extends Component {
currency={ticker.currency} currency={ticker.currency}
/> />
<ContactsForm {...contactsFormProps} />
<Form formType={form.formType} formProps={formProps} closeForm={closeForm} /> <Form formType={form.formType} formProps={formProps} closeForm={closeForm} />
<div className={styles.content}> <div className={styles.content}>
{children} {children}
</div> </div>
<Network
channels={channels} <Network {...networkTabProps} />
balance={balance}
currentTicker={currentTicker}
/>
</div> </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 { 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 { fetchBalance } from 'reducers/balance'
import { fetchDescribeNetwork } from 'reducers/network'
import { clearError } from 'reducers/error' import { clearError } from 'reducers/error'
@ -53,8 +76,23 @@ const mapDispatchToProps = {
fetchBlockHeight, fetchBlockHeight,
clearError, clearError,
fetchBalance,
fetchChannels, fetchChannels,
fetchBalance openChannel,
toggleFilterPulldown,
changeFilter,
updateChannelSearchQuery,
openContactsForm,
closeContactsForm,
updateContactFormSearchQuery,
updateManualFormSearchQuery,
updateContactCapacity,
contactFormSelectors,
updateManualFormErrors,
fetchDescribeNetwork
} }
const mapStateToProps = state => ({ const mapStateToProps = state => ({
@ -65,7 +103,9 @@ const mapStateToProps = state => ({
info: state.info, info: state.info,
payment: state.payment, payment: state.payment,
transaction: state.transaction, transaction: state.transaction,
peers: state.peers,
channels: state.channels, channels: state.channels,
contactsform: state.contactsform,
balance: state.balance, balance: state.balance,
form: state.form, form: state.form,
@ -77,6 +117,8 @@ const mapStateToProps = state => ({
error: state.error, error: state.error,
network: state.network,
currentTicker: tickerSelectors.currentTicker(state), currentTicker: tickerSelectors.currentTicker(state),
isOnchain: payFormSelectors.isOnchain(state), isOnchain: payFormSelectors.isOnchain(state),
isLn: payFormSelectors.isLn(state), isLn: payFormSelectors.isLn(state),
@ -84,7 +126,17 @@ const mapStateToProps = state => ({
inputCaption: payFormSelectors.inputCaption(state), inputCaption: payFormSelectors.inputCaption(state),
showPayLoadingScreen: payFormSelectors.showPayLoadingScreen(state), showPayLoadingScreen: payFormSelectors.showPayLoadingScreen(state),
payFormIsValid: payFormSelectors.payFormIsValid(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) => { const mergeProps = (stateProps, dispatchProps, ownProps) => {
@ -180,16 +232,57 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
return {} 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 { return {
...stateProps, ...stateProps,
...dispatchProps, ...dispatchProps,
...ownProps, ...ownProps,
// props for the network sidebar
networkTabProps,
// props for the contacts form
contactsFormProps,
// Props to pass to the pay form // Props to pass to the pay form
formProps: formProps(stateProps.form.formType), formProps: formProps(stateProps.form.formType),
// action to close form // action to close form
closeForm: () => dispatchProps.setFormType(null) closeForm: () => dispatchProps.setFormType(null)
} }
} }

Loading…
Cancel
Save