Browse Source

Merge pull request #303 from LN-Zap/feature/new-onboarding

Feature/new onboarding
renovate/lint-staged-8.x
JimmyMow 7 years ago
committed by GitHub
parent
commit
8a28f3fd96
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 83
      app/components/LndSyncing/LndSyncing.js
  2. 242
      app/components/LndSyncing/LndSyncing.scss
  3. 3
      app/components/LndSyncing/index.js
  4. 23
      app/components/Onboarding/Alias.js
  5. 15
      app/components/Onboarding/Alias.scss
  6. 55
      app/components/Onboarding/FormContainer.js
  7. 67
      app/components/Onboarding/FormContainer.scss
  8. 49
      app/components/Onboarding/Onboarding.js
  9. 0
      app/components/Onboarding/Onboarding.scss
  10. 45
      app/components/Onboarding/Syncing.js
  11. 50
      app/components/Onboarding/Syncing.scss
  12. 3
      app/components/Onboarding/index.js
  13. 3
      app/components/Wallet/Wallet.js
  14. 74
      app/containers/Root.js
  15. 34
      app/main.dev.js
  16. 3
      app/reducers/index.js
  17. 6
      app/reducers/ipc.js
  18. 69
      app/reducers/onboarding.js
  19. 2
      app/variables.scss

83
app/components/LndSyncing/LndSyncing.js

@ -1,83 +0,0 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import styles from './LndSyncing.scss'
class LndSyncing extends Component {
constructor(props) {
super(props)
this.state = {
facts: [
{
title: 'The Lightning Network',
description: 'The Lightning Network is a second layer solution built on top of the Bitcoin block chain that attempts to increase Bitcoin\'s scalability and privacy' // eslint-disable-line max-len
},
{
title: 'Payment Channel',
description: 'A payment channel is a class of techniques designed to allow users to make multiple Bitcoin transactions without commiting all of the transactions to the Bitcoin block chain. You can think of payment channels like tubes of money' // eslint-disable-line max-len
},
{
title: 'HTLC',
description: 'Hashed TimeLock Contracts is a class of payments that use hashlocks and timelocks to require the receiver of a payment either acknowledge receiving the payment before a deadline or forfeit the ability to claim the payment. HTLCs are useful within the Lightning Network for routing payments across two or more payment channels' // eslint-disable-line max-len
},
{
title: 'Onion Routing',
description: 'Onion routing is a technique for anonymous communication over a computer network. In an onion network, messages are encapsulated in layers of encryption, analogous to layers of an onion.' // eslint-disable-line max-len
}
],
currentFact: 0
}
}
componentWillMount() {
this.props.fetchBlockHeight()
}
render() {
const { syncPercentage } = this.props
const { facts, currentFact } = this.state
const renderCurrentFact = facts[currentFact]
return (
<div className={styles.container}>
<header>
<section>
<h3>zap</h3>
</section>
<section className={`${styles.loading} hint--left`} data-hint='Syncing your Lightning Network node to the blockchain'>
<h4>{syncPercentage.toString().length > 0 && `${syncPercentage}%`}</h4>
<div className={styles.spinner} />
</section>
</header>
<div className={styles.facts}>
<div className={styles.fact}>
<h2>{renderCurrentFact.title}</h2>
<p>{renderCurrentFact.description}</p>
</div>
<ul className={styles.factButtons}>
{
facts.map((fact, index) => (
<li
className={`${styles.factButton} ${currentFact === index && styles.active}`}
key={index}
onClick={() => this.setState({ currentFact: index })}
/>
))
}
</ul>
</div>
</div>
)
}
}
LndSyncing.propTypes = {
fetchBlockHeight: PropTypes.func.isRequired,
syncPercentage: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string
]).isRequired
}
export default LndSyncing

242
app/components/LndSyncing/LndSyncing.scss

