From 932058400936e08bc93b733a20385a7a72481bd8 Mon Sep 17 00:00:00 2001 From: Jack Mallers Date: Tue, 6 Feb 2018 22:49:22 -0600 Subject: [PATCH] feature(network): network tab all hooked up to backend --- app/components/Contacts/ContactModal.js | 2 +- app/components/Contacts/ContactsForm.scss | 2 +- app/components/Contacts/Network.js | 195 ++++++++++++++---- app/components/Contacts/Network.scss | 62 +++++- app/components/Wallet/ReceiveModal.js | 2 +- app/reducers/channels.js | 12 +- .../components/components/Activity.scss | 4 +- .../components/components/Modal/Modal.js | 2 +- app/routes/app/components/App.js | 17 +- app/routes/app/containers/AppContainer.js | 99 ++++++++- 10 files changed, 328 insertions(+), 69 deletions(-) diff --git a/app/components/Contacts/ContactModal.js b/app/components/Contacts/ContactModal.js index 58a760ab..77f41fc3 100644 --- a/app/components/Contacts/ContactModal.js +++ b/app/components/Contacts/ContactModal.js @@ -26,7 +26,7 @@ const ContactModal = ({ }, content: { top: 'auto', - left: '20%', + left: '0', right: '0', bottom: 'auto', width: '40%', diff --git a/app/components/Contacts/ContactsForm.scss b/app/components/Contacts/ContactsForm.scss index 19123176..977195f9 100644 --- a/app/components/Contacts/ContactsForm.scss +++ b/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; diff --git a/app/components/Contacts/Network.js b/app/components/Contacts/Network.js index fd0b6c99..58ced5e1 100644 --- a/app/components/Contacts/Network.js +++ b/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 ( -
-
-
-

My Network

- - {btc.satoshisToBtc(balance.channelBalance)}BTC ≈ ${btc.satoshisToUsd(balance.channelBalance, currentTicker.price_usd).toLocaleString()} - -
-
- -
-
- -
-
-
- All -
-
- Refresh -
+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 ( +
+
+
+

My Network

+ + {btc.satoshisToBtc(balance.channelBalance)}BTC ≈ ${btc.satoshisToUsd(balance.channelBalance, currentTicker.price_usd).toLocaleString()} + +
+
+ +
-
    - { - channels.channels.map(channel => { - console.log('channel: ', channel) - }) - } -
-
+
+
+
+

+ {filter.name} +

+
    + { + nonActiveFilters.map(f => ( +
  • changeFilter(f)}> + {f.name} +
  • + )) + } +
+
+ +
+ { this.repeat = ref }}> + { + this.state.refreshing ? + + : + 'Refresh' + } + +
+
-
- - -
-
- ) +
    + { + currentChannels.length > 0 && currentChannels.map((channelObj, index) => { + const channel = Object.prototype.hasOwnProperty.call(channelObj, 'channel') ? channelObj.channel : channelObj + return ( +
  • + {displayNodeName(channel)} + +
  • + ) + }) + } +
+
+ +
+ + updateChannelSearchQuery(event.target.value)} + /> +
+
+ ) + } } Network.propTypes = {} diff --git a/app/components/Contacts/Network.scss b/app/components/Contacts/Network.scss index 4b0c071d..39598abb 100644 --- a/app/components/Contacts/Network.scss +++ b/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; diff --git a/app/components/Wallet/ReceiveModal.js b/app/components/Wallet/ReceiveModal.js index 02498160..8a02f0db 100644 --- a/app/components/Wallet/ReceiveModal.js +++ b/app/components/Wallet/ReceiveModal.js @@ -17,7 +17,7 @@ const ReceiveModal = ({ }, content: { top: 'auto', - left: '20%', + left: '0', right: '0', bottom: 'auto', width: '40%', diff --git a/app/reducers/channels.js b/app/reducers/channels.js index f38c4ae3..6c255519 100644 --- a/app/reducers/channels.js +++ b/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: [], diff --git a/app/routes/activity/components/components/Activity.scss b/app/routes/activity/components/components/Activity.scss index 07cc2b56..2eca71a2 100644 --- a/app/routes/activity/components/components/Activity.scss +++ b/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; diff --git a/app/routes/activity/components/components/Modal/Modal.js b/app/routes/activity/components/components/Modal/Modal.js index 4e040aef..349dde3a 100644 --- a/app/routes/activity/components/components/Modal/Modal.js +++ b/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%', diff --git a/app/routes/app/components/App.js b/app/routes/app/components/App.js index 791ea8ba..79cbb9e4 100644 --- a/app/routes/app/components/App.js +++ b/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} /> + +
{children}
- + + ) } diff --git a/app/routes/app/containers/AppContainer.js b/app/routes/app/containers/AppContainer.js index 148c191e..e5180c5a 100644 --- a/app/routes/app/containers/AppContainer.js +++ b/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) + } }