Merge pull request #803 from Ducz0r/lm-sci-1599

Add global flash notifications/alerts [SCI-1599]
This commit is contained in:
Luka Murn 2017-10-03 11:15:27 +02:00 committed by GitHub
commit f8fc2737d5
12 changed files with 282 additions and 4 deletions

View file

@ -0,0 +1,81 @@
import React, { Component } from "react";
import styled from "styled-components";
import { string, number, func } from "prop-types";
import { Grid, Row, Col } from "react-bootstrap";
const Wrapper = styled.div`
margin-bottom: 0;
`;
class Alert extends Component {
static alertClass(type) {
const classes = {
error: "alert-danger",
alert: "alert-warning",
notice: "alert-info",
success: "alert-success"
};
return classes[type] || classes.success;
}
static glyphiconClass(type) {
const classes = {
error: "glyphicon-exclamation-sign",
alert: "glyphicon-exclamation-sign",
notice: "glyphicon-info-sign",
success: "glyphicon-ok-sign"
};
return classes[type] || classes.success;
}
componentDidMount() {
this.timer = setTimeout(
this.props.onClose,
this.props.timeout
);
}
componentWillUnmount() {
clearTimeout(this.timer);
}
render() {
const alertClassName =
`alert
${Alert.alertClass(this.props.type)}
alert-dismissable
alert-floating`;
const glyphiconClassName =
`glyphicon
${Alert.glyphiconClass(this.props.type)}`;
return (
<Wrapper className={alertClassName}>
<Grid>
<Row>
<Col>
<button type="button"
className="close"
data-dismiss="alert"
aria-label="Close"
onClick={this.props.onClose}>
<span aria-hidden="true">×</span>
</button>
<span className={glyphiconClassName} />
<span>&nbsp;{this.props.message}</span>
</Col>
</Row>
</Grid>
</Wrapper>
);
}
}
Alert.propTypes = {
message: string.isRequired,
type: string.isRequired,
timeout: number.isRequired,
onClose: func.isRequired
};
export default Alert;

View file

@ -0,0 +1,65 @@
import React, { Component } from "react";
import { connect } from "react-redux";
import styled from "styled-components";
import TransitionGroup from 'react-transition-group/TransitionGroup';
import CSSTransition from 'react-transition-group/CSSTransition';
import { shape, arrayOf, string, number, func } from "prop-types";
import { clearAlert } from "../actions/AlertsActions";
import Alert from "./components/Alert";
const Wrapper = styled.div`
position: absolute;
z-index: 1000;
width: 100%;
`;
class AlertsContainer extends Component {
constructor(props) {
super(props);
this.renderAlert = this.renderAlert.bind(this);
}
renderAlert(alert) {
return (
<Alert message={alert.message}
type={alert.type}
timeout={alert.timeout}
onClose={() => this.props.clearAlert(alert.id)}
/>
);
}
render() {
return (
<Wrapper>
<TransitionGroup>
{this.props.alerts.map((alert) =>
<CSSTransition key={alert.id}
timeout={500}
classNames="alert-animated">
{this.renderAlert(alert)}
</CSSTransition>
)}
</TransitionGroup>
</Wrapper>
);
}
}
AlertsContainer.propTypes = {
alerts: arrayOf(
shape({
message: string.isRequired,
type: string.isRequired,
id: string.isRequired,
timeout: number,
onClose: func
}).isRequired
).isRequired,
clearAlert: func.isRequired
}
const mapStateToProps = ({ alerts }) => ({ alerts });
export default connect(mapStateToProps, { clearAlert })(AlertsContainer);

View file

