Browse Source

Merge pull request #440 from mrfelton/build/developer-workflow

Developer workflow improvements
renovate/lint-staged-8.x
Ben Woosley 6 years ago
committed by GitHub
parent
commit
2e6e0a8f3d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .commitlintrc
  2. 37
      .eslintrc
  3. 4
      .gitignore
  4. 6
      .huskyrc
  5. 6
      .lintstagedrc
  6. 22
      .prettierignore
  7. 15
      .prettierrc
  8. 3
      app/api/index.js
  9. 12
      app/components/Activity/ActivityModal.js
  10. 35
      app/components/Activity/Countdown.js
  11. 38
      app/components/Activity/InvoiceModal.js
  12. 25
      app/components/Activity/PaymentModal.js
  13. 38
      app/components/Activity/TransactionModal.js
  14. 44
      app/components/Contacts/AddChannel.js
  15. 5
      app/components/Contacts/ChannelForm.js
  16. 13
      app/components/Contacts/ConnectManually.js
  17. 36
      app/components/Contacts/ContactModal.js
  18. 75
      app/components/Contacts/ContactsForm.js
  19. 147
      app/components/Contacts/Network.js
  20. 46
      app/components/Contacts/SubmitChannelForm.js
  21. 19
      app/components/Contacts/SuggestedNodes.js
  22. 7
      app/components/CurrencyIcon/CurrencyIcon.js
  23. 5
      app/components/Form/Form.js
  24. 106
      app/components/Form/Pay.js
  25. 48
      app/components/Form/Request.js
  26. 4
      app/components/Onboarding/Alias.js
  27. 28
      app/components/Onboarding/ConnectionDetails.js
  28. 29
      app/components/Onboarding/FormContainer.js
  29. 9
      app/components/Onboarding/InitWallet.js
  30. 23
      app/components/Onboarding/Login.js
  31. 8
      app/components/Onboarding/NewAezeedPassword.js
  32. 8
      app/components/Onboarding/NewWalletPassword.js
  33. 6
      app/components/Onboarding/NewWalletSeed.js
  34. 54
      app/components/Onboarding/Onboarding.js
  35. 11
      app/components/Onboarding/ReEnterSeed.js
  36. 13
      app/components/Onboarding/RecoverForm.js
  37. 22
      app/components/Onboarding/Signup.js
  38. 6
      app/components/Onboarding/Syncing.js
  39. 13
      app/components/Value/Value.js
  40. 36
      app/components/Wallet/ReceiveModal.js
  41. 44
      app/components/Wallet/Wallet.js
  42. 6
      app/containers/Root.js
  43. 4
      app/lnd/index.js
  44. 9
      app/lnd/lib/lightning.js
  45. 9
      app/lnd/lib/walletUnlocker.js
  46. 16
      app/lnd/methods/channelController.js
  47. 33
      app/lnd/methods/index.js
  48. 18
      app/lnd/methods/invoicesController.js
  49. 24
      app/lnd/methods/networkController.js
  50. 16
      app/lnd/methods/paymentsController.js
  51. 14
      app/lnd/methods/peersController.js
  52. 48
      app/lnd/methods/walletController.js
  53. 2
      app/lnd/subscribe/transactions.js
  54. 9
      app/lnd/walletUnlockerMethods/index.js
  55. 10
      app/main.dev.js
  56. 136
      app/menu.js
  57. 65
      app/reducers/activity.js
  58. 2
      app/reducers/address.js
  59. 15
      app/reducers/balance.js
  60. 133
      app/reducers/channels.js
  61. 36
      app/reducers/contactsform.js
  62. 2
      app/reducers/error.js
  63. 6
      app/reducers/info.js
  64. 43
      app/reducers/invoice.js
  65. 10
      app/reducers/ipc.js
  66. 21
      app/reducers/lnd.js
  67. 62
      app/reducers/network.js
  68. 18
      app/reducers/onboarding.js
  69. 53
      app/reducers/payform.js
  70. 14
      app/reducers/payment.js
  71. 41
      app/reducers/peers.js
  72. 6
      app/reducers/requestform.js
  73. 38
      app/reducers/ticker.js
  74. 27
      app/reducers/transaction.js
  75. 2
      app/routes.js
  76. 70
      app/routes/activity/components/Activity.js
  77. 28
      app/routes/activity/components/components/Invoice/Invoice.js
  78. 26
      app/routes/activity/components/components/Payment/Payment.js
  79. 20
      app/routes/activity/components/components/Transaction/Transaction.js
  80. 18
      app/routes/activity/containers/ActivityContainer.js
  81. 25
      app/routes/app/components/App.js
  82. 48
      app/routes/app/containers/AppContainer.js
  83. 5
      app/store/configureStore.dev.js
  84. 12
      app/utils/blockExplorer.js
  85. 3
      app/utils/log.js
  86. 4
      app/utils/usd.js
  87. 22
      package.json
  88. 2
      test/e2e/e2e.spec.js
  89. 5
      test/reducers/balance.spec.js
  90. 5
      test/reducers/info.spec.js
  91. 7
      test/reducers/ticker.spec.js
  92. 4
      test/runTests.js
  93. 5
      test/utils/usd.spec.js
  94. 11
      webpack.config.base.js
  95. 10
      webpack.config.renderer.dev.dll.js
  96. 6
      webpack.config.renderer.dev.js
  97. 6
      webpack.config.renderer.prod.js
  98. 774
      yarn.lock

3
.commitlintrc

@ -0,0 +1,3 @@
{
"extends": ["@commitlint/config-conventional"]
}

37
.eslintrc

@ -12,7 +12,11 @@
"plugin:import/errors",
"plugin:import/warnings",
"plugin:jsx-a11y/strict",
"plugin:promise/recommended"
"plugin:promise/recommended",
"prettier",
"prettier/flowtype",
"prettier/react",
"plugin:prettier/recommended"
],
"env": {
"browser": true,
@ -21,42 +25,45 @@
"rules": {
"comma-dangle": ["error", "never"],
"semi": ["error", "never"],
"indent": 2,
"jsx-quotes": ["error", "prefer-single"],
"prettier/prettier": "error",
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
"react/no-did-mount-set-state": 0,
"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, {
"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,
"curly": ["error", "all"],
"react/require-default-props": 0,
"max-len": ["error", 150],
"max-len": ["error", { "code": 150, "ignoreUrls": true }],
"import/no-extraneous-dependencies": 0,
"no-confusing-arrow": "error",
"no-mixed-operators": "error",
"no-new": 0,
"no-tabs": "error",
"compat/compat": "error",
"prefer-destructuring": ["error", {
"prefer-destructuring": [
"error",
{
"array": false,
"object": true
}],
}
],
"prefer-promise-reject-errors": 0,
"no-param-reassign": [2, { "props": false }]
},
"plugins": [
"flowtype",
"import",
"promise",
"compat",
"react"
],
"plugins": ["flowtype", "import", "json", "markdown", "prettier", "promise", "compat", "react"],
"settings": {
"import/resolver": {
"node": {

4
.gitignore

@ -53,3 +53,7 @@ npm-debug.log.*
# lnd binary
resources/bin/
# Opt in files
.opt-in
.opt-out

6
.huskyrc

@ -0,0 +1,6 @@
{
"hooks": {
"commit-msg": "opt --in commit-msg --exec 'commitlint -E HUSKY_GIT_PARAMS'",
"pre-commit": "opt --in pre-commit --exec 'lint-staged'"
}
}

6
.lintstagedrc

@ -0,0 +1,6 @@
{
"linters": {
"*.{js,json,md}": ["npm run lint-fix-base", "git add"],
"*.scss": ["npm run lint-styles-fix-base", "git add"]
}
}

22
.prettierignore

@ -0,0 +1,22 @@
# App source
package.json
package-lock.json
.git
.babelrc
node_modules
coverage
# App packaged
release
app/main.prod.js
app/main.prod.js.map
app/renderer.prod.js
app/renderer.prod.js.map
app/style.css
app/style.css.map
app/utils/bech32.js
dist
dll
main.js
main.js.map

15
.prettierrc

@ -0,0 +1,15 @@
{
"bracketSpacing": true,
"printWidth": 150,
"semi": false,
"singleQuote": true,
"trailingComma": "none",
"overrides": [
{
"files": [".babelrc", ".commitlintrc", ".eslintrc", ".huskyrc", ".lintstagedrc", ".prettierrc", ".stylelintrc"],
"options": {
"parser": "json"
}
}
]
}

3
app/api/index.js

@ -11,7 +11,8 @@ export function requestTicker(id) {
}
export function requestTickers(ids) {
return axios.all(ids.map(id => requestTicker(id)))
return axios
.all(ids.map(id => requestTicker(id)))
.then(axios.spread((btcTicker, ltcTicker) => ({ btcTicker: btcTicker[0], ltcTicker: ltcTicker[0] })))
}

12
app/components/Activity/ActivityModal.js

@ -25,7 +25,9 @@ const ActivityModal = ({
INVOICE: InvoiceModal
}
if (!modalType) { return null }
if (!modalType) {
return null
}
const SpecificModal = MODAL_COMPONENTS[modalType]
return (
@ -35,13 +37,7 @@ const ActivityModal = ({
<Isvg src={x} />
</span>
</div>
<SpecificModal
{...modalProps}
network={network}
ticker={ticker}
currentTicker={currentTicker}
toggleCurrencyProps={toggleCurrencyProps}
/>
<SpecificModal {...modalProps} network={network} ticker={ticker} currentTicker={currentTicker} toggleCurrencyProps={toggleCurrencyProps} />
</div>
)
}

35
app/components/Activity/Countdown.js

@ -34,7 +34,8 @@ class Countdown extends React.Component {
const convertTwoDigits = n => (n > 9 ? n : `0${n}`.slice(-2))
const now = new Date().getTime()
const distance = (this.props.countDownDate * 1000) - now
const countDownSeconds = this.props.countDownDate * 1000
const distance = countDownSeconds - now
if (distance <= 0) {
this.setState({ expired: true })
@ -56,32 +57,22 @@ class Countdown extends React.Component {
}
render() {
const {
days,
hours,
minutes,
seconds,
expired
} = this.state
const { days, hours, minutes, seconds, expired } = this.state
if (expired) { return <span className={`${styles.container} ${styles.expired}`}>Expired</span> }
if (!days && !hours && !minutes && !seconds) { return <span className={styles.container} /> }
if (expired) {
return <span className={`${styles.container} ${styles.expired}`}>Expired</span>
}
if (!days && !hours && !minutes && !seconds) {
return <span className={styles.container} />
}
return (
<span className={styles.container}>
<i className={styles.caption}>Expires in</i>
<i>
{days > 0 && `${days}:`}
</i>
<i>
{hours > 0 && `${hours}:`}
</i>
<i>
{minutes > 0 && `${minutes}:`}
</i>
<i>
{seconds >= 0 && `${seconds}`}
</i>
<i>{days > 0 && `${days}:`}</i>
<i>{hours > 0 && `${hours}:`}</i>
<i>{minutes > 0 && `${minutes}:`}</i>
<i>{seconds >= 0 && `${seconds}`}</i>
</span>
)
}

38
app/components/Activity/InvoiceModal.js

@ -20,20 +20,14 @@ const InvoiceModal = ({
ticker,
currentTicker,
toggleCurrencyProps: {
setActivityModalCurrencyFilters,
showCurrencyFilters,
currencyName,
currentCurrencyFilters,
onCurrencyFilterClick
}
toggleCurrencyProps: { setActivityModalCurrencyFilters, showCurrencyFilters, currencyName, currentCurrencyFilters, onCurrencyFilterClick }
}) => {
const copyPaymentRequest = () => {
copy(invoice.payment_request)
showNotification('Noice', 'Successfully copied to clipboard')
}
const countDownDate = (parseInt(invoice.creation_date, 10) + parseInt(invoice.expiry, 10))
const countDownDate = parseInt(invoice.creation_date, 10) + parseInt(invoice.expiry, 10)
return (
<div className={styles.container}>
@ -42,11 +36,11 @@ const InvoiceModal = ({
<h2>Payment Request</h2>
<QRCode
value={invoice.payment_request}
renderAs='svg'
renderAs="svg"
size={150}
bgColor='transparent'
fgColor='white'
level='L'
bgColor="transparent"
fgColor="white"
level="L"
className={styles.qrcode}
/>
<Countdown countDownDate={countDownDate} />
@ -58,22 +52,24 @@ const InvoiceModal = ({
<Value value={invoice.value} currency={ticker.currency} currentTicker={currentTicker} />
</h1>
<section className={styles.currentCurrency} onClick={() => setActivityModalCurrencyFilters(!showCurrencyFilters)}>
<span>{currencyName}</span><span><FaAngleDown /></span>
<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>)
}
{currentCurrencyFilters.map(filter => (
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>
{filter.name}
</li>
))}
</ul>
</section>
<section className={styles.date}>
<p>
<Moment format='MM/DD/YYYY'>{invoice.creation_date * 1000}</Moment>
</p>
<p className={styles.notPaid}>
{!invoice.settled && 'Not Paid'}
<Moment format="MM/DD/YYYY">{invoice.creation_date * 1000}</Moment>
</p>
<p className={styles.notPaid}>{!invoice.settled && 'Not Paid'}</p>
</section>
</div>

25
app/components/Activity/PaymentModal.js

@ -19,13 +19,7 @@ const PaymentModal = ({
ticker,
currentTicker,
toggleCurrencyProps: {
setActivityModalCurrencyFilters,
showCurrencyFilters,
currencyName,
currentCurrencyFilters,
onCurrencyFilterClick
}
toggleCurrencyProps: { setActivityModalCurrencyFilters, showCurrencyFilters, currencyName, currentCurrencyFilters, onCurrencyFilterClick }
}) => (
<div className={styles.container}>
<header className={styles.header}>
@ -51,19 +45,22 @@ const PaymentModal = ({
<Value value={payment.value} currency={ticker.currency} currentTicker={currentTicker} />
</h1>
<section className={styles.currentCurrency} onClick={() => setActivityModalCurrencyFilters(!showCurrencyFilters)}>
<span>{currencyName}</span><span><FaAngleDown /></span>
<span>{currencyName}</span>
<span>
<FaAngleDown />
</span>
<ul className={showCurrencyFilters && styles.active}>
{
currentCurrencyFilters.map(filter =>
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>{filter.name}</li>)
}
{currentCurrencyFilters.map(filter => (
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>
{filter.name}
</li>
))}
</ul>
</section>
</div>
<div className={styles.date}>
<Moment format='LLL'>{payment.creation_date * 1000}</Moment>
<Moment format="LLL">{payment.creation_date * 1000}</Moment>
</div>
<footer className={styles.footer}>

38
app/components/Activity/TransactionModal.js

@ -21,13 +21,7 @@ const TransactionModal = ({
currentTicker,
network,
toggleCurrencyProps: {
setActivityModalCurrencyFilters,
showCurrencyFilters,
currencyName,
currentCurrencyFilters,
onCurrencyFilterClick
}
toggleCurrencyProps: { setActivityModalCurrencyFilters, showCurrencyFilters, currencyName, currentCurrencyFilters, onCurrencyFilterClick }
}) => (
<div className={styles.container}>
<header className={styles.header}>
@ -38,7 +32,9 @@ const TransactionModal = ({
<section className={styles.details}>
<div>
<Isvg src={link} />
<span className={styles.link} onClick={() => blockExplorer.showTransaction(network, transaction.tx_hash)}>On-Chain</span>
<span className={styles.link} onClick={() => blockExplorer.showTransaction(network, transaction.tx_hash)}>
On-Chain
</span>
</div>
<div>
<Value value={transaction.total_fees} currency={ticker.currency} currentTicker={currentTicker} />
@ -49,30 +45,26 @@ const TransactionModal = ({
<div className={styles.amount}>
<h1>
<i className={`${styles.symbol} ${transaction.amount > 0 && styles.active}`}>
{
transaction.amount > 0 ?
'+'
:
'-'
}
</i>
<i className={`${styles.symbol} ${transaction.amount > 0 && styles.active}`}>{transaction.amount > 0 ? '+' : '-'}</i>
<Value value={transaction.amount} currency={ticker.currency} currentTicker={currentTicker} />
</h1>
<section className={styles.currentCurrency} onClick={() => setActivityModalCurrencyFilters(!showCurrencyFilters)}>
<span>{currencyName}</span><span><FaAngleDown /></span>
<span>{currencyName}</span>
<span>
<FaAngleDown />
</span>
<ul className={showCurrencyFilters && styles.active}>
{
currentCurrencyFilters.map(filter =>
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>{filter.name}</li>)
}
{currentCurrencyFilters.map(filter => (
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>
{filter.name}
</li>
))}
</ul>
</section>
</div>
<div className={styles.date}>
<Moment format='LLL'>{transaction.time_stamp * 1000}</Moment>
<Moment format="LLL">{transaction.time_stamp * 1000}</Moment>
</div>
<footer className={styles.footer}>

44
app/components/Contacts/AddChannel.js

@ -21,7 +21,7 @@ const AddChannel = ({
showManualForm,
openManualForm
}) => {
const renderRightSide = (node) => {
const renderRightSide = node => {
if (loadingChannelPubkeys.includes(node.pub_key)) {
return (
<span className={styles.inactive}>
@ -57,11 +57,7 @@ const AddChannel = ({
}
if (!node.addresses.length) {
return (
<span className={`${styles.private} ${styles.inactive}`}>
Private
</span>
)
return <span className={`${styles.private} ${styles.inactive}`}>Private</span>
}
return (
@ -79,7 +75,7 @@ const AddChannel = ({
)
}
const searchUpdated = (search) => {
const searchUpdated = search => {
updateContactFormSearchQuery(search)
if (search.includes('@') && search.split('@')[0].length === 66) {
@ -91,8 +87,8 @@ const AddChannel = ({
<div className={styles.container}>
<header className={styles.header}>
<input
type='text'
placeholder='Search the network...'
type="text"
placeholder="Search the network..."
className={styles.searchInput}
value={contactsform.searchQuery}
onChange={event => searchUpdated(event.target.value)}
@ -105,38 +101,36 @@ const AddChannel = ({
<section className={styles.nodes}>
<ul className={styles.networkResults}>
{
filteredNetworkNodes.map(node => (
{filteredNetworkNodes.map(node => (
<li key={node.pub_key}>
<section>
{
node.alias.length > 0 ?
{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>
<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>
<section>{renderRightSide(node)}</section>
</li>
))
}
))}
</ul>
</section>
{
showManualForm &&
{showManualForm && (
<section className={styles.manualForm}>
<p>Hm, looks like we can&apos;t see that node from here, wanna try to manually connect?</p>
<div className={styles.manualConnectButton} onClick={openManualForm}>Connect Manually</div>
<div className={styles.manualConnectButton} onClick={openManualForm}>
Connect Manually
</div>
</section>
}
)}
</div>
)
}

5
app/components/Contacts/ChannelForm.js

@ -15,7 +15,9 @@ const FORM_TYPES = {
}
const ChannelForm = ({ formType, formProps, closeForm }) => {
if (!formType) { return null }
if (!formType) {
return null
}
const FormComponent = FORM_TYPES[formType]
return (
@ -30,7 +32,6 @@ const ChannelForm = ({ formType, formProps, closeForm }) => {
)
}
ChannelForm.propTypes = {
formType: PropTypes.string,
formProps: PropTypes.object.isRequired,

13
app/components/Contacts/ConnectManually.js

@ -55,8 +55,8 @@ class ConnectManually extends React.Component {
<section className={styles.peer}>
<div className={styles.input}>
<input
type='text'
placeholder='pubkey@host'
type="text"
placeholder="pubkey@host"
value={manualSearchQuery}
onChange={event => updateManualFormSearchQuery(event.target.value)}
/>
@ -64,16 +64,11 @@ class ConnectManually extends React.Component {
</section>
<section className={`${styles.errorMessage} ${showErrors.manualInput && styles.active}`}>
{showErrors.manualInput &&
<span>{manualFormIsValid && manualFormIsValid.errors.manualInput}</span>
}
{showErrors.manualInput && <span>{manualFormIsValid && manualFormIsValid.errors.manualInput}</span>}
</section>
<section className={styles.submit}>
<div
className={`${styles.button} ${manualFormIsValid.isValid && styles.active}`}
onClick={formSubmitted}
>
<div className={`${styles.button} ${manualFormIsValid.isValid && styles.active}`} onClick={formSubmitted}>
Submit
</div>
</section>

36
app/components/Contacts/ContactModal.js

@ -9,15 +9,10 @@ import { btc } from 'utils'
import styles from './ContactModal.scss'
const ContactModal = ({
isOpen,
channel,
closeContactModal,
channelNodes,
closeChannel,
closingChannelIds
}) => {
if (!channel) { return <span /> }
const ContactModal = ({ isOpen, channel, closeContactModal, channelNodes, closeChannel, closingChannelIds }) => {
if (!channel) {
return <span />
}
const customStyles = {
overlay: {
@ -46,22 +41,19 @@ const ContactModal = ({
return (
<ReactModal
isOpen={isOpen}
contentLabel='No Overlay Click Modal'
contentLabel="No Overlay Click Modal"
ariaHideApp
shouldCloseOnOverlayClick
onRequestClose={closeContactModal}
parentSelector={() => document.body}
style={customStyles}
>
{
channel &&
{channel && (
<div className={styles.container}>
<header className={styles.header}>
<div className={`${styles.status} ${channel.active && styles.online}`}>
<FaCircle style={{ verticalAlign: 'top' }} />
<span>
{channel.active ? 'Online' : 'Offline'}
</span>
<span>{channel.active ? 'Online' : 'Offline'}</span>
</div>
<div className={styles.closeContainer}>
<span onClick={closeContactModal}>
@ -71,10 +63,7 @@ const ContactModal = ({
</header>
<section className={styles.title}>
{
node &&
<h1>{node.alias}</h1>
}
{node && <h1>{node.alias}</h1>}
<h2>{channel.remote_pubkey}</h2>
</section>
@ -106,19 +95,18 @@ const ContactModal = ({
</section>
<footer>
{
closingChannelIds.includes(channel.chan_id) ?
{closingChannelIds.includes(channel.chan_id) ? (
<span className={styles.inactive}>
<div className={styles.loading}>
<div className={styles.spinner} />
</div>
</span>
:
) : (
<div onClick={removeClicked}>Remove</div>
}
)}
</footer>
</div>
}
)}
</ReactModal>
)
}

75
app/components/Contacts/ContactsForm.js

@ -35,7 +35,7 @@ class ContactsForm extends React.Component {
const { editing } = this.state
const renderRightSide = (node) => {
const renderRightSide = node => {
if (loadingChannelPubkeys.includes(node.pub_key)) {
return (
<span className={styles.inactive}>
@ -71,11 +71,7 @@ class ContactsForm extends React.Component {
}
if (!node.addresses.length) {
return (
<span className={`${styles.private} ${styles.inactive}`}>
Private
</span>
)
return <span className={`${styles.private} ${styles.inactive}`}>Private</span>
}
return (
@ -90,12 +86,13 @@ class ContactsForm extends React.Component {
}
const inputClicked = () => {
if (editing) { return }
if (editing) {
return
}
this.setState({ editing: true })
}
const manualFormSubmit = () => {
if (!manualFormIsValid.isValid) {
updateManualFormErrors(manualFormIsValid.errors)
@ -112,7 +109,7 @@ class ContactsForm extends React.Component {
updateManualFormSearchQuery('')
}
const searchUpdated = (search) => {
const searchUpdated = search => {
updateContactFormSearchQuery(search)
if (search.includes('@') && search.split('@')[0].length === 66) {
@ -124,7 +121,7 @@ class ContactsForm extends React.Component {
<div>
<ReactModal
isOpen={contactsform.isOpen}
contentLabel='No Overlay Click Modal'
contentLabel="No Overlay Click Modal"
ariaHideApp
shouldCloseOnOverlayClick
onRequestClose={() => closeContactsForm}
@ -143,8 +140,8 @@ class ContactsForm extends React.Component {
<div className={styles.form}>
<div className={styles.search}>
<input
type='text'
placeholder='Find contact by alias or pubkey'
type="text"
placeholder="Find contact by alias or pubkey"
className={styles.searchInput}
value={contactsform.searchQuery}
onChange={event => searchUpdated(event.target.value)}
@ -152,70 +149,63 @@ class ContactsForm extends React.Component {
</div>
<ul className={styles.networkResults}>
{
filteredNetworkNodes.map(node => (
{filteredNetworkNodes.map(node => (
<li key={node.pub_key}>
<section>
{
node.alias.length > 0 ?
{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>
<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>
<section>{renderRightSide(node)}</section>
</li>
))
}
))}
</ul>
</div>
{
showManualForm &&
{showManualForm && (
<div className={styles.manualForm}>
<h2>Hm, looks like we cant see that contact from here. Want to try and manually connect?</h2>
<section>
<input
type='text'
placeholder='pubkey@host'
type="text"
placeholder="pubkey@host"
value={contactsform.manualSearchQuery}
onChange={event => updateManualFormSearchQuery(event.target.value)}
/>
<div className={styles.submit} onClick={manualFormSubmit}>Submit</div>
<div className={styles.submit} onClick={manualFormSubmit}>
Submit
</div>
{
loadingChannelPubkeys.length > 0 &&
{loadingChannelPubkeys.length > 0 && (
<div className={styles.manualFormSpinner}>
<div className={styles.loading}>
<div className={styles.spinner} />
</div>
</div>
}
)}
</section>
<section className={`${styles.errorMessage} ${showErrors.manualInput && styles.active}`}>
{showErrors.manualInput &&
<span>{manualFormIsValid && manualFormIsValid.errors.manualInput}</span>
}
{showErrors.manualInput && <span>{manualFormIsValid && manualFormIsValid.errors.manualInput}</span>}
</section>
</div>
}
)}
<footer className={styles.footer}>
<div>
<span>
Use
</span>
<span>Use</span>
<span className={styles.amount}>
<input
type='text'
type="text"
value={contactsform.contactCapacity}
onChange={event => updateContactCapacity(event.target.value)}
onClick={inputClicked}
@ -226,10 +216,7 @@ class ContactsForm extends React.Component {
</span>
<span className={styles.caption}>
BTC per contact
<i
data-hint="You aren't spending anything, just moving money onto the Lightning Network"
className='hint--top'
>
<i data-hint="You aren't spending anything, just moving money onto the Lightning Network" className="hint--top">
<FaQuestionCircle style={{ verticalAlign: 'top' }} />
</i>
</span>

147
app/components/Contacts/Network.js

@ -23,15 +23,7 @@ class Network extends Component {
render() {
const {
channels: {
searchQuery,
filterPulldown,
filter,
selectedChannel,
loadingChannelPubkeys,
closingChannelIds,
channels
},
channels: { searchQuery, filterPulldown, filter, selectedChannel, loadingChannelPubkeys, closingChannelIds, channels },
currentChannels,
balance,
ticker,
@ -85,12 +77,12 @@ class Network extends Component {
}
// when the user clicks the action to close the channel
const removeClicked = (channel) => {
const removeClicked = channel => {
closeChannel({ channel_point: channel.channel_point, chan_id: channel.chan_id, force: !channel.active })
}
// when a user clicks a channel
const channelClicked = (channel) => {
const channelClicked = channel => {
// selectedChannel === channel ? setSelectedChannel(null) : setSelectedChannel(channel)
if (selectedChannel === channel) {
setSelectedChannel(null)
@ -99,27 +91,36 @@ class Network extends Component {
}
}
const displayNodeName = (channel) => {
const displayNodeName = channel => {
const node = find(nodes, n => channel.remote_pubkey === n.pub_key)
if (node && node.alias.length) { return node.alias }
if (node && node.alias.length) {
return node.alias
}
return channel.remote_pubkey ? channel.remote_pubkey.substring(0, 10) : channel.remote_node_pub.substring(0, 10)
}
const channelStatus = (channel) => {
const channelStatus = channel => {
// if the channel has a confirmation_height property that means it's pending
if (Object.prototype.hasOwnProperty.call(channel, 'confirmation_height')) { return 'pending' }
if (Object.prototype.hasOwnProperty.call(channel, 'confirmation_height')) {
return 'pending'
}
// if the channel has a closing tx that means it's closing
if (Object.prototype.hasOwnProperty.call(channel, 'closing_txid')) { return 'closing' }
if (Object.prototype.hasOwnProperty.call(channel, 'closing_txid')) {
return 'closing'
}
// if we are in the process of closing this channel
if (closingChannelIds.includes(channel.chan_id)) { return 'closing' }
if (closingChannelIds.includes(channel.chan_id)) {
return 'closing'
}
// if the channel isn't active that means the remote peer isn't online
if (!channel.active) { return 'offline' }
if (!channel.active) {
return 'offline'
}
// if all of the above conditionals fail we can assume the node is online :)
return 'online'
@ -136,7 +137,7 @@ class Network extends Component {
{btc.satoshisToBtc(balance.channelBalance)}BTC ${usdAmount ? usdAmount.toLocaleString() : ''}
</span>
</section>
<section className={`${styles.addChannel} hint--bottom-left`} onClick={openContactsForm} data-hint='Open a channel'>
<section className={`${styles.addChannel} hint--bottom-left`} onClick={openContactsForm} data-hint="Open a channel">
<span className={styles.plusContainer}>
<Isvg src={plus} />
</span>
@ -144,48 +145,48 @@ class Network extends Component {
</header>
<div className={styles.channels}>
{
(!loadingChannelPubkeys.length && !channels.length) &&
<SuggestedNodes {...suggestedNodesProps} />
}
{!loadingChannelPubkeys.length && !channels.length && <SuggestedNodes {...suggestedNodesProps} />}
{
(loadingChannelPubkeys.length || channels.length) &&
{(loadingChannelPubkeys.length || channels.length) && (
<header className={styles.listHeader}>
<section>
<h2 onClick={toggleFilterPulldown} className={styles.filterTitle}>
{filter.name} <span className={filterPulldown && styles.pulldown}><FaAngleDown /></span>
{filter.name}{' '}
<span className={filterPulldown && styles.pulldown}>
<FaAngleDown />
</span>
</h2>
<ul className={`${styles.filters} ${filterPulldown && styles.active}`}>
{
nonActiveFilters.map(f => (
{nonActiveFilters.map(f => (
<li key={f.key} onClick={() => changeFilter(f)}>
{f.name}
</li>
))
}
))}
</ul>
</section>
<section className={styles.refreshContainer}>
<span className={styles.refresh} onClick={refreshClicked} ref={(ref) => { this.repeat = ref }}>
{
this.state.refreshing ?
<FaRepeat />
:
'Refresh'
}
<span
className={styles.refresh}
onClick={refreshClicked}
ref={ref => {
this.repeat = ref
}}
>
{this.state.refreshing ? <FaRepeat /> : 'Refresh'}
</span>
</section>
</header>
}
)}
<ul className={filterPulldown && styles.fade}>
{
loadingChannelPubkeys.length && loadingChannelPubkeys.map((loadingPubkey) => {
{loadingChannelPubkeys.length &&
loadingChannelPubkeys.map(loadingPubkey => {
// TODO(jimmymow): refactor this out. same logic is in displayNodeName above
const node = find(nodes, n => loadingPubkey === n.pub_key)
const nodeDisplay = () => {
if (node && node.alias.length) { return node.alias }
if (node && node.alias.length) {
return node.alias
}
return loadingPubkey.substring(0, 10)
}
@ -193,17 +194,16 @@ class Network extends Component {
return (
<li key={loadingPubkey} className={styles.channel}>
<section className={styles.channelTitle}>
<span className={`${styles.loading} hint--left`} data-hint='loading'>
<span className={`${styles.loading} hint--left`} data-hint="loading">
<i className={styles.spinner} />
</span>
<span>{nodeDisplay()}</span>
</section>
</li>
)
})
}
{
currentChannels.length && currentChannels.map((channelObj, index) => {
})}
{currentChannels.length &&
currentChannels.map((channelObj, index) => {
const channel = Object.prototype.hasOwnProperty.call(channelObj, 'channel') ? channelObj.channel : channelObj
const pubkey = channel.remote_node_pub || channel.remote_pubkey
@ -215,22 +215,20 @@ class Network extends Component {
>
<section className={styles.channelTitle}>
<span className={`${styles[channelStatus(channelObj)]} hint--right`} data-hint={channelStatus(channelObj)}>
{
closingChannelIds.includes(channel.chan_id) ?
{closingChannelIds.includes(channel.chan_id) ? (
<span className={styles.loading}>
<i className={`${styles.spinner} ${styles.closing}`} />
</span>
:
) : (
<FaCircle />
}
)}
</span>
<span>{displayNodeName(channel)}</span>
{
selectedChannel === channel &&
{selectedChannel === channel && (
<span onClick={() => blockExplorer.showTransaction(network, channel.channel_point.split(':')[0])}>
<FaExternalLink />
</span>
}
)}
</section>
<section className={styles.channelDetails}>
@ -242,65 +240,54 @@ class Network extends Component {
<section>
<h5>Pay Limit</h5>
<p>
<Value
value={channel.local_balance}
currency={ticker.currency}
currentTicker={currentTicker}
/>
<Value value={channel.local_balance} currency={ticker.currency} currentTicker={currentTicker} />
<i> {ticker.currency.toUpperCase()}</i>
</p>
</section>
<section>
<h5>Request Limit</h5>
<p>
<Value
value={channel.remote_balance}
currency={ticker.currency}
currentTicker={currentTicker}
/>
<Value value={channel.remote_balance} currency={ticker.currency} currentTicker={currentTicker} />
<i>{ticker.currency.toUpperCase()}</i>
</p>
</section>
</div>
<div className={styles.actions}>
{
closingChannelIds.includes(channel.chan_id) &&
{closingChannelIds.includes(channel.chan_id) && (
<section>
<span className={`${styles.loading} hint--left`} data-hint='closing'>
<span className={`${styles.loading} hint--left`} data-hint="closing">
<i>Closing</i> <i className={`${styles.spinner} ${styles.closing}`} />
</span>
</section>
}
{
(Object.prototype.hasOwnProperty.call(channel, 'active') && !closingChannelIds.includes(channel.chan_id)) &&
)}
{Object.prototype.hasOwnProperty.call(channel, 'active') &&
!closingChannelIds.includes(channel.chan_id) && (
<section onClick={() => removeClicked(channel)}>
<div>Disconnect</div>
</section>
}
)}
</div>
</section>
</li>
)
})
}
})}
</ul>
</div>
{
(loadingChannelPubkeys.length || channels.length) &&
{(loadingChannelPubkeys.length || channels.length) && (
<footer className={styles.search}>
<label htmlFor='search' className={`${styles.label} ${styles.input}`}>
<label htmlFor="search" className={`${styles.label} ${styles.input}`}>
<Isvg src={search} />
</label>
<input
id='search'
type='text'
id="search"
type="text"
className={`${styles.text} ${styles.input}`}
placeholder='search by alias or pubkey'
placeholder="search by alias or pubkey"
value={searchQuery}
onChange={event => updateChannelSearchQuery(event.target.value)}
/>
</footer>
}
)}
</div>
)
}

46
app/components/Contacts/SubmitChannelForm.js

@ -38,7 +38,9 @@ class SubmitChannelForm extends React.Component {
const formSubmitted = () => {
// dont submit to LND if they havent set channel capacity amount
if (contactCapacity <= 0) { return }
if (contactCapacity <= 0) {
return
}
// submit the channel to LND
openChannel({ pubkey: node.pub_key, host: node.addresses[0].addr, local_amt: contactCapacity })
@ -55,8 +57,8 @@ class SubmitChannelForm extends React.Component {
<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&apos;t spening any money, rather moving the money you plan to use onto the network.
Adding a connection will help you send and receive money on the Lightning Network. You aren&apos;t spening any money, rather moving the
money you plan to use onto the network.
</p>
</header>
@ -67,37 +69,36 @@ class SubmitChannelForm extends React.Component {
<section className={styles.amount}>
<div className={styles.input}>
<input
type='number'
min='0'
size=''
placeholder='0.00000000'
type="number"
min="0"
size=""
placeholder="0.00000000"
value={contactCapacity || ''}
onChange={event => updateContactCapacity(event.target.value)}
id='amount'
id="amount"
/>
<div className={styles.currency}>
<section className={styles.currentCurrency} onClick={() => setContactsCurrencyFilters(!showCurrencyFilters)}>
<span>{currencyName}</span><span><FaAngleDown /></span>
<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>)
}
{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>
<div className={styles.usdAmount}>{`${contactFormUsdAmount || 0} USD`}</div>
</section>
<section className={styles.submit}>
<div
className={`${styles.button} ${contactCapacity > 0 && styles.active}`}
onClick={formSubmitted}
>
<div className={`${styles.button} ${contactCapacity > 0 && styles.active}`} onClick={formSubmitted}>
Submit
</div>
</section>
@ -111,10 +112,7 @@ SubmitChannelForm.propTypes = {
closeContactsForm: PropTypes.func.isRequired,
node: PropTypes.object.isRequired,
contactCapacity: PropTypes.PropTypes.oneOfType([
PropTypes.number,
PropTypes.string
]),
contactCapacity: PropTypes.PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
updateContactCapacity: PropTypes.func.isRequired,
openChannel: PropTypes.func.isRequired,

19
app/components/Contacts/SuggestedNodes.js

@ -2,13 +2,8 @@ import React from 'react'
import PropTypes from 'prop-types'
import styles from './SuggestedNodes.scss'
const SuggestedNodes = ({
suggestedNodesLoading,
suggestedNodes,
setNode,
openSubmitChannelForm
}) => {
const nodeClicked = (n) => {
const SuggestedNodes = ({ suggestedNodesLoading, suggestedNodes, setNode, openSubmitChannelForm }) => {
const nodeClicked = n => {
// set the node public key for the submit form
setNode({ pub_key: n.pubkey, addresses: [{ addr: n.host }] })
// open the submit form
@ -26,13 +21,10 @@ const SuggestedNodes = ({
return (
<div className={styles.container}>
<header>
{'Hmmm, looks like you don\'t have any channels yet. Here are some suggested nodes to open a channel with to get started'}
</header>
<header>Hmmm, looks like you don&apos;t have any channels yet. Here are some suggested nodes to open a channel with to get started</header>
<ul className={styles.suggestedNodes}>
{
suggestedNodes.map(node => (
{suggestedNodes.map(node => (
<li key={node.pubkey}>
<section>
<span>{node.nickname}</span>
@ -42,8 +34,7 @@ const SuggestedNodes = ({
<span onClick={() => nodeClicked(node)}>Connect</span>
</section>
</li>
))
}
))}
</ul>
</div>
)

7
app/components/CurrencyIcon/CurrencyIcon.js

@ -3,10 +3,9 @@ import PropTypes from 'prop-types'
import { FaDollar } from 'react-icons/lib/fa'
import CryptoIcon from '../CryptoIcon'
const CurrencyIcon = ({ currency, crypto, styles }) => (currency === 'usd' ?
<FaDollar style={styles} />
:
<CryptoIcon styles={styles} currency={crypto} />)
const CurrencyIcon = ({ currency, crypto, styles }) => {
return currency === 'usd' ? <FaDollar style={styles} /> : <CryptoIcon styles={styles} currency={crypto} />
}
CurrencyIcon.propTypes = {
currency: PropTypes.string.isRequired,

5
app/components/Form/Form.js

@ -15,7 +15,9 @@ const FORM_TYPES = {
}
const Form = ({ formType, formProps, closeForm }) => {
if (!formType) { return null }
if (!formType) {
return null
}
const FormComponent = FORM_TYPES[formType]
return (
@ -30,7 +32,6 @@ const Form = ({ formType, formProps, closeForm }) => {
)
}
Form.propTypes = {
formType: PropTypes.string,
formProps: PropTypes.object.isRequired,

106
app/components/Form/Pay.js

@ -14,26 +14,26 @@ import styles from './Pay.scss'
class Pay extends Component {
componentDidUpdate(prevProps) {
const {
isOnchain, isLn, payform: { payInput }, fetchInvoice
isOnchain,
isLn,
payform: { payInput },
fetchInvoice
} = this.props
// If on-chain, focus on amount to let user know it's editable
if (isOnchain) { this.amountInput.focus() }
if (isOnchain) {
this.amountInput.focus()
}
// If LN go retrieve invoice details
if ((prevProps.payform.payInput !== payInput) && isLn) {
if (prevProps.payform.payInput !== payInput && isLn) {
fetchInvoice(payInput)
}
}
render() {
const {
payform: {
payInput,
showErrors,
invoice,
showCurrencyFilters
},
payform: { payInput, showErrors, invoice, showCurrencyFilters },
nodes,
ticker,
@ -58,15 +58,17 @@ class Pay extends Component {
setCurrency
} = this.props
const displayNodeName = (pubkey) => {
const displayNodeName = pubkey => {
const node = find(nodes, n => n.pub_key === pubkey)
if (node && node.alias.length) { return node.alias }
if (node && node.alias.length) {
return node.alias
}
return pubkey ? pubkey.substring(0, 10) : ''
}
const onCurrencyFilterClick = (currency) => {
const onCurrencyFilterClick = currency => {
if (!isLn) {
// change the input amount
setPayAmount(btc.convert(ticker.currency, currency, currentAmount))
@ -86,85 +88,87 @@ class Pay extends Component {
<div className={styles.content}>
<section className={styles.destination}>
<div className={styles.top}>
<label htmlFor='paymentRequest'>Destination</label>
<label htmlFor="paymentRequest">Destination</label>
<span className={`${styles.description} ${(isOnchain || isLn) && styles.active}`}>
{isOnchain &&
{isOnchain && (
<i>
<Isvg src={link} />
<span>On-Chain (~10 minutes)</span>
</i>
}
{isLn &&
)}
{isLn && (
<i>
<span>
{displayNodeName(invoice.destination)} ({invoice.description})
</span>
</i>
}
)}
</span>
</div>
<div className={styles.bottom}>
<textarea
type='text'
placeholder='Paste payment request or bitcoin address here'
type="text"
placeholder="Paste payment request or bitcoin address here"
value={payInput}
onChange={event => setPayInput(event.target.value)}
onBlur={onPayInputBlur}
id='paymentRequest'
rows='4'
id="paymentRequest"
rows="4"
/>
<section className={`${styles.errorMessage} ${showErrors.payInput && styles.active}`}>
{showErrors.payInput &&
<span>{errors.payInput}</span>
}
{showErrors.payInput && <span>{errors.payInput}</span>}
</section>
</div>
</section>
<section className={styles.amount}>
<div className={styles.top}>
<label htmlFor='amount'>Amount</label>
<label htmlFor="amount">Amount</label>
<span />
</div>
<div className={styles.bottom}>
<input
type='number'
min='0'
ref={(input) => { this.amountInput = input }}
size=''
placeholder='0.00000000'
type="number"
min="0"
ref={input => {
this.amountInput = input
}}
size=""
placeholder="0.00000000"
value={currentAmount || ''}
onChange={event => setPayAmount(event.target.value)}
onBlur={onPayAmountBlur}
id='amount'
id="amount"
readOnly={isLn}
/>
<div className={styles.currency}>
<section className={styles.currentCurrency} onClick={() => setCurrencyFilters(!showCurrencyFilters)}>
<span>{currencyName}</span><span><FaAngleDown /></span>
<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>)
}
{currentCurrencyFilters.map(filter => (
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>
{filter.name}
</li>
))}
</ul>
</div>
</div>
<div className={styles.usdAmount}>
{`${usdAmount || 0} USD`}
</div>
<div className={styles.usdAmount}>{`${usdAmount || 0} USD`}</div>
<section className={`${styles.errorMessage} ${styles.amount} ${showErrors.amount && styles.active}`}>
{showErrors.amount &&
<span>{errors.amount}</span>
}
{showErrors.amount && <span>{errors.amount}</span>}
</section>
</section>
<section className={styles.submit}>
<div className={`${styles.button} ${isValid && styles.active}`} onClick={onPaySubmit}>Pay</div>
<div className={`${styles.button} ${isValid && styles.active}`} onClick={onPaySubmit}>
Pay
</div>
</section>
</div>
</div>
@ -172,13 +176,9 @@ class Pay extends Component {
}
}
Pay.propTypes = {
payform: PropTypes.shape({
amount: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
amount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
payInput: PropTypes.string.isRequired,
invoice: PropTypes.object.isRequired,
showErrors: PropTypes.object.isRequired
@ -187,14 +187,8 @@ Pay.propTypes = {
isOnchain: PropTypes.bool.isRequired,
isLn: PropTypes.bool.isRequired,
currentAmount: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
usdAmount: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
currentAmount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
usdAmount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
payFormIsValid: PropTypes.shape({
errors: PropTypes.object,
isValid: PropTypes.bool

48
app/components/Form/Request.js

@ -23,7 +23,7 @@ const Request = ({
onRequestSubmit
}) => {
const onCurrencyFilterClick = (currency) => {
const onCurrencyFilterClick = currency => {
// change the input amount
setRequestAmount(btc.convert(ticker.currency, currency, amount))
@ -41,46 +41,42 @@ const Request = ({
<div className={styles.content}>
<section className={styles.amount}>
<div className={styles.top}>
<label htmlFor='amount'>Amount</label>
<label htmlFor="amount">Amount</label>
<span />
</div>
<div className={styles.bottom}>
<input
type='number'
value={amount || ''}
onChange={event => setRequestAmount(event.target.value)}
id='amount'
placeholder='0.00000000'
/>
<input type="number" value={amount || ''} onChange={event => setRequestAmount(event.target.value)} id="amount" placeholder="0.00000000" />
<div className={styles.currency}>
<section className={styles.currentCurrency} onClick={() => setRequestCurrencyFilters(!showCurrencyFilters)}>
<span>{currencyName}</span><span><FaAngleDown /></span>
<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>)
}
{currentCurrencyFilters.map(filter => (
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>
{filter.name}
</li>
))}
</ul>
</div>
</div>
<div className={styles.usdAmount}>
{`${requestUsdAmount || 0} USD`}
</div>
<div className={styles.usdAmount}>{`${requestUsdAmount || 0} USD`}</div>
</section>
<section className={styles.memo}>
<div className={styles.top}>
<label htmlFor='memo'>Memo</label>
<label htmlFor="memo">Memo</label>
</div>
<div className={styles.bottom}>
<input
type='text'
placeholder='Details about the request'
type="text"
placeholder="Details about the request"
value={memo}
onChange={event => setRequestMemo(event.target.value)}
id='memo'
id="memo"
/>
</div>
</section>
@ -97,17 +93,11 @@ const Request = ({
Request.propTypes = {
requestform: PropTypes.shape({
amount: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
amount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
memo: PropTypes.string
}).isRequired,
requestUsdAmount: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
requestUsdAmount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
currencyName: PropTypes.string.isRequired,
currentCurrencyFilters: PropTypes.array.isRequired,

4
app/components/Onboarding/Alias.js

@ -5,8 +5,8 @@ import styles from './Alias.scss'
const Alias = ({ alias, updateAlias }) => (
<div className={styles.container}>
<input
type='text'
placeholder='Satoshi'
type="text"
placeholder="Satoshi"
className={styles.alias}
ref={input => input && input.focus()}
value={alias}

28
app/components/Onboarding/ConnectionDetails.js

@ -2,16 +2,14 @@ import React from 'react'
import PropTypes from 'prop-types'
import styles from './ConnectionDetails.scss'
const ConnectionDetails = ({
connectionHost, connectionCert, connectionMacaroon, setConnectionHost, setConnectionCert, setConnectionMacaroon
}) => (
const ConnectionDetails = ({ connectionHost, connectionCert, connectionMacaroon, setConnectionHost, setConnectionCert, setConnectionMacaroon }) => (
<div className={styles.container}>
<div>
<label htmlFor='connectionHost'>Host:</label>
<label htmlFor="connectionHost">Host:</label>
<input
type='text'
id='connectionHost'
placeholder='Hostname / Port of the Lnd gRPC interface'
type="text"
id="connectionHost"
placeholder="Hostname / Port of the Lnd gRPC interface"
className={styles.host}
ref={input => input}
value={connectionHost}
@ -19,11 +17,11 @@ const ConnectionDetails = ({
/>
</div>
<div>
<label htmlFor='connectionCert'>TLS Certificate:</label>
<label htmlFor="connectionCert">TLS Certificate:</label>
<input
type='text'
id='connectionCert'
placeholder='Path to the lnd tls cert'
type="text"
id="connectionCert"
placeholder="Path to the lnd tls cert"
className={styles.cert}
ref={input => input}
value={connectionCert}
@ -31,11 +29,11 @@ const ConnectionDetails = ({
/>
</div>
<div>
<label htmlFor='connectionMacaroon'>Macaroon:</label>
<label htmlFor="connectionMacaroon">Macaroon:</label>
<input
type='text'
id='connectionMacaroon'
placeholder='Path to the lnd macaroon file'
type="text"
id="connectionMacaroon"
placeholder="Path to the lnd macaroon file"
className={styles.macaroon}
ref={input => input}
value={connectionMacaroon}

29
app/components/Onboarding/FormContainer.js

@ -7,13 +7,7 @@ import { FaAngleLeft, FaAngleRight } from 'react-icons/lib/fa'
import zapLogo from 'icons/zap_logo.svg'
import styles from './FormContainer.scss'
const FormContainer = ({
title,
description,
back,
next,
children
}) => (
const FormContainer = ({ title, description, back, next, children }) => (
<div className={styles.container}>
<div className={styles.titleBar} />
@ -29,28 +23,29 @@ const FormContainer = ({
<p>{description}</p>
</div>
<div className={styles.content}>
{children}
</div>
<div className={styles.content}>{children}</div>
<footer className={styles.footer}>
<div className={styles.buttonsContainer}>
<section>
{
back && <div onClick={back}><FaAngleLeft style={{ verticalAlign: 'top' }} /> Back</div>
}
{back && (
<div onClick={back}>
<FaAngleLeft style={{ verticalAlign: 'top' }} /> Back
</div>
)}
</section>
<section>
{
next && <div onClick={next}>Next <FaAngleRight style={{ verticalAlign: 'top' }} /></div>
}
{next && (
<div onClick={next}>
Next <FaAngleRight style={{ verticalAlign: 'top' }} />
</div>
)}
</section>
</div>
</footer>
</div>
)
FormContainer.propTypes = {
title: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,

9
app/components/Onboarding/InitWallet.js

@ -5,14 +5,7 @@ import Signup from './Signup'
import styles from './InitWallet.scss'
const InitWallet = ({ hasSeed, loginProps, signupProps }) => (
<div className={styles.container}>
{
hasSeed ?
<Login {...loginProps} />
:
<Signup {...signupProps} />
}
</div>
<div className={styles.container}>{hasSeed ? <Login {...loginProps} /> : <Signup {...signupProps} />}</div>
)
InitWallet.propTypes = {

23
app/components/Onboarding/Login.js

@ -2,35 +2,22 @@ import React from 'react'
import PropTypes from 'prop-types'
import styles from './Login.scss'
const Login = ({
password,
updatePassword,
unlockingWallet,
unlockWallet,
unlockWalletError
}) => (
const Login = ({ password, updatePassword, unlockingWallet, unlockWallet, unlockWalletError }) => (
<div className={styles.container}>
<input
type='password'
placeholder='Password'
type="password"
placeholder="Password"
className={`${styles.password} ${unlockWalletError.isError && styles.inputError}`}
ref={input => input && input.focus()}
value={password}
onChange={event => updatePassword(event.target.value)}
/>
<p className={`${unlockWalletError.isError && styles.active} ${styles.error}`}>
{unlockWalletError.message}
</p>
<p className={`${unlockWalletError.isError && styles.active} ${styles.error}`}>{unlockWalletError.message}</p>
<section className={styles.buttons}>
<div>
<span className={`${!unlockingWallet && styles.active} ${styles.button}`} onClick={() => unlockWallet(password)}>
{
unlockingWallet ?
<i className={styles.spinner} />
:
'Unlock'
}
{unlockingWallet ? <i className={styles.spinner} /> : 'Unlock'}
</span>
</div>
</section>

8
app/components/Onboarding/NewAezeedPassword.js

@ -12,8 +12,8 @@ const NewAezeedPassword = ({
<div className={styles.container}>
<section className={styles.input}>
<input
type='password'
placeholder='Password'
type="password"
placeholder="Password"
className={styles.password}
value={aezeedPassword}
onChange={event => updateAezeedPassword(event.target.value)}
@ -22,8 +22,8 @@ const NewAezeedPassword = ({
<section className={styles.input}>
<input
type='password'
placeholder='Confirm Password'
type="password"
placeholder="Confirm Password"
className={`${styles.password} ${showAezeedPasswordConfirmationError && styles.error}`}
value={aezeedPasswordConfirmation}
onChange={event => updateAezeedPasswordConfirmation(event.target.value)}

8
app/components/Onboarding/NewWalletPassword.js

@ -12,8 +12,8 @@ const NewWalletPassword = ({
<div className={styles.container}>
<section className={styles.input}>
<input
type='password'
placeholder='Password'
type="password"
placeholder="Password"
className={styles.password}
value={createWalletPassword}
onChange={event => updateCreateWalletPassword(event.target.value)}
@ -22,8 +22,8 @@ const NewWalletPassword = ({
<section className={styles.input}>
<input
type='password'
placeholder='Confirm Password'
type="password"
placeholder="Confirm Password"
className={`${styles.password} ${showCreateWalletPasswordConfirmationError && styles.error}`}
value={createWalletPasswordConfirmation}
onChange={event => updateCreateWalletPasswordConfirmation(event.target.value)}

6
app/components/Onboarding/NewWalletSeed.js

@ -5,8 +5,7 @@ import styles from './NewWalletSeed.scss'
const NewWalletSeed = ({ seed }) => (
<div className={styles.container}>
<ul className={styles.seedContainer}>
{
seed.map((word, index) => (
{seed.map((word, index) => (
<li key={index}>
<section>
<label htmlFor={word}>{index + 1}</label>
@ -15,8 +14,7 @@ const NewWalletSeed = ({ seed }) => (
<span>{word}</span>
</section>
</li>
))
}
))}
</ul>
</div>
)

54
app/components/Onboarding/Onboarding.js

@ -51,12 +51,12 @@ const Onboarding = ({
case 0.1:
return (
<FormContainer
title='How do you want to connect to the Lightning Network?'
description='
title="How do you want to connect to the Lightning Network?"
description="
By default Zap will spin up a node for you and handle all the nerdy stuff
in the background. However you can also setup a custom node connection and
use Zap to control a remote node if you desire (for advanced users).
'
"
back={null}
next={() => changeStep(connectionType === 'local' ? 1 : 0.2)}
>
@ -67,8 +67,8 @@ const Onboarding = ({
case 0.2:
return (
<FormContainer
title='Connection details'
description='Enter the connection details for your Lightning node.'
title="Connection details"
description="Enter the connection details for your Lightning node."
back={() => changeStep(0.1)}
next={() =>
startLnd({
@ -86,8 +86,8 @@ const Onboarding = ({
case 1:
return (
<FormContainer
title='What should we call you?'
description='Set your nickname to help others connect with you on the Lightning Network'
title="What should we call you?"
description="Set your nickname to help others connect with you on the Lightning Network"
back={() => changeStep(0.1)}
next={() => changeStep(2)}
>
@ -97,8 +97,8 @@ const Onboarding = ({
case 2:
return (
<FormContainer
title='Autopilot'
description='Autopilot is an automatic network manager. Instead of manually adding people to build your network to make payments, enable autopilot to automatically connect you to the Lightning Network using 60% of your balance.' // eslint-disable-line
title="Autopilot"
description="Autopilot is an automatic network manager. Instead of manually adding people to build your network to make payments, enable autopilot to automatically connect you to the Lightning Network using 60% of your balance." // eslint-disable-line
back={() => changeStep(1)}
next={() => startLnd({ connectionType, alias, autopilot })}
>
@ -108,8 +108,8 @@ const Onboarding = ({
case 3:
return (
<FormContainer
title='Welcome back!'
description='Enter your wallet password or create a new wallet' // eslint-disable-line
title="Welcome back!"
description="Enter your wallet password or create a new wallet" // eslint-disable-line
back={null}
next={null}
>
@ -119,8 +119,8 @@ const Onboarding = ({
case 4:
return (
<FormContainer
title='Welcome!'
description='Looks like you are new here. Set a password to encrypt your wallet. This password will be needed to unlock Zap in the future' // eslint-disable-line
title="Welcome!"
description="Looks like you are new here. Set a password to encrypt your wallet. This password will be needed to unlock Zap in the future" // eslint-disable-line
back={null}
next={() => {
// dont allow the user to move on if the confirmation password doesnt match the original password
@ -137,8 +137,8 @@ const Onboarding = ({
case 5:
return (
<FormContainer
title={'Alright, let\'s get set up'}
description='Would you like to create a new wallet or import an existing one?' // eslint-disable-line
title={"Alright, let's get set up"}
description="Would you like to create a new wallet or import an existing one?" // eslint-disable-line
back={() => changeStep(4)}
next={() => (initWalletProps.signupProps.signupForm.create ? changeStep(6) : changeStep(5.1))}
>
@ -148,8 +148,8 @@ const Onboarding = ({
case 5.1:
return (
<FormContainer
title='Import your seed'
description={'Recovering a wallet, nice. You don\'t need anyone else, you got yourself :)'} // eslint-disable-line
title="Import your seed"
description={"Recovering a wallet, nice. You don't need anyone else, you got yourself :)"} // eslint-disable-line
back={() => changeStep(5)}
next={() => changeStep(5.2)}
>
@ -159,8 +159,8 @@ const Onboarding = ({
case 5.2:
return (
<FormContainer
title='Seed passphrase'
description={'Enter your cipherseed passphrase (or just submit if you don\'t have one)'} // eslint-disable-line
title="Seed passphrase"
description={"Enter your cipherseed passphrase (or just submit if you don't have one)"} // eslint-disable-line
back={() => changeStep(5)}
next={() => {
const recoverySeed = recoverFormProps.seedInput.map(input => input.word)
@ -174,8 +174,8 @@ const Onboarding = ({
case 6:
return (
<FormContainer
title='Save your wallet seed'
description='Please save these 24 words securely! This will allow you to recover your wallet in the future' // eslint-disable-line
title="Save your wallet seed"
description="Please save these 24 words securely! This will allow you to recover your wallet in the future" // eslint-disable-line
back={() => changeStep(5)}
next={() => changeStep(7)}
>
@ -185,8 +185,8 @@ const Onboarding = ({
case 7:
return (
<FormContainer
title='Re-enter your seed'
description='Yeah I know, might be annoying, but just to be safe!' // eslint-disable-line
title="Re-enter your seed"
description="Yeah I know, might be annoying, but just to be safe!" // eslint-disable-line
back={() => changeStep(6)}
next={() => {
// don't allow them to move on if they havent re-entered the seed correctly
@ -203,12 +203,14 @@ const Onboarding = ({
case 8:
return (
<FormContainer
title='Encrypt your seed'
description='Totally optional, but we encourage it. Set a password that will be used to encrypt your wallet seed' // eslint-disable-line
title="Encrypt your seed"
description="Totally optional, but we encourage it. Set a password that will be used to encrypt your wallet seed" // eslint-disable-line
back={() => changeStep(6)}
next={() => {
// dont allow the user to move on if the confirmation password doesnt match the original password
if (newAezeedPasswordProps.showAezeedPasswordConfirmationError) { return }
if (newAezeedPasswordProps.showAezeedPasswordConfirmationError) {
return
}
submitNewWallet(createWalletPassword, seed, aezeedPassword)
}}

11
app/components/Onboarding/ReEnterSeed.js

@ -5,30 +5,27 @@ import styles from './ReEnterSeed.scss'
const ReEnterSeed = ({ seed, seedInput, updateSeedInput }) => (
<div className={styles.container}>
<ul className={styles.seedContainer}>
{
seed.map((word, index) => (
{seed.map((word, index) => (
<li key={index}>
<section>
<label htmlFor={word}>{index + 1}</label>
</section>
<section>
<input
type='text'
type="text"
id={word}
placeholder='word'
placeholder="word"
value={seedInput[index] ? seedInput[index].word : ''}
onChange={event => updateSeedInput({ word: event.target.value, index })}
className={`${styles.word} ${seedInput[index] && word === seedInput[index].word ? styles.valid : styles.invalid}`}
/>
</section>
</li>
))
}
))}
</ul>
</div>
)
ReEnterSeed.propTypes = {
seed: PropTypes.array.isRequired,
seedInput: PropTypes.array.isRequired,

13
app/components/Onboarding/RecoverForm.js

@ -5,30 +5,29 @@ import styles from './RecoverForm.scss'
const RecoverForm = ({ seedInput, updateSeedInput }) => (
<div className={styles.container}>
<ul className={styles.seedContainer}>
{
Array(24).fill('').map((word, index) => (
{Array(24)
.fill('')
.map((word, index) => (
<li key={index}>
<section>
<label htmlFor={index}>{index + 1}</label>
</section>
<section>
<input
type='text'
type="text"
id={index}
placeholder='word'
placeholder="word"
value={seedInput[index] ? seedInput[index].word : ''}
onChange={event => updateSeedInput({ word: event.target.value, index })}
className={styles.word}
/>
</section>
</li>
))
}
))}
</ul>
</div>
)
RecoverForm.propTypes = {
seedInput: PropTypes.array.isRequired,
updateSeedInput: PropTypes.func.isRequired

22
app/components/Onboarding/Signup.js

@ -7,28 +7,14 @@ const Signup = ({ signupForm, setSignupCreate, setSignupImport }) => (
<div className={styles.container}>
<section className={`${styles.enable} ${signupForm.create && styles.active}`}>
<div onClick={setSignupCreate}>
{
signupForm.create ?
<FaCircle />
:
<FaCircleThin />
}
<span className={styles.label}>
Create new wallet
</span>
{signupForm.create ? <FaCircle /> : <FaCircleThin />}
<span className={styles.label}>Create new wallet</span>
</div>
</section>
<section className={`${styles.disable} ${signupForm.import && styles.active}`}>
<div onClick={setSignupImport}>
{
signupForm.import ?
<FaCircle />
:
<FaCircleThin />
}
<span className={styles.label}>
Import existing wallet
</span>
{signupForm.import ? <FaCircle /> : <FaCircleThin />}
<span className={styles.label}>Import existing wallet</span>
</div>
</section>
</div>

6
app/components/Onboarding/Syncing.js

@ -4,7 +4,6 @@ import Isvg from 'react-inlinesvg'
import zapLogo from 'icons/zap_logo.svg'
import styles from './Syncing.scss'
class Syncing extends Component {
componentWillMount() {
this.props.fetchBlockHeight()
@ -36,10 +35,7 @@ class Syncing extends Component {
Syncing.propTypes = {
fetchBlockHeight: PropTypes.func.isRequired,
syncPercentage: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string
]).isRequired
syncPercentage: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired
}
export default Syncing

13
app/components/Value/Value.js

@ -3,18 +3,15 @@ import PropTypes from 'prop-types'
import { btc } from 'utils'
const Value = ({ value, currency, currentTicker }) => {
if (currency === 'sats') { return <i>{value > 0 ? value : value * -1}</i> }
if (currency === 'sats') {
return <i>{value > 0 ? value : value * -1}</i>
}
return (
<i>{btc.convert('sats', currency, value, currentTicker.price_usd)}</i>
)
return <i>{btc.convert('sats', currency, value, currentTicker.price_usd)}</i>
}
Value.propTypes = {
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
currency: PropTypes.string.isRequired,
currentTicker: PropTypes.object.isRequired
}

36
app/components/Wallet/ReceiveModal.js

@ -20,7 +20,7 @@ class ReceiveModal extends React.Component {
}
render() {
const copyOnClick = (data) => {
const copyOnClick = data => {
copy(data)
showNotification('Noice', 'Successfully copied to clipboard')
}
@ -33,18 +33,13 @@ class ReceiveModal extends React.Component {
}
}
const {
isOpen,
pubkey,
address,
alias,
closeReceiveModal,
network
} = this.props
const { isOpen, pubkey, address, alias, closeReceiveModal, network } = this.props
const { qrCodeType } = this.state
if (!isOpen) { return null }
if (!isOpen) {
return null
}
return (
<div className={styles.container}>
@ -60,20 +55,17 @@ class ReceiveModal extends React.Component {
<h2>{alias && alias.length ? alias : pubkey.substring(0, 10)}</h2>
<div className={styles.qrCodeOptions}>
<div className={qrCodeType === 1 && styles.active} onClick={changeQrCode}>Node Pubkey</div>
<div className={qrCodeType === 2 && styles.active} onClick={changeQrCode}>Bitcoin Address</div>
<div className={qrCodeType === 1 && styles.active} onClick={changeQrCode}>
Node Pubkey
</div>
<div className={qrCodeType === 2 && styles.active} onClick={changeQrCode}>
Bitcoin Address
</div>
</div>
</header>
<div className={styles.qrCodeContainer}>
<QRCode
value={qrCodeType === 1 ? pubkey : address}
renderAs='svg'
size={150}
bgColor='transparent'
fgColor='white'
level='L'
/>
<QRCode value={qrCodeType === 1 ? pubkey : address} renderAs="svg" size={150} bgColor="transparent" fgColor="white" level="L" />
</div>
</section>
<section className={styles.right}>
@ -81,7 +73,7 @@ class ReceiveModal extends React.Component {
<h4>Node Public Key</h4>
<p>
<span className={styles.data}>{pubkey}</span>
<span onClick={() => copyOnClick(pubkey)} className={`${styles.copy} hint--left`} data-hint='Copy pubkey'>
<span onClick={() => copyOnClick(pubkey)} className={`${styles.copy} hint--left`} data-hint="Copy pubkey">
<Isvg src={copyIcon} />
</span>
</p>
@ -91,7 +83,7 @@ class ReceiveModal extends React.Component {
<h4>Bitcoin {network.name} Address</h4>
<p>
<span className={styles.data}>{address}</span>
<span onClick={() => copyOnClick(address)} className={`${styles.copy} hint--left`} data-hint='Copy address'>
<span onClick={() => copyOnClick(address)} className={`${styles.copy} hint--left`} data-hint="Copy address">
<Isvg src={copyIcon} />
</span>
</p>

44
app/components/Wallet/Wallet.js

@ -29,9 +29,9 @@ const Wallet = ({
setWalletCurrencyFilters,
network
}) => {
const usdAmount = btc.satoshisToUsd((parseInt(balance.walletBalance, 10) + parseInt(balance.channelBalance, 10)), currentTicker.price_usd)
const usdAmount = btc.satoshisToUsd(parseInt(balance.walletBalance, 10) + parseInt(balance.channelBalance, 10), currentTicker.price_usd)
const onCurrencyFilterClick = (currency) => {
const onCurrencyFilterClick = currency => {
setCurrency(currency)
setWalletCurrencyFilters(false)
}
@ -73,10 +73,11 @@ const Wallet = ({
</span>
<ul className={info.showWalletCurrencyFilters && styles.active}>
{
currentCurrencyFilters.map(filter =>
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>{filter.name}</li>)
}
{currentCurrencyFilters.map(filter => (
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>
{filter.name}
</li>
))}
</ul>
</section>
</span>
@ -87,28 +88,33 @@ const Wallet = ({
</div>
<div className={styles.right}>
<div className={styles.rightContent}>
<div className={styles.pay} onClick={openPayForm}>Pay</div>
<div className={styles.request} onClick={openRequestForm}>Request</div>
<div className={styles.pay} onClick={openPayForm}>
Pay
</div>
<div className={styles.request} onClick={openRequestForm}>
Request
</div>
</div>
<div className={styles.notificationBox}>
{
showPayLoadingScreen &&
{showPayLoadingScreen && (
<span>
<section className={`${styles.spinner} ${styles.icon}`} />
<section>Sending your transaction...</section>
</span>
}
{
showSuccessPayScreen &&
)}
{showSuccessPayScreen && (
<span>
<section className={styles.icon}><AnimatedCheckmark /></section>
<section className={styles.icon}>
<AnimatedCheckmark />
</section>
<section>Successfully sent payment</section>
</span>
}
{
successTransactionScreen.show &&
)}
{successTransactionScreen.show && (
<span>
<section className={styles.icon}><AnimatedCheckmark /></section>
<section className={styles.icon}>
<AnimatedCheckmark />
</section>
<section>
{
// TODO(jimmymow): remove this
@ -116,7 +122,7 @@ const Wallet = ({
}Successfully <span className={styles.txLink} onClick={() => blockExplorer.showTransaction(network, successTransactionScreen.txid)}>sent</span> transaction
</section>
</span>
}
)}
</div>
</div>
</div>

6
app/containers/Root.js

@ -219,4 +219,8 @@ Root.propTypes = {
syncingProps: PropTypes.object.isRequired
}
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(Root)
export default connect(
mapStateToProps,
mapDispatchToProps,
mergeProps
)(Root)

4
app/lnd/index.js

@ -7,7 +7,7 @@ import walletUnlockerMethods from './walletUnlockerMethods'
// use mainLog because lndLog is reserved for the lnd binary itself
import { mainLog } from '../utils/log'
const initLnd = (callback) => {
const initLnd = callback => {
const lndConfig = config.lnd()
const lnd = lightning(lndConfig.lightningRpc, lndConfig.lightningHost)
@ -17,7 +17,7 @@ const initLnd = (callback) => {
callback(lndSubscribe, lndMethods)
}
const initWalletUnlocker = (callback) => {
const initWalletUnlocker = callback => {
const lndConfig = config.lnd()
const walletUnlockerObj = walletUnlocker(lndConfig.lightningRpc, lndConfig.lightningHost)

9
app/lnd/lib/lightning.js

@ -10,12 +10,9 @@ import config from '../config'
//
// We order the suites by priority, based on the recommendations provided by SSL Labs here:
// https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices#23-use-secure-cipher-suites
process.env.GRPC_SSL_CIPHER_SUITES = process.env.GRPC_SSL_CIPHER_SUITES || [
'ECDHE-ECDSA-AES128-GCM-SHA256',
'ECDHE-ECDSA-AES256-GCM-SHA384',
'ECDHE-ECDSA-AES128-CBC-SHA256',
'ECDHE-ECDSA-CHACHA20-POLY1305'
].join(':')
process.env.GRPC_SSL_CIPHER_SUITES =
process.env.GRPC_SSL_CIPHER_SUITES ||
['ECDHE-ECDSA-AES128-GCM-SHA256', 'ECDHE-ECDSA-AES256-GCM-SHA384', 'ECDHE-ECDSA-AES128-CBC-SHA256', 'ECDHE-ECDSA-CHACHA20-POLY1305'].join(':')
const lightning = (rpcpath, host) => {
const lndConfig = config.lnd()

9
app/lnd/lib/walletUnlocker.js

@ -10,12 +10,9 @@ import config from '../config'
//
// We order the suites by priority, based on the recommendations provided by SSL Labs here:
// https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices#23-use-secure-cipher-suites
process.env.GRPC_SSL_CIPHER_SUITES = process.env.GRPC_SSL_CIPHER_SUITES || [
'ECDHE-ECDSA-AES128-GCM-SHA256',
'ECDHE-ECDSA-AES256-GCM-SHA384',
'ECDHE-ECDSA-AES128-CBC-SHA256',
'ECDHE-ECDSA-CHACHA20-POLY1305'
].join(':')
process.env.GRPC_SSL_CIPHER_SUITES =
process.env.GRPC_SSL_CIPHER_SUITES ||
['ECDHE-ECDSA-AES128-GCM-SHA256', 'ECDHE-ECDSA-AES256-GCM-SHA384', 'ECDHE-ECDSA-AES128-CBC-SHA256', 'ECDHE-ECDSA-CHACHA20-POLY1305'].join(':')
const walletUnlocker = (rpcpath, host) => {
const lndConfig = config.lnd()

16
app/lnd/methods/channelController.js

@ -34,7 +34,7 @@ export function connectAndOpen(lnd, event, payload) {
return call
})
.catch((err) => {
.catch(err => {
event.sender.send('pushchannelerror', { pubkey, error: err.toString() })
throw err
})
@ -58,7 +58,8 @@ export function openChannel(lnd, event, payload) {
return new Promise((resolve, reject) =>
pushopenchannel(lnd, event, res)
.then(data => resolve(data))
.catch(error => reject(error)))
.catch(error => reject(error))
)
}
/**
@ -103,8 +104,15 @@ export function listChannels(lnd) {
* @return {[type]} [description]
*/
export function closeChannel(lnd, event, payload) {
const { channel_point: { funding_txid, output_index }, chan_id, force } = payload
const tx = funding_txid.match(/.{2}/g).reverse().join('')
const {
channel_point: { funding_txid, output_index },
chan_id,
force
} = payload
const tx = funding_txid
.match(/.{2}/g)
.reverse()
.join('')
const res = {
channel_point: {

33
app/lnd/methods/index.js

@ -22,7 +22,7 @@ export default function (lnd, log, event, msg, data) {
case 'info':
networkController
.getInfo(lnd)
.then((infoData) => {
.then(infoData => {
event.sender.send('receiveInfo', infoData)
event.sender.send('receiveCryptocurrency', infoData.chains[0])
return infoData
@ -50,7 +50,8 @@ export default function (lnd, log, event, msg, data) {
networkController.queryRoutes(lnd, {
pubkey: invoiceData.destination,
amount: invoiceData.num_satoshis
}))
})
)
.then(routes => event.sender.send('receiveInvoiceAndQueryRoutes', routes))
.catch(error => log.error('getInvoiceAndQueryRoutes invoice:', error))
break
@ -113,7 +114,7 @@ export default function (lnd, log, event, msg, data) {
case 'balance':
// Balance looks like [ { balance: '129477456' }, { balance: '243914' } ]
Promise.all([walletController.walletBalance, channelController.channelBalance].map(func => func(lnd)))
.then((balance) => {
.then(balance => {
event.sender.send('receiveBalance', { walletBalance: balance[0].total_balance, channelBalance: balance[1].balance })
return balance
})
@ -137,12 +138,14 @@ export default function (lnd, log, event, msg, data) {
payment_request: newinvoice.payment_request,
creation_date: Date.now() / 1000
})
))
.catch((error) => {
)
)
.catch(error => {
log.error('decodedInvoice:', error)
event.sender.send('invoiceFailed', { error: error.toString() })
}))
.catch((error) => {
})
)
.catch(error => {
log.error('addInvoice:', error)
event.sender.send('invoiceFailed', { error: error.toString() })
})
@ -152,7 +155,7 @@ export default function (lnd, log, event, msg, data) {
// { paymentRequest } = data
paymentsController
.sendPaymentSync(lnd, data)
.then((payment) => {
.then(payment => {
log.info('payment:', payment)
const { payment_route } = payment
log.error('payinvoice success:', payment_route)
@ -170,7 +173,7 @@ export default function (lnd, log, event, msg, data) {
walletController
.sendCoins(lnd, data)
.then(({ txid }) => event.sender.send('transactionSuccessful', { amount: data.amount, addr: data.addr, txid }))
.catch((error) => {
.catch(error => {
log.error('error: ', error)
event.sender.send('transactionError', { error: error.toString() })
})
@ -180,7 +183,7 @@ export default function (lnd, log, event, msg, data) {
// { pubkey, localamt, pushamt } = data
channelController
.openChannel(lnd, event, data)
.then((channel) => {
.then(channel => {
log.error('CHANNEL: ', channel)
event.sender.send('channelSuccessful', { channel })
return channel
@ -192,7 +195,7 @@ export default function (lnd, log, event, msg, data) {
// { channel_point, force } = data
channelController
.closeChannel(lnd, event, data)
.then((result) => {
.then(result => {
log.error('CLOSE CHANNEL: ', result)
event.sender.send('closeChannelSuccessful')
return result
@ -204,13 +207,13 @@ export default function (lnd, log, event, msg, data) {
// { pubkey, host } = data
peersController
.connectPeer(lnd, data)
.then((peer) => {
.then(peer => {
const { peer_id } = peer
log.error('peer_id: ', peer_id)
event.sender.send('connectSuccess', { pub_key: data.pubkey, address: data.host, peer_id })
return peer
})
.catch((error) => {
.catch(error => {
event.sender.send('connectFailure', { error: error.toString() })
log.error('connectPeer:', error)
})
@ -232,12 +235,12 @@ export default function (lnd, log, event, msg, data) {
// {} = data
channelController
.connectAndOpen(lnd, event, data)
.then((channelData) => {
.then(channelData => {
log.error('connectAndOpen data: ', channelData)
// event.sender.send('connectSuccess', { pub_key: data.pubkey, address: data.host, peer_id })
return channelData
})
.catch((error) => {
.catch(error => {
// event.sender.send('connectFailure', { error: error.toString() })
log.error('connectAndOpen:', error)
})

18
app/lnd/methods/invoicesController.js

@ -10,7 +10,9 @@ import pushinvoices from '../push/subscribeinvoice'
export function addInvoice(lnd, { memo, value }) {
return new Promise((resolve, reject) => {
lnd.addInvoice({ memo, value }, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})
@ -25,7 +27,9 @@ export function addInvoice(lnd, { memo, value }) {
export function listInvoices(lnd) {
return new Promise((resolve, reject) => {
lnd.listInvoices({}, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})
@ -39,14 +43,15 @@ export function listInvoices(lnd) {
export function getInvoice(lnd, { pay_req }) {
return new Promise((resolve, reject) => {
lnd.decodePayReq({ pay_req }, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})
})
}
/**
* Attemps to look up an invoice according to its payment hash
* @param {[type]} lnd [description]
@ -56,14 +61,15 @@ export function getInvoice(lnd, { pay_req }) {
export function lookupInvoice(lnd, { rhash }) {
return new Promise((resolve, reject) => {
lnd.lookupInvoice({ r_hash: rhash }, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})
})
}
/**
* Returns a uni-directional stream (server -> client) for notifying the client of newly added/settled invoices
* @param {[type]} lnd [description]

24
app/lnd/methods/networkController.js

@ -6,14 +6,15 @@
export function getInfo(lnd) {
return new Promise((resolve, reject) => {
lnd.getInfo({}, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})
})
}
/**
* Returns general information concerning the lightning node
* @param {[type]} lnd [description]
@ -23,14 +24,15 @@ export function getInfo(lnd) {
export function getNodeInfo(lnd, { pubkey }) {
return new Promise((resolve, reject) => {
lnd.getNodeInfo({ pub_key: pubkey }, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})
})
}
/**
* Returns a description of the latest graph state from the point of view of the node
* @param {[type]} lnd [description]
@ -39,14 +41,15 @@ export function getNodeInfo(lnd, { pubkey }) {
export function describeGraph(lnd) {
return new Promise((resolve, reject) => {
lnd.describeGraph({}, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})
})
}
/**
* Attempts to query the daemons Channel Router for a possible route to a target destination capable of carrying a specific amount of satoshis
* @param {[type]} lnd [description]
@ -57,14 +60,15 @@ export function describeGraph(lnd) {
export function queryRoutes(lnd, { pubkey, amount }) {
return new Promise((resolve, reject) => {
lnd.queryRoutes({ pub_key: pubkey, amt: amount }, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})
})
}
/**
* Returns some basic stats about the known channel graph from the point of view of the node
* @param {[type]} lnd [description]
@ -73,7 +77,9 @@ export function queryRoutes(lnd, { pubkey, amount }) {
export function getNetworkInfo(lnd) {
return new Promise((resolve, reject) => {
lnd.getNetworkInfo({}, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})

16
app/lnd/methods/paymentsController.js

@ -27,7 +27,9 @@ export function sendPaymentSync(lnd, { paymentRequest }) {
export function sendPayment(lnd, { paymentRequest }) {
return new Promise((resolve, reject) => {
lnd.sendPayment({ payment_request: paymentRequest }, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})
@ -43,7 +45,9 @@ export function sendPayment(lnd, { paymentRequest }) {
export function decodePayReq(lnd, { payReq }) {
return new Promise((resolve, reject) => {
lnd.decodePayReq({ pay_req: payReq }, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})
@ -58,7 +62,9 @@ export function decodePayReq(lnd, { payReq }) {
export function listPayments(lnd) {
return new Promise((resolve, reject) => {
lnd.listPayments({}, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})
@ -73,7 +79,9 @@ export function listPayments(lnd) {
export function deleteAllPayments(lnd) {
return new Promise((resolve, reject) => {
lnd.deleteAllPayments({}, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})

14
app/lnd/methods/peersController.js

@ -8,14 +8,15 @@
export function connectPeer(lnd, { pubkey, host }) {
return new Promise((resolve, reject) => {
lnd.connectPeer({ addr: { pubkey, host } }, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})
})
}
/**
* Attempts to disconnect one peer from another
* @param {[type]} lnd [description]
@ -25,14 +26,15 @@ export function connectPeer(lnd, { pubkey, host }) {
export function disconnectPeer(lnd, { pubkey }) {
return new Promise((resolve, reject) => {
lnd.disconnectPeer({ pub_key: pubkey }, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})
})
}
/**
* Returns a verbose listing of all currently active peers
* @param {[type]} lnd [description]
@ -41,7 +43,9 @@ export function disconnectPeer(lnd, { pubkey }) {
export function listPeers(lnd) {
return new Promise((resolve, reject) => {
lnd.listPeers({}, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})

48
app/lnd/methods/walletController.js

@ -6,14 +6,15 @@
export function walletBalance(lnd) {
return new Promise((resolve, reject) => {
lnd.walletBalance({}, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})
})
}
/**
* Creates a new address under control of the local wallet
* @param {[type]} lnd [description]
@ -23,7 +24,9 @@ export function walletBalance(lnd) {
export function newAddress(lnd, type) {
return new Promise((resolve, reject) => {
lnd.newAddress({ type }, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})
@ -38,14 +41,15 @@ export function newAddress(lnd, type) {
export function newWitnessAddress(lnd, { addr }) {
return new Promise((resolve, reject) => {
lnd.newWitnessAddress({ address: addr }, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})
})
}
/**
* Returns a list describing all the known transactions relevant to the wallet
* @param {[type]} lnd [description]
@ -54,14 +58,15 @@ export function newWitnessAddress(lnd, { addr }) {
export function getTransactions(lnd) {
return new Promise((resolve, reject) => {
lnd.getTransactions({}, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})
})
}
/**
* Executes a request to send coins to a particular address
* @param {[type]} lnd [description]
@ -72,7 +77,9 @@ export function getTransactions(lnd) {
export function sendCoins(lnd, { addr, amount }) {
return new Promise((resolve, reject) => {
lnd.sendCoins({ addr, amount }, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})
@ -86,7 +93,9 @@ export function sendCoins(lnd, { addr, amount }) {
export function setAlias(lnd, { new_alias }) {
return new Promise((resolve, reject) => {
lnd.setAlias({ new_alias }, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})
@ -99,7 +108,9 @@ export function setAlias(lnd, { new_alias }) {
export function genSeed(walletUnlocker) {
return new Promise((resolve, reject) => {
walletUnlocker.genSeed({}, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})
@ -113,7 +124,9 @@ export function genSeed(walletUnlocker) {
export function unlockWallet(walletUnlocker, { wallet_password }) {
return new Promise((resolve, reject) => {
walletUnlocker.unlockWallet({ wallet_password: Buffer.from(wallet_password) }, (err, data) => {
if (err) { reject(err) }
if (err) {
reject(err)
}
resolve(data)
})
@ -127,15 +140,20 @@ export function unlockWallet(walletUnlocker, { wallet_password }) {
*/
export function initWallet(walletUnlocker, { wallet_password, cipher_seed_mnemonic, aezeed_passphrase }) {
return new Promise((resolve, reject) => {
walletUnlocker.initWallet({
walletUnlocker.initWallet(
{
wallet_password: Buffer.from(wallet_password),
cipher_seed_mnemonic,
aezeed_passphrase: Buffer.from(aezeed_passphrase, 'hex'),
recovery_window: 250
}, (err, data) => {
if (err) { reject(err) }
},
(err, data) => {
if (err) {
reject(err)
}
resolve(data)
})
}
)
})
}

2
app/lnd/subscribe/transactions.js

@ -1,6 +1,6 @@
export default function subscribeToTransactions(mainWindow, lnd, log) {
const call = lnd.subscribeTransactions({})
call.on('data', (transaction) => {
call.on('data', transaction => {
lnd.log.info('TRANSACTION:', transaction)
mainWindow.send('newTransaction', { transaction })
})

9
app/lnd/walletUnlockerMethods/index.js

@ -3,17 +3,20 @@ import * as walletController from '../methods/walletController'
export default function(walletUnlocker, log, event, msg, data) {
switch (msg) {
case 'genSeed':
walletController.genSeed(walletUnlocker)
walletController
.genSeed(walletUnlocker)
.then(genSeedData => event.sender.send('receiveSeed', genSeedData))
.catch(error => event.sender.send('receiveSeedError', error))
break
case 'unlockWallet':
walletController.unlockWallet(walletUnlocker, data)
walletController
.unlockWallet(walletUnlocker, data)
.then(() => event.sender.send('walletUnlocked'))
.catch(() => event.sender.send('unlockWalletError'))
break
case 'initWallet':
walletController.initWallet(walletUnlocker, data)
walletController
.initWallet(walletUnlocker, data)
.then(() => event.sender.send('successfullyCreatedWallet'))
.catch(error => log.error('initWallet:', error))
break

10
app/main.dev.js

@ -120,7 +120,7 @@ const startGrpc = () => {
// Create and subscribe the grpc object
const startWalletUnlocker = () => {
lnd.initWalletUnlocker((walletUnlockerMethods) => {
lnd.initWalletUnlocker(walletUnlockerMethods => {
// Listen for all gRPC restful methods
ipcMain.on('walletUnlocker', (event, { msg, data }) => {
walletUnlockerMethods(event, msg, data)
@ -166,27 +166,27 @@ const startLnd = (alias, autopilot) => {
]
const neutrino = spawn(lndConfig.lndPath, neutrinoArgs)
.on('error', (error) => {
.on('error', error => {
lndLog.error(`lnd error: ${error}`)
dialog.showMessageBox({
type: 'error',
message: `lnd error: ${error}`
})
})
.on('close', (code) => {
.on('close', code => {
lndLog.info(`lnd shutting down ${code}`)
app.quit()
})
// Listen for when neutrino prints odata to stderr.
neutrino.stderr.pipe(split2()).on('data', (line) => {
neutrino.stderr.pipe(split2()).on('data', line => {
if (process.env.NODE_ENV === 'development') {
lndLog[lndLogGetLevel(line)](line)
}
})
// Listen for when neutrino prints data to stdout.
neutrino.stdout.pipe(split2()).on('data', (line) => {
neutrino.stdout.pipe(split2()).on('data', line => {
if (process.env.NODE_ENV === 'development') {
lndLog[lndLogGetLevel(line)](line)
}

136
app/menu.js

@ -2,7 +2,7 @@
import { app, Menu, shell, BrowserWindow } from 'electron'
export default class MenuBuilder {
mainWindow: BrowserWindow;
mainWindow: BrowserWindow
constructor(mainWindow: BrowserWindow) {
this.mainWindow = mainWindow
@ -26,16 +26,11 @@ export default class MenuBuilder {
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
return menu
}
setupInputTemplate() {
const selectionMenu = Menu.buildFromTemplate([
{ role: 'copy' },
{ type: 'separator' },
{ role: 'selectall' }
])
const selectionMenu = Menu.buildFromTemplate([{ role: 'copy' }, { type: 'separator' }, { role: 'selectall' }])
const inputMenu = Menu.buildFromTemplate([
{ role: 'undo' },
@ -73,7 +68,13 @@ export default class MenuBuilder {
{ label: 'Hide Others', accelerator: 'Command+Shift+H', selector: 'hideOtherApplications:' },
{ label: 'Show All', selector: 'unhideAllApplications:' },
{ type: 'separator' },
{ label: 'Quit', accelerator: 'Command+Q', click: () => { app.quit() } }
{
label: 'Quit',
accelerator: 'Command+Q',
click: () => {
app.quit()
}
}
]
}
const subMenuEdit = {
@ -91,13 +92,27 @@ export default class MenuBuilder {
const subMenuViewDev = {
label: 'View',
submenu: [
{ label: 'Reload', accelerator: 'Command+R', click: () => { this.mainWindow.webContents.reload() } },
{
label: 'Reload',
accelerator: 'Command+R',
click: () => {
this.mainWindow.webContents.reload()
}
},
{
label: 'Toggle Full Screen',
accelerator: 'Ctrl+Command+F',
click: () => { this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()) }
click: () => {
this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen())
}
},
{ label: 'Toggle Developer Tools', accelerator: 'Alt+Command+I', click: () => { this.mainWindow.toggleDevTools() } }
{
label: 'Toggle Developer Tools',
accelerator: 'Alt+Command+I',
click: () => {
this.mainWindow.toggleDevTools()
}
}
]
}
const subMenuViewProd = {
@ -106,7 +121,9 @@ export default class MenuBuilder {
{
label: 'Toggle Full Screen',
accelerator: 'Ctrl+Command+F',
click: () => { this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()) }
click: () => {
this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen())
}
}
]
}
@ -122,90 +139,123 @@ export default class MenuBuilder {
const subMenuHelp = {
label: 'Help',
submenu: [
{ label: 'Learn More', click() { shell.openExternal('https://zap.jackmallers.com/') } },
{ label: 'Documentation', click() { shell.openExternal('https://github.com/LN-Zap/zap-desktop') } },
{ label: 'Community Discussions', click() { shell.openExternal('zaphq.slack.com') } },
{ label: 'Search Issues', click() { shell.openExternal('https://github.com/LN-Zap/zap-desktop/issues') } }
{
label: 'Learn More',
click() {
shell.openExternal('https://zap.jackmallers.com/')
}
},
{
label: 'Documentation',
click() {
shell.openExternal('https://github.com/LN-Zap/zap-desktop')
}
},
{
label: 'Community Discussions',
click() {
shell.openExternal('zaphq.slack.com')
}
},
{
label: 'Search Issues',
click() {
shell.openExternal('https://github.com/LN-Zap/zap-desktop/issues')
}
}
]
}
const subMenuView = process.env.NODE_ENV === 'development'
? subMenuViewDev
: subMenuViewProd
const subMenuView = process.env.NODE_ENV === 'development' ? subMenuViewDev : subMenuViewProd
return [
subMenuAbout,
subMenuEdit,
subMenuView,
subMenuWindow,
subMenuHelp
]
return [subMenuAbout, subMenuEdit, subMenuView, subMenuWindow, subMenuHelp]
}
buildDefaultTemplate() {
const templateDefault = [{
const templateDefault = [
{
label: '&File',
submenu: [{
submenu: [
{
label: '&Open',
accelerator: 'Ctrl+O'
}, {
},
{
label: '&Close',
accelerator: 'Ctrl+W',
click: () => {
this.mainWindow.close()
}
}]
}, {
}
]
},
{
label: '&View',
submenu: (process.env.NODE_ENV === 'development') ? [{
submenu:
process.env.NODE_ENV === 'development'
? [
{
label: '&Reload',
accelerator: 'Ctrl+R',
click: () => {
this.mainWindow.webContents.reload()
}
}, {
},
{
label: 'Toggle &Full Screen',
accelerator: 'F11',
click: () => {
this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen())
}
}, {
},
{
label: 'Toggle &Developer Tools',
accelerator: 'Alt+Ctrl+I',
click: () => {
this.mainWindow.toggleDevTools()
}
}] : [{
}
]
: [
{
label: 'Toggle &Full Screen',
accelerator: 'F11',
click: () => {
this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen())
}
}]
}, {
}
]
},
{
label: 'Help',
submenu: [{
submenu: [
{
label: 'Learn More',
click() {
shell.openExternal('https://zap.jackmallers.com/')
}
}, {
},
{
label: 'Documentation',
click() {
shell.openExternal('https://github.com/LN-Zap/zap-desktop')
}
}, {
},
{
label: 'Community Discussions',
click() {
shell.openExternal('zaphq.slack.com')
}
}, {
},
{
label: 'Search Issues',
click() {
shell.openExternal('https://github.com/LN-Zap/zap-desktop/issues')
}
}]
}]
}
]
}
]
return templateDefault
}

65
app/reducers/activity.js

@ -96,9 +96,10 @@ const ACTION_HANDLERS = {
[CHANGE_FILTER]: (state, { filter }) => ({ ...state, filter, filterPulldown: false }),
[TOGGLE_PULLDOWN]: state => ({ ...state, filterPulldown: !state.filterPulldown }),
[SET_ACTIVITY_MODAL_CURRENCY_FILTERS]: (state, { showCurrencyFilters }) => (
{ ...state, modal: { modalType: state.modal.modalType, modalProps: state.modal.modalProps, showCurrencyFilters } }
),
[SET_ACTIVITY_MODAL_CURRENCY_FILTERS]: (state, { showCurrencyFilters }) => ({
...state,
modal: { modalType: state.modal.modalType, modalProps: state.modal.modalProps, showCurrencyFilters }
}),
[UPDATE_SEARCH_ACTIVE]: (state, { searchActive }) => ({ ...state, searchActive }),
[UPDATE_SEARCH_TEXT]: (state, { searchText }) => ({ ...state, searchText })
@ -115,17 +116,21 @@ const paymentsSelector = state => state.payment.payments
const invoicesSelector = state => state.invoice.invoices
const transactionsSelector = state => state.transaction.transactions
const invoiceExpired = (invoice) => {
const expiresAt = (parseInt(invoice.creation_date, 10) + parseInt(invoice.expiry, 10))
return expiresAt < (Date.now() / 1000)
const invoiceExpired = invoice => {
const expiresAt = parseInt(invoice.creation_date, 10) + parseInt(invoice.expiry, 10)
return expiresAt < Date.now() / 1000
}
// helper function that returns invoice, payment or transaction timestamp
function returnTimestamp(transaction) {
// if on-chain txn
if (Object.prototype.hasOwnProperty.call(transaction, 'time_stamp')) { return transaction.time_stamp }
if (Object.prototype.hasOwnProperty.call(transaction, 'time_stamp')) {
return transaction.time_stamp
}
// if invoice that has been paid
if (transaction.settled) { return transaction.settle_date }
if (transaction.settled) {
return transaction.settle_date
}
// if invoice that has not been paid or an LN payment
return transaction.creation_date
}
@ -141,7 +146,9 @@ function groupData(data) {
const date = d.getDate()
const title = `${months[d.getMonth()]} ${date}, ${d.getFullYear()}`
if (!arr[title]) { arr[title] = [] }
if (!arr[title]) {
arr[title] = []
}
arr[title].push({ el })
@ -175,36 +182,34 @@ const allActivity = createSelector(
invoicesSelector,
transactionsSelector,
(searchText, payments, invoices, transactions) => {
const searchedArr = [...payments, ...invoices, ...transactions].filter((tx) => {
if ((tx.tx_hash && tx.tx_hash.includes(searchText)) ||
const searchedArr = [...payments, ...invoices, ...transactions].filter(tx => {
if (
(tx.tx_hash && tx.tx_hash.includes(searchText)) ||
(tx.payment_hash && tx.payment_hash.includes(searchText)) ||
(tx.payment_request && tx.payment_request.includes(searchText))) {
(tx.payment_request && tx.payment_request.includes(searchText))
) {
return true
}
return false
})
if (!searchedArr.length) { return [] }
if (!searchedArr.length) {
return []
}
return groupAll(searchedArr)
}
)
const invoiceActivity = createSelector(
invoicesSelector,
invoices => groupAll(invoices)
)
const invoiceActivity = createSelector(invoicesSelector, invoices => groupAll(invoices))
const sentActivity = createSelector(
transactionsSelector,
paymentsSelector,
(transactions, payments) => groupAll([...transactions.filter(transaction => transaction.amount < 0), ...payments])
const sentActivity = createSelector(transactionsSelector, paymentsSelector, (transactions, payments) =>
groupAll([...transactions.filter(transaction => transaction.amount < 0), ...payments])
)
const pendingActivity = createSelector(
invoicesSelector,
invoices => groupAll(invoices.filter(invoice => !invoice.settled && !invoiceExpired(invoice)))
const pendingActivity = createSelector(invoicesSelector, invoices =>
groupAll(invoices.filter(invoice => !invoice.settled && !invoiceExpired(invoice)))
)
const FILTERS = {
@ -214,20 +219,12 @@ const FILTERS = {
PENDING_ACTIVITY: pendingActivity
}
activitySelectors.currentActivity = createSelector(
filterSelector,
filter => FILTERS[filter.key]
)
activitySelectors.currentActivity = createSelector(filterSelector, filter => FILTERS[filter.key])
activitySelectors.nonActiveFilters = createSelector(
filtersSelector,
filterSelector,
(filters, filter) => filters.filter(f => f.key !== filter.key)
)
activitySelectors.nonActiveFilters = createSelector(filtersSelector, filterSelector, (filters, filter) => filters.filter(f => f.key !== filter.key))
export { activitySelectors }
// ------------------------------------
// Reducer
// ------------------------------------

2
app/reducers/address.js

@ -37,7 +37,7 @@ export function closeWalletModal() {
}
// Send IPC event for getinfo
export const newAddress = type => async (dispatch) => {
export const newAddress = type => async dispatch => {
dispatch(getAddress())
ipcRenderer.send('lnd', { msg: 'newaddress', data: { type: addressTypes[type] } })
}

15
app/reducers/balance.js

@ -15,13 +15,13 @@ export function getBalance() {
}
// Send IPC event for balance
export const fetchBalance = () => async (dispatch) => {
export const fetchBalance = () => async dispatch => {
dispatch(getBalance())
ipcRenderer.send('lnd', { msg: 'balance' })
}
// Receive IPC event for balance
export const receiveBalance = (event, { walletBalance, channelBalance }) => (dispatch) => {
export const receiveBalance = (event, { walletBalance, channelBalance }) => dispatch => {
dispatch({ type: RECEIVE_BALANCE, walletBalance, channelBalance })
}
@ -30,11 +30,12 @@ export const receiveBalance = (event, { walletBalance, channelBalance }) => (dis
// ------------------------------------
const ACTION_HANDLERS = {
[GET_BALANCE]: state => ({ ...state, balanceLoading: true }),
[RECEIVE_BALANCE]: (state, { walletBalance, channelBalance }) => (
{
...state, balanceLoading: false, walletBalance, channelBalance
}
)
[RECEIVE_BALANCE]: (state, { walletBalance, channelBalance }) => ({
...state,
balanceLoading: false,
walletBalance,
channelBalance
})
}
// ------------------------------------

133
app/reducers/channels.js

@ -54,7 +54,6 @@ export function setChannelForm(form) {
}
}
export function setChannel(channel) {
return {
type: SET_CHANNEL,
@ -167,7 +166,7 @@ export function receiveSuggestedNodes(suggestedNodes) {
}
}
export const fetchSuggestedNodes = () => async (dispatch) => {
export const fetchSuggestedNodes = () => async dispatch => {
dispatch(getSuggestedNodes())
const suggestedNodes = await requestSuggestedNodes()
@ -175,7 +174,7 @@ export const fetchSuggestedNodes = () => async (dispatch) => {
}
// Send IPC event for peers
export const fetchChannels = () => async (dispatch) => {
export const fetchChannels = () => async dispatch => {
dispatch(getChannels())
ipcRenderer.send('lnd', { msg: 'channels' })
}
@ -184,9 +183,7 @@ export const fetchChannels = () => async (dispatch) => {
export const receiveChannels = (event, { channels, pendingChannels }) => dispatch => dispatch({ type: RECEIVE_CHANNELS, channels, pendingChannels })
// Send IPC event for opening a channel
export const openChannel = ({
pubkey, host, local_amt
}) => (dispatch) => {
export const openChannel = ({ pubkey, host, local_amt }) => dispatch => {
const localamt = btc.btcToSatoshis(local_amt)
dispatch(openingChannel())
@ -197,42 +194,42 @@ export const openChannel = ({
// TODO: Decide how to handle streamed updates for channels
// Receive IPC event for openChannel
export const channelSuccessful = () => (dispatch) => {
export const channelSuccessful = () => dispatch => {
dispatch(fetchChannels())
}
// Receive IPC event for updated channel
export const pushchannelupdated = (event, { pubkey }) => (dispatch) => {
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 no-unused-vars
// eslint-disable-next-line no-unused-vars
export const pushchannelend = event => dispatch => {
dispatch(fetchChannels())
}
// Receive IPC event for channel error
export const pushchannelerror = (event, { pubkey, error }) => (dispatch) => {
export const pushchannelerror = (event, { pubkey, error }) => dispatch => {
dispatch(openingFailure())
dispatch(setError(error))
dispatch(removeLoadingPubkey(pubkey))
}
// Receive IPC event for channel status
export const pushchannelstatus = (event, data) => (dispatch) => { // eslint-disable-line no-unused-vars
// eslint-disable-next-line no-unused-vars
export const pushchannelstatus = (event, data) => dispatch => {
dispatch(fetchChannels())
}
// Send IPC event for opening a channel
export const closeChannel = ({ channel_point, chan_id, force }) => (dispatch) => {
export const closeChannel = ({ channel_point, chan_id, force }) => dispatch => {
dispatch(closingChannel())
dispatch(addClosingChanId(chan_id))
const [funding_txid, output_index] = channel_point.split(':')
ipcRenderer.send(
'lnd',
{
ipcRenderer.send('lnd', {
msg: 'closeChannel',
data: {
channel_point: {
@ -241,43 +238,44 @@ export const closeChannel = ({ channel_point, chan_id, force }) => (dispatch) =>
},
force
}
}
)
})
}
// TODO: Decide how to handle streamed updates for closing channels
// Receive IPC event for closeChannel
export const closeChannelSuccessful = () => (dispatch) => {
export const closeChannelSuccessful = () => dispatch => {
dispatch(fetchChannels())
}
// Receive IPC event for updated closing channel
export const pushclosechannelupdated = (event, { chan_id }) => (dispatch) => {
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 = () => (dispatch) => {
export const pushclosechannelend = () => dispatch => {
dispatch(fetchChannels())
}
// Receive IPC event for closing channel error
export const pushclosechannelerror = (event, { error, chan_id }) => (dispatch) => {
export const pushclosechannelerror = (event, { error, chan_id }) => dispatch => {
dispatch(setError(error))
dispatch(removeClosingChanId(chan_id))
}
// Receive IPC event for closing channel status
export const pushclosechannelstatus = () => (dispatch) => {
export const pushclosechannelstatus = () => dispatch => {
dispatch(fetchChannels())
}
// IPC event for channel graph data
export const channelGraphData = (event, data) => (dispatch, getState) => {
const { info } = getState()
const { channelGraphData: { channel_updates } } = data
const {
channelGraphData: { channel_updates }
} = data
// if there are any new channel updates
if (channel_updates.length) {
@ -328,18 +326,17 @@ export function changeFilter(channelFilter) {
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
[SET_CHANNEL_FORM]: (state, { form }) => (
{ ...state, channelForm: Object.assign({}, state.channelForm, form) }
),
[SET_CHANNEL_FORM]: (state, { form }) => ({ ...state, channelForm: Object.assign({}, state.channelForm, form) }),
[SET_CHANNEL]: (state, { channel }) => ({ ...state, channel }),
[GET_CHANNELS]: state => ({ ...state, channelsLoading: true }),
[RECEIVE_CHANNELS]: (state, { channels, pendingChannels }) => (
{
...state, channelsLoading: false, channels, pendingChannels
}
),
[RECEIVE_CHANNELS]: (state, { channels, pendingChannels }) => ({
...state,
channelsLoading: false,
channels,
pendingChannels
}),
[OPENING_CHANNEL]: state => ({ ...state, openingChannel: true }),
[OPENING_FAILURE]: state => ({ ...state, openingChannel: false }),
@ -351,19 +348,19 @@ const ACTION_HANDLERS = {
[SET_VIEW_TYPE]: (state, { viewType }) => ({ ...state, viewType }),
[TOGGLE_CHANNEL_PULLDOWN]: state => ({ ...state, filterPulldown: !state.filterPulldown }),
[CHANGE_CHANNEL_FILTER]: (state, { channelFilter }) => (
{ ...state, filterPulldown: false, filter: channelFilter }
),
[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 } }),
@ -397,39 +394,24 @@ const channelMatchesQuery = (channel, nodes, searchQuery) => {
return remoteNodePub.includes(query) || remotePubkey.includes(query) || displayName.includes(query)
}
channelsSelectors.channelModalOpen = createSelector(
channelSelector,
channel => (!!channel)
)
channelsSelectors.channelModalOpen = createSelector(channelSelector, channel => !!channel)
channelsSelectors.activeChannels = createSelector(
channelsSelector,
openChannels => openChannels.filter(channel => channel.active)
)
channelsSelectors.activeChannels = createSelector(channelsSelector, openChannels => openChannels.filter(channel => channel.active))
channelsSelectors.activeChannelPubkeys = createSelector(
channelsSelector,
openChannels => openChannels.filter(channel => channel.active).map(c => c.remote_pubkey)
channelsSelectors.activeChannelPubkeys = createSelector(channelsSelector, openChannels =>
openChannels.filter(channel => channel.active).map(c => c.remote_pubkey)
)
channelsSelectors.nonActiveChannels = createSelector(
channelsSelector,
openChannels => openChannels.filter(channel => !channel.active)
)
channelsSelectors.nonActiveChannels = createSelector(channelsSelector, openChannels => openChannels.filter(channel => !channel.active))
channelsSelectors.nonActiveChannelPubkeys = createSelector(
channelsSelector,
openChannels => openChannels.filter(channel => !channel.active).map(c => c.remote_pubkey)
channelsSelectors.nonActiveChannelPubkeys = createSelector(channelsSelector, openChannels =>
openChannels.filter(channel => !channel.active).map(c => c.remote_pubkey)
)
channelsSelectors.pendingOpenChannels = createSelector(
pendingOpenChannelsSelector,
pendingOpenChannels => pendingOpenChannels
)
channelsSelectors.pendingOpenChannels = createSelector(pendingOpenChannelsSelector, pendingOpenChannels => pendingOpenChannels)
channelsSelectors.pendingOpenChannelPubkeys = createSelector(
pendingOpenChannelsSelector,
pendingOpenChannels => pendingOpenChannels.map(pendingChannel => pendingChannel.channel.remote_node_pub)
channelsSelectors.pendingOpenChannelPubkeys = createSelector(pendingOpenChannelsSelector, pendingOpenChannels =>
pendingOpenChannels.map(pendingChannel => pendingChannel.channel.remote_node_pub)
)
channelsSelectors.closingPendingChannels = createSelector(
@ -438,26 +420,17 @@ channelsSelectors.closingPendingChannels = createSelector(
(pendingClosedChannels, pendingForcedClosedChannels) => [...pendingClosedChannels, ...pendingForcedClosedChannels]
)
channelsSelectors.activeChanIds = createSelector(
channelsSelector,
channels => channels.map(channel => channel.chan_id)
)
channelsSelectors.activeChanIds = createSelector(channelsSelector, channels => channels.map(channel => channel.chan_id))
channelsSelectors.nonActiveFilters = createSelector(
filtersSelector,
filterSelector,
(filters, channelFilter) => filters.filter(f => f.key !== channelFilter.key)
channelsSelectors.nonActiveFilters = createSelector(filtersSelector, filterSelector, (filters, channelFilter) =>
filters.filter(f => f.key !== channelFilter.key)
)
channelsSelectors.channelNodes = createSelector(
channelsSelector,
nodesSelector,
(channels, nodes) => {
channelsSelectors.channelNodes = createSelector(channelsSelector, nodesSelector, (channels, nodes) => {
const chanPubkeys = channels.map(channel => channel.remote_pubkey)
return filter(nodes, node => chanPubkeys.includes(node.pub_key))
}
)
})
const allChannels = createSelector(
channelsSelectors.activeChannels,
@ -523,7 +496,7 @@ export const currentChannels = createSelector(
nodes
) => {
// Helper function to deliver correct channel array based on filter
const filteredArray = (filterKey) => {
const filteredArray = filterKey => {
switch (filterKey) {
case 'ALL_CHANNELS':
return allChannelsArr

36
app/reducers/contactsform.js

@ -195,9 +195,7 @@ 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
)
const contactable = node => node.addresses.length > 0
// comparator to sort the contacts list with contactable contacts first
const contactableFirst = (a, b) => {
@ -209,16 +207,15 @@ const contactableFirst = (a, b) => {
return 0
}
contactFormSelectors.filteredNetworkNodes = createSelector(
networkNodesSelector,
searchQuerySelector,
(nodes, searchQuery) => {
contactFormSelectors.filteredNetworkNodes = createSelector(networkNodesSelector, searchQuerySelector, (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) }
// return an empty array if there is no search query
if (!searchQuery.length) { return [] }
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
@ -230,26 +227,27 @@ contactFormSelectors.filteredNetworkNodes = createSelector(
// if we don't limit the nodes returned then we take a huge performance hit
// rendering thousands of nodes potentially, so we just render 20 for the time being
return list.slice(0, 20)
}
)
})
contactFormSelectors.showManualForm = createSelector(
searchQuerySelector,
contactFormSelectors.filteredNetworkNodes,
(searchQuery, filteredNetworkNodes) => {
if (!searchQuery.length) { return false }
if (!searchQuery.length) {
return false
}
const connectableNodes = filteredNetworkNodes.filter(node => node.addresses.length > 0)
if (!filteredNetworkNodes.length || !connectableNodes.length) { return true }
if (!filteredNetworkNodes.length || !connectableNodes.length) {
return true
}
return false
}
)
contactFormSelectors.manualFormIsValid = createSelector(
manualSearchQuerySelector,
(input) => {
contactFormSelectors.manualFormIsValid = createSelector(manualSearchQuerySelector, input => {
const errors = {}
if (!input.length || !input.includes('@')) {
errors.manualInput = 'Invalid format'
@ -258,21 +256,21 @@ contactFormSelectors.manualFormIsValid = createSelector(
errors,
isValid: isEmpty(errors)
}
}
)
})
contactFormSelectors.contactFormUsdAmount = createSelector(
contactCapacitySelector,
currencySelector,
tickerSelectors.currentTicker,
(amount, currency, ticker) => {
if (!ticker || !ticker.price_usd) { return false }
if (!ticker || !ticker.price_usd) {
return false
}
return btc.convert(currency, 'usd', amount, ticker.price_usd)
}
)
export { contactFormSelectors }
// ------------------------------------

2
app/reducers/error.js

@ -32,7 +32,7 @@ export function clearError() {
// ------------------------------------
const ACTION_HANDLERS = {
[SET_ERROR]: (state, { error }) => ({ ...state, error }),
[CLEAR_ERROR]: () => (initialState)
[CLEAR_ERROR]: () => initialState
}
// ------------------------------------

6
app/reducers/info.js

@ -26,13 +26,13 @@ export function setWalletCurrencyFilters(showWalletCurrencyFilters) {
}
// Send IPC event for getinfo
export const fetchInfo = () => async (dispatch) => {
export const fetchInfo = () => async dispatch => {
dispatch(getInfo())
ipcRenderer.send('lnd', { msg: 'info' })
}
// Receive IPC event for info
export const receiveInfo = (event, data) => (dispatch) => {
export const receiveInfo = (event, data) => dispatch => {
dispatch({ type: RECEIVE_INFO, data })
}
@ -61,7 +61,7 @@ const ACTION_HANDLERS = {
[RECEIVE_INFO]: (state, { data }) => ({
...state,
infoLoading: false,
network: (data.testnet ? networks.testnet : networks.mainnet),
network: data.testnet ? networks.testnet : networks.mainnet,
data
}),
[SET_WALLET_CURRENCY_FILTERS]: (state, { showWalletCurrencyFilters }) => ({ ...state, showWalletCurrencyFilters })

43
app/reducers/invoice.js

@ -75,19 +75,19 @@ export function sendInvoice() {
}
// Send IPC event for a specific invoice
export const fetchInvoice = payreq => (dispatch) => {
export const fetchInvoice = payreq => dispatch => {
dispatch(getInvoice())
ipcRenderer.send('lnd', { msg: 'invoice', data: { payreq } })
}
// Receive IPC event for form invoice
export const receiveFormInvoice = (event, invoice) => (dispatch) => {
export const receiveFormInvoice = (event, invoice) => dispatch => {
dispatch(setPayInvoice(invoice))
dispatch({ type: RECEIVE_FORM_INVOICE })
}
// Send IPC event for invoices
export const fetchInvoices = () => (dispatch) => {
export const fetchInvoices = () => dispatch => {
dispatch(getInvoices())
ipcRenderer.send('lnd', { msg: 'invoices' })
}
@ -96,7 +96,7 @@ export const fetchInvoices = () => (dispatch) => {
export const receiveInvoices = (event, { invoices }) => dispatch => dispatch({ type: RECEIVE_INVOICES, invoices })
// Send IPC event for creating an invoice
export const createInvoice = (amount, memo, currency) => (dispatch) => {
export const createInvoice = (amount, memo, currency) => dispatch => {
// backend needs value in satoshis no matter what currency we are using
const value = btc.convert(currency, 'sats', amount)
@ -105,7 +105,7 @@ export const createInvoice = (amount, memo, currency) => (dispatch) => {
}
// Receive IPC event for newly created invoice
export const createdInvoice = (event, invoice) => (dispatch) => {
export const createdInvoice = (event, invoice) => dispatch => {
// Close the form modal once the payment was succesful
dispatch(setFormType(null))
@ -122,20 +122,20 @@ export const createdInvoice = (event, invoice) => (dispatch) => {
dispatch(showActivityModal('INVOICE', { invoice }))
}
export const invoiceFailed = (event, { error }) => (dispatch) => {
export const invoiceFailed = (event, { error }) => dispatch => {
dispatch({ type: INVOICE_FAILED })
dispatch(setError(error))
}
// Listen for invoice updates pushed from backend from subscribeToInvoices
export const invoiceUpdate = (event, { invoice }) => (dispatch) => {
export const invoiceUpdate = (event, { invoice }) => dispatch => {
dispatch({ type: UPDATE_INVOICE, invoice })
// Fetch new balance
dispatch(fetchBalance())
// HTML 5 desktop notification for the invoice update
const notifTitle = 'You\'ve been Zapped'
const notifTitle = "You've been Zapped"
const notifBody = 'Congrats, someone just paid an invoice of yours'
showNotification(notifTitle, notifBody)
@ -156,14 +156,14 @@ const ACTION_HANDLERS = {
[RECEIVE_INVOICES]: (state, { invoices }) => ({ ...state, invoiceLoading: false, invoices }),
[SEND_INVOICE]: state => ({ ...state, invoiceLoading: true }),
[INVOICE_SUCCESSFUL]: (state, { invoice }) => (
{ ...state, invoiceLoading: false, invoices: [invoice, ...state.invoices] }
),
[INVOICE_SUCCESSFUL]: (state, { invoice }) => ({ ...state, invoiceLoading: false, invoices: [invoice, ...state.invoices] }),
[INVOICE_FAILED]: state => ({ ...state, invoiceLoading: false, data: null }),
[UPDATE_INVOICE]: (state, action) => {
const updatedInvoices = state.invoices.map((invoice) => {
if (invoice.r_hash.toString('hex') !== action.invoice.r_hash.toString('hex')) { return invoice }
const updatedInvoices = state.invoices.map(invoice => {
if (invoice.r_hash.toString('hex') !== action.invoice.r_hash.toString('hex')) {
return invoice
}
return {
...invoice,
@ -180,21 +180,14 @@ const invoiceSelector = state => state.invoice.invoice
const invoicesSelector = state => state.invoice.invoices
const invoicesSearchTextSelector = state => state.invoice.invoicesSearchText
invoiceSelectors.invoiceModalOpen = createSelector(
invoiceSelector,
invoice => (!!invoice)
)
invoiceSelectors.invoiceModalOpen = createSelector(invoiceSelector, invoice => !!invoice)
invoiceSelectors.invoices = createSelector(
invoicesSelector,
invoicesSearchTextSelector,
(invoices, invoicesSearchText) => invoices.filter(invoice => invoice.memo.includes(invoicesSearchText))
invoiceSelectors.invoices = createSelector(invoicesSelector, invoicesSearchTextSelector, (invoices, invoicesSearchText) =>
invoices.filter(invoice => invoice.memo.includes(invoicesSearchText))
)
invoiceSelectors.invoices = createSelector(
invoicesSelector,
invoicesSearchTextSelector,
(invoices, invoicesSearchText) => invoices.filter(invoice => invoice.memo.includes(invoicesSearchText))
invoiceSelectors.invoices = createSelector(invoicesSelector, invoicesSearchTextSelector, (invoices, invoicesSearchText) =>
invoices.filter(invoice => invoice.memo.includes(invoicesSearchText))
)
export { invoiceSelectors }

10
app/reducers/ipc.js

@ -6,19 +6,16 @@ import { receiveCryptocurrency } from './ticker'
import { receivePeers, connectSuccess, disconnectSuccess, connectFailure } from './peers'
import {
receiveChannels,
channelSuccessful,
pushchannelupdated,
pushchannelend,
pushchannelerror,
pushchannelstatus,
closeChannelSuccessful,
pushclosechannelupdated,
pushclosechannelend,
pushclosechannelerror,
pushclosechannelstatus,
channelGraphData,
channelGraphStatus
} from './channels'
@ -26,12 +23,7 @@ import { lightningPaymentUri } from './payform'
import { receivePayments, paymentSuccessful, paymentFailed } from './payment'
import { receiveInvoices, createdInvoice, receiveFormInvoice, invoiceUpdate, invoiceFailed } from './invoice'
import { receiveBalance } from './balance'
import {
receiveTransactions,
transactionSuccessful,
transactionError,
newTransaction
} from './transaction'
import { receiveTransactions, transactionSuccessful, transactionError, newTransaction } from './transaction'
import { receiveDescribeNetwork, receiveQueryRoutes, receiveInvoiceAndQueryRoutes } from './network'

21
app/reducers/lnd.js

@ -26,7 +26,7 @@ export const GRPC_CONNECTED = 'GRPC_CONNECTED'
export const lndSyncing = () => dispatch => dispatch({ type: START_SYNCING })
// Receive IPC event for LND stoping sync
export const lndSynced = () => (dispatch) => {
export const lndSynced = () => dispatch => {
// Fetch data now that we know LND is synced
dispatch(fetchTicker())
dispatch(fetchBalance())
@ -36,7 +36,7 @@ export const lndSynced = () => (dispatch) => {
// HTML 5 desktop notification for the new transaction
const notifTitle = 'Lightning Node Synced'
const notifBody = 'Visa who? You\'re your own payment processor now!'
const notifBody = "Visa who? You're your own payment processor now!"
showNotification(notifTitle, notifBody)
}
@ -46,7 +46,7 @@ export const grpcDisconnected = () => dispatch => dispatch({ type: GRPC_DISCONNE
export const grpcConnected = () => dispatch => dispatch({ type: GRPC_CONNECTED })
// Receive IPC event for LND streaming a line
export const lndStdout = (event, line) => (dispatch) => {
export const lndStdout = (event, line) => dispatch => {
let height
let trimmed
@ -63,7 +63,6 @@ export const lndStdout = (event, line) => (dispatch) => {
dispatch({ type: RECEIVE_LINE, lndBlockHeight: height })
}
export function getBlockHeight() {
return {
type: GET_BLOCK_HEIGHT
@ -78,7 +77,7 @@ export function receiveBlockHeight(blockHeight) {
}
// Fetch current block height
export const fetchBlockHeight = () => async (dispatch) => {
export const fetchBlockHeight = () => async dispatch => {
dispatch(getBlockHeight())
const blockData = await requestBlockHeight()
dispatch(receiveBlockHeight(blockData.blocks[0].height))
@ -119,17 +118,15 @@ const lndSelectors = {}
const blockHeightSelector = state => state.lnd.blockHeight
const lndBlockHeightSelector = state => state.lnd.lndBlockHeight
lndSelectors.syncPercentage = createSelector(
blockHeightSelector,
lndBlockHeightSelector,
(blockHeight, lndBlockHeight) => {
lndSelectors.syncPercentage = createSelector(blockHeightSelector, lndBlockHeightSelector, (blockHeight, lndBlockHeight) => {
const percentage = Math.floor((lndBlockHeight / blockHeight) * 100)
if (percentage === Infinity) { return '' }
if (percentage === Infinity) {
return ''
}
return percentage
}
)
})
export { lndSelectors }

62
app/reducers/network.js

@ -128,16 +128,15 @@ export function clearSelectedChannels() {
}
// Send IPC event for describeNetwork
export const fetchDescribeNetwork = () => (dispatch) => {
export const fetchDescribeNetwork = () => dispatch => {
dispatch(getDescribeNetwork())
ipcRenderer.send('lnd', { msg: 'describeNetwork' })
}
// Receive IPC event for describeNetwork
export const receiveDescribeNetwork = (event, { nodes, edges }) => dispatch =>
dispatch({ type: RECEIVE_DESCRIBE_NETWORK, nodes, edges })
export const receiveDescribeNetwork = (event, { nodes, edges }) => dispatch => dispatch({ type: RECEIVE_DESCRIBE_NETWORK, nodes, edges })
export const queryRoutes = (pubkey, amount) => (dispatch) => {
export const queryRoutes = (pubkey, amount) => dispatch => {
dispatch(getQueryRoutes(pubkey))
ipcRenderer.send('lnd', { msg: 'queryRoutes', data: { pubkey, amount } })
}
@ -145,13 +144,12 @@ export const queryRoutes = (pubkey, amount) => (dispatch) => {
export const receiveQueryRoutes = (event, { routes }) => dispatch => dispatch({ type: RECEIVE_QUERY_ROUTES, routes })
// take a payreq and query routes for it
export const fetchInvoiceAndQueryRoutes = payreq => (dispatch) => {
export const fetchInvoiceAndQueryRoutes = payreq => dispatch => {
dispatch(getInvoiceAndQueryRoutes())
ipcRenderer.send('lnd', { msg: 'getInvoiceAndQueryRoutes', data: { payreq } })
}
export const receiveInvoiceAndQueryRoutes = (event, { routes }) => dispatch =>
dispatch({ type: RECEIVE_INFO_AND_QUERY_ROUTES, routes })
export const receiveInvoiceAndQueryRoutes = (event, { routes }) => dispatch => dispatch({ type: RECEIVE_INFO_AND_QUERY_ROUTES, routes })
// ------------------------------------
// Action Handlers
@ -159,17 +157,18 @@ export const receiveInvoiceAndQueryRoutes = (event, { routes }) => dispatch =>
const ACTION_HANDLERS = {
[GET_DESCRIBE_NETWORK]: state => ({ ...state, networkLoading: true }),
[RECEIVE_DESCRIBE_NETWORK]: (state, { nodes, edges }) => ({
...state, networkLoading: false, nodes, edges
...state,
networkLoading: false,
nodes,
edges
}),
[GET_QUERY_ROUTES]: (state, { pubkey }) => ({ ...state, networkLoading: true, selectedNode: { pubkey, routes: [], currentRoute: {} } }),
[RECEIVE_QUERY_ROUTES]: (state, { routes }) => (
{
[RECEIVE_QUERY_ROUTES]: (state, { routes }) => ({
...state,
networkLoading: false,
selectedNode: { pubkey: state.selectedNode.pubkey, routes, currentRoute: routes[0] }
}
),
}),
[SET_CURRENT_ROUTE]: (state, { route }) => ({ ...state, currentRoute: route }),
@ -198,7 +197,8 @@ const ACTION_HANDLERS = {
}
return {
...state, selectedPeers
...state,
selectedPeers
}
},
[CLEAR_SELECTED_PEERS]: state => ({ ...state, selectedPeers: [] }),
@ -215,7 +215,8 @@ const ACTION_HANDLERS = {
}
return {
...state, selectedChannels
...state,
selectedChannels
}
},
[CLEAR_SELECTED_CHANNELS]: state => ({ ...state, selectedChannels: [] })
@ -239,20 +240,14 @@ const currentRouteSelector = state => state.network.currentRoute
// }
// )
networkSelectors.selectedPeerPubkeys = createSelector(
selectedPeersSelector,
peers => peers.map(peer => peer.pub_key)
)
networkSelectors.selectedPeerPubkeys = createSelector(selectedPeersSelector, peers => peers.map(peer => peer.pub_key))
networkSelectors.selectedChannelIds = createSelector(
selectedChannelsSelector,
channels => channels.map(channel => channel.chan_id)
)
networkSelectors.selectedChannelIds = createSelector(selectedChannelsSelector, channels => channels.map(channel => channel.chan_id))
networkSelectors.payReqIsLn = createSelector(
payReqSelector,
(input) => {
if (!input.startsWith('ln')) { return false }
networkSelectors.payReqIsLn = createSelector(payReqSelector, input => {
if (!input.startsWith('ln')) {
return false
}
try {
bech32.decode(input)
@ -260,17 +255,15 @@ networkSelectors.payReqIsLn = createSelector(
} catch (e) {
return false
}
}
)
})
networkSelectors.currentRouteChanIds = createSelector(
currentRouteSelector,
(route) => {
if (!route.hops || !route.hops.length) { return [] }
networkSelectors.currentRouteChanIds = createSelector(currentRouteSelector, route => {
if (!route.hops || !route.hops.length) {
return []
}
return route.hops.map(hop => hop.chan_id)
}
)
})
export { networkSelectors }
@ -295,7 +288,6 @@ const initialState = {
selectedChannels: []
}
// ------------------------------------
// Reducer
// ------------------------------------

18
app/reducers/onboarding.js

@ -155,23 +155,23 @@ export function startLnd(options) {
}
}
export const submitNewWallet = (wallet_password, cipher_seed_mnemonic, aezeed_passphrase) => (dispatch) => {
export const submitNewWallet = (wallet_password, cipher_seed_mnemonic, aezeed_passphrase) => dispatch => {
// once the user submits the data needed to start LND we will alert the app that it should start LND
ipcRenderer.send('walletUnlocker', { msg: 'initWallet', data: { wallet_password, cipher_seed_mnemonic, aezeed_passphrase } })
dispatch({ type: CREATING_NEW_WALLET })
}
export const startOnboarding = () => (dispatch) => {
export const startOnboarding = () => dispatch => {
dispatch({ type: ONBOARDING_STARTED })
}
// Listener from after the LND walletUnlocker has started
export const walletUnlockerStarted = () => (dispatch) => {
export const walletUnlockerStarted = () => dispatch => {
dispatch({ type: LND_STARTED })
ipcRenderer.send('walletUnlocker', { msg: 'genSeed' })
}
export const createWallet = () => (dispatch) => {
export const createWallet = () => dispatch => {
ipcRenderer.send('walletUnlocker', { msg: 'genSeed' })
dispatch({ type: CHANGE_STEP, step: 4 })
}
@ -179,31 +179,31 @@ export const createWallet = () => (dispatch) => {
export const successfullyCreatedWallet = () => dispatch => dispatch({ type: ONBOARDING_FINISHED })
// Listener for when LND creates and sends us a generated seed
export const receiveSeed = (event, { cipher_seed_mnemonic }) => (dispatch) => {
export const receiveSeed = (event, { cipher_seed_mnemonic }) => dispatch => {
dispatch({ type: CHANGE_STEP, step: 4 })
// there was no seed and we just generated a new one, send user to the login component
dispatch({ type: SET_SEED, seed: cipher_seed_mnemonic })
}
// Listener for when LND throws an error on seed creation
export const receiveSeedError = () => (dispatch) => {
export const receiveSeedError = () => dispatch => {
dispatch({ type: SET_HAS_SEED, hasSeed: true })
// there is already a seed, send user to the login component
dispatch({ type: CHANGE_STEP, step: 3 })
}
// Unlock an existing wallet with a wallet password
export const unlockWallet = wallet_password => (dispatch) => {
export const unlockWallet = wallet_password => dispatch => {
ipcRenderer.send('walletUnlocker', { msg: 'unlockWallet', data: { wallet_password } })
dispatch({ type: UNLOCKING_WALLET })
}
export const walletUnlocked = () => (dispatch) => {
export const walletUnlocked = () => dispatch => {
dispatch({ type: WALLET_UNLOCKED })
dispatch({ type: ONBOARDING_FINISHED })
}
export const unlockWalletError = () => (dispatch) => {
export const unlockWalletError = () => dispatch => {
dispatch({ type: SET_UNLOCK_WALLET_ERROR })
}

53
app/reducers/payform.js

@ -79,7 +79,7 @@ export function updatePayErrors(errorsObject) {
}
}
export const lightningPaymentUri = (event, { payreq }) => (dispatch) => {
export const lightningPaymentUri = (event, { payreq }) => dispatch => {
// Open pay form
dispatch(setFormType('PAY_FORM'))
// Set payreq
@ -103,7 +103,7 @@ const ACTION_HANDLERS = {
[UPDATE_PAY_ERRORS]: (state, { errorsObject }) => ({ ...state, showErrors: Object.assign(state.showErrors, errorsObject) }),
[RESET_FORM]: () => (initialState)
[RESET_FORM]: () => initialState
}
// ------------------------------------
@ -123,23 +123,19 @@ const sendingPaymentSelector = state => state.payment.sendingPayment
// ticker
const currencySelector = state => state.ticker.currency
payFormSelectors.isOnchain = createSelector(
payInputSelector,
infoSelectors.networkSelector,
(input, network) => {
payFormSelectors.isOnchain = createSelector(payInputSelector, infoSelectors.networkSelector, (input, network) => {
try {
bitcoin.address.toOutputScript(input, network.bitcoinJsNetwork)
return true
} catch (e) {
return false
}
}
)
})
payFormSelectors.isLn = createSelector(
payInputSelector,
(input) => {
if (!input.startsWith('ln')) { return false }
payFormSelectors.isLn = createSelector(payInputSelector, input => {
if (!input.startsWith('ln')) {
return false
}
try {
bech32.decode(input)
@ -147,8 +143,7 @@ payFormSelectors.isLn = createSelector(
} catch (e) {
return false
}
}
)
})
payFormSelectors.currentAmount = createSelector(
payFormSelectors.isLn,
@ -159,9 +154,9 @@ payFormSelectors.currentAmount = createSelector(
if (isLn) {
switch (currency) {
case 'btc':
return btc.satoshisToBtc((invoice.num_satoshis || 0))
return btc.satoshisToBtc(invoice.num_satoshis || 0)
case 'bits':
return btc.satoshisToBits((invoice.num_satoshis || 0))
return btc.satoshisToBits(invoice.num_satoshis || 0)
case 'sats':
return invoice.num_satoshis
default:
@ -180,19 +175,19 @@ payFormSelectors.usdAmount = createSelector(
currencySelector,
tickerSelectors.currentTicker,
(isLn, amount, invoice, currency, ticker) => {
if (!ticker || !ticker.price_usd) { return false }
if (!ticker || !ticker.price_usd) {
return false
}
if (isLn) {
return btc.satoshisToUsd((invoice.num_satoshis || 0), ticker.price_usd)
return btc.satoshisToUsd(invoice.num_satoshis || 0, ticker.price_usd)
}
return btc.convert(currency, 'usd', amount, ticker.price_usd)
}
)
payFormSelectors.payInputMin = createSelector(
currencySelector,
(currency) => {
payFormSelectors.payInputMin = createSelector(currencySelector, currency => {
switch (currency) {
case 'btc':
return '0.00000001'
@ -203,8 +198,7 @@ payFormSelectors.payInputMin = createSelector(
default:
return '0'
}
}
)
})
payFormSelectors.inputCaption = createSelector(
payFormSelectors.isOnchain,
@ -212,7 +206,9 @@ payFormSelectors.inputCaption = createSelector(
payFormSelectors.currentAmount,
currencySelector,
(isOnchain, isLn, amount, currency) => {
if (!isOnchain && !isLn) { return '' }
if (!isOnchain && !isLn) {
return ''
}
if (isOnchain) {
return `You're about to send ${amount} ${currency.toUpperCase()} on-chain which should take around 10 minutes`
@ -232,11 +228,7 @@ payFormSelectors.showPayLoadingScreen = createSelector(
(sendingTransaction, sendingPayment) => sendingTransaction || sendingPayment
)
payFormSelectors.payFormIsValid = createSelector(
payFormSelectors.isOnchain,
payFormSelectors.isLn,
payAmountSelector,
(isOnchain, isLn, amount) => {
payFormSelectors.payFormIsValid = createSelector(payFormSelectors.isOnchain, payFormSelectors.isLn, payAmountSelector, (isOnchain, isLn, amount) => {
const errors = {}
if (!isLn && amount <= 0) {
@ -253,8 +245,7 @@ payFormSelectors.payFormIsValid = createSelector(
payInputIsValid: isEmpty(errors.payInput),
isValid: isEmpty(errors)
}
}
)
})
export { payFormSelectors }

14
app/reducers/payment.js

@ -63,7 +63,7 @@ export function hideSuccessScreen() {
}
// Send IPC event for payments
export const fetchPayments = () => (dispatch) => {
export const fetchPayments = () => dispatch => {
dispatch(getPayments())
ipcRenderer.send('lnd', { msg: 'payments' })
}
@ -73,7 +73,7 @@ export const receivePayments = (event, { payments }) => dispatch => dispatch({ t
// Receive IPC event for successful payment
// TODO: Add payment to state, not a total re-fetch
export const paymentSuccessful = () => (dispatch) => {
export const paymentSuccessful = () => dispatch => {
// Dispatch successful payment to stop loading screen
dispatch(paymentSuccessfull())
@ -90,12 +90,12 @@ export const paymentSuccessful = () => (dispatch) => {
dispatch(fetchBalance())
}
export const paymentFailed = (event, { error }) => (dispatch) => {
export const paymentFailed = (event, { error }) => dispatch => {
dispatch({ type: PAYMENT_FAILED })
dispatch(setError(error))
}
export const payInvoice = paymentRequest => (dispatch) => {
export const payInvoice = paymentRequest => dispatch => {
dispatch(sendPayment())
ipcRenderer.send('lnd', { msg: 'sendPayment', data: { paymentRequest } })
@ -112,7 +112,6 @@ export const payInvoice = paymentRequest => (dispatch) => {
// }, 10000)
}
// ------------------------------------
// Action Handlers
// ------------------------------------
@ -132,10 +131,7 @@ const ACTION_HANDLERS = {
const paymentSelectors = {}
const modalPaymentSelector = state => state.payment.payment
paymentSelectors.paymentModalOpen = createSelector(
modalPaymentSelector,
payment => (!!payment)
)
paymentSelectors.paymentModalOpen = createSelector(modalPaymentSelector, payment => !!payment)
export { paymentSelectors }

41
app/reducers/peers.js

@ -70,7 +70,7 @@ export function updateSearchQuery(searchQuery) {
}
// Send IPC event for peers
export const fetchPeers = () => async (dispatch) => {
export const fetchPeers = () => async dispatch => {
dispatch(getPeers())
ipcRenderer.send('lnd', { msg: 'peers' })
}
@ -79,7 +79,7 @@ export const fetchPeers = () => async (dispatch) => {
export const receivePeers = (event, { peers }) => dispatch => dispatch({ type: RECEIVE_PEERS, peers })
// Send IPC event for connecting to a peer
export const connectRequest = ({ pubkey, host }) => (dispatch) => {
export const connectRequest = ({ pubkey, host }) => dispatch => {
dispatch(connectPeer())
ipcRenderer.send('lnd', { msg: 'connectPeer', data: { pubkey, host } })
}
@ -88,13 +88,13 @@ export const connectRequest = ({ pubkey, host }) => (dispatch) => {
export const connectSuccess = (event, peer) => dispatch => dispatch({ type: CONNECT_SUCCESS, peer })
// Send IPC receive for unsuccessfully connecting to a peer
export const connectFailure = (event, { error }) => (dispatch) => {
export const connectFailure = (event, { error }) => dispatch => {
dispatch({ type: CONNECT_FAILURE })
dispatch(setError(error))
}
// Send IPC send for disconnecting from a peer
export const disconnectRequest = ({ pubkey }) => (dispatch) => {
export const disconnectRequest = ({ pubkey }) => dispatch => {
dispatch(disconnectPeer())
ipcRenderer.send('lnd', { msg: 'disconnectPeer', data: { pubkey } })
}
@ -107,19 +107,21 @@ export const disconnectSuccess = (event, { pubkey }) => dispatch => dispatch({ t
// ------------------------------------
const ACTION_HANDLERS = {
[DISCONNECT_PEER]: state => ({ ...state, disconnecting: true }),
[DISCONNECT_SUCCESS]: (state, { pubkey }) => (
{
...state, disconnecting: false, peer: null, peers: state.peers.filter(peer => peer.pub_key !== pubkey)
}
),
[DISCONNECT_SUCCESS]: (state, { pubkey }) => ({
...state,
disconnecting: false,
peer: null,
peers: state.peers.filter(peer => peer.pub_key !== pubkey)
}),
[DISCONNECT_FAILURE]: state => ({ ...state, disconnecting: false }),
[CONNECT_PEER]: state => ({ ...state, connecting: true }),
[CONNECT_SUCCESS]: (state, { peer }) => (
{
...state, connecting: false, peerForm: { pubkey: '', host: '', isOpen: false }, peers: [...state.peers, peer]
}
),
[CONNECT_SUCCESS]: (state, { peer }) => ({
...state,
connecting: false,
peerForm: { pubkey: '', host: '', isOpen: false },
peers: [...state.peers, peer]
}),
[CONNECT_FAILURE]: state => ({ ...state, connecting: false }),
[SET_PEER_FORM]: (state, { form }) => ({ ...state, peerForm: Object.assign({}, state.peerForm, form) }),
@ -137,15 +139,10 @@ const peerSelector = state => state.peers.peer
const peersSelector = state => state.peers.peers
const peersSearchQuerySelector = state => state.peers.searchQuery
peersSelectors.peerModalOpen = createSelector(
peerSelector,
peer => (!!peer)
)
peersSelectors.peerModalOpen = createSelector(peerSelector, peer => !!peer)
peersSelectors.filteredPeers = createSelector(
peersSelector,
peersSearchQuerySelector,
(peers, query) => peers.filter(peer => peer.pub_key.includes(query) || peer.address.includes(query))
peersSelectors.filteredPeers = createSelector(peersSelector, peersSearchQuerySelector, (peers, query) =>
peers.filter(peer => peer.pub_key.includes(query) || peer.address.includes(query))
)
export { peersSelectors }

6
app/reducers/requestform.js

@ -57,7 +57,7 @@ const ACTION_HANDLERS = {
[SET_REQUEST_MEMO]: (state, { memo }) => ({ ...state, memo }),
[SET_REQUEST_CURRENCY_FILTERS]: (state, { showCurrencyFilters }) => ({ ...state, showCurrencyFilters }),
[RESET_FORM]: () => (initialState)
[RESET_FORM]: () => initialState
}
const requestFormSelectors = {}
@ -70,7 +70,9 @@ requestFormSelectors.usdAmount = createSelector(
tickerSelectors.currentTicker,
(amount, currency, ticker) => {
if (!ticker || !ticker.price_usd) { return false }
if (!ticker || !ticker.price_usd) {
return false
}
return btc.convert(currency, 'usd', amount, ticker.price_usd)
}

38
app/reducers/ticker.js

@ -47,7 +47,7 @@ export function recieveTickers({ btcTicker, ltcTicker }) {
}
}
export const fetchTicker = () => async (dispatch) => {
export const fetchTicker = () => async dispatch => {
dispatch(getTickers())
const tickers = await requestTickers(['bitcoin', 'litecoin'])
dispatch(recieveTickers(tickers))
@ -56,12 +56,11 @@ export const fetchTicker = () => async (dispatch) => {
}
// Receive IPC event for receiveCryptocurrency
export const receiveCryptocurrency = (event, currency) => (dispatch) => {
export const receiveCryptocurrency = (event, currency) => dispatch => {
dispatch({ type: SET_CURRENCY, currency: cryptoTickers[currency] })
dispatch({ type: SET_CRYPTO, crypto: cryptoTickers[currency] })
}
// ------------------------------------
// Action Handlers
// ------------------------------------
@ -69,11 +68,12 @@ const ACTION_HANDLERS = {
[SET_CURRENCY]: (state, { currency }) => ({ ...state, fromCurrency: state.currency, currency }),
[SET_CRYPTO]: (state, { crypto }) => ({ ...state, crypto }),
[GET_TICKERS]: state => ({ ...state, tickerLoading: true }),
[RECIEVE_TICKERS]: (state, { btcTicker, ltcTicker }) => (
{
...state, tickerLoading: false, btcTicker, ltcTicker
}
)
[RECIEVE_TICKERS]: (state, { btcTicker, ltcTicker }) => ({
...state,
tickerLoading: false,
btcTicker,
ltcTicker
})
}
// Selectors
@ -91,23 +91,21 @@ tickerSelectors.currentTicker = createSelector(
(crypto, btcTicker, ltcTicker) => (crypto === 'btc' ? btcTicker : ltcTicker)
)
tickerSelectors.currentCurrencyFilters = createSelector(
currencySelector,
currencyFiltersSelector,
(currency, filters) => filters.filter(f => f.key !== currency)
tickerSelectors.currentCurrencyFilters = createSelector(currencySelector, currencyFiltersSelector, (currency, filters) =>
filters.filter(f => f.key !== currency)
)
tickerSelectors.currencyName = createSelector(
currencySelector,
infoSelectors.networkSelector,
(currency, network) => {
tickerSelectors.currencyName = createSelector(currencySelector, infoSelectors.networkSelector, (currency, network) => {
let unit = currency
if (currency === 'btc') { unit = 'BTC' }
if (currency === 'sats') { unit = 'satoshis' }
if (currency === 'btc') {
unit = 'BTC'
}
if (currency === 'sats') {
unit = 'satoshis'
}
return `${network.unitPrefix}${unit}`
}
)
})
export { tickerSelectors }

27
app/reducers/transaction.js

@ -52,7 +52,7 @@ export function hideSuccessTransactionScreen() {
}
// Send IPC event for payments
export const fetchTransactions = () => (dispatch) => {
export const fetchTransactions = () => dispatch => {
dispatch(getTransactions())
ipcRenderer.send('lnd', { msg: 'transactions' })
}
@ -60,9 +60,7 @@ export const fetchTransactions = () => (dispatch) => {
// Receive IPC event for payments
export const receiveTransactions = (event, { transactions }) => dispatch => dispatch({ type: RECEIVE_TRANSACTIONS, transactions })
export const sendCoins = ({
value, addr, currency
}) => (dispatch) => {
export const sendCoins = ({ value, addr, currency }) => dispatch => {
// backend needs amount in satoshis no matter what currency we are using
const amount = btc.convert(currency, 'sats', value)
@ -78,7 +76,7 @@ export const sendCoins = ({
// Receive IPC event for successful payment
// TODO: Add payment to state, not a total re-fetch
export const transactionSuccessful = (event, { txid }) => (dispatch) => {
export const transactionSuccessful = (event, { txid }) => dispatch => {
// Get the new list of transactions (TODO dont do an entire new fetch)
dispatch(fetchTransactions())
// Show successful payment state
@ -93,13 +91,13 @@ export const transactionSuccessful = (event, { txid }) => (dispatch) => {
dispatch(resetPayForm())
}
export const transactionError = (event, { error }) => (dispatch) => {
export const transactionError = (event, { error }) => dispatch => {
dispatch({ type: TRANSACTION_FAILED })
dispatch(setError(error))
}
// Listener for when a new transaction is pushed from the subscriber
export const newTransaction = (event, { transaction }) => (dispatch) => {
export const newTransaction = (event, { transaction }) => dispatch => {
// Fetch new balance
dispatch(fetchBalance())
@ -107,7 +105,10 @@ export const newTransaction = (event, { transaction }) => (dispatch) => {
// HTML 5 desktop notification for the new transaction
const notifTitle = transaction.amount > 0 ? 'On-chain Transaction Received!' : 'On-chain Transaction Sent!'
const notifBody = transaction.amount > 0 ? 'Lucky you, you just received a new on-chain transaction. I\'m jealous.' : 'Hate to see \'em go but love to watch \'em leave. Your on-chain transaction successfully sent.' // eslint-disable-line max-len
const notifBody =
transaction.amount > 0
? "Lucky you, you just received a new on-chain transaction. I'm jealous."
: "Hate to see 'em go but love to watch 'em leave. Your on-chain transaction successfully sent." // eslint-disable-line max-len
showNotification(notifTitle, notifBody)
@ -115,7 +116,6 @@ export const newTransaction = (event, { transaction }) => (dispatch) => {
dispatch(newAddress('p2pkh'))
}
// ------------------------------------
// Action Handlers
// ------------------------------------
@ -125,14 +125,15 @@ const ACTION_HANDLERS = {
[RECEIVE_TRANSACTIONS]: (state, { transactions }) => ({ ...state, transactionLoading: false, transactions }),
[TRANSACTION_SUCCESSFULL]: state => ({ ...state, sendingTransaction: false }),
[TRANSACTION_FAILED]: state => ({ ...state, sendingTransaction: false }),
[ADD_TRANSACTION]: (state, { transaction }) => (
[ADD_TRANSACTION]: (state, { transaction }) => {
// add the transaction only if we are not already aware of it
state.transactions.find(tx => (tx.tx_hash === transaction.tx_hash)) ? state : {
return state.transactions.find(tx => tx.tx_hash === transaction.tx_hash)
? state
: {
...state,
transactions: [transaction, ...state.transactions]
}
),
},
[SHOW_SUCCESS_TRANSACTION_SCREEN]: (state, { txid }) => ({ ...state, successTransactionScreen: { show: true, txid } }),
[HIDE_SUCCESS_TRANSACTION_SCREEN]: state => ({ ...state, successTransactionScreen: { show: false, txid: '' } })
}

2
app/routes.js

@ -7,7 +7,7 @@ import Activity from './routes/activity'
const routes = () => (
<App>
<Switch>
<Route path='/' component={Activity} />
<Route path="/" component={Activity} />
</Switch>
</App>
)

70
app/routes/activity/components/Activity.js

@ -19,9 +19,7 @@ class Activity extends Component {
}
componentWillMount() {
const {
fetchPayments, fetchInvoices, fetchTransactions, fetchBalance
} = this.props
const { fetchPayments, fetchInvoices, fetchTransactions, fetchBalance } = this.props
fetchBalance()
fetchPayments()
@ -30,13 +28,7 @@ class Activity extends Component {
}
renderActivity(activity) {
const {
ticker,
currentTicker,
showActivityModal,
network,
currencyName
} = this.props
const { ticker, currentTicker, showActivityModal, network, currencyName } = this.props
if (Object.prototype.hasOwnProperty.call(activity, 'block_hash')) {
// activity is an on-chain tx
@ -52,13 +44,7 @@ class Activity extends Component {
} else if (Object.prototype.hasOwnProperty.call(activity, 'payment_request')) {
// activity is an LN invoice
return (
<Invoice
invoice={activity}
ticker={ticker}
currentTicker={currentTicker}
showActivityModal={showActivityModal}
currencyName={currencyName}
/>
<Invoice invoice={activity} ticker={ticker} currentTicker={currentTicker} showActivityModal={showActivityModal} currencyName={currencyName} />
)
}
// activity is an LN payment
@ -77,13 +63,7 @@ class Activity extends Component {
render() {
const {
balance,
activity: {
filters,
filter,
filterPulldown,
searchActive,
searchText
},
activity: { filters, filter, filterPulldown, searchActive, searchText },
changeFilter,
currentActivity,
@ -93,62 +73,56 @@ class Activity extends Component {
walletProps
} = this.props
if (!balance.channelBalance || !balance.walletBalance) { return <LoadingBolt /> }
if (!balance.channelBalance || !balance.walletBalance) {
return <LoadingBolt />
}
return (
<div>
<Wallet {...walletProps} />
<div className={styles.activities}>
{
searchActive ?
{searchActive ? (
<header className={`${styles.header} ${styles.search}`}>
<section>
<input
placeholder='Search'
value={searchText}
onChange={event => updateSearchText(event.target.value)}
/>
<input placeholder="Search" value={searchText} onChange={event => updateSearchText(event.target.value)} />
</section>
<section onClick={() => { updateSearchActive(false); updateSearchText('') }}>
<section
onClick={() => {
updateSearchActive(false)
updateSearchText('')
}}
>
<span className={styles.xIcon}>
<Isvg src={xIcon} />
</span>
</section>
</header>
:
) : (
<header className={styles.header}>
<section>
<ul className={styles.filters}>
{
filters.map(f => (
{filters.map(f => (
<li key={f.key} className={f.key === filter.key && styles.activeFilter} onClick={() => changeFilter(f)}>
<span>{f.name}</span>
<div className={f.key === filter.key && styles.activeBorder} />
</li>
))
}
))}
</ul>
</section>
<section onClick={() => updateSearchActive(true)}>
<Isvg src={searchIcon} />
</section>
</header>
}
)}
<ul className={`${styles.activityContainer} ${filterPulldown && styles.pulldown}`}>
{
currentActivity.map((activityBlock, index) => (
{currentActivity.map((activityBlock, index) => (
<li className={styles.activity} key={index}>
<h2>{activityBlock.title}</h2>
<ul>
{
activityBlock.activity.map((activity, i) => <li key={i}>{this.renderActivity(activity.el)}</li>)
}
</ul>
<ul>{activityBlock.activity.map((activity, i) => <li key={i}>{this.renderActivity(activity.el)}</li>)}</ul>
</li>
))
}
))}
</ul>
</div>
</div>

28
app/routes/activity/components/components/Invoice/Invoice.js

@ -9,42 +9,30 @@ import Value from 'components/Value'
import checkmarkIcon from 'icons/check_circle.svg'
import styles from '../Activity.scss'
const Invoice = ({
invoice, ticker, currentTicker, showActivityModal, currencyName
}) => (
const Invoice = ({ invoice, ticker, currentTicker, showActivityModal, currencyName }) => (
<div className={`${styles.container} ${!invoice.settled && styles.unpaid}`} onClick={() => showActivityModal('INVOICE', { invoice })}>
{
!invoice.settled && (
{!invoice.settled && (
<div className={styles.pendingIcon}>
<Isvg src={checkmarkIcon} />
</div>
)
}
)}
<div className={styles.data}>
<div className={styles.title}>
<h3>
{ invoice.settled ? 'Received payment' : 'Requested payment' }
</h3>
<h3>{invoice.settled ? 'Received payment' : 'Requested payment'}</h3>
</div>
<div className={styles.subtitle}>
<Moment format='h:mm a'>{invoice.settled ? invoice.settled_date * 1000 : invoice.creation_date * 1000}</Moment>
<Moment format="h:mm a">{invoice.settled ? invoice.settled_date * 1000 : invoice.creation_date * 1000}</Moment>
</div>
</div>
<div className={`${styles.amount} ${invoice.settled ? styles.positive : styles.negative}`}>
<span className='hint--top' data-hint='Invoice amount'>
<span className="hint--top" data-hint="Invoice amount">
<i className={styles.plus}>+</i>
<Value
value={invoice.value}
currency={ticker.currency}
currentTicker={currentTicker}
/>
<Value value={invoice.value} currency={ticker.currency} currentTicker={currentTicker} />
<i> {currencyName}</i>
</span>
<span>
<span>
${btc.convert('sats', 'usd', invoice.value, currentTicker.price_usd)}
</span>
<span>${btc.convert('sats', 'usd', invoice.value, currentTicker.price_usd)}</span>
</span>
</div>
</div>

26
app/routes/activity/components/components/Payment/Payment.js

@ -8,13 +8,13 @@ import { btc } from 'utils'
import Value from 'components/Value'
import styles from '../Activity.scss'
const Payment = ({
payment, ticker, currentTicker, showActivityModal, nodes, currencyName
}) => {
const displayNodeName = (pubkey) => {
const Payment = ({ payment, ticker, currentTicker, showActivityModal, nodes, currencyName }) => {
const displayNodeName = pubkey => {
const node = find(nodes, n => pubkey === n.pub_key)
if (node && node.alias.length) { return node.alias }
if (node && node.alias.length) {
return node.alias
}
return pubkey.substring(0, 10)
}
@ -23,25 +23,19 @@ const Payment = ({
<div className={styles.container} onClick={() => showActivityModal('PAYMENT', { payment })}>
<div className={styles.data}>
<div className={styles.title}>
<h3>
{displayNodeName(payment.path[payment.path.length - 1])}
</h3>
<h3>{displayNodeName(payment.path[payment.path.length - 1])}</h3>
</div>
<div className={styles.subtitle}>
<Moment format='h:mm a'>{payment.creation_date * 1000}</Moment>
<Moment format="h:mm a">{payment.creation_date * 1000}</Moment>
</div>
</div>
<div className={styles.amount}>
<span className='hint--top' data-hint='Payment amount'>
<span className="hint--top" data-hint="Payment amount">
<i className={styles.minus}>-</i>
<Value
value={payment.value}
currency={ticker.currency}
currentTicker={currentTicker}
/>
<Value value={payment.value} currency={ticker.currency} currentTicker={currentTicker} />
<i> {currencyName}</i>
</span>
<span className='hint--bottom' data-hint='Payment fee'>
<span className="hint--bottom" data-hint="Payment fee">
${btc.convert('sats', 'usd', payment.value, currentTicker.price_usd)}
</span>
</div>

20
app/routes/activity/components/components/Transaction/Transaction.js

@ -7,31 +7,23 @@ import { btc } from 'utils'
import Value from 'components/Value'
import styles from '../Activity.scss'
const Transaction = ({
transaction, ticker, currentTicker, showActivityModal, currencyName
}) => (
const Transaction = ({ transaction, ticker, currentTicker, showActivityModal, currencyName }) => (
<div className={styles.container} onClick={() => showActivityModal('TRANSACTION', { transaction })}>
<div className={styles.data}>
<div className={styles.title}>
<h3>
{ transaction.amount > 0 ? 'Received' : 'Sent' }
</h3>
<h3>{transaction.amount > 0 ? 'Received' : 'Sent'}</h3>
</div>
<div className={styles.subtitle}>
<Moment format='h:mm a'>{transaction.time_stamp * 1000}</Moment>
<Moment format="h:mm a">{transaction.time_stamp * 1000}</Moment>
</div>
</div>
<div className={`${styles.amount} ${transaction.amount > 0 ? styles.positive : styles.negative}`}>
<span className='hint--top' data-hint='Transaction amount'>
<span className="hint--top" data-hint="Transaction amount">
<i className={transaction.amount > 0 ? styles.plus : styles.minus}>{transaction.amount > 0 ? '+' : '-'}</i>
<Value
value={transaction.amount}
currency={ticker.currency}
currentTicker={currentTicker}
/>
<Value value={transaction.amount} currency={ticker.currency} currentTicker={currentTicker} />
<i> {currencyName}</i>
</span>
<span className='hint--bottom' data-hint='Transaction fee'>
<span className="hint--bottom" data-hint="Transaction fee">
${btc.convert('sats', 'usd', transaction.amount, currentTicker.price_usd)}
</span>
</div>

18
app/routes/activity/containers/ActivityContainer.js

@ -1,16 +1,8 @@
import { connect } from 'react-redux'
import { setCurrency, tickerSelectors } from 'reducers/ticker'
import { fetchBalance } from 'reducers/balance'
import {
fetchInvoices,
setInvoice,
invoiceSelectors
} from 'reducers/invoice'
import {
setPayment,
fetchPayments,
paymentSelectors
} from 'reducers/payment'
import { fetchInvoices, setInvoice, invoiceSelectors } from 'reducers/invoice'
import { setPayment, fetchPayments, paymentSelectors } from 'reducers/payment'
import { fetchTransactions } from 'reducers/transaction'
import {
showActivityModal,
@ -108,4 +100,8 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => ({
}
})
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(Activity)
export default connect(
mapStateToProps,
mapDispatchToProps,
mergeProps
)(Activity)

25
app/routes/app/components/App.js

@ -18,15 +18,7 @@ import styles from './App.scss'
class App extends Component {
componentWillMount() {
const {
fetchTicker,
fetchInfo,
newAddress,
fetchChannels,
fetchSuggestedNodes,
fetchBalance,
fetchDescribeNetwork
} = this.props
const { fetchTicker, fetchInfo, newAddress, fetchChannels, fetchSuggestedNodes, fetchBalance, fetchDescribeNetwork } = this.props
// fetch price ticker
fetchTicker()
@ -65,7 +57,9 @@ class App extends Component {
children
} = this.props
if (!currentTicker) { return <LoadingBolt /> }
if (!currentTicker) {
return <LoadingBolt />
}
return (
<div>
@ -79,16 +73,9 @@ class App extends Component {
<ReceiveModal {...receiveModalProps} />
<ActivityModal {...activityModalProps} />
<div className={styles.content}>
{children}
</div>
<div className={styles.content}>{children}</div>
{
contactsFormProps.contactsform.isOpen ?
<AddChannel {...contactsFormProps} />
:
<Network {...networkTabProps} />
}
{contactsFormProps.contactsform.isOpen ? <AddChannel {...contactsFormProps} /> : <Network {...networkTabProps} />}
<Form formType={form.formType} formProps={formProps} closeForm={closeForm} />
</div>

48
app/routes/app/containers/AppContainer.js

@ -40,23 +40,17 @@ import {
import {
openContactsForm,
closeContactsForm,
setChannelFormType,
openManualForm,
closeManualForm,
openSubmitChannelForm,
closeSubmitChannelForm,
updateContactFormSearchQuery,
updateManualFormSearchQuery,
updateContactCapacity,
setNode,
contactFormSelectors,
updateManualFormErrors,
setContactsCurrencyFilters
} from 'reducers/contactsform'
@ -271,21 +265,26 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
setCurrency: dispatchProps.setCurrency,
setRequestCurrencyFilters: dispatchProps.setRequestCurrencyFilters,
onRequestSubmit: () => (
onRequestSubmit: () =>
dispatchProps.createInvoice(
stateProps.requestform.amount,
stateProps.requestform.memo,
stateProps.ticker.currency,
stateProps.currentTicker.price_usd
)
)
}
const formProps = (formType) => {
if (!formType) { return {} }
const formProps = formType => {
if (!formType) {
return {}
}
if (formType === 'PAY_FORM') { return payFormProps }
if (formType === 'REQUEST_FORM') { return requestFormProps }
if (formType === 'PAY_FORM') {
return payFormProps
}
if (formType === 'REQUEST_FORM') {
return requestFormProps
}
return {}
}
@ -367,7 +366,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
setActivityModalCurrencyFilters: dispatchProps.setActivityModalCurrencyFilters,
setCurrencyFilters: dispatchProps.setCurrencyFilters,
onCurrencyFilterClick: (currency) => {
onCurrencyFilterClick: currency => {
dispatchProps.setCurrency(currency)
dispatchProps.setActivityModalCurrencyFilters(false)
}
@ -404,7 +403,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
setContactsCurrencyFilters: dispatchProps.setContactsCurrencyFilters,
setCurrencyFilters: dispatchProps.setCurrencyFilters,
onCurrencyFilterClick: (currency) => {
onCurrencyFilterClick: currency => {
dispatchProps.updateContactCapacity(btc.convert(stateProps.ticker.currency, currency, stateProps.contactsform.contactCapacity))
dispatchProps.setCurrency(currency)
dispatchProps.setContactsCurrencyFilters(false)
@ -425,9 +424,13 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
showErrors: stateProps.contactsform.showErrors
}
const calcChannelFormProps = (formType) => {
if (formType === 'MANUAL_FORM') { return connectManuallyProps }
if (formType === 'SUBMIT_CHANNEL_FORM') { return submitChannelFormProps }
const calcChannelFormProps = formType => {
if (formType === 'MANUAL_FORM') {
return connectManuallyProps
}
if (formType === 'SUBMIT_CHANNEL_FORM') {
return submitChannelFormProps
}
return {}
}
@ -438,7 +441,6 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
closeForm: () => dispatchProps.setChannelFormType(null)
}
return {
...stateProps,
...dispatchProps,
@ -464,9 +466,13 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
formProps: formProps(stateProps.form.formType),
// action to close form
closeForm: () => dispatchProps.setFormType(null)
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps, mergeProps)(App))
export default withRouter(
connect(
mapStateToProps,
mapDispatchToProps,
mergeProps
)(App)
)

5
app/store/configureStore.dev.js

@ -8,7 +8,7 @@ import ipc from '../reducers/ipc'
const history = createHashHistory()
const configureStore = (initialState) => {
const configureStore = initialState => {
// Redux Configuration
const middleware = []
const enhancers = []
@ -49,8 +49,7 @@ const configureStore = (initialState) => {
const store = createStore(rootReducer, initialState, enhancer)
if (module.hot) {
module.hot.accept('../reducers', () =>
store.replaceReducer(require('../reducers'))) // eslint-disable-line global-require
module.hot.accept('../reducers', () => store.replaceReducer(require('../reducers'))) // eslint-disable-line global-require
}
return store

12
app/utils/blockExplorer.js

@ -1,16 +1,12 @@
import { shell } from 'electron'
const showTransaction = (network, txid) =>
shell.openExternal(`${network.explorerUrl}/tx/${txid}`)
const showTransaction = (network, txid) => shell.openExternal(`${network.explorerUrl}/tx/${txid}`)
const showBlock = (network, blockHash) =>
shell.openExternal(`${network.explorerUrl}/block/${blockHash}`)
const showBlock = (network, blockHash) => shell.openExternal(`${network.explorerUrl}/block/${blockHash}`)
const showChannelClosing = channel =>
showTransaction(channel.closing_txid)
const showChannelClosing = channel => showTransaction(channel.closing_txid)
const showChannelPoint = channel =>
showTransaction(channel.channel.channel_point.split(':')[0])
const showChannelPoint = channel => showTransaction(channel.channel.channel_point.split(':')[0])
export default {
showTransaction,

3
app/utils/log.js

@ -47,13 +47,12 @@ const logConfig = name => ({
}
})
// Create 2 logs for use in the app.
export const mainLog = debugLogger.config(logConfig('main'))('zap')
export const lndLog = debugLogger.config(logConfig('lnd '))('zap')
let lndLogLevel = null // stored most recent log level for continuity
export const lndLogGetLevel = (msg) => {
export const lndLogGetLevel = msg => {
// Define a mapping between log level prefixes and log level names.
const levelMap = {
TRC: 'trace',

4
app/utils/usd.js

@ -3,7 +3,9 @@ export function formatUsd(usd) {
}
export function usdToBtc(usd, rate) {
if (usd === undefined || usd === null || usd === '') return null
if (usd === undefined || usd === null || usd === '') {
return null
}
return (usd / rate).toFixed(8)
}

22
package.json

@ -11,11 +11,15 @@
"dev": "cross-env START_HOT=1 npm run start-renderer-dev",
"flow": "flow",
"flow-typed": "rimraf flow-typed/npm && flow-typed install --overwrite || true",
"lint": "cross-env NODE_ENV=development eslint --cache --format=node_modules/eslint-formatter-pretty .",
"lint-base": "eslint --cache --format=node_modules/eslint-formatter-pretty",
"lint": "npm run lint-base -- .",
"lint-fix-base": "npm run lint-base -- --fix",
"lint-fix": "npm run lint-fix-base -- \"./**/*.{js,json,md}\"",
"lint-styles-base": "stylelint --syntax scss",
"lint-styles": "npm run lint-styles-base -- app/*.scss app/components/*.scss",
"lint-styles-fix-base": "npm run lint-styles-base -- --fix",
"lint-styles-fix": "npm run lint-styles-fix-base -- app/*.scss app/components/*.scss",
"lint-ci": "npm run lint && npm run lint-styles && npm run flow",
"lint-fix": "npm run lint -- --fix",
"lint-styles": "stylelint app/*.scss app/components/*.scss --syntax scss",
"lint-styles-fix": "stylelint --fix app/*.scss app/components/*.scss --syntax scss",
"package": "npm run build && build --publish never",
"package-all": "npm run build && build -mwl",
"package-linux": "npm run build && build --linux",
@ -136,6 +140,8 @@
]
},
"devDependencies": {
"@commitlint/cli": "^7.0.0",
"@commitlint/config-conventional": "^7.0.1",
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.3",
"babel-jest": "^23.0.1",
@ -164,13 +170,17 @@
"enzyme-to-json": "^1.5.1",
"eslint": "^4.19.1",
"eslint-config-airbnb": "^16.1.0",
"eslint-config-prettier": "^2.9.0",
"eslint-formatter-pretty": "^1.3.0",
"eslint-import-resolver-webpack": "^0.10.0",
"eslint-plugin-compat": "^2.4.0",
"eslint-plugin-flowtype": "^2.49.3",
"eslint-plugin-import": "^2.12.0",
"eslint-plugin-jest": "^21.17.0",
"eslint-plugin-json": "^1.2.0",
"eslint-plugin-jsx-a11y": "6.0.3",
"eslint-plugin-markdown": "^1.0.0-beta.6",
"eslint-plugin-prettier": "^2.6.0",
"eslint-plugin-promise": "^3.8.0",
"eslint-plugin-react": "^7.9.1",
"express": "^4.15.3",
@ -181,11 +191,15 @@
"flow-runtime": "^0.17.0",
"flow-typed": "^2.1.2",
"html-webpack-plugin": "^3.2.0",
"husky": "^1.0.0-rc.9",
"identity-obj-proxy": "^3.0.0",
"jest": "^23.1.0",
"jsdom": "^11.0.0",
"lint-staged": "^7.2.0",
"minimist": "^1.2.0",
"node-sass": "^4.9.0",
"opt-cli": "^1.6.0",
"prettier": "^1.13.5",
"ps-node": "^0.1.6",
"react-addons-test-utils": "^15.6.0",
"react-test-renderer": "^15.6.1",

2
test/e2e/e2e.spec.js

@ -27,7 +27,7 @@ describe('main window', function spec() {
expect(title).toBe('Zap')
})
it('should haven\'t any logs in console of main window', async () => {
it("should haven't any logs in console of main window", async () => {
const { client } = this.app
const logs = await client.getRenderProcessLogs()
expect(logs).toHaveLength(0)

5
test/reducers/balance.spec.js

@ -1,7 +1,4 @@
import balanceReducer, {
GET_BALANCE,
RECEIVE_BALANCE
} from '../../app/reducers/balance'
import balanceReducer, { GET_BALANCE, RECEIVE_BALANCE } from '../../app/reducers/balance'
describe('reducers', () => {
describe('balanceReducer', () => {

5
test/reducers/info.spec.js

@ -1,7 +1,4 @@
import infoReducer, {
GET_INFO,
RECEIVE_INFO
} from '../../app/reducers/info'
import infoReducer, { GET_INFO, RECEIVE_INFO } from '../../app/reducers/info'
describe('reducers', () => {
describe('infoReducer', () => {

7
test/reducers/ticker.spec.js

@ -1,9 +1,4 @@
import tickerReducer, {
SET_CURRENCY,
SET_CRYPTO,
GET_TICKERS,
RECIEVE_TICKERS
} from '../../app/reducers/ticker'
import tickerReducer, { SET_CURRENCY, SET_CRYPTO, GET_TICKERS, RECIEVE_TICKERS } from '../../app/reducers/ticker'
describe('reducers', () => {
describe('tickerReducer', () => {

4
test/runTests.js

@ -2,9 +2,7 @@ const spawn = require('cross-spawn')
const path = require('path')
const s = `\\${path.sep}`
const pattern = process.argv[2] === 'e2e'
? `test${s}e2e${s}.+\\.spec\\.js`
: `test${s}(?!e2e${s})[^${s}]+${s}.+\\.spec\\.js$`
const pattern = process.argv[2] === 'e2e' ? `test${s}e2e${s}.+\\.spec\\.js` : `test${s}(?!e2e${s})[^${s}]+${s}.+\\.spec\\.js$`
const result = spawn.sync(path.normalize('./node_modules/.bin/jest'), [pattern], { stdio: 'inherit' })

5
test/utils/usd.spec.js

@ -1,7 +1,4 @@
import {
formatUsd,
usdToBtc
} from '../../app/utils/usd'
import { formatUsd, usdToBtc } from '../../app/utils/usd'
describe('usd', () => {
describe('formatUsd', () => {

11
webpack.config.base.js

@ -10,7 +10,8 @@ export default {
externals: Object.keys(externals || {}),
module: {
rules: [{
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
@ -19,7 +20,8 @@ export default {
cacheDirectory: true
}
}
}]
}
]
},
output: {
@ -34,10 +36,7 @@ export default {
*/
resolve: {
extensions: ['.js', '.jsx', '.json'],
modules: [
path.join(__dirname, 'app'),
'node_modules'
]
modules: [path.join(__dirname, 'app'), 'node_modules']
},
plugins: [

10
webpack.config.renderer.dev.dll.js

@ -158,17 +158,11 @@ export default merge.smart(baseConfig, {
},
resolve: {
modules: [
'app'
]
modules: ['app']
},
entry: {
renderer: (
Object
.keys(dependencies || {})
.filter(dependency => dependency !== 'font-awesome')
)
renderer: Object.keys(dependencies || {}).filter(dependency => dependency !== 'font-awesome')
},
output: {

6
webpack.config.renderer.dev.js

@ -264,11 +264,7 @@ export default merge.smart(baseConfig, {
},
before() {
if (process.env.START_HOT) {
spawn(
'npm',
['run', 'start-main-dev'],
{ shell: true, env: process.env, stdio: 'inherit' }
)
spawn('npm', ['run', 'start-main-dev'], { shell: true, env: process.env, stdio: 'inherit' })
.on('close', code => process.exit(code))
.on('error', spawnError => mainLog.error(spawnError))
}

6
webpack.config.renderer.prod.js

@ -70,7 +70,8 @@ export default merge.smart(baseConfig, {
{
test: /^((?!\.global).)*\.scss$/,
use: ExtractTextPlugin.extract({
use: [{
use: [
{
loader: 'css-loader',
options: {
modules: true,
@ -80,7 +81,8 @@ export default merge.smart(baseConfig, {
},
{
loader: 'sass-loader'
}]
}
]
})
},
// WOFF Font

774
yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save