Commit e11735d7 authored by Andrew Hrdy's avatar Andrew Hrdy
Browse files

Cache the alert ID's that have been viewed, show number of unread alerts in corner.

parent c6eb5df3
...@@ -3,6 +3,7 @@ export const SET_FACILITIES = 'SET_FACILITIES'; ...@@ -3,6 +3,7 @@ export const SET_FACILITIES = 'SET_FACILITIES';
export const GET_FACILITIES = 'GET_FACILITIES'; export const GET_FACILITIES = 'GET_FACILITIES';
export const SET_ALERTS = 'SET_ALERTS'; export const SET_ALERTS = 'SET_ALERTS';
export const GET_ALERTS = 'GET_ALERTS'; export const GET_ALERTS = 'GET_ALERTS';
export const VIEW_ALERT = 'VIEW_ALERT';
export const SET_SELECTED_FACILITY = 'SET_SELECTED_FACILITY'; export const SET_SELECTED_FACILITY = 'SET_SELECTED_FACILITY';
export const SET_SEARCH_TERM = 'SET_SEARCH_TERM'; export const SET_SEARCH_TERM = 'SET_SEARCH_TERM';
export const SET_CAMPUS_REGION = 'SET_CAMPUS_REGION'; export const SET_CAMPUS_REGION = 'SET_CAMPUS_REGION';
......
import {GET_ALERTS, GET_FACILITIES, SET_ALERTS, SET_FACILITIES, SORT_BY_FAVORITES} from './action-types'; import {GET_ALERTS, GET_FACILITIES, SET_ALERTS, SET_FACILITIES, SORT_BY_FAVORITES, VIEW_ALERT} from './action-types';
const API_GET_FACILITIES = 'https://api.srct.gmu.edu/whatsopen/v2/facilities/'; const API_GET_FACILITIES = 'https://api.srct.gmu.edu/whatsopen/v2/facilities/';
const API_GET_ALERTS = 'https://api.srct.gmu.edu/whatsopen/v2/alerts/?ordering=urgency_tag'; const API_GET_ALERTS = 'https://api.srct.gmu.edu/whatsopen/v2/alerts/?ordering=urgency_tag';
...@@ -59,13 +59,43 @@ export const getAlerts = () => (dispatch) => { ...@@ -59,13 +59,43 @@ export const getAlerts = () => (dispatch) => {
return res.json(); return res.json();
}).then((json) => { }).then((json) => {
dispatch(setAlerts(JSON.stringify(json))); dispatch(setAlerts(json));
}); });
}; };
export const setAlerts = (alerts) => { export const setAlerts = (alerts) => {
const viewedAlerts = JSON.parse(localStorage.getItem('viewedAlerts'));
if (viewedAlerts) {
alerts.forEach((alert) => {
alert['viewed'] = viewedAlerts.includes(alert.id);
});
}
return { return {
type: SET_ALERTS, type: SET_ALERTS,
alerts: JSON.parse(alerts) alerts: alerts
};
};
export const viewAlert = (alert) => {
try {
let viewedAlerts = JSON.parse(localStorage.getItem('viewedAlerts'));
if (!viewedAlerts) {
viewedAlerts = [];
}
if (!viewedAlerts.includes(alert.id)) {
viewedAlerts.push(alert.id);
}
localStorage.setItem('viewedAlerts', JSON.stringify(viewedAlerts));
} catch (e) {
//Empty
}
return {
type: VIEW_ALERT,
alert
}; };
}; };
...@@ -39,8 +39,10 @@ class CustomAppBar extends React.Component { ...@@ -39,8 +39,10 @@ class CustomAppBar extends React.Component {
What's Open What's Open
</Typography> </Typography>
</div> </div>
<AlertContainer/> <div className={'app-bar-right-section'}>
<div className={'app-bar-search-menu'}> <div className={'app-bar-alert-container'}>
<AlertContainer/>
</div>
<SearchBar onSearchExpand={() => this.setState({ <SearchBar onSearchExpand={() => this.setState({
isSearchExpanded: true isSearchExpanded: true
})} })}
......
import React from 'react'; import React from 'react';
import {findDOMNode} from 'react-dom'; import {findDOMNode} from 'react-dom';
import Button from 'material-ui/Button'; import IconButton from 'material-ui/IconButton';
import Popover from 'material-ui/Popover'; import Popover from 'material-ui/Popover';
import Alert from '../components/Alert'; import Alert from '../components/Alert';
import NotificationsIcon from 'material-ui-icons/Notifications'; import NotificationsIcon from 'material-ui-icons/Notifications';
import Typography from 'material-ui/Typography';
import {connect} from 'react-redux'; import {connect} from 'react-redux';
import {viewAlert} from '../actions/api';
class AlertContainer extends React.Component { class AlertContainer extends React.Component {
...@@ -20,6 +22,7 @@ class AlertContainer extends React.Component { ...@@ -20,6 +22,7 @@ class AlertContainer extends React.Component {
this.setState({ this.setState({
isOpen: true isOpen: true
}); });
this.props.alerts.forEach((alert) => this.props.viewAlert(alert));
}; };
handleClose = () => { handleClose = () => {
...@@ -44,12 +47,20 @@ class AlertContainer extends React.Component { ...@@ -44,12 +47,20 @@ class AlertContainer extends React.Component {
render() { render() {
const {alerts} = this.props; const {alerts} = this.props;
const activeAlerts = alerts.filter(this.isAlertActive);
return ( return (
<div> <div>
<Button fab mini color={'primary'} ref={this.handleBtnRef} onClick={this.handleOpen}> <IconButton color={'primary'} ref={this.handleBtnRef} onClick={this.handleOpen}>
<NotificationsIcon/> {activeAlerts.filter((alert) => !alert.viewed).length !== 0 &&
</Button> <span className={'alert-container-number'}>
<Typography type={'caption'} className={'alert-container-number-text'}>
{activeAlerts.length}
</Typography>
</span>}
<NotificationsIcon>
</NotificationsIcon>
</IconButton>
<Popover <Popover
open={this.state.isOpen} open={this.state.isOpen}
anchorEl={this.state.anchorEl} anchorEl={this.state.anchorEl}
...@@ -63,9 +74,9 @@ class AlertContainer extends React.Component { ...@@ -63,9 +74,9 @@ class AlertContainer extends React.Component {
}} }}
onClose={this.handleClose}> onClose={this.handleClose}>
<div className={'alert-container-popover'}> <div className={'alert-container-popover'}>
{alerts.filter(this.isAlertActive).map((alert) => { {activeAlerts.map((alert) => {
return ( return (
<Alert key={alert.id} alert={alert} /> <Alert key={alert.id} alert={alert}/>
); );
})} })}
</div> </div>
...@@ -82,4 +93,6 @@ function mapStateToProps(state) { ...@@ -82,4 +93,6 @@ function mapStateToProps(state) {
}; };
} }
export default connect(mapStateToProps)(AlertContainer); export default connect(mapStateToProps, {
viewAlert
})(AlertContainer);
import {GET_ALERTS, GET_FACILITIES, SET_ALERTS, SET_FACILITIES, SORT_BY_FAVORITES} from '../actions/action-types'; import {
GET_ALERTS,
GET_FACILITIES,
SET_ALERTS,
SET_FACILITIES,
SORT_BY_FAVORITES,
VIEW_ALERT
} from '../actions/action-types';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
const defaultFacilityState = { const defaultFacilityState = {
...@@ -51,6 +58,16 @@ export const alerts = (state = [], action) => { ...@@ -51,6 +58,16 @@ export const alerts = (state = [], action) => {
return state; return state;
case SET_ALERTS: case SET_ALERTS:
return [...state, ...action.alerts]; return [...state, ...action.alerts];
case VIEW_ALERT:
const index = state.findIndex((alert) => alert.id === action.alert.id);
const alert = cloneDeep(state[index]);
alert.viewed = true;
const stateClone = state.slice();
stateClone[index] = alert;
return stateClone;
default: default:
return state; return state;
} }
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
color: #354052; color: #354052;
} }
.app-bar-logo-name, .app-bar-search-menu { .app-bar-logo-name, .app-bar-right-section {
display: flex; display: flex;
align-items: center; align-items: center;
} }
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
} }
.app-bar-search-expanded { .app-bar-search-expanded {
.app-bar-logo, .app-bar-title, .app-bar-menu-button, .app-bar-link-container { .app-bar-logo, .app-bar-title, .app-bar-alert-container, .app-bar-menu-button, .app-bar-link-container {
display: none !important; display: none !important;
} }
} }
......
.alert-container-popover { .alert-container-popover {
max-width: 500px; max-width: 450px;
}
.alert-container-number {
position: absolute;
top: 3px;
right: 5px;
background-color: #FF0000;
padding: 4px;
border-radius: 100%;
height: 10px;
width: 10px;
}
.alert-container-number-text {
color: rgba(255, 255, 255, 1) !important;
} }
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment