Browse Source

fix(lint): fix linting errors

renovate/lint-staged-8.x
Jack Mallers 7 years ago
parent
commit
962d79bb88
  1. 8
      .eslintrc
  2. 90
      app/components/ChannelForm/ChannelForm.js
  3. 57
      app/components/ChannelForm/ChannelForm.scss
  4. 46
      app/components/ChannelForm/Footer.js
  5. 17
      app/components/ChannelForm/Footer.scss
  6. 37
      app/components/ChannelForm/StepFour.js
  7. 36
      app/components/ChannelForm/StepFour.scss
  8. 74
      app/components/ChannelForm/StepOne.js
  9. 74
      app/components/ChannelForm/StepOne.scss
  10. 50
      app/components/ChannelForm/StepThree.js
  11. 58
      app/components/ChannelForm/StepThree.scss
  12. 49
      app/components/ChannelForm/StepTwo.js
  13. 58
      app/components/ChannelForm/StepTwo.scss
  14. 3
      app/components/ChannelForm/index.js
  15. 95
      app/components/Channels/Channel.js
  16. 125
      app/components/Channels/Channel.scss
  17. 128
      app/components/Channels/ChannelForm.js
  18. 123
      app/components/Channels/ChannelForm.scss
  19. 103
      app/components/Channels/ChannelModal.js
  20. 124
      app/components/Channels/ChannelModal.scss
  21. 140
      app/components/Channels/Channels.js
  22. 69
      app/components/Channels/Channels.scss
  23. 69
      app/components/Channels/ClosedPendingChannel.js
  24. 95
      app/components/Channels/ClosedPendingChannel.scss
  25. 72
      app/components/Channels/OpenPendingChannel.js
  26. 98
      app/components/Channels/OpenPendingChannel.scss
  27. 3
      app/components/Channels/index.js
  28. 2
      app/components/Contacts/ClosingContact.js
  29. 7
      app/components/Contacts/ContactModal.js
  30. 16
      app/components/Contacts/ContactsForm.js
  31. 31
      app/components/Contacts/Donut.js
  32. 20
      app/components/Contacts/Donut.scss
  33. 4
      app/components/Contacts/LoadingContact.js
  34. 3
      app/components/Contacts/OfflineContact.js
  35. 3
      app/components/Contacts/OnlineContact.js
  36. 2
      app/components/Contacts/PendingContact.js
  37. 6
      app/components/Form/RequestForm.js
  38. 1
      app/components/Network/CanvasNetworkGraph.js
  39. 20
      app/components/Peers/Peer.js
  40. 38
      app/components/Peers/Peer.scss
  41. 71
      app/components/Peers/PeerForm.js
  42. 107
      app/components/Peers/PeerForm.scss
  43. 80
      app/components/Peers/PeerModal.js
  44. 75
      app/components/Peers/PeerModal.scss
  45. 21
      app/lnd/methods/channelController.js
  46. 6
      app/lnd/methods/index.js
  47. 4
      app/lnd/methods/paymentsController.js
  48. 5
      app/lnd/subscribe/channelgraph.js
  49. 61
      app/reducers/channels.js
  50. 1
      app/reducers/contactsform.js
  51. 4
      app/reducers/lnd.js
  52. 33
      app/routes/contacts/components/Contacts.js
  53. 3
      app/store/configureStore.dev.js
  54. 125
      test/components/Channels.spec.js
  55. 72
      test/components/Peers.spec.js
  56. 33
      test/reducers/__snapshots__/balance.spec.js.snap
  57. 308
      test/reducers/__snapshots__/channels.spec.js.snap
  58. 13
      test/reducers/__snapshots__/form.spec.js.snap
  59. 22
      test/reducers/__snapshots__/info.spec.js.snap
  60. 171
      test/reducers/__snapshots__/invoice.spec.js.snap
  61. 49
      test/reducers/__snapshots__/payment.spec.js.snap
  62. 182
      test/reducers/__snapshots__/peers.spec.js.snap
  63. 51
      test/reducers/__snapshots__/ticker.spec.js.snap

8
.eslintrc

@ -17,6 +17,14 @@
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
"jsx-a11y/no-static-element-interactions": 0,
"jsx-a11y/no-noninteractive-element-interactions": 0,
"jsx-a11y/click-events-have-key-events": 0,
"jsx-a11y/label-has-for": [ 2, {
"components": [ "Label" ],
"required": {
"every": [ "id" ]
},
"allowChildren": false
}],
"react/no-array-index-key": 0,
"react/forbid-prop-types": 0,
"camelcase": 0,

90
app/components/ChannelForm/ChannelForm.js

@ -1,90 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import ReactModal from 'react-modal'
import { FaClose } from 'react-icons/lib/fa'
import StepOne from './StepOne'
import StepTwo from './StepTwo'
import StepThree from './StepThree'
import StepFour from './StepFour'
import Footer from './Footer'
import styles from './ChannelForm.scss'
const ChannelForm = ({
channelform,
openChannel,
closeChannelForm,
changeStep,
setNodeKey,
setLocalAmount,
setPushAmount,
channelFormHeader,
channelFormProgress,
stepTwoIsValid,
peers
}) => {
const renderStep = () => {
const { step } = channelform
switch (step) {
case 1:
return <StepOne peers={peers} changeStep={changeStep} setNodeKey={setNodeKey} />
case 2:
return <StepTwo local_amt={channelform.local_amt} setLocalAmount={setLocalAmount} />
case 3:
return <StepThree push_amt={channelform.push_amt} setPushAmount={setPushAmount} />
default:
return <StepFour node_key={channelform.node_key} local_amt={channelform.local_amt} push_amt={channelform.push_amt} />
}
}
return (
<ReactModal
isOpen={channelform.isOpen}
ariaHideApp
shouldCloseOnOverlayClick
contentLabel='No Overlay Click Modal'
onRequestClose={closeChannelForm}
parentSelector={() => document.body}
className={styles.modal}
>
<div onClick={closeChannelForm} className={styles.modalClose}>
<FaClose />
</div>
<header className={styles.header}>
<h3>{channelFormHeader}</h3>
<div className={styles.progress} style={{ width: `${channelFormProgress}%` }} />
</header>
<div className={styles.content}>
{renderStep()}
</div>
<Footer
step={channelform.step}
changeStep={changeStep}
stepTwoIsValid={stepTwoIsValid}
submit={() => openChannel({ pubkey: channelform.node_key, local_amt: channelform.local_amt, push_amt: channelform.push_amt })}
/>
</ReactModal>
)
}
ChannelForm.propTypes = {
channelform: PropTypes.object.isRequired,
openChannel: PropTypes.func.isRequired,
closeChannelForm: PropTypes.func.isRequired,
changeStep: PropTypes.func.isRequired,
setNodeKey: PropTypes.func.isRequired,
setLocalAmount: PropTypes.func.isRequired,
setPushAmount: PropTypes.func.isRequired,
channelFormHeader: PropTypes.string.isRequired,
channelFormProgress: PropTypes.number.isRequired,
stepTwoIsValid: PropTypes.bool.isRequired,
peers: PropTypes.array.isRequired
}
export default ChannelForm

57
app/components/ChannelForm/ChannelForm.scss

@ -1,57 +0,0 @@
@import '../../variables.scss';
.modal {
width: 40%;
margin: 50px auto;
position: absolute;
top: auto;
left: 20%;
right: 0;
bottom: auto;
background: $white;
outline: none;
z-index: -2;
border: 1px solid $darkgrey;
}
.modalClose {
position: absolute;
top: -13px;
right: -13px;
display: block;
font-size: 16px;
line-height: 27px;
width: 32px;
height: 32px;
background: $white;
border-radius: 50%;
color: $darkestgrey;
cursor: pointer;
text-align: center;
z-index: 2;
transition: all 0.25s;
}
.modalClose:hover {
background: $darkgrey;
}
.header {
padding: 20px;
background: $lightgrey;
text-align: center;
font-family: 'Jigsaw Light';
text-transform: uppercase;
position: relative;
z-index: -2;
}
.progress {
transition: all 0.2s ease;
background: $main;
position: absolute;
height: 100%;
top: 0;
left: 0;
z-index: -1;
}

46
app/components/ChannelForm/Footer.js

@ -1,46 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import styles from './Footer.scss'
const Footer = ({
step, changeStep, stepTwoIsValid, submit
}) => {
if (step === 1) { return null }
// See if the next button on step 2 should be active
const nextIsInactive = step === 2 && !stepTwoIsValid
// Function that's called when the user clicks "next" in the form
const nextFunc = () => {
if (nextIsInactive) { return }
changeStep(step + 1)
}
const rightButtonText = step === 4 ? 'Submit' : 'Next'
const rightButtonOnClick = step === 4 ? () => submit() : nextFunc
return (
<div className={styles.footer}>
<div className='buttonContainer'>
<div className='buttonPrimary' onClick={() => changeStep(step - 1)}>
Back
</div>
</div>
<div className='buttonContainer' onClick={rightButtonOnClick}>
<div className={`buttonPrimary ${nextIsInactive && 'inactive'}`}>
{rightButtonText}
</div>
</div>
</div>
)
}
Footer.propTypes = {
step: PropTypes.number.isRequired,
changeStep: PropTypes.func.isRequired,
stepTwoIsValid: PropTypes.bool.isRequired,
submit: PropTypes.func.isRequired
}
export default Footer

17
app/components/ChannelForm/Footer.scss

@ -1,17 +0,0 @@
@import '../../variables.scss';
.footer {
display: flex;
flex-direction: row;
justify-content: space-between;
padding-bottom: 30px;
div {
margin: 0 20px;
div {
padding: 18px 60px 15px 60px;
color: $black;
}
}
}

37
app/components/ChannelForm/StepFour.js

@ -1,37 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import styles from './StepFour.scss'
const StepFour = ({ node_key, local_amt, push_amt }) => (
<div className={styles.container}>
<div className={styles.nodekey}>
<h4>Peer</h4>
<h2>{node_key}</h2>
</div>
<div className={styles.amounts}>
<div className={styles.localamt}>
<h4>Local Amount</h4>
<h3>{local_amt}</h3>
</div>
<div className={styles.pushamt}>
<h4>Push Amount</h4>
<h3>{push_amt}</h3>
</div>
</div>
</div>
)
StepFour.propTypes = {
node_key: PropTypes.string.isRequired,
local_amt: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string
]),
push_amt: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string
])
}
export default StepFour

36
app/components/ChannelForm/StepFour.scss

@ -1,36 +0,0 @@
@import '../../variables.scss';
.container {
padding: 50px;
h4 {
text-transform: uppercase;
font-size: 14px;
margin-bottom: 10px;
}
h3 {
text-align: center;
color: $main;
font-size: 50px;
}
}
.nodekey {
margin-bottom: 50px;
padding: 20px;
border-bottom: 1px solid $main;
h2 {
font-size: 12px;
font-weight: bold;
}
}
.amounts {
display: flex;
flex-direction: row;
justify-content: space-around;
margin-bottom: 50px;
padding: 20px;
}

74
app/components/ChannelForm/StepOne.js

@ -1,74 +0,0 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { MdSearch } from 'react-icons/lib/md'
import styles from './StepOne.scss'
class StepOne extends Component {
constructor(props) {
super(props)
this.state = {
peers: props.peers,
searchQuery: ''
}
this.onSearchQuery = this.onSearchQuery.bind(this)
this.peerClicked = this.peerClicked.bind(this)
}
onSearchQuery(searchQuery) {
const peers = this.props.peers.filter(peer => peer.pub_key.includes(searchQuery))
this.setState({ peers, searchQuery })
}
peerClicked(peer) {
const { setNodeKey, changeStep } = this.props
setNodeKey(peer.pub_key)
changeStep(2)
}
render() {
const { peers, searchQuery } = this.state
return (
<div>
<div className={styles.search}>
<label className={`${styles.label} ${styles.input}`} htmlFor='peersSearch'>
<MdSearch />
</label>
<input
value={searchQuery}
onChange={event => this.onSearchQuery(event.target.value)}
className={`${styles.text} ${styles.input}`}
placeholder='Search your peers by their public key'
type='text'
id='peersSearch'
/>
</div>
<ul className={styles.peers}>
{peers.length > 0 &&
peers.map(peer => (
<li
key={peer.peer_id}
className={styles.peer}
onClick={() => this.peerClicked(peer)}
>
<h4>{peer.address}</h4>
<h1>{peer.pub_key}</h1>
</li>
))}
</ul>
</div>
)
}
}
StepOne.propTypes = {
peers: PropTypes.array.isRequired,
setNodeKey: PropTypes.func.isRequired,
changeStep: PropTypes.func.isRequired
}
export default StepOne

