notifications
This commit is contained in:
parent
59b0d446f2
commit
d0becf7746
|
@ -0,0 +1,43 @@
|
|||
// @flow
|
||||
|
||||
export type NOTIFICATION_LEVEL = 'danger' | 'warning' | 'success' | 'info';
|
||||
|
||||
export type Notification = {
|
||||
level: NOTIFICATION_LEVEL,
|
||||
msg: string,
|
||||
duration?: number
|
||||
};
|
||||
|
||||
export type ShowNotificationAction = {
|
||||
type: 'SHOW_NOTIFICATION',
|
||||
payload: Notification
|
||||
};
|
||||
|
||||
type CloseNotificationAction = {
|
||||
type: 'CLOSE_NOTIFICATION',
|
||||
payload: Notification
|
||||
};
|
||||
|
||||
export type NotificationsAction = ShowNotificationAction | CloseNotificationAction;
|
||||
|
||||
export function showNotification(
|
||||
level: NOTIFICATION_LEVEL = 'info',
|
||||
msg: string,
|
||||
duration?: number
|
||||
): ShowNotificationAction {
|
||||
return {
|
||||
type: 'SHOW_NOTIFICATION',
|
||||
payload: {
|
||||
level,
|
||||
msg,
|
||||
duration
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function closeNotification(notification: Notification): CloseNotificationAction {
|
||||
return {
|
||||
type: 'CLOSE_NOTIFICATION',
|
||||
payload: notification
|
||||
};
|
||||
}
|
|
@ -44,12 +44,16 @@
|
|||
}
|
||||
}
|
||||
|
||||
.alert.popup {
|
||||
.alerts-container {
|
||||
position: fixed;
|
||||
border-radius: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.alert.popup {
|
||||
position: relative;
|
||||
border-radius: 0;
|
||||
padding: @space 0;
|
||||
box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.33);
|
||||
transition: @transition;
|
||||
|
@ -153,4 +157,3 @@
|
|||
background-image: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20viewBox%3D%220%200%20512%20512%22%20width%3D%2232%22%20height%3D%2232%22%3E%3Cpath%20d%3D%22M505.403%20406.394L295.389%2058.102c-8.274-13.721-23.367-22.245-39.39-22.245s-31.116%208.524-39.391%2022.246L6.595%20406.394c-8.551%2014.182-8.804%2031.95-.661%2046.37%208.145%2014.42%2023.491%2023.378%2040.051%2023.378h420.028c16.56%200%2031.907-8.958%2040.052-23.379%208.143-14.421%207.89-32.189-.662-46.369zm-28.364%2029.978a12.684%2012.684%200%200%201-11.026%206.436H45.985a12.68%2012.68%200%200%201-11.025-6.435%2012.683%2012.683%200%200%201%20.181-12.765L245.156%2075.316A12.732%2012.732%200%200%201%20256%2069.192c4.41%200%208.565%202.347%2010.843%206.124l210.013%20348.292a12.677%2012.677%200%200%201%20.183%2012.764z%22%20fill%3D%22%23FFF%22/%3E%3Cpath%20d%3D%22M256.154%20173.005c-12.68%200-22.576%206.804-22.576%2018.866%200%2036.802%204.329%2089.686%204.329%20126.489.001%209.587%208.352%2013.607%2018.248%2013.607%207.422%200%2017.937-4.02%2017.937-13.607%200-36.802%204.329-89.686%204.329-126.489%200-12.061-10.205-18.866-22.267-18.866zM256.465%20353.306c-13.607%200-23.814%2010.824-23.814%2023.814%200%2012.68%2010.206%2023.814%2023.814%2023.814%2012.68%200%2023.505-11.134%2023.505-23.814%200-12.99-10.826-23.814-23.505-23.814z%22%20fill%3D%22%23FFF%22/%3E%3C/svg%3E);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { closeNotification } from 'actions/notifications';
|
||||
import type { Notification } from 'actions/notifications';
|
||||
|
||||
function NotificationRow(props: {
|
||||
notification: Notification,
|
||||
onClose: (n: Notification) => void
|
||||
}) {
|
||||
const { msg, level } = props.notification;
|
||||
let klass = '';
|
||||
|
||||
switch (level) {
|
||||
case 'danger':
|
||||
klass = 'alert-danger';
|
||||
break;
|
||||
case 'success':
|
||||
klass = 'alert-success';
|
||||
break;
|
||||
case 'warning':
|
||||
klass = 'alert-warning';
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`alert popup ${klass} animated-show-hide`} role="alert" aria-live="assertive">
|
||||
<span className="sr-only">{level}</span>
|
||||
<div className="container" dangerouslySetInnerHTML={{ __html: msg }} />
|
||||
<i
|
||||
tabIndex="0"
|
||||
aria-label="dismiss"
|
||||
className="icon-close"
|
||||
onClick={() => props.onClose(props.notification)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export class Notifications extends React.Component {
|
||||
props: {
|
||||
notifications: Notification[],
|
||||
closeNotification: (n: Notification) => void
|
||||
};
|
||||
render() {
|
||||
if (!this.props.notifications.length) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className="alerts-container">
|
||||
{this.props.notifications.map((n, i) =>
|
||||
<NotificationRow key={`${n.level}-${i}`} notification={n} onClose={this.props.closeNotification} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
notifications: state.notifications
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, { closeNotification })(Notifications);
|
|
@ -3,8 +3,10 @@ import React, { Component } from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import { Footer, Header } from 'components';
|
||||
import PropTypes from 'prop-types';
|
||||
import Notifications from './Notifications';
|
||||
|
||||
import { CHANGE_LANGUAGE, CHANGE_NODE } from 'actions/config';
|
||||
import {showNotification} from 'actions/notifications'
|
||||
|
||||
class App extends Component {
|
||||
constructor(props) {
|
||||
|
@ -24,9 +26,19 @@ class App extends Component {
|
|||
changeLanguage: PropTypes.func,
|
||||
|
||||
changeNode: PropTypes.func,
|
||||
nodeSelection: PropTypes.object
|
||||
nodeSelection: PropTypes.object,
|
||||
|
||||
showNotification: PropTypes.func
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
// 0 is forever
|
||||
this.props.showNotification('info', 'I am in <b>App/index</b>', 0)
|
||||
this.props.showNotification('danger', 'Danger', 5000)
|
||||
this.props.showNotification('warning', 'Warning', 6000)
|
||||
this.props.showNotification('success', 'Success', 7000)
|
||||
}
|
||||
|
||||
render() {
|
||||
let {
|
||||
children,
|
||||
|
@ -54,7 +66,7 @@ class App extends Component {
|
|||
</div>
|
||||
<Footer />
|
||||
</main>
|
||||
|
||||
<Notifications />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -77,6 +89,9 @@ function mapDispatchToProps(dispatch) {
|
|||
},
|
||||
changeLanguage: (i: any) => {
|
||||
dispatch(CHANGE_LANGUAGE(i));
|
||||
},
|
||||
showNotification: (level, msg, duration) => {
|
||||
dispatch(showNotification(level, msg, duration))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,24 +4,26 @@ import {syncHistoryWithStore, routerMiddleware} from 'react-router-redux'
|
|||
import {composeWithDevTools} from 'redux-devtools-extension'
|
||||
import Perf from 'react-addons-perf'
|
||||
import {createStore, applyMiddleware} from 'redux'
|
||||
import thunk from 'redux-thunk'
|
||||
import RootReducer from './reducers'
|
||||
import {Root} from 'components'
|
||||
import {Routing, history} from './routing'
|
||||
import {createLogger} from 'redux-logger'
|
||||
import createSagaMiddleware from 'redux-saga'
|
||||
import notificationsSaga from './sagas/notifications'
|
||||
|
||||
// application styles
|
||||
import 'assets/styles/etherwallet-master.less'
|
||||
|
||||
const sagaMiddleware = createSagaMiddleware()
|
||||
|
||||
const configureStore = () => {
|
||||
let thunkApplied = applyMiddleware(thunk);
|
||||
let sagaApplied = applyMiddleware(sagaMiddleware);
|
||||
let store;
|
||||
let middleware;
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
window.Perf = Perf;
|
||||
thunkApplied = composeWithDevTools(thunkApplied);
|
||||
sagaApplied = composeWithDevTools(sagaApplied);
|
||||
const logger = createLogger({
|
||||
collapsed: true
|
||||
});
|
||||
|
@ -30,8 +32,8 @@ const configureStore = () => {
|
|||
middleware = applyMiddleware(routerMiddleware(history));
|
||||
}
|
||||
|
||||
store = createStore(RootReducer, thunkApplied, middleware);
|
||||
|
||||
store = createStore(RootReducer, sagaApplied, middleware);
|
||||
sagaMiddleware.run(notificationsSaga)
|
||||
return store
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// @flow
|
||||
import * as generateWallet from './generateWallet'
|
||||
import * as config from './config'
|
||||
import * as swap from './swap'
|
||||
import * as notifications from './notifications'
|
||||
|
||||
import { reducer as formReducer } from 'redux-form'
|
||||
import {combineReducers} from 'redux';
|
||||
|
@ -10,6 +12,7 @@ export default combineReducers({
|
|||
...generateWallet,
|
||||
...config,
|
||||
...swap,
|
||||
...notifications,
|
||||
form: formReducer,
|
||||
routing: routerReducer
|
||||
})
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
// @flow
|
||||
import type { NotificationsAction, Notification } from 'actions/notifications';
|
||||
|
||||
type State = Notification[];
|
||||
|
||||
const initialState: State = [];
|
||||
|
||||
export function notifications(state: State = initialState, action: NotificationsAction): State {
|
||||
switch (action.type) {
|
||||
case 'SHOW_NOTIFICATION':
|
||||
return state.concat(action.payload);
|
||||
case 'CLOSE_NOTIFICATION':
|
||||
state = [...state]
|
||||
state.splice(state.indexOf(action.payload), 1);
|
||||
return state
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
// @flow
|
||||
import { takeEvery, put } from 'redux-saga/effects';
|
||||
import {delay} from 'redux-saga'
|
||||
import {closeNotification} from 'actions/notifications'
|
||||
import type {ShowNotificationAction} from 'actions/notifications'
|
||||
|
||||
function* handleNotification(action: ShowNotificationAction) {
|
||||
const {duration} = action.payload
|
||||
// show forever
|
||||
if (duration === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// FIXME
|
||||
yield delay(duration || 5000)
|
||||
yield put(closeNotification(action.payload))
|
||||
}
|
||||
|
||||
export default function* notificationsSaga() {
|
||||
yield takeEvery('SHOW_NOTIFICATION', handleNotification);
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
"redux": "^3.6.0",
|
||||
"redux-form": "^6.6.3",
|
||||
"redux-logger": "^3.0.1",
|
||||
"redux-thunk": "^2.2.0",
|
||||
"redux-saga": "^0.15.3",
|
||||
"store2": "^2.5.0",
|
||||
"whatwg-fetch": "^2.0.2"
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue