Browse Source

feature(contactsform): tie up new contacts form to reducer

renovate/lint-staged-8.x
Jack Mallers 7 years ago
parent
commit
a0f9027180
  1. 154
      app/components/Contacts/AddChannel.js
  2. 141
      app/components/Contacts/AddChannel.scss
  3. 5
      app/components/Contacts/Network.js
  4. 12
      app/components/Contacts/Network.scss
  5. 89
      app/components/Contacts/SubmitChannelForm.js
  6. 154
      app/components/Contacts/SubmitChannelForm.scss
  7. 2
      app/components/Form/Form.js
  8. 2
      app/main.dev.js
  9. 89
      app/reducers/contactsform.js
  10. 13
      app/routes/app/components/App.js
  11. 2
      app/routes/app/components/App.scss
  12. 45
      app/routes/app/containers/AppContainer.js

154
app/components/Contacts/AddChannel.js

@ -0,0 +1,154 @@
import React from 'react'
import PropTypes from 'prop-types'
import Isvg from 'react-inlinesvg'
import { FaCircle, FaQuestionCircle } from 'react-icons/lib/fa'
import x from 'icons/x.svg'
import styles from './AddChannel.scss'
const AddChannel = ({
contactsform,
contactsform: { showErrors },
closeContactsForm,
openSubmitChannelForm,
updateContactFormSearchQuery,
updateManualFormSearchQuery,
updateContactCapacity,
setPubkey,
openChannel,
updateManualFormErrors,
activeChannelPubkeys,
nonActiveChannelPubkeys,
pendingOpenChannelPubkeys,
filteredNetworkNodes,
loadingChannelPubkeys,
showManualForm,
manualFormIsValid
}) => {
const renderRightSide = (node) => {
if (loadingChannelPubkeys.includes(node.pub_key)) {
return (
<span className={styles.inactive}>
<div className={styles.loading}>
<div className={styles.spinner} />
</div>
</span>
)
}
if (activeChannelPubkeys.includes(node.pub_key)) {
return (
<span className={`${styles.online} ${styles.inactive}`}>
<span>Online</span>
</span>
)
}
if (nonActiveChannelPubkeys.includes(node.pub_key)) {
return (
<span className={`${styles.offline} ${styles.inactive}`}>
<span>Offline</span>
</span>
)
}
if (pendingOpenChannelPubkeys.includes(node.pub_key)) {
return (
<span className={`${styles.pending} ${styles.inactive}`}>
<span>Pending</span>
</span>
)
}
if (!node.addresses.length) {
return (
<span className={`${styles.private} ${styles.inactive}`}>
Private
</span>
)
}
return (
<span
className={styles.connect}
onClick={() => {
// set the node public key for the submit form
setPubkey(node.pub_key)
// open the submit form
openSubmitChannelForm()
}}
>
Connect
</span>
)
}
const searchUpdated = (search) => {
updateContactFormSearchQuery(search)
if (search.includes('@') && search.split('@')[0].length === 66) {
updateManualFormSearchQuery(search)
}
}
return (
<div className={styles.container}>
<header className={styles.header}>
<input
type='text'
placeholder='Search the network...'
className={styles.searchInput}
value={contactsform.searchQuery}
onChange={event => searchUpdated(event.target.value)}
ref={input => input && input.focus()}
/>
<span onClick={closeContactsForm} className={styles.closeIcon}>
<Isvg src={x} />
</span>
</header>
<section className={styles.nodes}>
<ul className={styles.networkResults}>
{
filteredNetworkNodes.map(node => (
<li key={node.pub_key}>
<section>
{
node.alias.length > 0 ?
<h2>
<span>{node.alias.trim()}</span>
<span>({node.pub_key.substr(0, 10)}...{node.pub_key.substr(node.pub_key.length - 10)})</span>
</h2>
:
<h2>
<span>{node.pub_key}</span>
</h2>
}
</section>
<section>
{renderRightSide(node)}
</section>
</li>
))
}
</ul>
</section>
{
showManualForm &&
<section className={styles.manualForm}>
<p>Hm, looks like we can't see that node from here, wanna try to manually connect?</p>
<div className={styles.manualConnectButton}>Connect Manually</div>
</section>
}
</div>
)
}
AddChannel.propTypes = {
}
export default AddChannel

141
app/components/Contacts/AddChannel.scss

@ -0,0 +1,141 @@
@import '../../variables.scss';
.container {
position: relative;
width: 30%;
display: inline-block;
vertical-align: top;
height: 100vh;
background: #31343f;
}
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
background: linear-gradient(270deg, #868B9F 0%, #333C5E 100%);
padding: 15px 10px;
color: $white;
input {
background: transparent;
outline: 0;
border: 0;
color: $white;
font-size: 14px;
width: 90%;
}
.closeIcon {
cursor: pointer;
transition: all 0.25s;
&:hover {
opacity: 0.5;
}
svg {
height: 14px;
width: 14px;
}
}
}
.nodes {
background: #31343F;
.networkResults {
overflow-y: auto;
margin-top: 30px;
padding: 0 10px;
color: $white;
li {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 10px 0;
h2 {
font-size: 10px;
font-weight: bold;
letter-spacing: 1.3px;
span {
display: inline-block;
vertical-align: middle;
&:nth-child(1) {
font-size: 10px;
font-weight: bold;
letter-spacing: 1.3px;
}
&:nth-child(2) {
display: block;
color: $darkestgrey;
font-size: 8px;
margin-top: 5px;
}
}
}
.connect {
cursor: pointer;
color: $darkestgrey;
transition: all 0.25s;
font-size: 10px;
&:hover {
color: darken($darkestgrey, 10%);
}
}
.inactive {
font-size: 10px;
display: inline-block;
vertical-align: top;
&.online {
color: $green;
}
&.offline {
color: $darkestgrey;
}
&.pending {
color: $orange;
}
&.private {
color: darken($darkestgrey, 50%);
}
}
}
}
}
.manualForm {
color: $white;
text-align: center;
margin: 0 25px;
p {
font-size: 14px;
margin: 20px 0;
}
div {
background: #383B47;
font-size: 16px;
padding: 10px;
cursor: pointer;
transition: all 0.25s;
&:hover {
background: lighten(#383B47, 10%);
}
}
}

5
app/components/Contacts/Network.js

@ -6,6 +6,7 @@ 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'
class Network extends Component {
@ -100,7 +101,9 @@ class Network extends Component {
</span>
</section>
<section className={`${styles.addChannel} hint--bottom-left`} onClick={openContactsForm} data-hint='Open a channel'>
<Isvg src={plus} />
<span className={styles.plusContainer}>
<Isvg src={plus} />
</span>
</section>
</header>

12
app/components/Contacts/Network.scss

@ -2,7 +2,7 @@
.network {
position: relative;
width: 20%;
width: 30%;
display: inline-block;
vertical-align: top;
height: 100vh;
@ -33,8 +33,16 @@
cursor: pointer;
transition: all 0.25s;
svg {
border-radius: 5px;
}
&:hover {
color: $darkestgrey;
opacity: 0.5;
svg {
background: #272931;
}
}
}
}

89
app/components/Contacts/SubmitChannelForm.js

@ -0,0 +1,89 @@
import React from 'react'
import PropTypes from 'prop-types'
import { FaAngleDown } from 'react-icons/lib/fa'
import Isvg from 'react-inlinesvg'
import x from 'icons/x.svg'
import styles from './SubmitChannelForm.scss'
class SubmitChannelForm extends React.Component {
render() {
const {
submitChannelFormOpen,
closeSubmitChannelForm,
pubkey,
contactCapacity,
updateContactCapacity,
toggleCurrencyProps: {
setContactsCurrencyFilters,
setCurrencyFilters,
showCurrencyFilters,
currencyName,
currentCurrencyFilters,
onCurrencyFilterClick,
contactFormUsdAmount
}
} = this.props
if (!submitChannelFormOpen) { return null }
return (
<div className={styles.container}>
<div className={styles.closeContainer}>
<span onClick={closeSubmitChannelForm}>
<Isvg src={x} />
</span>
</div>
<div className={styles.content}>
<header className={styles.header}>
<h1>Add Funds to Network</h1>
<p>Adding a connection will help you send and receive money on the Lightning Network. You aren't spening any money, rather moving the money you plan to use onto the network.</p>
</header>
<section className={styles.title}>
<h2>{pubkey}</h2>
</section>
<section className={styles.amount}>
<div className={styles.input}>
<input
type='number'
min='0'
ref={(input) => { this.amountInput = input }}
size=''
placeholder='0.00000000'
value={contactCapacity || ''}
onChange={event => updateContactCapacity(event.target.value)}
// onBlur={onPayAmountBlur}
id='amount'
/>
<div className={styles.currency}>
<section className={styles.currentCurrency} onClick={() => setContactsCurrencyFilters(!showCurrencyFilters)}>
<span>{currencyName}</span><span><FaAngleDown /></span>
</section>
<ul className={showCurrencyFilters && styles.active}>
{
currentCurrencyFilters.map(filter =>
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>{filter.name}</li>)
}
</ul>
</div>
</div>
<div className={styles.usdAmount}>
{`${contactFormUsdAmount || 0} USD`}
</div>
</section>
</div>
</div>
)
}
}
SubmitChannelForm.propTypes = {}
export default SubmitChannelForm

154
app/components/Contacts/SubmitChannelForm.scss

@ -0,0 +1,154 @@
@import '../../variables.scss';
.container {
position: absolute;
top: 0;
z-index: 10;
height: 100vh;
width: 100%;
background: #31343F;
}
.closeContainer {
text-align: right;
padding: 20px 40px 0px;
span {
cursor: pointer;
opacity: 1.0;
transition: 0.25s all;
&:hover {
opacity: 0.5;
}
}
svg {
color: $white;
}
}
.content {
padding: 0 40px;
font-family: Roboto;
color: $white;
.header {
padding: 20px 100px;
h1 {
margin-bottom: 15px;
font-size: 20px;
}
p {
text-align: left;
line-height: 1.3;
font-size: 12px;
}
}
}
.header {
text-align: center;
padding-bottom: 20px;
border-bottom: 1px solid $spaceborder;
h1 {
font-size: 22px;
font-weight: 100;
margin-top: 10px;
letter-spacing: 1.5px;
}
}
.title {
margin: 50px 0;
h2 {
font-size: 14px;
background: $spaceblue;
padding: 10px;
border-radius: 17.5px;
display: inline;
}
}
.input {
display: flex;
flex-direction: row;
align-items: center;
input {
font-size: 40px;
max-width: 230px;
}
}
.input input {
background: transparent;
outline: none;
border: 0;
color: $gold;
-webkit-text-fill-color: $white;
width: 100%;
font-weight: 200;
}
.input input::-webkit-input-placeholder, ::-webkit-input-placeholder {
text-shadow: none;
-webkit-text-fill-color: initial;
}
.currency {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
.currentCurrency {
cursor: pointer;
transition: 0.25s all;
&:hover {
opacity: 0.5;
}
span {
font-size: 14px;
&:nth-child(1) {
font-weight: bold;
}
}
}
ul {
visibility: hidden;
position: absolute;
top: 30px;
&.active {
visibility: visible;
}
li {
padding: 8px 15px;
background: #191919;
cursor: pointer;
transition: 0.25s hover;
border-bottom: 1px solid #0f0f0f;
&:hover {
background: #0f0f0f;
}
}
}
}
.usdAmount {
margin-top: 20px;
opacity: 0.5;
}

2
app/components/Form/Form.js

@ -19,7 +19,7 @@ const Form = ({ formType, formProps, closeForm }) => {
const FormComponent = FORM_TYPES[formType]
return (
<div className={`${styles.container} ${formType && styles.open}`}>
<div className={styles.container}>
<div className={styles.closeContainer}>
<span onClick={closeForm}>
<Isvg src={x} />

2
app/main.dev.js

@ -200,7 +200,7 @@ const startLnd = (alias, autopilot) => {
}, 1000)
}
if (line.includes('The wallet has been unlocked')) {
if (line.includes('LightningWallet opened')) {
console.log('WALLET OPENED, STARTING LIGHTNING GRPC CONNECTION')
sendLndSyncing()
startGrpc()

89
app/reducers/contactsform.js

@ -2,15 +2,24 @@ import { createSelector } from 'reselect'
import filter from 'lodash/filter'
import isEmpty from 'lodash/isEmpty'
import { tickerSelectors } from './ticker'
import { btc } from '../utils'
// Initial State
const initialState = {
isOpen: false,
searchQuery: '',
manualSearchQuery: '',
contactCapacity: 0.1,
pubkey: '',
showErrors: {
manualInput: false
}
},
manualFormOpen: false,
submitChannelFormOpen: false,
showCurrencyFilters: false
}
// Constants
@ -18,6 +27,14 @@ const initialState = {
export const OPEN_CONTACTS_FORM = 'OPEN_CONTACTS_FORM'
export const CLOSE_CONTACTS_FORM = 'CLOSE_CONTACTS_FORM'
export const OPEN_MANUAL_FORM = 'OPEN_MANUAL_FORM'
export const CLOSE_MANUAL_FORM = 'CLOSE_MANUAL_FORM'
export const OPEN_SUBMIT_CHANNEL_FORM = 'OPEN_SUBMIT_CHANNEL_FORM'
export const CLOSE_SUBMIT_CHANNEL_FORM = 'CLOSE_SUBMIT_CHANNEL_FORM'
export const SET_PUBKEY = 'SET_PUBKEY'
export const UPDATE_CONTACT_FORM_SEARCH_QUERY = 'UPDATE_CONTACT_FORM_SEARCH_QUERY'
export const UPDATE_CONTACT_CAPACITY = 'UPDATE_CONTACT_CAPACITY'
@ -26,6 +43,8 @@ export const UPDATE_MANUAL_FORM_ERRORS = 'UPDATE_MANUAL_FORM_ERRORS'
export const UPDATE_MANUAL_FORM_SEARCH_QUERY = 'UPDATE_MANUAL_FORM_SEARCH_QUERY'
export const SET_CONTACTS_CURRENCY_FILTERS = 'SET_CONTACTS_CURRENCY_FILTERS'
// ------------------------------------
// Actions
// ------------------------------------
@ -41,6 +60,30 @@ export function closeContactsForm() {
}
}
export function openManualForm() {
return {
type: OPEN_MANUAL_FORM
}
}
export function closeManualForm() {
return {
type: CLOSE_MANUAL_FORM
}
}
export function openSubmitChannelForm() {
return {
type: OPEN_SUBMIT_CHANNEL_FORM
}
}
export function closeSubmitChannelForm() {
return {
type: CLOSE_SUBMIT_CHANNEL_FORM
}
}
export function updateContactFormSearchQuery(searchQuery) {
return {
type: UPDATE_CONTACT_FORM_SEARCH_QUERY,
@ -62,6 +105,13 @@ export function updateContactCapacity(contactCapacity) {
}
}
export function setPubkey(pubkey) {
return {
type: SET_PUBKEY,
pubkey
}
}
export function updateManualFormErrors(errorsObject) {
return {
type: UPDATE_MANUAL_FORM_ERRORS,
@ -69,6 +119,13 @@ export function updateManualFormErrors(errorsObject) {
}
}
export function setContactsCurrencyFilters(showCurrencyFilters) {
return {
type: SET_CONTACTS_CURRENCY_FILTERS,
showCurrencyFilters
}
}
// ------------------------------------
// Action Handlers
// ------------------------------------
@ -76,15 +133,25 @@ const ACTION_HANDLERS = {
[OPEN_CONTACTS_FORM]: state => ({ ...state, isOpen: true }),
[CLOSE_CONTACTS_FORM]: state => ({ ...state, isOpen: false }),
[OPEN_MANUAL_FORM]: state => ({ ...state, manualFormOpen: true }),
[CLOSE_MANUAL_FORM]: state => ({ ...state, manualFormOpen: false }),
[OPEN_SUBMIT_CHANNEL_FORM]: state => ({ ...state, submitChannelFormOpen: true }),
[CLOSE_SUBMIT_CHANNEL_FORM]: state => ({ ...state, submitChannelFormOpen: false }),
[UPDATE_CONTACT_FORM_SEARCH_QUERY]: (state, { searchQuery }) => ({ ...state, searchQuery }),
[UPDATE_MANUAL_FORM_SEARCH_QUERY]: (state, { searchQuery }) => ({ ...state, searchQuery }),
[UPDATE_CONTACT_CAPACITY]: (state, { contactCapacity }) => ({ ...state, contactCapacity }),
[SET_PUBKEY]: (state, { pubkey }) => ({ ...state, pubkey }),
[UPDATE_MANUAL_FORM_ERRORS]: (state, { errorsObject }) => ({ ...state, showErrors: Object.assign(state.showErrors, errorsObject) }),
[UPDATE_MANUAL_FORM_SEARCH_QUERY]: (state, { manualSearchQuery }) => ({ ...state, manualSearchQuery })
[UPDATE_MANUAL_FORM_SEARCH_QUERY]: (state, { manualSearchQuery }) => ({ ...state, manualSearchQuery }),
[SET_CONTACTS_CURRENCY_FILTERS]: (state, { showCurrencyFilters }) => ({ ...state, showCurrencyFilters })
}
// ------------------------------------
@ -94,6 +161,8 @@ const contactFormSelectors = {}
const networkNodesSelector = state => state.network.nodes
const searchQuerySelector = state => state.contactsform.searchQuery
const manualSearchQuerySelector = state => state.contactsform.manualSearchQuery
const contactCapacitySelector = state => state.contactsform.contactCapacity
const currencySelector = state => state.ticker.currency
const contactable = node => (
node.addresses.length > 0
@ -115,7 +184,10 @@ contactFormSelectors.filteredNetworkNodes = createSelector(
(nodes, searchQuery) => {
// If there is no search query default to showing the first 20 nodes from the nodes array
// (performance hit to render the entire thing by default)
if (!searchQuery.length) { return nodes.sort(contactableFirst).slice(0, 20) }
// if (!searchQuery.length) { return nodes.sort(contactableFirst).slice(0, 20) }
// return an empty array if there is no search query
if (!searchQuery.length) { return [] }
// if there is an '@' in the search query we are assuming they are using the format pubkey@host
// we can ignore the '@' and the host and just grab the pubkey for our search
@ -153,6 +225,17 @@ contactFormSelectors.manualFormIsValid = createSelector(
}
)
contactFormSelectors.contactFormUsdAmount = createSelector(
contactCapacitySelector,
currencySelector,
tickerSelectors.currentTicker,
(amount, currency, ticker) => {
if (!ticker || !ticker.price_usd) { return false }
return btc.convert(currency, 'usd', amount, ticker.price_usd)
}
)
export { contactFormSelectors }

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

@ -8,9 +8,12 @@ import Form from 'components/Form'
import ModalRoot from 'components/ModalRoot'
import Network from 'components/Contacts/Network'
import AddChannel from 'components/Contacts/AddChannel'
import ContactModal from 'components/Contacts/ContactModal'
import ContactsForm from 'components/Contacts/ContactsForm'
import SubmitChannelForm from 'components/Contacts/SubmitChannelForm'
import ReceiveModal from 'components/Wallet/ReceiveModal'
import ActivityModal from 'components/Activity/ActivityModal'
@ -60,6 +63,7 @@ class App extends Component {
networkTabProps,
receiveModalProps,
activityModalProps,
submitChannelFormProps,
children
} = this.props
@ -79,9 +83,9 @@ class App extends Component {
/>
<ContactModal {...contactModalProps} />
<ContactsForm {...contactsFormProps} />
<Form formType={form.formType} formProps={formProps} closeForm={closeForm} />
<SubmitChannelForm {...submitChannelFormProps} />
<ReceiveModal {...receiveModalProps} />
<ActivityModal {...activityModalProps} />
@ -90,7 +94,12 @@ class App extends Component {
{children}
</div>
<Network {...networkTabProps} />
{
contactsFormProps.contactsform.isOpen ?
<AddChannel {...contactsFormProps} />
:
<Network {...networkTabProps} />
}
</div>
)
}

2
app/routes/app/components/App.scss

@ -2,7 +2,7 @@
.content {
position: relative;
width: 80%;
width: 70%;
display: inline-block;
vertical-align: top;
overflow-y: auto;

45
app/routes/app/containers/AppContainer.js

@ -1,6 +1,8 @@
import { withRouter } from 'react-router'
import { connect } from 'react-redux'
import { btc } from 'utils'
import { fetchTicker, setCurrency, tickerSelectors } from 'reducers/ticker'
import { newAddress, closeWalletModal } from 'reducers/address'
@ -39,11 +41,19 @@ import {
import {
openContactsForm,
closeContactsForm,
openSubmitChannelForm,
closeSubmitChannelForm,
updateContactFormSearchQuery,
updateManualFormSearchQuery,
updateContactCapacity,
setPubkey,
contactFormSelectors,
updateManualFormErrors
updateManualFormErrors,
setContactsCurrencyFilters
} from 'reducers/contactsform'
import { fetchBalance } from 'reducers/balance'
@ -100,11 +110,15 @@ const mapDispatchToProps = {
openContactsForm,
closeContactsForm,
openSubmitChannelForm,
closeSubmitChannelForm,
updateContactFormSearchQuery,
updateManualFormSearchQuery,
updateContactCapacity,
setPubkey,
contactFormSelectors,
updateManualFormErrors,
setContactsCurrencyFilters,
fetchDescribeNetwork,
@ -155,6 +169,7 @@ const mapStateToProps = state => ({
filteredNetworkNodes: contactFormSelectors.filteredNetworkNodes(state),
showManualForm: contactFormSelectors.showManualForm(state),
manualFormIsValid: contactFormSelectors.manualFormIsValid(state),
contactFormUsdAmount: contactFormSelectors.contactFormUsdAmount(state),
currentChannels: currentChannels(state),
activeChannelPubkeys: channelsSelectors.activeChannelPubkeys(state),
@ -292,9 +307,11 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
const contactsFormProps = {
closeContactsForm: dispatchProps.closeContactsForm,
openSubmitChannelForm: dispatchProps.openSubmitChannelForm,
updateContactFormSearchQuery: dispatchProps.updateContactFormSearchQuery,
updateManualFormSearchQuery: dispatchProps.updateManualFormSearchQuery,
updateContactCapacity: dispatchProps.updateContactCapacity,
setPubkey: dispatchProps.setPubkey,
openChannel: dispatchProps.openChannel,
updateManualFormErrors: dispatchProps.updateManualFormErrors,
@ -348,6 +365,30 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
closeReceiveModal: dispatchProps.closeWalletModal
}
const submitChannelFormProps = {
submitChannelFormOpen: stateProps.contactsform.submitChannelFormOpen,
pubkey: stateProps.contactsform.pubkey,
contactCapacity: stateProps.contactsform.contactCapacity,
closeSubmitChannelForm: dispatchProps.closeSubmitChannelForm,
updateContactCapacity: dispatchProps.updateContactCapacity,
toggleCurrencyProps: {
currentCurrencyFilters: stateProps.currentCurrencyFilters,
currencyName: stateProps.currencyName,
showCurrencyFilters: stateProps.contactsform.showCurrencyFilters,
contactFormUsdAmount: stateProps.contactFormUsdAmount,
setContactsCurrencyFilters: dispatchProps.setContactsCurrencyFilters,
setCurrencyFilters: dispatchProps.setCurrencyFilters,
onCurrencyFilterClick: (currency) => {
dispatchProps.updateContactCapacity(btc.convert(stateProps.ticker.currency, currency, stateProps.contactsform.contactCapacity))
dispatchProps.setCurrency(currency)
dispatchProps.setContactsCurrencyFilters(false)
}
}
}
return {
...stateProps,
...dispatchProps,
@ -363,6 +404,8 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
receiveModalProps,
// props for the activity modals
activityModalProps,
// props for the for to open a channel
submitChannelFormProps,
// Props to pass to the pay form
formProps: formProps(stateProps.form.formType),
// action to close form

Loading…
Cancel
Save