diff --git a/app/components/LndSyncing/LndSyncing.js b/app/components/LndSyncing/LndSyncing.js deleted file mode 100644 index ba8cbdc9..00000000 --- a/app/components/LndSyncing/LndSyncing.js +++ /dev/null @@ -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 ( -
-
-
-

zap

-
-
-

{syncPercentage.toString().length > 0 && `${syncPercentage}%`}

-
-
-
- -
-
-

{renderCurrentFact.title}

-

{renderCurrentFact.description}

-
- -
-
- ) - } -} - -LndSyncing.propTypes = { - fetchBlockHeight: PropTypes.func.isRequired, - syncPercentage: PropTypes.oneOfType([ - PropTypes.number, - PropTypes.string - ]).isRequired -} - -export default LndSyncing diff --git a/app/components/LndSyncing/LndSyncing.scss b/app/components/LndSyncing/LndSyncing.scss deleted file mode 100644 index 43a3efed..00000000 --- a/app/components/LndSyncing/LndSyncing.scss +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/app/components/LndSyncing/index.js b/app/components/LndSyncing/index.js deleted file mode 100644 index 584bcb5e..00000000 --- a/app/components/LndSyncing/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import LndSyncing from './LndSyncing' - -export default LndSyncing diff --git a/app/components/Onboarding/Alias.js b/app/components/Onboarding/Alias.js new file mode 100644 index 00000000..ff288136 --- /dev/null +++ b/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 }) => ( +
+ input && input.focus()} + value={alias} + onChange={event => updateAlias(event.target.value)} + /> +
+) + +Alias.propTypes = { + alias: PropTypes.string.isRequired, + updateAlias: PropTypes.func.isRequired +} + +export default Alias diff --git a/app/components/Onboarding/Alias.scss b/app/components/Onboarding/Alias.scss new file mode 100644 index 00000000..59d32c8a --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/app/components/Onboarding/FormContainer.js b/app/components/Onboarding/FormContainer.js new file mode 100644 index 00000000..8d01c285 --- /dev/null +++ b/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 }) => ( +
+
+ +
+
+ +
+
+
+ +
+

{title}

+

{description}

+
+ +
+ {children} +
+ +
+
+
+ { + back &&
Back
+ } +
+
+ { + next &&
Next
+ } +
+
+
+
+) + + +FormContainer.propTypes = { + title: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, + + back: PropTypes.func, + next: PropTypes.func, + + children: PropTypes.object.isRequired +} + +export default FormContainer diff --git a/app/components/Onboarding/FormContainer.scss b/app/components/Onboarding/FormContainer.scss new file mode 100644 index 00000000..cdbbb737 --- /dev/null +++ b/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; + } + } + } +} + diff --git a/app/components/Onboarding/Onboarding.js b/app/components/Onboarding/Onboarding.js new file mode 100644 index 00000000..34ae5d46 --- /dev/null +++ b/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 ( + submit(alias)} + > + + + ) + default: + return + } + } + + return ( +
+ {renderStep()} +
+ ) +} + +Onboarding.propTypes = { + onboarding: PropTypes.object.isRequired, + aliasProps: PropTypes.object.isRequired, + submit: PropTypes.func.isRequired +} + +export default Onboarding diff --git a/app/components/Onboarding/Onboarding.scss b/app/components/Onboarding/Onboarding.scss new file mode 100644 index 00000000..e69de29b diff --git a/app/components/Onboarding/Syncing.js b/app/components/Onboarding/Syncing.js new file mode 100644 index 00000000..6426e6ae --- /dev/null +++ b/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 ( +
+
+ +
+
+ +
+
+

Syncing to the blockchain...

+
+
+
+

{isNaN(parseInt(syncPercentage, 10)) || syncPercentage.toString().length === 0 ? '' : `${syncPercentage}%`}

+
+
+
+ ) + } +} + +Syncing.propTypes = { + fetchBlockHeight: PropTypes.func.isRequired, + syncPercentage: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string + ]).isRequired +} + +export default Syncing diff --git a/app/components/Onboarding/Syncing.scss b/app/components/Onboarding/Syncing.scss new file mode 100644 index 00000000..6ca829a9 --- /dev/null +++ b/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; + } +} \ No newline at end of file diff --git a/app/components/Onboarding/index.js b/app/components/Onboarding/index.js new file mode 100644 index 00000000..cb4cc808 --- /dev/null +++ b/app/components/Onboarding/index.js @@ -0,0 +1,3 @@ +import Onboarding from './Onboarding' + +export default Onboarding diff --git a/app/components/Wallet/Wallet.js b/app/components/Wallet/Wallet.js index dcd0021c..50f5af84 100644 --- a/app/components/Wallet/Wallet.js +++ b/app/components/Wallet/Wallet.js @@ -32,7 +32,8 @@ class Wallet extends Component { } = this.props 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 qrCodeNum = this.state.qrCodeType === 1 ? 2 : 1 diff --git a/app/containers/Root.js b/app/containers/Root.js index 9c377f04..f2a922ec 100644 --- a/app/containers/Root.js +++ b/app/containers/Root.js @@ -5,43 +5,70 @@ import { ConnectedRouter } from 'react-router-redux' import PropTypes from 'prop-types' 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 { newAddress } from '../reducers/address' import Routes from '../routes' const mapDispatchToProps = { - fetchBlockHeight, - newAddress + updateAlias, + changeStep, + submit, + + fetchBlockHeight } const mapStateToProps = state => ({ lnd: state.lnd, - address: state.address, + onboarding: state.onboarding, 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 = ({ store, history, + lnd, - newAddress, // eslint-disable-line no-shadow - fetchBlockHeight, // eslint-disable-line no-shadow - syncPercentage, - address + onboardingProps, + syncingProps }) => { + // If we are syncing show the syncing screen + if (!onboardingProps.onboarding.onboarded) { + return + } + // If we are syncing show the syncing screen if (lnd.syncing) { - return ( - - ) + return } // Don't launch the app without gRPC connection @@ -60,13 +87,8 @@ Root.propTypes = { store: PropTypes.object.isRequired, history: PropTypes.object.isRequired, lnd: PropTypes.object.isRequired, - fetchBlockHeight: PropTypes.func.isRequired, - newAddress: PropTypes.func.isRequired, - syncPercentage: PropTypes.oneOfType([ - PropTypes.number, - PropTypes.string - ]).isRequired, - address: PropTypes.object.isRequired + onboardingProps: PropTypes.object.isRequired, + syncingProps: PropTypes.object.isRequired } -export default connect(mapStateToProps, mapDispatchToProps)(Root) +export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(Root) diff --git a/app/main.dev.js b/app/main.dev.js index 2a03bd76..bc84b8d2 100644 --- a/app/main.dev.js +++ b/app/main.dev.js @@ -8,7 +8,7 @@ * 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. * - * @flow + * */ import { app, BrowserWindow, ipcMain } from 'electron' import path from 'path' @@ -83,6 +83,19 @@ const sendLndSyncing = () => { }, 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 const sendGrpcConnected = () => { const sendGrpcConnectedInterval = setInterval(() => { @@ -126,7 +139,7 @@ const sendLndSynced = () => { } // Starts the LND node -const startLnd = () => { +const startLnd = (alias) => { let lndPath if (process.env.NODE_ENV === 'development') { @@ -146,7 +159,8 @@ const startLnd = () => { '--neutrino.connect=127.0.0.1:18333', '--autopilot.active', '--debuglevel=debug', - '--noencryptwallet' + '--noencryptwallet', + `--alias=${alias}` ] ) .on('error', error => console.log(`lnd error: ${error}`)) @@ -242,16 +256,17 @@ app.on('ready', async () => { menuBuilder.buildMenu() sendGrpcDisconnected() - // Check to see if and LND process is running + // Check to see if an LND process is running lookup({ command: 'lnd' }, (err, results) => { // There was an error checking for the LND process if (err) { throw new Error(err) } // No LND process was found if (!results.length) { - // Assign path to certs to certPath - sendLndSyncing() + // let the application know onboarding has started + sendStartOnboarding() + // Assign path to certs to certPath switch (os.platform()) { case 'darwin': certPath = path.join(homedir, 'Library/Application Support/Lnd/tls.cert') @@ -267,7 +282,12 @@ app.on('ready', async () => { } // 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 { // An LND process was found, no need to start our own console.log('LND ALREADY RUNNING') diff --git a/app/reducers/index.js b/app/reducers/index.js index 5abdb652..ea131b2e 100644 --- a/app/reducers/index.js +++ b/app/reducers/index.js @@ -1,6 +1,6 @@ -// @flow import { combineReducers } from 'redux' import { routerReducer as router } from 'react-router-redux' +import onboarding from './onboarding' import lnd from './lnd' import ticker from './ticker' import info from './info' @@ -26,6 +26,7 @@ import error from './error' const rootReducer = combineReducers({ router, + onboarding, lnd, ticker, info, diff --git a/app/reducers/ipc.js b/app/reducers/ipc.js index 8ea03f28..c8569d78 100644 --- a/app/reducers/ipc.js +++ b/app/reducers/ipc.js @@ -35,6 +35,8 @@ import { import { receiveDescribeNetwork, receiveQueryRoutes, receiveInvoiceAndQueryRoutes } from './network' +import { startOnboarding } from './onboarding' + // Import all receiving IPC event handlers and pass them into createIpc const ipc = createIpc({ lndSyncing, @@ -91,7 +93,9 @@ const ipc = createIpc({ receiveDescribeNetwork, receiveQueryRoutes, - receiveInvoiceAndQueryRoutes + receiveInvoiceAndQueryRoutes, + + startOnboarding }) export default ipc diff --git a/app/reducers/onboarding.js b/app/reducers/onboarding.js new file mode 100644 index 00000000..4fb5665f --- /dev/null +++ b/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 +} diff --git a/app/variables.scss b/app/variables.scss index 58a7b748..1da936ab 100644 --- a/app/variables.scss +++ b/app/variables.scss @@ -11,8 +11,10 @@ $darkestgrey: #999999; $bluegrey: #2A2D38; $spacegrey: #222E2B; $spaceblue: #252832; +$darkspaceblue: #1c1e26; $spaceborder: #404040; +$gold: #DEA326; $green: #0bb634; $terminalgreen: #00FF00; $red: #FF556A;