74
app/components/ChannelForm/StepOne.scss

@ -1,74 +0,0 @@
@import '../../variables.scss';
.peers {
h2 {
text-transform: uppercase;
font-weight: 200;
padding: 10px 0;
border-bottom: 1px solid $grey;
color: $darkestgrey;
}
}
.search {
height: 50px;
padding: 2px;
border-bottom: 1px solid $darkgrey;
.input {
display: inline-block;
vertical-align: top;
height: 100%;
}
.label {
width: 5%;
line-height: 50px;
font-size: 16px;
text-align: center;
cursor: pointer;
}
.text {
width: 95%;
outline: 0;
padding: 0;
border: 0;
border-radius: 0;
height: 50px;
font-size: 16px;
}
}
.peer {
position: relative;
background: $white;
padding: 10px;
border-top: 1px solid $grey;
cursor: pointer;
transition: all 0.25s;
&:hover {
opacity: 0.5;
}
&:first-child {
border: none;
}
h4, h1 {
margin: 10px 0;
}
h4 {
font-size: 12px;
font-weight: bold;
color: $black;
}
h1 {
font-size: 14px;
font-weight: 200;
color: $main;
}
}

50
app/components/ChannelForm/StepThree.js

@ -1,50 +0,0 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import CurrencyIcon from 'components/CurrencyIcon'
import styles from './StepThree.scss'
class StepThree extends Component {
render() {
const { push_amt, setPushAmount } = this.props
return (
<div className={styles.container}>
<div className={styles.explainer}>
<h2>Push Amount</h2>
<p>
The push amount is the amount of bitcoin (if any at all) you&apos;d like
to &quot;push&quot; to the other side of the channel when it opens.
This amount will be set on the remote side of the channel as part of the initial commitment state.
</p>
</div>
<form>
<label htmlFor='amount'>
<CurrencyIcon currency='btc' crypto='btc' />
</label>
<input
type='number'
min='0'
max='0.16'
ref={(input) => { this.input = input }}
size=''
value={push_amt}
onChange={event => setPushAmount(event.target.value)}
id='amount'
style={{ width: isNaN((push_amt.length + 1) * 55) ? 140 : (push_amt.length + 1) * 55 }}
/>
</form>
</div>
)
}
}
StepThree.propTypes = {
push_amt: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string
]).isRequired,
setPushAmount: PropTypes.func.isRequired
}
export default StepThree

58
app/components/ChannelForm/StepThree.scss

@ -1,58 +0,0 @@
@import '../../variables.scss';
.container {
margin-bottom: 50px;
padding: 20px;
.explainer {
margin: 0px 0 50px 0;
padding-bottom: 20px;
border-bottom: 1px solid $lightgrey;
h2 {
margin: 0 0 20px 0;
font-size: 28px;
}
p {
line-height: 1.5;
font-size: 16px;
}
}
form {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
label {
height: 200px;
color: $main;
svg {
width: 65px;
height: 65px;
}
svg[data-icon='ltc'] {
margin-right: 10px;
g {
transform: scale(1.75) translate(-5px, -5px);
}
}
}
input[type=number] {
color: $main;
width: 30px;
height: 200px;
font-size: 100px;
font-weight: 200;
border: none;
outline: 0;
-webkit-appearance: none;
}
}

49
app/components/ChannelForm/StepTwo.js

@ -1,49 +0,0 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import CurrencyIcon from 'components/CurrencyIcon'
import styles from './StepTwo.scss'
class StepTwo extends Component {
render() {
const { local_amt, setLocalAmount } = this.props
return (
<div className={styles.container}>
<div className={styles.explainer}>
<h2>Local Amount</h2>
<p>
Local amount is the amount of bitcoin that you would like to commit to the channel.
This is the amount that will be sent in an on-chain transaction to open your Lightning channel.
</p>
</div>
<form>
<label htmlFor='amount'>
<CurrencyIcon currency='btc' crypto='btc' />
</label>
<input
type='number'
min='0'
max='0.16'
ref={(input) => { this.input = input }}
size=''
value={local_amt}
onChange={event => setLocalAmount(event.target.value)}
id='amount'
style={{ width: isNaN((local_amt.length + 1) * 55) ? 140 : (local_amt.length + 1) * 55 }}
/>
</form>
</div>
)
}
}
StepTwo.propTypes = {
local_amt: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]).isRequired,
setLocalAmount: PropTypes.func.isRequired
}
export default StepTwo

58
app/components/ChannelForm/StepTwo.scss

@ -1,58 +0,0 @@
@import '../../variables.scss';
.container {
margin-bottom: 50px;
padding: 20px;
.explainer {
margin: 0px 0 50px 0;
padding-bottom: 20px;
border-bottom: 1px solid $lightgrey;
h2 {
margin: 0 0 20px 0;
font-size: 28px;
}
p {
line-height: 1.5;
font-size: 16px;
}
}
form {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
label {
height: 200px;
color: $main;
svg {
width: 65px;
height: 65px;
}
svg[data-icon='ltc'] {
margin-right: 10px;
g {
transform: scale(1.75) translate(-5px, -5px);
}
}
}
input[type=number] {
color: $main;
width: 30px;
height: 200px;
font-size: 100px;
font-weight: 200;
border: none;
outline: 0;
-webkit-appearance: none;
}
}

3
app/components/ChannelForm/index.js

@ -1,3 +0,0 @@
import ChannelForm from './ChannelForm'
export default ChannelForm

95
app/components/Channels/Channel.js

@ -1,95 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import { FaCircle } from 'react-icons/lib/fa'
import { btc } from 'utils'
import styles from './Channel.scss'
const Channel = ({
ticker, channel, closeChannel, currentTicker
}) => (
<li className={styles.channel}>
<header className={styles.header}>
<div>
<span className={styles.status}>Open</span>
{
channel.active ?
<span className={styles.active}>
<FaCircle />
<i>Active</i>
</span>
:
<span className={styles.notactive}>
<FaCircle />
<i>Not Active</i>
</span>
}
</div>
<div>
<p
className={styles.close}
onClick={() => closeChannel({ channel_point: channel.channel_point })}
>
Close channel
</p>
</div>
</header>
<div className={styles.content}>
<div className={styles.left}>
<section className={styles.remotePubkey}>
<span>Remote Pubkey</span>
<h4>{channel.remote_pubkey}</h4>
</section>
<section className={styles.channelPoint}>
<span>Channel Point</span>
<h4>{channel.channel_point}</h4>
</section>
</div>
<div className={styles.right}>
<section className={styles.capacity}>
<span>Capacity</span>
<h2>
{
ticker.currency === 'btc' ?
btc.satoshisToBtc(channel.capacity)
:
btc.satoshisToUsd(channel.capacity, currentTicker.price_usd)
}
</h2>
</section>
<div className={styles.balances}>
<section>
<span>Local</span>
<h4>
{
ticker.currency === 'btc' ?
btc.satoshisToBtc(channel.local_balance)
:
btc.satoshisToUsd(channel.local_balance, currentTicker.price_usd)
}
</h4>
</section>
<section>
<span>Remote</span>
<h4>
{
ticker.currency === 'btc' ?
btc.satoshisToBtc(channel.remote_balance)
:
btc.satoshisToUsd(channel.remote_balance, currentTicker.price_usd)
}
</h4>
</section>
</div>
</div>
</div>
</li>
)
Channel.propTypes = {
ticker: PropTypes.object.isRequired,
channel: PropTypes.object.isRequired,
closeChannel: PropTypes.func.isRequired,
currentTicker: PropTypes.object.isRequired
}
export default Channel

125
app/components/Channels/Channel.scss

@ -1,125 +0,0 @@
@import '../../variables.scss';
.channel {
position: relative;
background: $white;
margin: 5px 0;
padding: 10px;
border-top: 1px solid $white;
cursor: pointer;
transition: all 0.25s;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
&:hover {
opacity: 0.75;
box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
}
&:first-child {
border: none;
}
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
.status, .active, .notactive {
padding: 10px;
text-transform: uppercase;
font-weight: bold;
font-size: 10px;
}
.status {
color: $main;
}
.active i, .notactive i {
margin-left: 5px;
}
.active {
color: $green;
}
.notactive {
color: $red;
}
.close {
padding: 10px;
font-size: 10px;
text-transform: uppercase;
color: $red;
cursor: pointer;
&:hover {
color: lighten($red, 10%);
text-decoration: underline;
}
}
}
.content {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.left, .right {
padding: 0 10px;
margin-bottom: 5;
section {
margin-bottom: 20px;
span {
text-transform: uppercase;
letter-spacing: 1.6px;
color: $black;
font-size: 10px;
font-weight: bold;
}
h2 {
font-size: 30px;
padding: 5px 0;
color: $main;
}
h4 {
margin-top: 5px;
}
}
}
.left {
flex: 7;
}
.right {
flex: 3;
.capacity {
text-align: center;
margin-bottom: 10px;
}
.balances {
display: flex;
justify-content: space-between;
section {
flex: 5;
text-align: center;
h4 {
color: $main;
font-size: 16px;
}
}
}
}
}

128
app/components/Channels/ChannelForm.js

@ -1,128 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import ReactModal from 'react-modal'
import { FaUser } from 'react-icons/lib/fa'
import CurrencyIcon from 'components/CurrencyIcon'
import { usd, btc } from 'utils'
import styles from './ChannelForm.scss'
const ChannelForm = ({
form, setForm, ticker, peers, openChannel, currentTicker
}) => {
const submitClicked = () => {
const { node_key, local_amt, push_amt } = form
const localamt = ticker.currency === 'usd' ? btc.btcToSatoshis(usd.usdToBtc(local_amt, currentTicker.price_usd)) : btc.btcToSatoshis(local_amt)
const pushamt = ticker.currency === 'usd' ? btc.btcToSatoshis(usd.usdToBtc(push_amt, currentTicker.price_usd)) : btc.btcToSatoshis(push_amt)
openChannel({ pubkey: node_key, localamt, pushamt })
// setForm({ isOpen: false })
}
const customStyles = {
overlay: {
cursor: 'pointer',
overflowY: 'auto'
},
content: {
top: 'auto',
left: '20%',
right: '0',
bottom: 'auto',
width: '40%',
margin: '50px auto',
padding: '40px'
}
}
return (
<div>
<ReactModal
isOpen={form.isOpen}
contentLabel='No Overlay Click Modal'
ariaHideApp
shouldCloseOnOverlayClick
onRequestClose={() => setForm({ isOpen: false })}
parentSelector={() => document.body}
style={customStyles}
>
<div className={styles.form}>
<h1 className={styles.title}>Open a new channel</h1>
<section className={styles.pubkey}>
<label htmlFor='nodekey'><FaUser /></label>
<input
type='text'
size=''
placeholder='Peer public key'
value={form.node_key}
onChange={event => setForm({ node_key: event.target.value })}
id='nodekey'
/>
</section>
<section className={styles.local}>
<label htmlFor='localamount'>
<CurrencyIcon currency={ticker.currency} crypto={ticker.crypto} />
</label>
<input
type='text'
size=''
placeholder='Local amount'
value={form.local_amt}
onChange={event => setForm({ local_amt: event.target.value })}
id='localamount'
/>
</section>
<section className={styles.push}>
<label htmlFor='pushamount'>
<CurrencyIcon currency={ticker.currency} crypto={ticker.crypto} />
</label>
<input
type='text'
size=''
placeholder='Push amount'
value={form.push_amt}
onChange={event => setForm({ push_amt: event.target.value })}
id='pushamount'
/>
</section>
<ul className={styles.peers}>
<h2>Connected Peers</h2>
{
peers.length ?
peers.map(peer =>
(
<li
key={peer.peer_id}
className={styles.peer}
onClick={() => setForm({ node_key: peer.pub_key })}
>
<h4>{peer.address}</h4>
<h1>{peer.pub_key}</h1>
</li>
))
:
null
}
</ul>
<div className={styles.buttonGroup}>
<div className={styles.button} onClick={submitClicked}>Submit</div>
</div>
</div>
</ReactModal>
</div>
)
}
ChannelForm.propTypes = {
form: PropTypes.object.isRequired,
setForm: PropTypes.func.isRequired,
ticker: PropTypes.object.isRequired,
peers: PropTypes.array.isRequired,
openChannel: PropTypes.func.isRequired,
currentTicker: PropTypes.object.isRequired
}
export default ChannelForm

123
app/components/Channels/ChannelForm.scss

@ -1,123 +0,0 @@
@import '../../variables.scss';
.title {
text-align: center;
font-size: 24px;
color: $black;
margin-bottom: 50px;
}
.pubkey, .local, .push {
display: flex;
justify-content: center;
font-size: 18px;
height: auto;
min-height: 55px;
margin-bottom: 20px;
border: 1px solid $traditionalgrey;
border-radius: 6px;
position: relative;
padding: 0 20px;
label, input[type=text] {
font-size: inherit;
}
label {
padding-top: 19px;
padding-bottom: 12px;
color: $traditionalgrey;
svg[data-icon='ltc'] {
width: 18px;
height: 16px;
g {
transform: scale(1.75) translate(-5px, -5px);
}
}
}
input[type=text] {
width: 100%;
border: none;
outline: 0;
-webkit-appearance: none;
height: 55px;
padding: 0 10px;
}
}
.peers {
margin-bottom: 50px;
h2 {
text-transform: uppercase;
font-weight: 200;
padding: 10px 0;
border-bottom: 1px solid $grey;
color: $darkestgrey;
}
}
.peer {
position: relative;
background: $white;
padding: 10px;
border-top: 1px solid $grey;
cursor: pointer;
transition: all 0.25s;
&:hover {
opacity: 0.5;
}
&:first-child {
border: none;
}
h4, h1 {
margin: 10px 0;
}
h4 {
font-size: 12px;
font-weight: bold;
color: $black;
}
h1 {
font-size: 14px;
font-weight: 200;
color: $main;
}
}
.buttonGroup {
width: 100%;
display: flex;
flex-direction: row;
border-radius: 6px;
overflow: hidden;
.button {
cursor: pointer;
height: 55px;
min-height: 55px;
text-transform: none;
font-size: 18px;
transition: opacity .2s ease-out;
background: $main;
color: $white;
border: none;
font-weight: 500;
padding: 0;
width: 100%;
text-align: center;
line-height: 55px;
&:first-child {
border-right: 1px solid lighten($main, 20%);
}
}
}

103
app/components/Channels/ChannelModal.js

@ -1,103 +0,0 @@
import { shell } from 'electron'
import React from 'react'
import PropTypes from 'prop-types'
import ReactModal from 'react-modal'
import styles from './ChannelModal.scss'
const ChannelModal = ({
isOpen, resetChannel, channel, explorerLinkBase, closeChannel
}) => {
const customStyles = {
overlay: {
cursor: 'pointer',
overflowY: 'auto'
},
content: {
top: 'auto',
left: '20%',
right: '0',
bottom: 'auto',
width: '40%',
margin: '50px auto',
padding: '40px'
}
}
const closeChannelClicked = () => {
closeChannel({ channel_point: channel.channel_point })
resetChannel(null)
}
return (
<ReactModal
isOpen={isOpen}
contentLabel='No Overlay Click Modal'
ariaHideApp
shouldCloseOnOverlayClick
onRequestClose={() => resetChannel(null)}
parentSelector={() => document.body}
style={customStyles}
>
{
channel ?
<div className={styles.channel}>
<header className={styles.header}>
<h1 data-hint='Remote public key' className='hint--top-left'>{channel.remote_pubkey}</h1>
<h2
data-hint='Channel point'
className='hint--top-left'
onClick={() => shell.openExternal(`${explorerLinkBase}/tx/${channel.channel_point.split(':')[0]}`)}
>
{channel.channel_point}
</h2>
</header>
<div className={styles.balances}>
<section className={styles.capacity}>
<h3>{channel.capacity}</h3>
<span>Capacity</span>
</section>
<div className={styles.balance}>
<section className={styles.local}>
<h4>{channel.local_balance}</h4>
<span>Local</span>
</section>
<section className={styles.remote}>
<h4>{channel.remote_balance}</h4>
<span>Remote</span>
</section>
</div>
</div>
<div className={styles.details}>
<dl>
<dt>Sent</dt>
<dd>{channel.total_satoshis_sent}</dd>
<dt>Received</dt>
<dd>{channel.total_satoshis_received}</dd>
<dt>Updates</dt>
<dd>{channel.num_updates}</dd>
</dl>
</div>
<div className={styles.close} onClick={closeChannelClicked}>
<div>Close channel</div>
</div>
<footer className={styles.active}>
<p>{channel.active ? 'Active' : 'Not active'}</p>
</footer>
</div>
:
null
}
</ReactModal>
)
}
ChannelModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
resetChannel: PropTypes.func.isRequired,
channel: PropTypes.object,
explorerLinkBase: PropTypes.string.isRequired,
closeChannel: PropTypes.func.isRequired
}
export default ChannelModal

124
app/components/Channels/ChannelModal.scss

@ -1,124 +0,0 @@
@import '../../variables.scss';
.modalChannel {
padding: 40px;
}
.header {
margin-bottom: 50px;
h1 {
color: $black;
text-align: center;
margin-bottom: 5px;
font-weight: bold;
}
h2 {
color: $darkestgrey;
font-size: 14px;
text-align: center;
&:hover {
color: $main;
text-decoration: underline;
}
}
}
.balances {
.capacity {
text-align: center;
align-items: center;
h3 {
color: $main;
font-size: 40px;
}
span {
color: $black;
font-size: 16px;
}
}
.balance {
display: flex;
flex-direction: row;
justify-content: space-between;
.local, .remote {
flex: 5;
padding: 10px 30px;
text-align: center;
h4 {
font-size: 20px;
color: $main;
}
span {
color: $black;
font-size: 12px;
}
}
}
}
.details {
width: 75%;
margin: 20px auto;
dt {
text-align: left;
float: left;
clear: left;
font-weight: 500;
padding: 20px 35px 19px 0;
color: $black;
font-weight: bold;
}
dd {
text-align: right;
font-weight: 400;
padding: 19px 0;
margin-left: 0;
border-top: 1px solid $darkgrey;
}
}
.close {
text-align: center;
div {
width: 35%;
margin: 0 auto;
cursor: pointer;
height: 55px;
min-height: 55px;
text-transform: none;
font-size: 18px;
transition: opacity .2s ease-out;
background: $red;
color: $white;
border: none;
font-weight: 500;
padding: 0;
text-align: center;
line-height: 55px;
transition: all 0.25s;
border-radius: 5px;
&:hover {
background: darken($red, 10%);
}
}
}
.active {
color: $darkestgrey;
text-align: center;
margin-top: 50px;
text-transform: uppercase;
}

140
app/components/Channels/Channels.js

@ -1,140 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import { TiPlus } from 'react-icons/lib/ti'
import { FaRepeat } from 'react-icons/lib/fa'
import ChannelModal from './ChannelModal'
import ChannelForm from './ChannelForm'
import Channel from './Channel'
import OpenPendingChannel from './OpenPendingChannel'
import ClosedPendingChannel from './ClosedPendingChannel'
import styles from './Channels.scss'
const Channels = ({
fetchChannels,
ticker,
peers,
channelsLoading,
modalChannel,
setChannel,
channelModalOpen,
channelForm,
setChannelForm,
allChannels,
openChannel,
closeChannel,
currentTicker,
explorerLinkBase
}) => {
const refreshClicked = (event) => {
// store event in icon so we dont get an error when react clears it
const icon = event.currentTarget
// fetch channels
fetchChannels()
// clear animation after the second so we can reuse it
setTimeout(() => { icon.style.animation = '' }, 1000)
// spin icon for 1 sec
icon.style.animation = 'spin 1000ms linear 1'
}
return (
<div className={styles.channels}>
<ChannelModal
isOpen={channelModalOpen}
resetChannel={setChannel}
channel={modalChannel}
explorerLinkBase={explorerLinkBase}
closeChannel={closeChannel}
/>
<ChannelForm
form={channelForm}
setForm={setChannelForm}
ticker={ticker}
peers={peers}
openChannel={openChannel}
currentTicker={currentTicker}
/>
<div className={styles.header}>
<h3>Channels</h3>
<span
className={`${styles.refresh} hint--top`}
data-hint='Refresh your channels list'
>
<FaRepeat
style={{ verticalAlign: 'baseline' }}
onClick={refreshClicked}
/>
</span>
<div
className={`${styles.openChannel} hint--top`}
data-hint='Open a channel'
onClick={() => setChannelForm({ isOpen: true })}
>
<TiPlus />
</div>
</div>
<ul>
{
!channelsLoading ?
allChannels.map((channel, index) => {
if (Object.prototype.hasOwnProperty.call(channel, 'blocks_till_open')) {
return (
<OpenPendingChannel
key={index}
channel={channel}
ticker={ticker}
currentTicker={currentTicker}
explorerLinkBase={explorerLinkBase}
/>
)
} else if (Object.prototype.hasOwnProperty.call(channel, 'closing_txid')) {
return (
<ClosedPendingChannel
key={index}
channel={channel}
ticker={ticker}
currentTicker={currentTicker}
explorerLinkBase={explorerLinkBase}
/>
)
}
return (
<Channel
key={index}
ticker={ticker}
channel={channel}
setChannel={setChannel}
currentTicker={currentTicker}
closeChannel={closeChannel}
/>
)
})
:
'Loading...'
}
</ul>
</div>
)
}
Channels.propTypes = {
fetchChannels: PropTypes.func.isRequired,
ticker: PropTypes.object.isRequired,
peers: PropTypes.array.isRequired,
channelsLoading: PropTypes.bool.isRequired,
modalChannel: PropTypes.object,
setChannel: PropTypes.func.isRequired,
channelModalOpen: PropTypes.bool.isRequired,
channelForm: PropTypes.object.isRequired,
setChannelForm: PropTypes.func.isRequired,
allChannels: PropTypes.array.isRequired,
openChannel: PropTypes.func.isRequired,
closeChannel: PropTypes.func.isRequired,
currentTicker: PropTypes.object.isRequired,
explorerLinkBase: PropTypes.string.isRequired
}
export default Channels

69
app/components/Channels/Channels.scss

@ -1,69 +0,0 @@
@import '../../variables.scss';
@keyframes spin {
from {
transform: rotate(0deg)
}
to {
transform: rotate(360deg);
}
}
.channels {
width: 75%;
margin: 50px auto;
.header {
margin-bottom: 10px;
h3, .openChannel {
display: inline-block;
}
h3 {
text-align: left;
}
.refresh {
cursor: pointer;
margin-left: 5px;
font-size: 12px;
vertical-align: top;
color: $darkestgrey;
line-height: 14px;
transition: color 0.25s;
&:hover {
color: $main;
}
}
.openChannel {
float: right;
cursor: pointer;
svg {
padding: 3px;
border-radius: 50%;
border: 1px solid $main;
color: $main;
transition: all 0.25s;
&:hover {
border-color: darken($main, 10%);
color: darken($main, 10%);
}
}
}
}
h3 {
text-transform: uppercase;
color: $darkestgrey;
letter-spacing: 1.6px;
font-size: 14px;
font-weight: 400;
margin-bottom: 10px;
}
}

69
app/components/Channels/ClosedPendingChannel.js

@ -1,69 +0,0 @@
import { shell } from 'electron'
import React from 'react'
import PropTypes from 'prop-types'
import { btc } from 'utils'
import styles from './ClosedPendingChannel.scss'
const ClosedPendingChannel = ({
ticker, channel: { channel, closing_txid }, currentTicker, explorerLinkBase
}) => (
<li className={styles.channel} onClick={() => shell.openExternal(`${explorerLinkBase}/tx/${closing_txid}`)}>
<h1 className={styles.closing}>Closing Channel...</h1>
<div className={styles.left}>
<section className={styles.remotePubkey}>
<span>Remote Pubkey</span>
<h4>{channel.remote_node_pub}</h4>
</section>
<section className={styles.channelPoint}>
<span>Channel Point</span>
<h4>{channel.channel_point}</h4>
</section>
</div>
<div className={styles.right}>
<section className={styles.capacity}>
<span>Capacity</span>
<h2>
{
ticker.currency === 'btc' ?
btc.satoshisToBtc(channel.capacity)
:
btc.satoshisToUsd(channel.capacity, currentTicker.price_usd)
}
</h2>
</section>
<div className={styles.balances}>
<section>
<h4>
{
ticker.currency === 'btc' ?
btc.satoshisToBtc(channel.local_balance)
:
btc.satoshisToUsd(channel.local_balance, currentTicker.price_usd)
}
</h4>
<span>Local</span>
</section>
<section>
<h4>
{
ticker.currency === 'btc' ?
btc.satoshisToBtc(channel.remote_balance)
:
btc.satoshisToUsd(channel.remote_balance, currentTicker.price_usd)
}
</h4>
<span>Remote</span>
</section>
</div>
</div>
</li>
)
ClosedPendingChannel.propTypes = {
ticker: PropTypes.object.isRequired,
channel: PropTypes.object.isRequired,
currentTicker: PropTypes.object.isRequired,
explorerLinkBase: PropTypes.string.isRequired
}
export default ClosedPendingChannel

95
app/components/Channels/ClosedPendingChannel.scss

@ -1,95 +0,0 @@
@import '../../variables.scss';
.channel {
position: relative;
background: $white;
padding: 10px;
display: flex;
flex-direction: row;
justify-content: space-between;
border-top: 1px solid $grey;
cursor: pointer;
transition: all 0.25s;
opacity: 0.5;
&:hover {
opacity: 0.35;
}
&:first-child {
border: none;
}
.closing {
color: $red;
position: absolute;
top: 0;
left: 10px;
padding: 10px;
text-transform: uppercase;
font-weight: bold;
font-size: 10px;
}
.left, .right {
padding: 0 10px;
margin-bottom: 5;
margin-top: 25px;
section {
margin-bottom: 20px;
span {
text-transform: uppercase;
letter-spacing: 1.6px;
color: $black;
font-size: 10px;
font-weight: bold;
}
h2 {
font-size: 30px;
padding: 5px 0;
color: $main;
}
h4 {
margin-top: 5px;
}
}
}
.left {
flex: 7;
border-right: 1px solid $grey;
}
.right {
flex: 3;
.capacity {
text-align: center;
border-bottom: 1px solid $grey;
margin-bottom: 10px;
}
.balances {
display: flex;
justify-content: space-between;
section {
flex: 5;
text-align: center;
h4 {
color: $main;
font-size: 16px;
}
&:first-child {
border-right: 1px solid $grey;
}
}
}
}
}

72
app/components/Channels/OpenPendingChannel.js

@ -1,72 +0,0 @@
import { shell } from 'electron'
import React from 'react'
import PropTypes from 'prop-types'
import { btc } from 'utils'
import styles from './OpenPendingChannel.scss'
const OpenPendingChannel = ({
ticker, channel, currentTicker, explorerLinkBase
}) => (
<li className={styles.channel} onClick={() => shell.openExternal(`${explorerLinkBase}/tx/${channel.channel.channel_point.split(':')[0]}`)}>
<div className={styles.pending}>
<h1>Opening Channel...</h1>
<span>Blocks till open: {channel.blocks_till_open}</span>
</div>
<div className={styles.left}>
<section className={styles.remotePubkey}>
<span>Remote Pubkey</span>
<h4>{channel.channel.remote_node_pub}</h4>
</section>
<section className={styles.channelPoint}>
<span>Channel Point</span>
<h4>{channel.channel.channel_point}</h4>
</section>
</div>
<div className={styles.right}>
<section className={styles.capacity}>
<span>Capacity</span>
<h2>
{
ticker.currency === 'btc' ?
btc.satoshisToBtc(channel.channel.capacity)
:
btc.satoshisToUsd(channel.channel.capacity, currentTicker.price_usd)
}
</h2>
</section>
<div className={styles.balances}>
<section>
<h4>
{
ticker.currency === 'btc' ?
btc.satoshisToBtc(channel.channel.local_balance)
:
btc.satoshisToUsd(channel.channel.local_balance, currentTicker.price_usd)
}
</h4>
<span>Local</span>
</section>
<section>
<h4>
{
ticker.currency === 'btc' ?
btc.satoshisToBtc(channel.channel.remote_balance)
:
btc.satoshisToUsd(channel.channel.remote_balance, currentTicker.price_usd)
}
</h4>
<span>Remote</span>
</section>
</div>
</div>
</li>
)
OpenPendingChannel.propTypes = {
ticker: PropTypes.object.isRequired,
channel: PropTypes.object.isRequired,
currentTicker: PropTypes.object.isRequired,
explorerLinkBase: PropTypes.string.isRequired
}
export default OpenPendingChannel

98
app/components/Channels/OpenPendingChannel.scss

@ -1,98 +0,0 @@
@import '../../variables.scss';
.channel {
position: relative;
background: $lightgrey;
padding: 10px;
display: flex;
flex-direction: row;
justify-content: space-between;
border-top: 1px solid $grey;
cursor: pointer;
transition: all 0.25s;
opacity: 0.5;
.pending {
position: absolute;
top: 0;
left: 10px;
padding: 10px;
text-transform: uppercase;
font-weight: bold;
h1 {
color: $main;
font-size: 10px;
}
span {
font-size: 8px;
}
}
&:first-child {
border: none;
}
.left, .right {
padding: 0 10px;
margin-bottom: 5;
margin-top: 40px;
section {
margin-bottom: 20px;
span {
text-transform: uppercase;
letter-spacing: 1.6px;
color: $black;
font-size: 10px;
font-weight: bold;
}
h2 {
font-size: 30px;
padding: 5px 0;
color: $main;
}
h4 {
margin-top: 5px;
}
}
}
.left {
flex: 7;
border-right: 1px solid $grey;
}
.right {
flex: 3;
.capacity {
text-align: center;
border-bottom: 1px solid $grey;
margin-bottom: 10px;
}
.balances {
display: flex;
justify-content: space-between;
section {
flex: 5;
text-align: center;
h4 {
color: $main;
font-size: 16px;
}
&:first-child {
border-right: 1px solid $grey;
}
}
}
}
}

3
app/components/Channels/index.js

@ -1,3 +0,0 @@
import Channels from './Channels'
export default Channels

2
app/components/Contacts/ClosingContact.js

@ -33,7 +33,7 @@ const ClosingContact = ({ channel }) => (
)
ClosingContact.propTypes = {
channel: PropTypes.object.isRequired
}
export default ClosingContact

7
app/components/Contacts/ContactModal.js

