Skip to content
Permalink
Browse files

feat(wallet): ability to log out from loader

Sometimes it happens that a wallet login is successful, but the wallet
ui doesn't show due to missing dependencies such as ticker information
or required information about the lnd node that we connected to.

Provide a means to cancel the failed login attempt and return to the
launchpad.

Fix #1989
  • Loading branch information...
mrfelton committed Apr 18, 2019
1 parent 984f93b commit 9261d7bfc69262b2290779b5bc320fa3652f339f
@@ -2,10 +2,11 @@ import React from 'react'
import PropTypes from 'prop-types'
import { animated, Transition } from 'react-spring/renderprops.cjs'
import { FormattedMessage } from 'react-intl'
import { Flex } from 'rebass'
import { Box, Flex } from 'rebass'
import styled, { keyframes, withTheme } from 'styled-components'
import CloudLightning from 'components/Icon/CloudLightning'
import Heading from 'components/UI/Heading'
import X from 'components/Icon/X'
import messages from './messages'

const gradientMotion = keyframes`
@@ -20,7 +21,7 @@ const gradientMotion = keyframes`
}
`

const Container = styled(animated.div)`
const AnimationContainer = styled(animated.div)`
z-index: 1000;
position: absolute;
top: 0;
@@ -29,16 +30,18 @@ const Container = styled(animated.div)`
right: 0;
`

const FullPageGradient = styled(Flex)`
position: absolute;
const FullHeightContainer = styled(Box)`
z-index: 1000;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
`

const FullPageGradient = styled(Box)`
width: 100%;
height: 100%;
text-align: center;
background: linear-gradient(
-45deg,
${props => props.theme.colors.lightningOrange},
@@ -48,43 +51,79 @@ const FullPageGradient = styled(Flex)`
);
background-size: 400% 400%;
animation: ${gradientMotion} 10s ease infinite;
pointer-events: none;
`

const FullHeightContent = styled(Flex)`
width: 100%;
height: 100%;
`

const LoadingCloseButtonWrapper = styled(Box)`
height: 40px;
cursor: pointer;
opacity: 0.6;
&:hover: {
opacity: 1;
}
`

const LoadingCloseButton = ({ onClose }) => (
<Flex justifyContent="flex-end">
<LoadingCloseButtonWrapper onClick={onClose} p={2}>
<X height={20} width={20} />
</LoadingCloseButtonWrapper>
</Flex>
)

LoadingCloseButton.propTypes = {
onClose: PropTypes.func,
}

class LoadingBolt extends React.PureComponent {
static propTypes = {
hasClose: PropTypes.bool,
isLoading: PropTypes.bool.isRequired,
message: PropTypes.object,
onClose: PropTypes.func,
}

static defaultProps = {
message: messages.loading,
}

render() {
const { isLoading, message } = this.props
const { isLoading, message, onClose, hasClose } = this.props

return (
<Transition
enter={{ opacity: 1, pointerEvents: 'auto' }}
from={{ opacity: 1, pointerEvents: 'auto' }}
enter={{ opacity: 1 }}
from={{ opacity: 1 }}
items={isLoading}
leave={{ opacity: 0, pointerEvents: 'none' }}
leave={{ opacity: 0 }}
native
>
{show =>
show &&
(springStyles => (
<Container style={springStyles}>
<FullPageGradient alignItems="center" color="primaryText" justifyContent="center">
<Flex alignItems="center" flexDirection="column">
<AnimationContainer style={springStyles}>
<FullPageGradient color="primaryText">
<FullHeightContent
alignItems="center"
flexDirection="column"
justifyContent="center"
>
<CloudLightning height="140px" width="140px" />
<Heading.h2 mt={4}>
<FormattedMessage {...message} />
</Heading.h2>
</Flex>
</FullHeightContent>
{hasClose && (
<FullHeightContainer>
<LoadingCloseButton onClose={onClose} />
</FullHeightContainer>
)}
</FullPageGradient>
</Container>
</AnimationContainer>
))
}
</Transition>
@@ -2,32 +2,30 @@ import React, { useEffect } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Redirect } from 'react-router'
import { stopLnd } from 'reducers/lnd'
import { resetApp } from 'reducers/app'
import { setIsWalletOpen } from 'reducers/wallet'
import { logout } from 'reducers/app'
import { walletSelectors } from 'reducers/wallet'

function Logout({ resetApp, setIsWalletOpen, stopLnd }) {
function Logout({ logout, hasWallets }) {
useEffect(() => {
stopLnd()
setIsWalletOpen(false)
resetApp()
}, [resetApp, setIsWalletOpen, stopLnd])
return <Redirect to="/" />
logout()
}, [logout])
return <Redirect to={hasWallets ? '/home' : '/onboarding'} />
}

Logout.propTypes = {
resetApp: PropTypes.func.isRequired,
setIsWalletOpen: PropTypes.func.isRequired,
stopLnd: PropTypes.func.isRequired,
hasWallets: PropTypes.bool,
logout: PropTypes.func.isRequired,
}

const mapStateToProps = state => ({
hasWallets: walletSelectors.hasWallets(state),
})

const mapDispatchToProps = {
resetApp,
stopLnd,
setIsWalletOpen,
logout,
}

export default connect(
null,
mapStateToProps,
mapDispatchToProps
)(Logout)
@@ -41,7 +41,6 @@ class Root extends React.Component {
isLoading: PropTypes.bool.isRequired,
isMounted: PropTypes.bool.isRequired,
loadingMessage: PropTypes.object,

notifications: PropTypes.array.isRequired,
removeNotification: PropTypes.func.isRequired,
setMounted: PropTypes.func.isRequired,
@@ -69,6 +68,11 @@ class Root extends React.Component {
history.push('/logout')
}

canLogout = () => {
const { history } = this.props
return history.location.pathname === '/app'
}

render() {
const {
hasWallets,
@@ -97,7 +101,12 @@ class Root extends React.Component {
/>
<DialogLndCrashed />
<ModalStack />
<PageWithLoading isLoading={isLoading} loadingMessage={loadingMessage}>
<PageWithLoading
hasClose={this.canLogout()}
isLoading={isLoading}
loadingMessage={loadingMessage}
onClose={this.redirectToLogout}
>
<Switch>
<Route component={Initializer} exact path="/" />
<Route component={WalletStarter} exact path="/wallet-starter" />
@@ -12,19 +12,26 @@ const withLoading = Component =>

static propTypes = {
children: PropTypes.node,
hasClose: PropTypes.bool,
isLoading: PropTypes.bool.isRequired,
loadingMessage: PropTypes.object,
onClose: PropTypes.func,
}

/**
* Render the loading bolt ontop of the wrapped component for as long as needed.
*/
render() {
const { isLoading, loadingMessage, children, ...rest } = this.props
const { isLoading, loadingMessage, onClose, hasClose, children, ...rest } = this.props
return (
<>
<Component {...rest}>{children}</Component>
<LoadingBolt isLoading={isLoading} message={loadingMessage} />
<LoadingBolt
hasClose={hasClose}
isLoading={isLoading}
message={loadingMessage}
onClose={onClose}
/>
</>
)
}
@@ -2,7 +2,7 @@ import delay from '@zap/utils/delay'
import { send } from 'redux-electron-ipc'
import { createSelector } from 'reselect'
import { tickerSelectors } from './ticker'
import { walletSelectors } from './wallet'
import { setIsWalletOpen, walletSelectors } from './wallet'
import { stopLnd } from './lnd'

// ------------------------------------
@@ -13,6 +13,7 @@ const initialState = {
isMounted: false,
isRunning: false,
isTerminating: false,
isLoggingOut: false,
}

// ------------------------------------
@@ -21,6 +22,8 @@ const initialState = {
export const SET_LOADING = 'SET_LOADING'
export const SET_MOUNTED = 'SET_MOUNTED'
export const INIT_APP = 'INIT_APP'
export const LOGOUT = 'LOGOUT'
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS'
export const RESET_APP = 'RESET_APP'
export const TERMINATE_APP = 'TERMINATE_APP'
export const TERMINATE_APP_SUCCESS = 'TERMINATE_APP_SUCCESS'
@@ -49,6 +52,14 @@ export function resetApp() {
}
}

export const logout = () => dispatch => {
dispatch({ type: LOGOUT })
dispatch(setIsWalletOpen(false))
dispatch(stopLnd())
dispatch(resetApp())
dispatch({ type: LOGOUT_SUCCESS })
}

export const initApp = () => async (dispatch, getState) => {
dispatch({ type: INIT_APP })
// add some delay if the app is starting for the first time vs logging out of the the opened wallet
@@ -80,6 +91,8 @@ const ACTION_HANDLERS = {
[INIT_APP]: state => ({ ...state, isRunning: true }),
[SET_LOADING]: (state, { isLoading }) => ({ ...state, isLoading }),
[SET_MOUNTED]: (state, { isMounted }) => ({ ...state, isMounted }),
[LOGOUT]: state => ({ ...state, isLoggingOut: true }),
[LOGOUT_SUCCESS]: state => ({ ...state, isLoggingOut: false }),
[TERMINATE_APP]: state => ({ ...state, isTerminating: true }),
[TERMINATE_APP_SUCCESS]: state => ({ ...state, isTerminating: false, isRunning: false }),
}
@@ -64,8 +64,11 @@ export default history => {
return (state, action) => {
// Reset all reducers, except for selected reducers which should persist.
if (action.type === 'RESET_APP') {
const { app, settings, intl, theme, wallet, lnd, neutrino } = state
return appReducer({ app, settings, intl, theme, wallet, lnd, neutrino }, action)
const { app, settings, intl, theme, wallet, lnd, neutrino, router, ticker } = state
return appReducer(
{ app, settings, intl, theme, wallet, lnd, neutrino, router, ticker },
action
)
}
return appReducer(state, action)
}

0 comments on commit 9261d7b

Please sign in to comment.
You can’t perform that action at this time.