Skip to content
This repository has been archived by the owner on Nov 17, 2023. It is now read-only.

Commit

Permalink
fix(ui): ensure error messages are visible
Browse files Browse the repository at this point in the history
Enhance the error reducer and GlobalError component to allow for
multiple messages and ensure that error messages are visible for 10
seconds or until they are manually cleared.

Fix #1206
  • Loading branch information
mrfelton committed Jan 7, 2019
1 parent 0967c59 commit af86dd0
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 69 deletions.
68 changes: 24 additions & 44 deletions app/components/GlobalError/GlobalError.js
Expand Up @@ -7,57 +7,37 @@ import { Notification } from 'components/UI'

class GlobalError extends React.Component {
static propTypes = {
error: PropTypes.oneOfType([PropTypes.string, PropTypes.array, PropTypes.object]),
errors: PropTypes.array,
clearError: PropTypes.func.isRequired
}

componentDidUpdate(prevProps) {
const { clearError, error } = this.props
if (!prevProps.error && error) {
setTimeout(clearError, 10000)
}
}

render() {
const { error, clearError } = this.props

let errors = []

if (error) {
if (Array.isArray(error)) {
errors = error
} else if (typeof error === 'string') {
errors.push(error)
} else if (typeof error === 'object') {
Object.keys(error).forEach(key => {
errors.push(`${key}: ${error[key]}`)
})
}
}
const { errors, clearError } = this.props

return (
<Transition
native
items={error}
from={{ opacity: 0 }}
enter={{ opacity: 1 }}
leave={{ opacity: 0 }}
>
{show =>
show &&
(springStyles => (
<Box mt="22px" px={3} width={1} css={{ position: 'absolute', 'z-index': '99999' }}>
<animated.div style={springStyles}>
{errors.map(error => (
<Notification key={error} variant="error" onClick={clearError}>
{errorToUserFriendly(error)}
<Box mt="22px" px={3} width={1} css={{ position: 'absolute', 'z-index': '99999' }}>
{errors.map(item => (
<Transition
native
key={item.id}
items={item}
from={{ opacity: 0 }}
enter={{ opacity: 1 }}
leave={{ opacity: 0 }}
>
{show =>
show &&
(springStyles => (
<animated.div style={springStyles}>
<Notification variant="error" onClick={() => clearError(item.id)} mb={2}>
{errorToUserFriendly(item.message)}
</Notification>
))}
</animated.div>
</Box>
))
}
</Transition>
</animated.div>
))
}
</Transition>
))}
</Box>
)
}
}
Expand Down
4 changes: 2 additions & 2 deletions app/components/Home/WalletLauncher.js
Expand Up @@ -29,7 +29,7 @@ class WalletLauncher extends React.Component {

// If there are lnd start errors, show as a global error.
if (startLndError) {
setError(startLndError)
Object.keys(startLndError).forEach(key => setError(startLndError[key]))
setStartLndError(null)
}
}
Expand All @@ -50,7 +50,7 @@ class WalletLauncher extends React.Component {

// If we got lnd start errors, show as a global error.
if (startLndError && !prevProps.startLndError) {
setError(startLndError)
Object.keys(startLndError).forEach(key => setError(startLndError.startLndError[key]))
setStartLndError(null)
}

Expand Down
14 changes: 5 additions & 9 deletions app/components/UI/Notification.js
Expand Up @@ -27,23 +27,18 @@ class Notification extends React.Component {
variant: PropTypes.string
}

constructor(props) {
super(props)
this.state = { hover: false }
this.hoverOn = this.hoverOn.bind(this)
this.hoverOff = this.hoverOff.bind(this)
}
state = { hover: false }

hoverOn() {
hoverOn = () => {
this.setState({ hover: true })
}

hoverOff() {
hoverOff = () => {
this.setState({ hover: false })
}

render() {
const { children, processing, variant } = this.props
const { children, processing, variant, ...rest } = this.props
const { hover } = this.state
return (
<Card
Expand All @@ -55,6 +50,7 @@ class Notification extends React.Component {
{...this.props}
onMouseEnter={this.hoverOn}
onMouseLeave={this.hoverOff}
{...rest}
>
<Flex justifyContent="space-between">
<Flex alignItems="center">
Expand Down
8 changes: 4 additions & 4 deletions app/containers/Root.js
Expand Up @@ -34,7 +34,7 @@ class Root extends React.Component {
hasWallets: PropTypes.bool,
clearError: PropTypes.func.isRequired,
theme: PropTypes.object,
error: PropTypes.oneOfType([PropTypes.string, PropTypes.array, PropTypes.object]),
errors: PropTypes.array.isRequired,
history: PropTypes.object.isRequired,
isLoading: PropTypes.bool.isRequired,

Expand Down Expand Up @@ -76,7 +76,7 @@ class Root extends React.Component {
}

render() {
const { hasWallets, clearError, theme, error, history, isLoading } = this.props
const { hasWallets, clearError, theme, errors, history, isLoading } = this.props

// Wait until we have loaded essential data before displaying anything.
if (!theme) {
Expand All @@ -89,7 +89,7 @@ class Root extends React.Component {
<React.Fragment>
<GlobalStyle />
<Titlebar />
<GlobalError error={error} clearError={clearError} />
<GlobalError errors={errors} clearError={clearError} />
<PageWithLoading isLoading={isLoading}>
<Switch>
<Route exact path="/" component={Initializer} />
Expand Down Expand Up @@ -125,7 +125,7 @@ class Root extends React.Component {

const mapStateToProps = state => ({
hasWallets: walletSelectors.hasWallets(state),
error: errorSelectors.getErrorState(state),
errors: errorSelectors.getErrorState(state),
theme: themeSelectors.currentThemeSettings(state),
isLoading: appSelectors.isLoading(state) || state.lnd.startingLnd,
isMounted: appSelectors.isMounted(state)
Expand Down
39 changes: 29 additions & 10 deletions app/reducers/error.js
Expand Up @@ -2,45 +2,64 @@
// Initial State
// ------------------------------------
const initialState = {
error: null
errors: []
}

// ------------------------------------
// Constants
// ------------------------------------
const ERROR_TIMEOUT = 10000
export const SET_ERROR = 'SET_ERROR'
export const CLEAR_ERROR = 'CLEAR_ERROR'

// ------------------------------------
// Actions
// ------------------------------------
export function setError(error) {
return {
type: SET_ERROR,
error
export const setError = error => dispatch => {
// Cooerce the error into an error item with a random id that we can use for clearning the error later.
const errorItem = {
id: Math.random()
.toString(36)
.substring(7),
message: error
}

// Set a timer to clear the error after 10 seconds.
setTimeout(() => dispatch(clearError(errorItem.id)), ERROR_TIMEOUT)

return dispatch({
type: SET_ERROR,
errorItem
})
}

export function clearError() {
export function clearError(id) {
return {
type: CLEAR_ERROR
type: CLEAR_ERROR,
id
}
}

// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
[SET_ERROR]: (state, { error }) => ({ ...state, error }),
[CLEAR_ERROR]: () => initialState
[SET_ERROR]: (state, { errorItem }) => ({
...state,
errors: [...state.errors, errorItem]
}),
[CLEAR_ERROR]: (state, { id }) => ({
...state,
errors: state.errors.filter(item => item.id !== id)
})
}

// ------------------------------------
// Selectors
// ------------------------------------

const errorSelectors = {}
errorSelectors.getErrorState = state => state.error.error
errorSelectors.getErrorState = state => state.error.errors

export { errorSelectors }

Expand Down

0 comments on commit af86dd0

Please sign in to comment.