@ -20,6 +20,7 @@ import UserAccountDropdown from "./components/UserAccountDropdown";
const StyledNavbar = styled(Navbar)`
background-color: ${WHITE_COLOR};
border-color: ${BORDER_GRAY_COLOR};
margin-bottom: 0;
`;
const StyledBrand = styled.a`

View file

@ -0,0 +1,29 @@
import shortid from "shortid";
import {
ADD_ALERT,
CLEAR_ALERT,
CLEAR_ALL_ALERTS
} from "../../config/action_types";
export function addAlert(message,
type,
id = shortid.generate(),
timeout = 5000) {
return {
payload: {
message,
type,
id,
timeout
},
type: ADD_ALERT
};
}
export function clearAlert(id) {
return { payload: id, type: CLEAR_ALERT }
}
export function clearAllAlerts() {
return { type: CLEAR_ALL_ALERTS };
}

View file

@ -0,0 +1,31 @@
import {
ADD_ALERT,
CLEAR_ALERT,
CLEAR_ALL_ALERTS
} from "../../config/action_types";
export const alerts = (
state = [],
action
) => {
switch(action.type) {
case ADD_ALERT:
return [
...state,
{
message: action.payload.message,
type: action.payload.type,
id: action.payload.id,
timeout: action.payload.timeout
}
];
case CLEAR_ALERT:
return state.filter((alert) => (
alert.id !== action.payload
));
case CLEAR_ALL_ALERTS:
return [];
default:
return state;
}
};

View file

@ -35,3 +35,8 @@ export const UPDATE_TEAM_DESCRIPTION_MODAL = "UPDATE_TEAM_DESCRIPTION_MODAL";
// spinner
export const SPINNER_ON = "SPINNER_ON";
export const SPINNER_OFF = "SPINNER_OFF";
// alerts
export const ADD_ALERT = "ADD_ALERT";
export const CLEAR_ALERT = "CLEAR_ALERT";
export const CLEAR_ALL_ALERTS = "CLEAR_ALL_ALERTS";

View file

@ -6,11 +6,13 @@ import {
} from "../components/reducers/TeamReducers";
import { globalActivities } from "../components/reducers/ActivitiesReducers";
import { currentUser } from "../components/reducers/UsersReducer";
import { alerts } from "../components/reducers/AlertsReducers";
export default combineReducers({
current_team: setCurrentTeam,
all_teams: getListOfTeams,
global_activities: globalActivities,
current_user: currentUser,
showLeaveTeamModal
showLeaveTeamModal,
alerts
});

View file

@ -3,11 +3,13 @@ import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import { IntlProvider, addLocaleData } from "react-intl";
import enLocaleData from "react-intl/locale-data/en";
import styled from "styled-components";
import { flattenMessages } from "./config/locales/utils";
import messages from "./config/locales/messages";
import store from "./config/store";
import Spinner from "./components/Spinner";
import AlertsContainer from "./components/AlertsContainer";
import ModalsContainer from "./components/ModalsContainer";
import SettingsPage from "./scenes/SettingsPage";
import Navigation from "./components/Navigation";
@ -15,14 +17,22 @@ import Navigation from "./components/Navigation";
addLocaleData([...enLocaleData]);
const locale = "en-US";
export default () =>
const ContentWrapper = styled.div`
margin-top: 15px;
`;
const ScinoteApp = () =>
<Provider store={store}>
<IntlProvider locale={locale} messages={flattenMessages(messages[locale])}>
<IntlProvider locale={locale}
messages={flattenMessages(messages[locale])}>
<div>
<BrowserRouter>
<div>
<Navigation />
<SettingsPage />
<AlertsContainer />
<ContentWrapper>
<SettingsPage />
</ContentWrapper>
</div>
</BrowserRouter>
@ -31,3 +41,5 @@ export default () =>
</div>
</IntlProvider>
</Provider>;
export default ScinoteApp;

View file

@ -0,0 +1,30 @@
.alert-animated-enter {
opacity: .01;
&.alert-animated-enter-active {
opacity: 1;
transition: opacity 150ms ease-in;
}
}
.alert-animated-exit {
opacity: 1;
padding-bottom: 15px;
padding-top: 15px;
margin-bottom: 20px;
height: 50px;
&.alert-animated-exit-active {
overflow: hidden;
opacity: .01;
padding-top: 0;
padding-bottom: 0;
margin-bottom: 0;
height: 0;
transition: opacity 300ms ease-in,
padding-top 500ms ease-in,
padding-bottom 500ms ease-in,
margin-bottom 500ms ease-in,
height 500ms ease-in;
}
}

View file

@ -1,4 +1,5 @@
@import 'constants';
@import 'animations';
@import 'react-bootstrap-timezone-picker/dist/react-bootstrap-timezone-picker.min.css';
@import '~react-bootstrap-table/dist/react-bootstrap-table.min';
@import 'react-tagsinput/react-tagsinput.css';

View file

@ -69,6 +69,7 @@
"react-bootstrap-timezone-picker": "^1.0.11",
"react-data-grid": "^2.0.2",
"react-tagsinput": "^3.17.0",
"react-transition-group": "^2.2.0",
"react-dom": "^15.6.1",
"react-intl": "^2.3.0",
"react-intl-redux": "^0.6.0",
@ -82,6 +83,7 @@
"redux-thunk": "^2.2.0",
"resolve-url-loader": "^2.1.0",
"sass-loader": "^6.0.6",
"shortid": "^2.2.8",
"style-loader": "^0.18.2",
"styled-components": "^2.1.1",
"webpack": "^3.2.0",

View file

@ -1156,6 +1156,10 @@ center-align@^0.1.1:
align-text "^0.1.3"
lazy-cache "^1.0.3"
chain-function@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc"
chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
@ -4825,6 +4829,17 @@ react-timezone@^0.2.0:
dependencies:
classnames "^2.2.1"
react-transition-group@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.2.0.tgz#793bf8cb15bfe91b3101b24bce1c1d2891659575"
dependencies:
chain-function "^1.0.0"
classnames "^2.2.5"
dom-helpers "^3.2.0"
loose-envify "^1.3.1"
prop-types "^15.5.8"
warning "^3.0.0"
react@^15.6.1:
version "15.6.1"
resolved "https://registry.yarnpkg.com/react/-/react-15.6.1.tgz#baa8434ec6780bde997cdc380b79cd33b96393df"
@ -5298,6 +5313,10 @@ shelljs@^0.7.5:
interpret "^1.0.0"
rechoir "^0.6.2"
shortid@^2.2.8:
version "2.2.8"
resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.8.tgz#033b117d6a2e975804f6f0969dbe7d3d0b355131"
signal-exit@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"