@ -1,242 +0,0 @@
@import '../../variables.scss';
.container {
height: 100vh;
background: $secondary;
header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: top;
padding: 100px;
}
h3 {
font-size: 50px;
color: $white;
}
.loading {
text-align: center;
position: relative;
.spinner, h1 {
display: inline-block;
vertical-align: top;
}
h4 {
position: absolute;
min-width: 100px;
top: calc(50% - 5px);
left: calc(50% - 48px);
color: $main;
font-size: 10px;
}
h1 {
color: $main;
line-height: 50px;
margin-left: 20px;
}
}
.facts {
color: $white;
.fact {
transition: all 0.25s;
width: 50%;
margin: 0 auto;
text-align: center;
line-height: 1.5;
letter-spacing: 1.1px;
height: 250px;
h2 {
font-size: 50px;
margin-bottom: 10px;
}
p {
margin-bottom: 20px;
}
}
}
.factButtons {
text-align: center;
}
.factButton {
cursor: pointer;
display: inline-block;
width: 15px;
height: 15px;
background: $white;
opacity: 0.5;
border-radius: 50%;
margin: 0 5px;
&:hover {
opacity: 0.75;
}
&.active {
opacity: 1;
}
}
}
.aliasForm {
width: 50%;
margin: 0 auto;
h1 {
text-align: center;
font-size: 32px;
color: $white;
}
p {
color: $darkgrey;
text-align: center;
margin-top: 20px;
font-weight: 100;
}
.inputContainer {
text-align: center;
margin-top: 50px;
}
.input {
padding: 20px;
font-size: 18px;
color: $darkestgrey;
background: lighten($black, 15%);
border: none;
outline: 0;
-webkit-appearance: none;
font-weight: 200;
width: calc(100% - 20px);
}
.submit {
background: $main;
color: $white;
font-size: 18px;
cursor: pointer;
width: 10%;
margin: 50px auto 0 auto;
padding: 20px 60px;
opacity: 0.5;
}
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
background: darken($secondary, 5%);
white-space: nowrap;
section {
display: inline-block;
vertical-align: top;
width: 50%;
white-space: normal;
padding: 30px 0;
h2 {
color: $white;
font-size: 22px;
letter-spacing: 1.2px;
font-weight: bold;
margin-bottom: 20px;
padding: 0 30px;
}
p {
color: $white;
padding: 0 30px;
line-height: 1.5;
}
}
.address {
display: flex;
flex-direction: row;
font-family: 'Roboto';
font-size: 14px;
font-weight: 200;
background: lighten($black, 15%);
color: $darkestgrey;
width: 75%;
margin: 0 auto;
span {
padding: 20px;
}
span:nth-child(1) {
flex: 9;
overflow-x: scroll;
font-size: 14px;
}
span:nth-child(2) {
background: $darkestgrey;
color: $white;
cursor: pointer;
transition: all 0.25s;
&:hover {
background: $darkestgrey;
}
}
}
}
.spinner {
border: 1px solid rgba(235, 184, 100, 0.1);
border-left-color: rgba(235, 184, 100, 0.4);
-webkit-border-radius: 999px;
-moz-border-radius: 999px;
border-radius: 999px;
}
.spinner {
margin: 0 auto;
height: 50px;
width: 50px;
-webkit-animation: animation-rotate 1000ms linear infinite;
-moz-animation: animation-rotate 1000ms linear infinite;
-o-animation: animation-rotate 1000ms linear infinite;
animation: animation-rotate 1000ms linear infinite;
}
@-webkit-keyframes animation-rotate {
100% {
-webkit-transform: rotate(360deg);
}
}
@-moz-keyframes animation-rotate {
100% {
-moz-transform: rotate(360deg);
}
}
@-o-keyframes animation-rotate {
100% {
-o-transform: rotate(360deg);
}
}
@keyframes animation-rotate {
100% {
transform: rotate(360deg);
}
}

3
app/components/LndSyncing/index.js

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

23
app/components/Onboarding/Alias.js

@ -0,0 +1,23 @@
import React from 'react'
import PropTypes from 'prop-types'
import styles from './Alias.scss'
const Alias = ({ alias, updateAlias }) => (
<div className={styles.container}>
<input
type='text'
placeholder='Satoshi'
className={styles.alias}
ref={input => input && input.focus()}
value={alias}
onChange={event => updateAlias(event.target.value)}
/>
</div>
)
Alias.propTypes = {
alias: PropTypes.string.isRequired,
updateAlias: PropTypes.func.isRequired
}
export default Alias