@ -124,7 +124,12 @@ const ContactModal = ({
}
ContactModal.propTypes = {
channel: PropTypes.object,
isOpen: PropTypes.bool.isRequired,
closeContactModal: PropTypes.func.isRequired,
channelNodes: PropTypes.array.isRequired,
closeChannel: PropTypes.func.isRequired,
closingChannelIds: PropTypes.array.isRequired
}
export default ContactModal

16
app/components/Contacts/ContactsForm.js

@ -133,7 +133,6 @@ class ContactsForm extends React.Component {
className={styles.searchInput}
value={contactsform.searchQuery}
onChange={event => updateContactFormSearchQuery(event.target.value)}
autoFocus
/>
</div>
@ -166,7 +165,7 @@ class ContactsForm extends React.Component {
{
showManualForm &&
<div className={styles.manualForm}>
<h2>Hm, looks like we can't see that contact from here. Want to try and manually connect?</h2>
<h2>Hm, looks like we cant see that contact from here. Want to try and manually connect?</h2>
<section>
<input
type='text'
@ -214,7 +213,18 @@ class ContactsForm extends React.Component {
ContactsForm.propTypes = {
contactsform: PropTypes.object.isRequired,
closeContactsForm: PropTypes.func.isRequired,
updateContactFormSearchQuery: PropTypes.func.isRequired,
updateContactCapacity: PropTypes.func.isRequired,
openChannel: PropTypes.func.isRequired,
activeChannelPubkeys: PropTypes.array.isRequired,
nonActiveChannelPubkeys: PropTypes.array.isRequired,
pendingOpenChannelPubkeys: PropTypes.array.isRequired,
filteredNetworkNodes: PropTypes.array.isRequired,
loadingChannelPubkeys: PropTypes.array.isRequired,
showManualForm: PropTypes.bool.isRequired
}
export default ContactsForm

31
app/components/Contacts/Donut.js

@ -1,31 +0,0 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import styles from './Donut.scss'
const Donut = ({ value, size, strokewidth }) => {
console.log('value: ', value)
console.log('size: ', size)
console.log('strokewidth: ', strokewidth)
const halfsize = (size * 0.5)
const radius = halfsize - (strokewidth * 0.5)
const circumference = 2 * Math.PI * radius
const strokeval = ((value * circumference) / 100)
const dashval = (`${strokeval} ${circumference}`)
const trackstyle = { strokeWidth: 5 }
const indicatorstyle = { strokeWidth: strokewidth, strokeDasharray: dashval }
const rotateval = `rotate(-90 ${37.5},${37.5})`
return (
<svg width={75} height={75} className={styles.donutchart}>
<circle r={30} cx={37.5} cy={37.5} transform={rotateval} style={trackstyle} className={styles.donutchartTrack} />
<circle r={30} cx={37.5} cy={37.5} transform={rotateval} style={indicatorstyle} className={styles.donutchartIndicator} />
</svg>
)
}
Donut.propTypes = {
}
export default Donut

20
app/components/Contacts/Donut.scss

@ -1,20 +0,0 @@
@import '../../variables.scss';
.donutchartTrack{
fill: transparent;
stroke: $lightgrey;
stroke-width: 26;
}
.donutchartIndicator {
fill: transparent;
stroke: $main;
stroke-width: 26;
stroke-dasharray: 0 10000;
transition: stroke-dasharray .3s ease;
}
.donutchart {
margin: 0 auto;
border-radius: 50%;
display: block;
}

4
app/components/Contacts/LoadingContact.js

@ -1,7 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import { FaCircle } from 'react-icons/lib/fa'
import { btc } from 'utils'
import styles from './Contact.scss'
const LoadingContact = ({ pubkey, isClosing }) => (
@ -29,7 +28,8 @@ const LoadingContact = ({ pubkey, isClosing }) => (
)
LoadingContact.propTypes = {
pubkey: PropTypes.string.isRequired,
isClosing: PropTypes.bool.isRequired
}
export default LoadingContact

3
app/components/Contacts/OfflineContact.js

@ -27,7 +27,8 @@ const OfflineContact = ({ channel, openContactModal }) => (
)
OfflineContact.propTypes = {
channel: PropTypes.object.isRequired,
openContactModal: PropTypes.func.isRequired
}
export default OfflineContact

3
app/components/Contacts/OnlineContact.js

@ -27,7 +27,8 @@ const OnlineContact = ({ channel, openContactModal }) => (
)
OnlineContact.propTypes = {
channel: PropTypes.object.isRequired,
openContactModal: PropTypes.func.isRequired
}
export default OnlineContact

2
app/components/Contacts/PendingContact.js

@ -33,7 +33,7 @@ const PendingContact = ({ channel }) => (
)
PendingContact.propTypes = {
channel: PropTypes.object.isRequired
}
export default PendingContact

6
app/components/Form/RequestForm.js

@ -28,7 +28,9 @@ const RequestForm = ({
/>
</section>
<section className={styles.inputContainer}>
<label htmlFor='memo'>Request:</label>
<label htmlFor='memo'>
Request:
</label>
<input
type='text'
placeholder='Dinner, Rent, etc'
@ -39,7 +41,7 @@ const RequestForm = ({
</section>
<section className={styles.buttonGroup}>
<div className={`buttonPrimary ${styles.button}`} onClick={onRequestSubmit}>
Request
Request
</div>
</section>
</div>

1
app/components/Network/CanvasNetworkGraph.js

@ -23,7 +23,6 @@ class CanvasNetworkGraph extends Component {
super(props)
this.state = {
simulation: {},
simulationData: {
nodes: [],
links: []

20
app/components/Peers/Peer.js

@ -1,20 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import styles from './Peer.scss'
const Peer = ({ peer, setPeer }) => (
<li className={styles.peer} onClick={() => setPeer(peer)}>
<h4>{peer.address}</h4>
<h1>{peer.pub_key}</h1>
</li>
)
Peer.propTypes = {
peer: PropTypes.shape({
address: PropTypes.string.isRequired,
pub_key: PropTypes.string.isRequired
}).isRequired,
setPeer: PropTypes.func.isRequired
}
export default Peer

38
app/components/Peers/Peer.scss

@ -1,38 +0,0 @@
@import '../../variables.scss';
.peer {
position: relative;
margin: 5px 0;
padding: 10px;
border-top: 1px solid $white;
cursor: pointer;
transition: all 0.25s;
list-style: none;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
transition: 0.3s;
&:hover {
opacity: 0.75;
box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
}
&:first-child {
border: none;
}
h4, h1 {
margin: 10px 0;
}
h4 {
font-size: 14px;
font-weight: bold;
color: $black;
}
h1 {
font-size: 18px;
font-weight: 200;
color: $main;
}
}

71
app/components/Peers/PeerForm.js

@ -1,71 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import ReactModal from 'react-modal'
import { FaClose } from 'react-icons/lib/fa'
import styles from './PeerForm.scss'
const PeerForm = ({ form, setForm, connect }) => {
const submit = () => {
const { pubkey, host } = form
connect({ pubkey, host })
}
return (
<div>
<ReactModal
isOpen={form.isOpen}
contentLabel='No Overlay Click Modal'
ariaHideApp
shouldCloseOnOverlayClick
onRequestClose={() => setForm({ isOpen: false })}
parentSelector={() => document.body}
className={styles.modal}
>
<div onClick={() => setForm({ isOpen: false })} className={styles.modalClose}>
<FaClose />
</div>
<div className={styles.form} onKeyPress={event => event.charCode === 13 && submit()}>
<h1 className={styles.title}>Connect to a peer</h1>
<section className={styles.pubkey}>
<label htmlFor='pubkey'>Pubkey</label>
<input
type='text'
size=''
placeholder='Public key'
value={form.pubkey}
onChange={event => setForm({ pubkey: event.target.value })}
id='pubkey'
/>
</section>
<section className={styles.local}>
<label htmlFor='address'>Address</label>
<input
type='text'
size=''
placeholder='Host address'
value={form.host}
onChange={event => setForm({ host: event.target.value })}
id='address'
/>
</section>
<div className='buttonContainer' onClick={submit}>
<div className='buttonPrimary'>
Submit
</div>
</div>
</div>
</ReactModal>
</div>
)
}
PeerForm.propTypes = {
form: PropTypes.object.isRequired,
setForm: PropTypes.func.isRequired,
connect: PropTypes.func.isRequired
}
export default PeerForm

107
app/components/Peers/PeerForm.scss

@ -1,107 +0,0 @@
@import '../../variables.scss';
.modal {
position: relative;
width: 40%;
margin: 50px auto;
padding: 40px;
position: absolute;
top: auto;
left: 20%;
right: 0;
bottom: auto;
background: $white;
outline: none;
z-index: -2;
border: 1px solid $darkgrey;
}
.modalClose {
position: absolute;
top: -13px;
right: -13px;
display: block;
font-size: 16px;
line-height: 27px;
width: 32px;
height: 32px;
background: $white;
border-radius: 50%;
color: $darkestgrey;
cursor: pointer;
text-align: center;
z-index: 2;
transition: all 0.25s;
}
.modalClose:hover {
background: $darkgrey;
}
.title {
text-align: center;
font-size: 24px;
color: $black;
margin-bottom: 50px;
}
.pubkey, .local, .push {
display: flex;
justify-content: center;
font-size: 18px;
height: auto;
min-height: 55px;
margin-bottom: 20px;
border: 1px solid $traditionalgrey;
border-radius: 6px;
position: relative;
padding: 0 20px;
label, input[type=text] {
font-size: inherit;
}
label {
padding-top: 19px;
padding-bottom: 12px;
color: $traditionalgrey;
}
input[type=text] {
width: 100%;
border: none;
outline: 0;
-webkit-appearance: none;
height: 55px;
padding: 0 10px;
}
}
.buttonGroup {
width: 100%;
display: flex;
flex-direction: row;
border-radius: 6px;
overflow: hidden;
.button {
cursor: pointer;
height: 55px;
min-height: 55px;
text-transform: none;
font-size: 18px;
transition: opacity .2s ease-out;
background: $main;
color: $white;
border: none;
font-weight: 500;
padding: 0;
width: 100%;
text-align: center;
line-height: 55px;
&:first-child {
border-right: 1px solid lighten($main, 20%);
}
}
}

80
app/components/Peers/PeerModal.js

@ -1,80 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import ReactModal from 'react-modal'
import { FaClose } from 'react-icons/lib/fa'
import styles from './PeerModal.scss'
const PeerModal = ({
isOpen, resetPeer, peer, disconnect
}) => {
const customStyles = {
overlay: {
cursor: 'pointer',
overflowY: 'auto'
},
content: {
top: 'auto',
left: '20%',
right: '0',
bottom: 'auto',
width: '40%',
margin: '50px auto',
borderRadius: 'none',
padding: '0'
}
}
return (
<ReactModal
isOpen={isOpen}
contentLabel='No Overlay Click Modal'
ariaHideApp
shouldCloseOnOverlayClick
onRequestClose={() => resetPeer(null)}
parentSelector={() => document.body}
style={customStyles}
>
<div className={styles.closeContainer}>
<span onClick={() => resetPeer(null)}>
<FaClose />
</span>
</div>
{
peer &&
<div className={styles.peer}>
<header className={styles.header}>
<h1 data-hint='Peer address' className='hint--top-left'>{peer.address}</h1>
<h2 data-hint='Peer public key' className='hint--top-left'>{peer.pub_key}</h2>
</header>
<div className={styles.details}>
<dl>
<dt>Satoshis Received</dt>
<dd>{peer.sat_recv}</dd>
<dt>Satoshis Sent</dt>
<dd>{peer.sat_sent}</dd>
<dt>Bytes Received</dt>
<dd>{peer.bytes_recv}</dd>
<dt>Bytes Sent</dt>
<dd>{peer.bytes_sent}</dd>
</dl>
</div>
<div className={styles.close} onClick={() => disconnect({ pubkey: peer.pub_key })}>
<div>Disconnect peer</div>
</div>
</div>
}
</ReactModal>
)
}
PeerModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
resetPeer: PropTypes.func.isRequired,
peer: PropTypes.object,
disconnect: PropTypes.func.isRequired
}
export default PeerModal

75
app/components/Peers/PeerModal.scss

@ -1,75 +0,0 @@
@import '../../variables.scss';
.closeContainer {
background: $lightgrey;
text-align: right;
padding: 10px;
span {
color: $darkestgrey;
font-size: 20px;
cursor: pointer;
}
}
.header {
background: $lightgrey;
padding: 20px;
h1 {
color: $black;
text-align: center;
margin-bottom: 20px;
font-weight: bold;
}
h2 {
color: $darkestgrey;
font-size: 12px;
text-align: center;
}
}
.details {
dl {
padding: 40px 40px 40px 40px;
}
dt {
text-align: left;
float: left;
clear: left;
font-weight: 500;
padding: 20px 35px 19px 0;
color: $black;
font-weight: bold;
}
dd {
text-align: right;
font-weight: 400;
padding: 30px 0 10px 0;
margin-left: 0;
border-bottom: 1px solid $darkgrey;
}
}
.close {
text-align: center;
padding-bottom: 40px;
div {
margin: 0 auto;
cursor: pointer;
font-size: 18px;
color: $red;
border: none;
padding: 0;
text-align: center;
transition: all 0.25s;
&:hover {
color: lighten($red, 10%);
}
}
}

21
app/lnd/methods/channelController.js

@ -2,7 +2,6 @@ import bitcore from 'bitcore-lib'
import find from 'lodash/find'
import { listPeers, connectPeer } from './peersController'
import pushopenchannel from '../push/openchannel'
import pushclosechannel from '../push/closechannel'
const BufferUtil = bitcore.util.buffer
@ -14,48 +13,39 @@ const BufferUtil = bitcore.util.buffer
* @return {[type]} [description]
*/
export function connectAndOpen(lnd, meta, event, payload) {
console.log('payload: ', payload)
const { pubkey, host, localamt } = payload
const channelPayload = {
node_pubkey: BufferUtil.hexToBuffer(pubkey),
local_funding_amount: Number(localamt),
num_confs: 1
local_funding_amount: Number(localamt)
}
return new Promise((resolve, reject) => {
listPeers(lnd, meta)
.then(({ peers }) => {
console.log('peers: ', peers)
const peer = find(peers, { pub_key: pubkey })
if (peer) {
console.log('already have the peer. can open the channel now')
const call = lnd.openChannel(channelPayload, meta)
call.on('data', data => event.sender.send('pushchannelupdated', { pubkey, data }))
call.on('error', error => event.sender.send('pushchannelerror', { pubkey, error: error.toString() }))
} else {
console.log('connect to the peer first')
connectPeer(lnd, meta, { pubkey, host })
.then((data) => {
console.log('connectPeer data: ', data)
.then(() => {
const call = lnd.openChannel(channelPayload, meta)
call.on('data', data => event.sender.send('pushchannelupdated', { pubkey, data }))
call.on('error', error => event.sender.send('pushchannelerror', { pubkey, error: error.toString() }))
})
.catch((err) => {
console.log('connect peer err: ', err)
event.sender.send('pushchannelerror', { pubkey, error: err.toString() })
reject(err)
})
}
})
.catch((err) => {
console.log('list peer err: ', err)
event.sender.send('pushchannelerror', { pubkey, error: err.toString() })
reject(err)
})
})
}
@ -68,7 +58,6 @@ export function connectAndOpen(lnd, meta, event, payload) {
* @return {[type]} [description]
*/
export function openChannel(lnd, meta, event, payload) {
console.log('opening the channel')
const { pubkey, localamt, pushamt } = payload
const res = {
node_pubkey: BufferUtil.hexToBuffer(pubkey),
@ -123,7 +112,7 @@ export function listChannels(lnd, meta) {
* @return {[type]} [description]
*/
export function closeChannel(lnd, meta, event, payload) {
const chan_id = payload.chan_id
const { chan_id } = payload
const tx = payload.channel_point.funding_txid.match(/.{2}/g).reverse().join('')
const res = {
channel_point: {

6
app/lnd/methods/index.js

@ -119,7 +119,7 @@ export default function (lnd, meta, event, msg, data) {
Object.assign(newinvoice, {
memo: data.memo,
value: data.value,
r_hash: new Buffer(newinvoice.r_hash, 'hex').toString('hex'),
r_hash: Buffer.from(newinvoice.r_hash, 'hex').toString('hex'),
creation_date: Date.now() / 1000
})
))
@ -198,8 +198,8 @@ export default function (lnd, meta, event, msg, data) {
// Connects to a peer if we aren't connected already and then attempt to open a channel
// {} = data
channelController.connectAndOpen(lnd, meta, event, data)
.then((data) => {
console.log('connectAndOpen data: ', data)
.then((channelData) => {
console.log('connectAndOpen data: ', channelData)
// event.sender.send('connectSuccess', { pub_key: data.pubkey, address: data.host, peer_id })
})
.catch((error) => {

4
app/lnd/methods/paymentsController.js

@ -8,11 +8,11 @@ export function sendPaymentSync(lnd, meta, { paymentRequest }) {
return new Promise((resolve, reject) => {
lnd.sendPaymentSync({ payment_request: paymentRequest }, meta, (error, data) => {
if (error) {
reject({ error })
reject({ error }) // eslint-disable-line
return
}
if (!data || !data.payment_route) { reject({ error: data.payment_error }) }
if (!data || !data.payment_route) { reject({ error: data.payment_error }) } // eslint-disable-line
resolve(data)
})

5
app/lnd/subscribe/channelgraph.js

@ -1,11 +1,6 @@
export default function subscribeToChannelGraph(mainWindow, lnd, meta) {
console.log('subscribeChannelGraph is happening')
const call = lnd.subscribeChannelGraph({}, meta)
call.on('data', channelGraphData => mainWindow.send('channelGraphData', { channelGraphData }))
call.on('end', () => console.log('channel graph end'))
call.on('error', error => console.log('channelgraph error: ', error))
call.on('status', channelGraphStatus => mainWindow.send('channelGraphStatus', { channelGraphStatus }))
}

61
app/reducers/channels.js

@ -153,14 +153,10 @@ export const receiveChannels = (event, { channels, pendingChannels }) => dispatc
// Send IPC event for opening a channel
export const openChannel = ({
pubkey, host, local_amt, push_amt
pubkey, host, local_amt
}) => (dispatch) => {
const localamt = btc.btcToSatoshis(local_amt)
console.log('localamt: ', localamt)
console.log('pubkey: ', pubkey)
console.log('host: ', host)
dispatch(openingChannel())
dispatch(addLoadingPubkey(pubkey))
@ -170,28 +166,24 @@ export const openChannel = ({
// TODO: Decide how to handle streamed updates for channels
// Receive IPC event for openChannel
export const channelSuccessful = () => (dispatch) => {
console.log('CHANNEL channelSuccessful')
dispatch(fetchChannels())
dispatch(closeChannelForm())
dispatch(resetChannelForm())
}
// Receive IPC event for updated channel
export const pushchannelupdated = (event, { pubkey, data }) => (dispatch) => {
console.log('PUSH CHANNEL UPDATED: ', data)
export const pushchannelupdated = (event, { pubkey }) => (dispatch) => {
dispatch(fetchChannels())
dispatch(removeLoadingPubkey(pubkey))
}
// Receive IPC event for channel end
export const pushchannelend = event => (dispatch) => { // eslint-disable-line
console.log('PUSH CHANNEL END: ')
dispatch(fetchChannels())
}
// Receive IPC event for channel error
export const pushchannelerror = (event, { pubkey, error }) => (dispatch) => {
console.log('PUSH CHANNEL ERROR: ', error)
dispatch(openingFailure())
dispatch(setError(error))
dispatch(removeLoadingPubkey(pubkey))
@ -199,7 +191,6 @@ export const pushchannelerror = (event, { pubkey, error }) => (dispatch) => {
// Receive IPC event for channel status
export const pushchannelstatus = (event, data) => (dispatch) => { // eslint-disable-line
console.log('PUSH CHANNEL STATUS: ', data)
dispatch(fetchChannels())
}
@ -209,9 +200,6 @@ export const closeChannel = ({ channel_point, chan_id }) => (dispatch) => {
dispatch(addClosingChanId(chan_id))
const [funding_txid, output_index] = channel_point.split(':')
console.log('funding_txid: ', funding_txid)
console.log('output_index: ', output_index)
console.log('chan_id: ', chan_id)
ipcRenderer.send(
'lnd',
{
@ -229,43 +217,36 @@ export const closeChannel = ({ channel_point, chan_id }) => (dispatch) => {
// TODO: Decide how to handle streamed updates for closing channels
// Receive IPC event for closeChannel
export const closeChannelSuccessful = (event, data) => (dispatch) => {
console.log('PUSH CLOSE CHANNEL SUCCESSFUL: ', data)
export const closeChannelSuccessful = () => (dispatch) => {
dispatch(fetchChannels())
}
// Receive IPC event for updated closing channel
export const pushclosechannelupdated = (event, { data, chan_id }) => (dispatch) => {
console.log('PUSH CLOSE CHANNEL UPDATED: ', data)
console.log('PUSH CLOSE CHANNEL chan_id: ', chan_id)
export const pushclosechannelupdated = (event, { chan_id }) => (dispatch) => {
dispatch(fetchChannels())
dispatch(removeClosingChanId(chan_id))
dispatch(closeContactModal())
}
// Receive IPC event for closing channel end
export const pushclosechannelend = (event, data) => (dispatch) => {
console.log('PUSH CLOSE CHANNEL END: ', data)
export const pushclosechannelend = () => (dispatch) => {
dispatch(fetchChannels())
}
// Receive IPC event for closing channel error
export const pushclosechannelerror = (event, { error, chan_id }) => (dispatch) => {
console.log('PUSH CLOSE CHANNEL END: ', error)
console.log('PUSH CLOSE CHANNEL chan_id: ', chan_id)
dispatch(setError(error))
dispatch(removeClosingChanId(chan_id))
}
// Receive IPC event for closing channel status
export const pushclosechannelstatus = (event, data) => (dispatch) => {
console.log('PUSH CLOSE CHANNEL STATUS: ', data)
export const pushclosechannelstatus = () => (dispatch) => {
dispatch(fetchChannels())
}
// IPC event for channel graph data
export const channelGraphData = (event, data) => (dispatch, getState) => {
const info = getState().info
const { info } = getState()
const { channelGraphData: { channel_updates } } = data
// if there are any new channel updates
@ -298,9 +279,7 @@ export const channelGraphData = (event, data) => (dispatch, getState) => {
}
// IPC event for channel graph status
export const channelGraphStatus = (event, data) => () => {
console.log('channelGraphStatus: ', data)
}
export const channelGraphStatus = () => () => {}
export function toggleFilterPulldown() {
return {
@ -308,10 +287,10 @@ export function toggleFilterPulldown() {
}
}
export function changeFilter(filter) {
export function changeFilter(channelFilter) {
return {
type: CHANGE_CHANNEL_FILTER,
filter
channelFilter
}
}
@ -342,13 +321,19 @@ const ACTION_HANDLERS = {
[SET_VIEW_TYPE]: (state, { viewType }) => ({ ...state, viewType }),
[TOGGLE_CHANNEL_PULLDOWN]: state => ({ ...state, filterPulldown: !state.filterPulldown }),
[CHANGE_CHANNEL_FILTER]: (state, { filter }) => ({ ...state, filterPulldown: false, filter }),
[CHANGE_CHANNEL_FILTER]: (state, { channelFilter }) => (
{ ...state, filterPulldown: false, filter: channelFilter }
),
[ADD_LOADING_PUBKEY]: (state, { pubkey }) => ({ ...state, loadingChannelPubkeys: [pubkey, ...state.loadingChannelPubkeys] }),
[REMOVE_LOADING_PUBKEY]: (state, { pubkey }) => ({ ...state, loadingChannelPubkeys: state.loadingChannelPubkeys.filter(loadingPubkey => loadingPubkey !== pubkey) }),
[REMOVE_LOADING_PUBKEY]: (state, { pubkey }) => (
{ ...state, loadingChannelPubkeys: state.loadingChannelPubkeys.filter(loadingPubkey => loadingPubkey !== pubkey) }
),
[ADD_ClOSING_CHAN_ID]: (state, { chanId }) => ({ ...state, closingChannelIds: [chanId, ...state.closingChannelIds] }),
[REMOVE_ClOSING_CHAN_ID]: (state, { chanId }) => ({ ...state, closingChannelIds: state.closingChannelIds.filter(closingChanId => closingChanId !== chanId) }),
[REMOVE_ClOSING_CHAN_ID]: (state, { chanId }) => (
{ ...state, closingChannelIds: state.closingChannelIds.filter(closingChanId => closingChanId !== chanId) }
),
[OPEN_CONTACT_MODAL]: (state, { channel }) => ({ ...state, contactModal: { isOpen: true, channel } }),
[CLOSE_CONTACT_MODAL]: state => ({ ...state, contactModal: { isOpen: false, channel: null } })
@ -414,7 +399,7 @@ channelsSelectors.activeChanIds = createSelector(
channelsSelectors.nonActiveFilters = createSelector(
filtersSelector,
filterSelector,
(filters, filter) => filters.filter(f => f.key !== filter.key)
(filters, channelFilter) => filters.filter(f => f.key !== channelFilter.key)
)
channelsSelectors.channelNodes = createSelector(
@ -443,7 +428,7 @@ const allChannels = createSelector(
const filteredPendingForcedClosedChannels = pendingForcedClosedChannels.filter(channel => channel.channel.remote_node_pub.includes(searchQuery) || channel.channel.channel_point.includes(searchQuery)) // eslint-disable-line
return [...filteredActiveChannels, ...filteredPendingOpenChannels, ...filteredPendingClosedChannels, ...filteredPendingForcedClosedChannels, ...filteredNonActiveChannels]
return [...filteredActiveChannels, ...filteredPendingOpenChannels, ...filteredPendingClosedChannels, ...filteredPendingForcedClosedChannels, ...filteredNonActiveChannels] // eslint-disable-line
}
)
@ -456,7 +441,7 @@ export const currentChannels = createSelector(
channelsSelectors.closingPendingChannels,
filterSelector,
channelSearchQuerySelector,
(allChannelsArr, activeChannelsArr, nonActiveChannelsArr, openChannels, pendingOpenChannels, pendingClosedChannels, filter, searchQuery) => {
(allChannelsArr, activeChannelsArr, nonActiveChannelsArr, openChannels, pendingOpenChannels, pendingClosedChannels, channelFilter, searchQuery) => {
// Helper function to deliver correct channel array based on filter
const filteredArray = (filterKey) => {
switch (filterKey) {
@ -477,7 +462,7 @@ export const currentChannels = createSelector(
}
}
const channelArray = filteredArray(filter.key)
const channelArray = filteredArray(channelFilter.key)
return channelArray.filter(channel => (Object.prototype.hasOwnProperty.call(channel, 'channel') ?
channel.channel.remote_node_pub.includes(searchQuery) || channel.channel.channel_point.includes(searchQuery)

1
app/reducers/contactsform.js

@ -80,7 +80,6 @@ contactFormSelectors.showManualForm = createSelector(
if (!searchQuery.length) { return false }
const connectableNodes = filteredNetworkNodes.filter(node => node.addresses.length > 0)
console.log('connectableNodes: ', connectableNodes)
if (!filteredNetworkNodes.length || !connectableNodes.length) { return true }

4
app/reducers/lnd.js

@ -52,12 +52,12 @@ export const lndStdout = (event, line) => (dispatch) => {
if (line.includes('Caught up to height')) {
trimmed = line.slice(line.indexOf('Caught up to height') + 'Caught up to height'.length).trim()
height = trimmed.split(' ')[0].split(/(\r\n|\n|\r)/gm)[0]
height = trimmed.split(' ')[0].split(/(\r\n|\n|\r)/gm)[0] // eslint-disable-line
}
if (line.includes('Catching up block hashes to height')) {
trimmed = line.slice(line.indexOf('Catching up block hashes to height') + 'Catching up block hashes to height'.length).trim()
height = trimmed.match(/[-]{0,1}[\d.]*[\d]+/g)[0]
height = trimmed.match(/[-]{0,1}[\d.]*[\d]+/g)[0] // eslint-disable-line
}
dispatch({ type: RECEIVE_LINE, lndBlockHeight: height })

33
app/routes/contacts/components/Contacts.js

@ -5,8 +5,6 @@ import Isvg from 'react-inlinesvg'
import { MdSearch } from 'react-icons/lib/md'
import { FaAngleDown, FaRepeat } from 'react-icons/lib/fa'
import { btc } from 'utils'
import ContactModal from 'components/Contacts/ContactModal'
import ContactsForm from 'components/Contacts/ContactsForm'
import OnlineContact from 'components/Contacts/OnlineContact'
@ -42,15 +40,11 @@ class Contacts extends Component {
searchQuery,
filterPulldown,
filter,
viewType,
loadingChannelPubkeys,
closingChannelIds
},
currentChannels,
activeChannels,
nonActiveChannels,
pendingOpenChannels,
closingPendingChannels,
fetchChannels,
updateChannelSearchQuery,
@ -60,12 +54,9 @@ class Contacts extends Component {
openContactsForm,
openContactModal,
closeContactModal,
contactModalProps,
contactsFormProps,
peers
contactsFormProps
} = this.props
const refreshClicked = () => {
@ -173,6 +164,7 @@ class Contacts extends Component {
} else if (!channel.active) {
return <OfflineContact channel={channel} key={index} openContactModal={openContactModal} />
}
return <span />
})
}
</ul>
@ -181,6 +173,25 @@ class Contacts extends Component {
}
}
Contacts.propTypes = {}
Contacts.propTypes = {
fetchPeers: PropTypes.func.isRequired,
fetchDescribeNetwork: PropTypes.func.isRequired,
channels: PropTypes.object.isRequired,
currentChannels: PropTypes.array.isRequired,
activeChannels: PropTypes.array.isRequired,
fetchChannels: PropTypes.func.isRequired,
updateChannelSearchQuery: PropTypes.func.isRequired,
toggleFilterPulldown: PropTypes.func.isRequired,
changeFilter: PropTypes.func.isRequired,
nonActiveFilters: PropTypes.array.isRequired,
openContactsForm: PropTypes.func.isRequired,
openContactModal: PropTypes.func.isRequired,
contactModalProps: PropTypes.object.isRequired,
contactsFormProps: PropTypes.object.isRequired
}
export default Contacts

3
app/store/configureStore.dev.js

@ -50,8 +50,7 @@ const configureStore = (initialState?: counterStateType) => {
if (module.hot) {
module.hot.accept('../reducers', () =>
store.replaceReducer(require('../reducers')) // eslint-disable-line global-require
)
store.replaceReducer(require('../reducers'))) // eslint-disable-line global-require
}
return store

125
test/components/Channels.spec.js

@ -1,125 +0,0 @@
import React from 'react'
import { shallow } from 'enzyme'
import { TiPlus } from 'react-icons/lib/ti'
import Channels from '../../app/components/Channels'
import ChannelModal from '../../app/components/Channels/ChannelModal'
import ChannelForm from '../../app/components/Channels/ChannelForm'
import Channel from '../../app/components/Channels/Channel'
import OpenPendingChannel from '../../app/components/Channels/OpenPendingChannel'
import ClosedPendingChannel from '../../app/components/Channels/ClosedPendingChannel'
const defaultProps = {
ticker: {},
peers: [],
channelsLoading: false,
modalChannel: {},
setChannel: () => {},
channelModalOpen: false,
channelForm: {},
setChannelForm: () => {},
allChannels: [],
openChannel: () => {},
closeChannel: () => {},
fetchChannels: () => {},
currentTicker: {},
explorerLinkBase: 'https://testnet.smartbit.com.au'
}
const channel_open = {
active: true,
capacity: '10000000',
chan_id: '1322138543153545216',
channel_point: '7efb80bf568cf55eb43ba439fdafea99b43f53493ec9ae7c0eae88de2d2b4577:0',
commit_fee: '8688',
commit_weight: '600',
fee_per_kw: '12000',
local_balance: '9991312',
num_updates: '0',
pending_htlcs: [],
remote_balance: '0',
remote_pubkey: '020178567c0f881b579a7ddbcd8ce362a33ebba2b3c2d218e667f7e3b390e40d4e',
total_satoshis_received: '0',
total_satoshis_sent: '0',
unsettled_balance: '0'
}
const channel_pending = {
capacity: '10000000',
channel_point: '7efb80bf568cf55eb43ba439fdafea99b43f53493ec9ae7c0eae88de2d2b4577:0',
local_balance: '9991312',
remote_balance: '0',
remote_node_pub: '020178567c0f881b579a7ddbcd8ce362a33ebba2b3c2d218e667f7e3b390e40d4e'
}
const pending_open_channels = {
blocks_till_open: 0,
channel: channel_pending,
commit_fee: '8688',
commit_weight: '600',
confirmation_height: 0,
fee_per_kw: '12000'
}
const pending_closing_channels = {
channel: channel_pending,
closing_txid: '8d623d1ddd32945cace3351d511df2b5be3e0f7c7e5622989d2fc0215e8a2a7e'
}
describe('Channels', () => {
describe('should show default components', () => {
const props = { ...defaultProps, channelsLoading: true }
const el = shallow(<Channels {...props} />)
it('should contain Modal and Form', () => {
expect(el.find(ChannelModal)).toHaveLength(1)
expect(el.find(ChannelForm)).toHaveLength(1)
})
it('should have Channels header, and plus button', () => {
expect(el.contains('Channels')).toBe(true)
expect(el.find(TiPlus)).toHaveLength(1)
})
})
describe('channels are loading', () => {
const props = { ...defaultProps, channelsLoading: true }
const el = shallow(<Channels {...props} />)
it('should display loading msg', () => {
expect(el.contains('Loading...')).toBe(true)
})
})
describe('channels are loaded', () => {
describe('no channels', () => {
const props = { ...defaultProps, allChannels: [] }
const el = shallow(<Channels {...props} />)
it('should not show channels or loading', () => {
expect(el.contains('Loading...')).toBe(false)
expect(el.find(Channel)).toHaveLength(0)
})
})
describe('channel is open-pending', () => {
const props = { ...defaultProps, allChannels: [pending_open_channels] }
const el = shallow(<Channels {...props} />)
it('should display open-pending', () => {
expect(el.find(OpenPendingChannel)).toHaveLength(1)
})
})
describe('channel is open', () => {
const props = { ...defaultProps, allChannels: [channel_open] }
const el = shallow(<Channels {...props} />)
it('should display open channel', () => {
expect(el.find(Channel)).toHaveLength(1)
})
})
describe('channel is closed-pending', () => {
const props = { ...defaultProps, allChannels: [pending_closing_channels] }
const el = shallow(<Channels {...props} />)
it('should display closed-pending', () => {
expect(el.find(ClosedPendingChannel)).toHaveLength(1)
})
})
})
})

72
test/components/Peers.spec.js

@ -1,72 +0,0 @@
import React from 'react'
import { shallow } from 'enzyme'
import Peers from '../../app/routes/peers/components/Peers'
import PeerModal from '../../app/components/Peers/PeerModal'
import PeerForm from '../../app/components/Peers/PeerForm'
import Peer from '../../app/components/Peers/Peer'
const defaultProps = {
fetchPeers: () => {},
peerFormProps: {
form: {},
setForm: () => {},
connect: () => {}
},
setPeerForm: () => {},
setPeer: () => {},
updateSearchQuery: () => {},
disconnectRequest: () => {},
peerModalOpen: false,
filteredPeers: [],
peers: {
peer: null,
searchQuery: ''
}
}
const peer = {
address: '45.77.115.33:9735',
bytes_recv: '63322',
bytes_sent: '68714',
inbound: true,
peer_id: 3,
ping_time: '261996',
pub_key: '0293cb97aac77eacjc5377d761640f1b51ebba350902801e1aa62853fa7bc3a1f30',
sat_recv: '0',
sat_sent: '0'
}
describe('component.Peers', () => {
describe('default components', () => {
const props = { ...defaultProps }
const el = shallow(<Peers {...props} />)
it('should contain Modal and Form', () => {
expect(el.find(PeerModal)).toHaveLength(1)
expect(el.find(PeerForm)).toHaveLength(1)
})
it('should have Peers header, and plus button', () => {
expect(el.contains('Peers')).toBe(true)
expect(el.contains('Add new peer')).toBe(true)
})
})
describe('peers are loaded', () => {
describe('no peers', () => {
const props = { ...defaultProps }
const el = shallow(<Peers {...props} />)
it('should show no peers', () => {
expect(el.find(Peer)).toHaveLength(0)
})
})
describe('peer connected', () => {
const props = { ...defaultProps, filteredPeers: [peer] }
const el = shallow(<Peers {...props} />)
it('should show peer information', () => {
expect(el.find(Peer)).toHaveLength(1)
})
})
})
})

33
test/reducers/__snapshots__/balance.spec.js.snap

@ -1,33 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`reducers balanceReducer should handle DECREMENT_COUNTER 1`] = `
Object {
"balanceLoading": false,
"channelBalance": undefined,
"walletBalance": undefined,
}
`;
exports[`reducers balanceReducer should handle INCREMENT_COUNTER 1`] = `
Object {
"balanceLoading": true,
"channelBalance": null,
"walletBalance": null,
}
`;
exports[`reducers balanceReducer should handle initial state 1`] = `
Object {
"balanceLoading": false,
"channelBalance": null,
"walletBalance": null,
}
`;
exports[`reducers balanceReducer should handle unknown action type 1`] = `
Object {
"balanceLoading": false,
"channelBalance": null,
"walletBalance": null,
}
`;

308
test/reducers/__snapshots__/channels.spec.js.snap

@ -1,308 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`reducers channelsReducer should correctly getChannels 1`] = `
Object {
"channel": null,
"channelForm": Object {
"isOpen": false,
"local_amt": "",
"node_key": "",
"push_amt": "",
},
"channels": Array [],
"channelsLoading": true,
"closingChannel": false,
"filter": Object {
"key": "ALL_CHANNELS",
"name": "All Channels",
},
"filterPulldown": false,
"filters": Array [
Object {
"key": "ALL_CHANNELS",
"name": "All Channels",
},
Object {
"key": "ACTIVE_CHANNELS",
"name": "Active Channels",
},
Object {
"key": "OPEN_CHANNELS",
"name": "Open Channels",
},
Object {
"key": "OPEN_PENDING_CHANNELS",
"name": "Open Pending Channels",
},
Object {
"key": "CLOSING_PENDING_CHANNELS",
"name": "Closing Pending Channels",
},
],
"openingChannel": false,
"pendingChannels": Object {
"pending_closing_channels": Array [],
"pending_force_closing_channels": Array [],
"pending_open_channels": Array [],
"total_limbo_balance": "",
},
"searchQuery": "",
"viewType": 0,
}
`;
exports[`reducers channelsReducer should correctly openingChannel 1`] = `
Object {
"channel": null,
"channelForm": Object {
"isOpen": false,
"local_amt": "",
"node_key": "",
"push_amt": "",
},
"channels": Array [],
"channelsLoading": false,
"closingChannel": false,
"filter": Object {
"key": "ALL_CHANNELS",
"name": "All Channels",
},
"filterPulldown": false,
"filters": Array [
Object {
"key": "ALL_CHANNELS",
"name": "All Channels",
},
Object {
"key": "ACTIVE_CHANNELS",
"name": "Active Channels",
},
Object {
"key": "OPEN_CHANNELS",
"name": "Open Channels",
},
Object {
"key": "OPEN_PENDING_CHANNELS",
"name": "Open Pending Channels",
},
Object {
"key": "CLOSING_PENDING_CHANNELS",
"name": "Closing Pending Channels",
},
],
"openingChannel": true,
"pendingChannels": Object {
"pending_closing_channels": Array [],
"pending_force_closing_channels": Array [],
"pending_open_channels": Array [],
"total_limbo_balance": "",
},
"searchQuery": "",
"viewType": 0,
}
`;
exports[`reducers channelsReducer should correctly receiveChannel 1`] = `
Object {
"channel": null,
"channelForm": Object {
"isOpen": false,
"local_amt": "",
"node_key": "",
"push_amt": "",
},
"channels": Array [
1,
2,
],
"channelsLoading": false,
"closingChannel": false,
"filter": Object {
"key": "ALL_CHANNELS",
"name": "All Channels",
},
"filterPulldown": false,
"filters": Array [
Object {
"key": "ALL_CHANNELS",
"name": "All Channels",
},
Object {
"key": "ACTIVE_CHANNELS",
"name": "Active Channels",
},
Object {
"key": "OPEN_CHANNELS",
"name": "Open Channels",
},
Object {
"key": "OPEN_PENDING_CHANNELS",
"name": "Open Pending Channels",
},
Object {
"key": "CLOSING_PENDING_CHANNELS",
"name": "Closing Pending Channels",
},
],
"openingChannel": false,
"pendingChannels": Array [
3,
4,
],
"searchQuery": "",
"viewType": 0,
}
`;
exports[`reducers channelsReducer should correctly setChannel 1`] = `
Object {
"channel": "channel",
"channelForm": Object {
"isOpen": false,
"local_amt": "",
"node_key": "",
"push_amt": "",
},
"channels": Array [],
"channelsLoading": false,
"closingChannel": false,
"filter": Object {
"key": "ALL_CHANNELS",
"name": "All Channels",
},
"filterPulldown": false,
"filters": Array [
Object {
"key": "ALL_CHANNELS",
"name": "All Channels",
},
Object {
"key": "ACTIVE_CHANNELS",
"name": "Active Channels",
},
Object {
"key": "OPEN_CHANNELS",
"name": "Open Channels",
},
Object {
"key": "OPEN_PENDING_CHANNELS",
"name": "Open Pending Channels",
},
Object {
"key": "CLOSING_PENDING_CHANNELS",
"name": "Closing Pending Channels",
},
],
"openingChannel": false,
"pendingChannels": Object {
"pending_closing_channels": Array [],
"pending_force_closing_channels": Array [],
"pending_open_channels": Array [],
"total_limbo_balance": "",
},
"searchQuery": "",
"viewType": 0,
}
`;
exports[`reducers channelsReducer should correctly setChannelForm 1`] = `
Object {
"channel": null,
"channelForm": Object {
"isOpen": true,
"local_amt": "",
"node_key": "",
"push_amt": "",
},
"channels": Array [],
"channelsLoading": false,
"closingChannel": false,
"filter": Object {
"key": "ALL_CHANNELS",
"name": "All Channels",
},
"filterPulldown": false,
"filters": Array [
Object {
"key": "ALL_CHANNELS",
"name": "All Channels",
},
Object {
"key": "ACTIVE_CHANNELS",
"name": "Active Channels",
},
Object {
"key": "OPEN_CHANNELS",
"name": "Open Channels",
},
Object {
"key": "OPEN_PENDING_CHANNELS",
"name": "Open Pending Channels",
},
Object {
"key": "CLOSING_PENDING_CHANNELS",
"name": "Closing Pending Channels",
},
],
"openingChannel": false,
"pendingChannels": Object {
"pending_closing_channels": Array [],
"pending_force_closing_channels": Array [],
"pending_open_channels": Array [],
"total_limbo_balance": "",
},
"searchQuery": "",
"viewType": 0,
}
`;
exports[`reducers channelsReducer should handle initial state 1`] = `
Object {
"channel": null,
"channelForm": Object {
"isOpen": false,
"local_amt": "",
"node_key": "",
"push_amt": "",
},
"channels": Array [],
"channelsLoading": false,
"closingChannel": false,
"filter": Object {
"key": "ALL_CHANNELS",
"name": "All Channels",
},
"filterPulldown": false,
"filters": Array [
Object {
"key": "ALL_CHANNELS",
"name": "All Channels",
},
Object {
"key": "ACTIVE_CHANNELS",
"name": "Active Channels",
},
Object {
"key": "OPEN_CHANNELS",
"name": "Open Channels",
},
Object {
"key": "OPEN_PENDING_CHANNELS",
"name": "Open Pending Channels",
},
Object {
"key": "CLOSING_PENDING_CHANNELS",
"name": "Closing Pending Channels",
},
],
"openingChannel": false,
"pendingChannels": Object {
"pending_closing_channels": Array [],
"pending_force_closing_channels": Array [],
"pending_open_channels": Array [],
"total_limbo_balance": "",
},
"searchQuery": "",
"viewType": 0,
}
`;

13
test/reducers/__snapshots__/form.spec.js.snap

@ -1,13 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`reducers formReducer should correctly setFormType 1`] = `
Object {
"formType": "FOO",
}
`;
exports[`reducers formReducer should handle initial state 1`] = `
Object {
"formType": null,
}
`;

22
test/reducers/__snapshots__/info.spec.js.snap

@ -1,22 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`reducers infoReducer should correctly getInfo 1`] = `
Object {
"data": Object {},
"infoLoading": true,
}
`;
exports[`reducers infoReducer should correctly receiveInfo 1`] = `
Object {
"data": "foo",
"infoLoading": false,
}
`;
exports[`reducers infoReducer should handle initial state 1`] = `
Object {
"data": Object {},
"infoLoading": false,
}
`;

171
test/reducers/__snapshots__/invoice.spec.js.snap

@ -1,171 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`reducers invoiceReducer should correctly getInvoice 1`] = `
Object {
"data": Object {},
"formInvoice": Object {
"amount": "0",
"payreq": "",
"r_hash": "",
},
"invoice": null,
"invoiceLoading": true,
"invoices": Array [],
"invoicesSearchText": "",
}
`;
exports[`reducers invoiceReducer should correctly getInvoices 1`] = `
Object {
"data": Object {},
"formInvoice": Object {
"amount": "0",
"payreq": "",
"r_hash": "",
},
"invoice": null,
"invoiceLoading": true,
"invoices": Array [],
"invoicesSearchText": "",
}
`;
exports[`reducers invoiceReducer should correctly invcoiceFailed 1`] = `
Object {
"data": null,
"formInvoice": Object {
"amount": "0",
"payreq": "",
"r_hash": "",
},
"invoice": null,
"invoiceLoading": false,
"invoices": Array [],
"invoicesSearchText": "",
}
`;
exports[`reducers invoiceReducer should correctly invcoiceSuccessful 1`] = `
Object {
"data": Object {},
"formInvoice": Object {
"amount": "0",
"payreq": "",
"r_hash": "",
},
"invoice": null,
"invoiceLoading": false,
"invoices": Array [
"foo",
],
"invoicesSearchText": "",
}
`;
exports[`reducers invoiceReducer should correctly receiveFormInvoice 1`] = `
Object {
"data": Object {},
"formInvoice": Object {
"amount": "0",
"payreq": "",
"r_hash": "",
},
"invoice": null,
"invoiceLoading": false,
"invoices": Array [],
"invoicesSearchText": "",
}
`;
exports[`reducers invoiceReducer should correctly receiveInvoice 1`] = `
Object {
"data": Object {},
"formInvoice": Object {
"amount": "0",
"payreq": "",
"r_hash": "",
},
"invoice": "foo",
"invoiceLoading": false,
"invoices": Array [],
"invoicesSearchText": "",
}
`;
exports[`reducers invoiceReducer should correctly receiveInvoices 1`] = `
Object {
"data": Object {},
"formInvoice": Object {
"amount": "0",
"payreq": "",
"r_hash": "",
},
"invoice": null,
"invoiceLoading": false,
"invoices": Array [
1,
2,
],
"invoicesSearchText": "",
}
`;
exports[`reducers invoiceReducer should correctly searchInvoices 1`] = `
Object {
"data": Object {},
"formInvoice": Object {
"amount": "0",
"payreq": "",
"r_hash": "",
},
"invoice": null,
"invoiceLoading": false,
"invoices": Array [],
"invoicesSearchText": "foo",
}
`;
exports[`reducers invoiceReducer should correctly sendInvoice 1`] = `
Object {
"data": Object {},
"formInvoice": Object {
"amount": "0",
"payreq": "",
"r_hash": "",
},
"invoice": null,
"invoiceLoading": true,
"invoices": Array [],
"invoicesSearchText": "",
}
`;
exports[`reducers invoiceReducer should correctly setInvoice 1`] = `
Object {
"data": Object {},
"formInvoice": Object {
"amount": "0",
"payreq": "",
"r_hash": "",
},
"invoice": "foo",
"invoiceLoading": false,
"invoices": Array [],
"invoicesSearchText": "",
}
`;
exports[`reducers invoiceReducer should handle initial state 1`] = `
Object {
"data": Object {},
"formInvoice": Object {
"amount": "0",
"payreq": "",
"r_hash": "",
},
"invoice": null,
"invoiceLoading": false,
"invoices": Array [],
"invoicesSearchText": "",
}
`;

49
test/reducers/__snapshots__/payment.spec.js.snap

@ -1,49 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`reducers paymentReducer should correctly getPayments 1`] = `
Object {
"payment": null,
"paymentLoading": true,
"payments": Array [],
"sendingPayment": false,
}
`;
exports[`reducers paymentReducer should correctly paymentSuccessful 1`] = `
Object {
"payment": null,
"paymentLoading": false,
"payments": Array [],
"sendingPayment": false,
}
`;
exports[`reducers paymentReducer should correctly receivePayments 1`] = `
Object {
"payment": null,
"paymentLoading": false,
"payments": Array [
1,
2,
],
"sendingPayment": false,
}
`;
exports[`reducers paymentReducer should correctly sendPayment 1`] = `
Object {
"payment": "foo",
"paymentLoading": false,
"payments": Array [],
"sendingPayment": false,
}
`;
exports[`reducers paymentReducer should handle initial state 1`] = `
Object {
"payment": null,
"paymentLoading": false,
"payments": Array [],
"sendingPayment": false,
}
`;

182
test/reducers/__snapshots__/peers.spec.js.snap

@ -1,182 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`reducers peersReducer should correctly connectFailure 1`] = `
Object {
"connecting": false,
"disconnecting": false,
"peer": null,
"peerForm": Object {
"host": "",
"isOpen": false,
"pubkey": "",
},
"peers": Array [],
"peersLoading": false,
"searchQuery": "",
}
`;
exports[`reducers peersReducer should correctly connectPeer 1`] = `
Object {
"connecting": true,
"disconnecting": false,
"peer": null,
"peerForm": Object {
"host": "",
"isOpen": false,
"pubkey": "",
},
"peers": Array [],
"peersLoading": false,
"searchQuery": "",
}
`;
exports[`reducers peersReducer should correctly connectSuccess 1`] = `
Object {
"connecting": false,
"disconnecting": false,
"peer": null,
"peerForm": Object {
"host": "",
"isOpen": false,
"pubkey": "",
},
"peers": Array [
"foo",
],
"peersLoading": false,
"searchQuery": "",
}
`;
exports[`reducers peersReducer should correctly disconnectFailure 1`] = `
Object {
"connecting": false,
"disconnecting": false,
"peer": null,
"peerForm": Object {
"host": "",
"isOpen": false,
"pubkey": "",
},
"peers": Array [],
"peersLoading": false,
"searchQuery": "",
}
`;
exports[`reducers peersReducer should correctly disconnectPeer 1`] = `
Object {
"connecting": false,
"disconnecting": true,
"peer": null,
"peerForm": Object {
"host": "",
"isOpen": false,
"pubkey": "",
},
"peers": Array [],
"peersLoading": false,
"searchQuery": "",
}
`;
exports[`reducers peersReducer should correctly disconnectSuccess 1`] = `
Object {
"connecting": false,
"disconnecting": false,
"peer": null,
"peerForm": Object {
"host": "",
"isOpen": false,
"pubkey": "",
},
"peers": Array [],
"peersLoading": false,
"searchQuery": "",
}
`;
exports[`reducers peersReducer should correctly getPeers 1`] = `
Object {
"connecting": false,
"disconnecting": false,
"peer": null,
"peerForm": Object {
"host": "",
"isOpen": false,
"pubkey": "",
},
"peers": Array [],
"peersLoading": true,
"searchQuery": "",
}
`;
exports[`reducers peersReducer should correctly receivePeers 1`] = `
Object {
"connecting": false,
"disconnecting": false,
"peer": null,
"peerForm": Object {
"host": "",
"isOpen": false,
"pubkey": "",
},
"peers": "foo",
"peersLoading": false,
"searchQuery": "",
}
`;
exports[`reducers peersReducer should correctly setPeer 1`] = `
Object {
"connecting": false,
"disconnecting": false,
"peer": "foo",
"peerForm": Object {
"host": "",
"isOpen": false,
"pubkey": "",
},
"peers": Array [],
"peersLoading": false,
"searchQuery": "",
}
`;
exports[`reducers peersReducer should correctly setPeerForm 1`] = `
Object {
"connecting": false,
"disconnecting": false,
"peer": null,
"peerForm": Object {
"0": "f",
"1": "o",
"2": "o",
"host": "",
"isOpen": false,
"pubkey": "",
},
"peers": Array [],
"peersLoading": false,
"searchQuery": "",
}
`;
exports[`reducers peersReducer should handle initial state 1`] = `
Object {
"connecting": false,
"disconnecting": false,
"peer": null,
"peerForm": Object {
"host": "",
"isOpen": false,
"pubkey": "",
},
"peers": Array [],
"peersLoading": false,
"searchQuery": "",
}
`;

51
test/reducers/__snapshots__/ticker.spec.js.snap

@ -1,51 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`reducers tickerReducer should correctly getTicker 1`] = `
Object {
"btcTicker": null,
"crypto": "",
"currency": "",
"ltcTicker": null,
"tickerLoading": true,
}
`;
exports[`reducers tickerReducer should correctly receiveTicker 1`] = `
Object {
"btcTicker": undefined,
"crypto": "",
"currency": "",
"ltcTicker": undefined,
"tickerLoading": false,
}
`;
exports[`reducers tickerReducer should correctly setCrypto 1`] = `
Object {
"btcTicker": null,
"crypto": "foo",
"currency": "",
"ltcTicker": null,
"tickerLoading": false,
}
`;
exports[`reducers tickerReducer should correctly setCurrency 1`] = `
Object {
"btcTicker": null,
"crypto": "",
"currency": "foo",
"ltcTicker": null,
"tickerLoading": false,
}
`;
exports[`reducers tickerReducer should handle initial state 1`] = `
Object {
"btcTicker": null,
"crypto": "",
"currency": "",
"ltcTicker": null,
"tickerLoading": false,
}
`;
Loading…
Cancel
Save