15
app/components/Onboarding/Alias.scss

@ -0,0 +1,15 @@
@import '../../variables.scss';
.alias {
background: transparent;
outline: none;
border: 0;
color: $gold;
-webkit-text-fill-color: $white;
font-size: 22px;
}
.alias::-webkit-input-placeholder {
text-shadow: none;
-webkit-text-fill-color: initial;
}

55
app/components/Onboarding/FormContainer.js

@ -0,0 +1,55 @@
import React from 'react'
import PropTypes from 'prop-types'
import Isvg from 'react-inlinesvg'
import zapLogo from 'icons/zap_logo.svg'
import styles from './FormContainer.scss'
const FormContainer = ({ title, description, back, next, children }) => (
<div className={styles.container}>
<div className={styles.titleBar} />
<header className={styles.header}>
<section>
<Isvg src={zapLogo} />
</section>
<section />
</header>
<div className={styles.info}>
<h1>{title}</h1>
<p>{description}</p>
</div>
<div className={styles.content}>
{children}
</div>
<footer className={styles.footer}>
<div className={styles.buttonsContainer}>
<section>
{
back && <div onClick={back}>Back</div>
}
</section>
<section>
{
next && <div onClick={next}>Next</div>
}
</section>
</div>
</footer>
</div>
)
FormContainer.propTypes = {
title: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
back: PropTypes.func,
next: PropTypes.func,
children: PropTypes.object.isRequired
}
export default FormContainer

67
app/components/Onboarding/FormContainer.scss

@ -0,0 +1,67 @@
@import '../../variables.scss';
.container {
position: relative;
height: 100vh;
background: $darkspaceblue;
}
.titleBar {
background: $spacegrey;
height: 20px;
-webkit-user-select: none;
-webkit-app-region: drag;
}
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 20px 40px;
}
.info {
color: $white;
margin: 20px 0 20px 0;
padding: 20px 40px;
h1 {
font-size: 22px;
margin-bottom: 10px;
}
p {
font-size: 12px;
}
}
.content {
position: relative;
background: $spaceblue;
height: 100vh;
padding: 60px 40px;
}
.footer {
position: absolute;
bottom: 0;
padding: 20px 40px;
color: $white;
width: calc(100% - 80px);
.buttonsContainer {
display: flex;
flex-direction: row;
justify-content: space-between;
div {
cursor: pointer;
transition: all 0.25s;
&:hover {
opacity: 0.5;
}
}
}
}

49
app/components/Onboarding/Onboarding.js

@ -0,0 +1,49 @@
import React from 'react'
import PropTypes from 'prop-types'
import LoadingBolt from 'components/LoadingBolt'
import FormContainer from './FormContainer'
import Alias from './Alias'
import styles from './Onboarding.scss'
const Onboarding = ({
onboarding: {
step,
alias
},
submit,
aliasProps
}) => {
const renderStep = () => {
switch (step) {
case 1:
return (
<FormContainer
title={'1. What should we call you?'}
description={'Set your nickname to help others connect with you on the Lightning Network'}
back={null}
next={() => submit(alias)}
>
<Alias {...aliasProps} />
</FormContainer>
)
default:
return <LoadingBolt />
}
}
return (
<div className={styles.container}>
{renderStep()}
</div>
)
}
Onboarding.propTypes = {
onboarding: PropTypes.object.isRequired,
aliasProps: PropTypes.object.isRequired,
submit: PropTypes.func.isRequired
}
export default Onboarding

0
app/components/Onboarding/Onboarding.scss

45
app/components/Onboarding/Syncing.js

@ -0,0 +1,45 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
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()
}
render() {
const { syncPercentage } = this.props
return (
<div className={styles.container}>
<div className={styles.titleBar} />
<div className={styles.content}>
<header>
<Isvg className={styles.bitcoinLogo} src={zapLogo} />
</header>
<section className={styles.progressContainer}>
<h1>Syncing to the blockchain...</h1>
<div className={styles.progressBar}>
<div className={styles.progress} style={{ width: isNaN(syncPercentage) ? 0 : `${syncPercentage}%` }} />
</div>
<h4>{isNaN(parseInt(syncPercentage, 10)) || syncPercentage.toString().length === 0 ? '' : `${syncPercentage}%`}</h4>
</section>
</div>
</div>
)
}
}
Syncing.propTypes = {
fetchBlockHeight: PropTypes.func.isRequired,
syncPercentage: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string
]).isRequired
}
export default Syncing

50
app/components/Onboarding/Syncing.scss

@ -0,0 +1,50 @@
@import '../../variables.scss';
.container {
position: relative;
height: 100vh;
background: $spaceblue;
}
.titleBar {
background: $spacegrey;
height: 20px;
-webkit-user-select: none;
-webkit-app-region: drag;
}
.content {
padding: 20px 40px;
}
.progressContainer {
color: $white;
text-align: center;
margin-top: 20%;
h1 {
margin-bottom: 20px;
}
.progressBar {
width: 75%;
max-width: 700px;
margin: 0 auto;
height: 10px;
border-radius: 5px;
background: $spaceborder;
}
.progress {
background: $gold;
background: #DEA326;
height: 10px;
border-radius: 5px;
transition: all 0.25s;
}
h4 {
color: $gold;
margin-top: 10px;
}
}

3
app/components/Onboarding/index.js

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

3
app/components/Wallet/Wallet.js

@ -32,7 +32,8 @@ class Wallet extends Component {
} = this.props } = this.props
const { modalOpen, qrCodeType } = this.state const { modalOpen, qrCodeType } = this.state
const usdAmount = parseFloat(btc.satoshisToUsd(balance.walletBalance, currentTicker.price_usd)) const usdAmount = btc.satoshisToUsd(balance.channelBalance, currentTicker.price_usd)
console.log('usdAmount: ', usdAmount)
const changeQrCode = () => { const changeQrCode = () => {
const qrCodeNum = this.state.qrCodeType === 1 ? 2 : 1 const qrCodeNum = this.state.qrCodeType === 1 ? 2 : 1

74
app/containers/Root.js

@ -5,43 +5,70 @@ import { ConnectedRouter } from 'react-router-redux'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import LoadingBolt from '../components/LoadingBolt' import LoadingBolt from '../components/LoadingBolt'
import LndSyncing from '../components/LndSyncing' import Onboarding from '../components/Onboarding'
import Syncing from '../components/Onboarding/Syncing'
import { updateAlias, changeStep, submit } from '../reducers/onboarding'
import { fetchBlockHeight, lndSelectors } from '../reducers/lnd' import { fetchBlockHeight, lndSelectors } from '../reducers/lnd'
import { newAddress } from '../reducers/address'
import Routes from '../routes' import Routes from '../routes'
const mapDispatchToProps = { const mapDispatchToProps = {
fetchBlockHeight, updateAlias,
newAddress changeStep,
submit,
fetchBlockHeight
} }
const mapStateToProps = state => ({ const mapStateToProps = state => ({
lnd: state.lnd, lnd: state.lnd,
address: state.address, onboarding: state.onboarding,
syncPercentage: lndSelectors.syncPercentage(state) syncPercentage: lndSelectors.syncPercentage(state)
}) })
const mergeProps = (stateProps, dispatchProps, ownProps) => {
const syncingProps = {
fetchBlockHeight: dispatchProps.fetchBlockHeight,
syncPercentage: stateProps.syncPercentage
}
const aliasProps = {
updateAlias: dispatchProps.updateAlias,
alias: stateProps.onboarding.alias
}
const onboardingProps = {
onboarding: stateProps.onboarding,
submit: dispatchProps.submit,
aliasProps
}
return {
...stateProps,
...dispatchProps,
...ownProps,
onboardingProps,
syncingProps
}
}
const Root = ({ const Root = ({
store, store,
history, history,
lnd, lnd,
newAddress, // eslint-disable-line no-shadow onboardingProps,
fetchBlockHeight, // eslint-disable-line no-shadow syncingProps
syncPercentage,
address
}) => { }) => {
// If we are syncing show the syncing screen
if (!onboardingProps.onboarding.onboarded) {
return <Onboarding {...onboardingProps} />
}
// If we are syncing show the syncing screen // If we are syncing show the syncing screen
if (lnd.syncing) { if (lnd.syncing) {
return ( return <Syncing {...syncingProps} />
<LndSyncing
newAddress={newAddress}
fetchBlockHeight={fetchBlockHeight}
syncPercentage={syncPercentage}
address={address}
grpcStarted={lnd.grpcStarted}
/>
)
} }
// Don't launch the app without gRPC connection // Don't launch the app without gRPC connection
@ -60,13 +87,8 @@ Root.propTypes = {
store: PropTypes.object.isRequired, store: PropTypes.object.isRequired,
history: PropTypes.object.isRequired, history: PropTypes.object.isRequired,
lnd: PropTypes.object.isRequired, lnd: PropTypes.object.isRequired,
fetchBlockHeight: PropTypes.func.isRequired, onboardingProps: PropTypes.object.isRequired,
newAddress: PropTypes.func.isRequired, syncingProps: PropTypes.object.isRequired
syncPercentage: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string
]).isRequired,
address: PropTypes.object.isRequired
} }
export default connect(mapStateToProps, mapDispatchToProps)(Root) export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(Root)

34
app/main.dev.js

@ -8,7 +8,7 @@
* When running `npm run build` or `npm run build-main`, this file is compiled to * When running `npm run build` or `npm run build-main`, this file is compiled to
* `./app/main.prod.js` using webpack. This gives us some performance wins. * `./app/main.prod.js` using webpack. This gives us some performance wins.
* *
* @flow *
*/ */
import { app, BrowserWindow, ipcMain } from 'electron' import { app, BrowserWindow, ipcMain } from 'electron'
import path from 'path' import path from 'path'
@ -83,6 +83,19 @@ const sendLndSyncing = () => {
}, 1000) }, 1000)
} }
const sendStartOnboarding = () => {
const sendStartOnboardingInterval = setInterval(() => {
if (didFinishLoad) {
clearInterval(sendStartOnboardingInterval)
if (mainWindow) {
console.log('STARTING ONBOARDING')
mainWindow.webContents.send('startOnboarding')
}
}
}, 1000)
}
// Send the front end event letting them know the gRPC connection has started // Send the front end event letting them know the gRPC connection has started
const sendGrpcConnected = () => { const sendGrpcConnected = () => {
const sendGrpcConnectedInterval = setInterval(() => { const sendGrpcConnectedInterval = setInterval(() => {
@ -126,7 +139,7 @@ const sendLndSynced = () => {
} }
// Starts the LND node // Starts the LND node
const startLnd = () => { const startLnd = (alias) => {
let lndPath let lndPath
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
@ -146,7 +159,8 @@ const startLnd = () => {
'--neutrino.connect=127.0.0.1:18333', '--neutrino.connect=127.0.0.1:18333',
'--autopilot.active', '--autopilot.active',
'--debuglevel=debug', '--debuglevel=debug',
'--noencryptwallet' '--noencryptwallet',
`--alias=${alias}`
] ]
) )
.on('error', error => console.log(`lnd error: ${error}`)) .on('error', error => console.log(`lnd error: ${error}`))
@ -242,16 +256,17 @@ app.on('ready', async () => {
menuBuilder.buildMenu() menuBuilder.buildMenu()
sendGrpcDisconnected() sendGrpcDisconnected()
// Check to see if and LND process is running // Check to see if an LND process is running
lookup({ command: 'lnd' }, (err, results) => { lookup({ command: 'lnd' }, (err, results) => {
// There was an error checking for the LND process // There was an error checking for the LND process
if (err) { throw new Error(err) } if (err) { throw new Error(err) }
// No LND process was found // No LND process was found
if (!results.length) { if (!results.length) {
// Assign path to certs to certPath // let the application know onboarding has started
sendLndSyncing() sendStartOnboarding()
// Assign path to certs to certPath
switch (os.platform()) { switch (os.platform()) {
case 'darwin': case 'darwin':
certPath = path.join(homedir, 'Library/Application Support/Lnd/tls.cert') certPath = path.join(homedir, 'Library/Application Support/Lnd/tls.cert')
@ -267,7 +282,12 @@ app.on('ready', async () => {
} }
// Start LND // Start LND
startLnd() // startLnd()
// once the onboarding has finished we wanna let the application we have started syncing and start LND
ipcMain.on('onboardingFinished', (event, { alias }) => {
sendLndSyncing()
startLnd(alias)
})
} else { } else {
// An LND process was found, no need to start our own // An LND process was found, no need to start our own
console.log('LND ALREADY RUNNING') console.log('LND ALREADY RUNNING')

3
app/reducers/index.js

@ -1,6 +1,6 @@
// @flow
import { combineReducers } from 'redux' import { combineReducers } from 'redux'
import { routerReducer as router } from 'react-router-redux' import { routerReducer as router } from 'react-router-redux'
import onboarding from './onboarding'
import lnd from './lnd' import lnd from './lnd'
import ticker from './ticker' import ticker from './ticker'
import info from './info' import info from './info'
@ -26,6 +26,7 @@ import error from './error'
const rootReducer = combineReducers({ const rootReducer = combineReducers({
router, router,
onboarding,
lnd, lnd,
ticker, ticker,
info, info,

6
app/reducers/ipc.js

@ -35,6 +35,8 @@ import {
import { receiveDescribeNetwork, receiveQueryRoutes, receiveInvoiceAndQueryRoutes } from './network' import { receiveDescribeNetwork, receiveQueryRoutes, receiveInvoiceAndQueryRoutes } from './network'
import { startOnboarding } from './onboarding'
// Import all receiving IPC event handlers and pass them into createIpc // Import all receiving IPC event handlers and pass them into createIpc
const ipc = createIpc({ const ipc = createIpc({
lndSyncing, lndSyncing,
@ -91,7 +93,9 @@ const ipc = createIpc({
receiveDescribeNetwork, receiveDescribeNetwork,
receiveQueryRoutes, receiveQueryRoutes,
receiveInvoiceAndQueryRoutes receiveInvoiceAndQueryRoutes,
startOnboarding
}) })
export default ipc export default ipc

69
app/reducers/onboarding.js

@ -0,0 +1,69 @@
import { ipcRenderer } from 'electron'
// ------------------------------------
// Constants
// ------------------------------------
export const UPDATE_ALIAS = 'UPDATE_ALIAS'
export const CHANGE_STEP = 'CHANGE_STEP'
export const ONBOARDING_STARTED = 'ONBOARDING_STARTED'
export const ONBOARDING_FINISHED = 'ONBOARDING_FINISHED'
// ------------------------------------
// Actions
// ------------------------------------
export function updateAlias(alias) {
return {
type: UPDATE_ALIAS,
alias
}
}
export function changeStep(step) {
return {
type: CHANGE_STEP,
step
}
}
export function submit(alias) {
// alert the app we're done onboarding and it's cool to start LND
ipcRenderer.send('onboardingFinished', { alias })
return {
type: ONBOARDING_FINISHED
}
}
export const startOnboarding = () => (dispatch) => {
dispatch({ type: ONBOARDING_STARTED })
}
// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
[UPDATE_ALIAS]: (state, { alias }) => ({ ...state, alias }),
[CHANGE_STEP]: (state, { step }) => ({ ...state, step }),
[ONBOARDING_STARTED]: state => ({ ...state, onboarded: false }),
[ONBOARDING_FINISHED]: state => ({ ...state, onboarded: true })
}
// ------------------------------------
// Reducer
// ------------------------------------
const initialState = {
onboarded: true,
step: 1,
alias: ''
}
// ------------------------------------
// Reducer
// ------------------------------------
export default function lndReducer(state = initialState, action) {
const handler = ACTION_HANDLERS[action.type]
return handler ? handler(state, action) : state
}

2
app/variables.scss

@ -11,8 +11,10 @@ $darkestgrey: #999999;
$bluegrey: #2A2D38; $bluegrey: #2A2D38;
$spacegrey: #222E2B; $spacegrey: #222E2B;
$spaceblue: #252832; $spaceblue: #252832;
$darkspaceblue: #1c1e26;
$spaceborder: #404040; $spaceborder: #404040;
$gold: #DEA326;
$green: #0bb634; $green: #0bb634;
$terminalgreen: #00FF00; $terminalgreen: #00FF00;
$red: #FF556A; $red: #FF556A;

Loading…
Cancel
Save