Skip to content

Commit

Permalink
feat(notificcations): implement opt in or out of notifications
Browse files Browse the repository at this point in the history
- ensure users can opt in or out of notifications
- write tests for the functionality
[Delivers #165305236]
  • Loading branch information
AlvinMugambi committed Jun 20, 2019
1 parent 621e1cf commit 6322379
Show file tree
Hide file tree
Showing 12 changed files with 535 additions and 8 deletions.
79 changes: 79 additions & 0 deletions src/assets/styles/header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,82 @@
.navbar-custom {
display: flex;
}

.toggle {
padding: 20px;
font-size: 25px;
}

.notify {
display: inline-block;
}

.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}

.switch input {
opacity: 0;
width: 0;
height: 0;
}

.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: 0.4s;
transition: 0.4s;
}

.slider:before {
position: absolute;
content: '';
height: 16px;
width: 16px;
left: 0px;
bottom: 2px;
background-color: white;
-webkit-transition: 0.4s;
transition: 0.4s;
}

input:checked + .slider {
background-color: #2196f3;
}

input:focus + .slider {
box-shadow: 0 0 1px #2196f3;
}

input:checked + .slider:before {
-webkit-transform: translateX(23px);
-ms-transform: translateX(23px);
transform: translateX(23px);
}

/* Rounded sliders */
.slider.round {
border-radius: 34px;
width: 40px;
height: 20px;
}

.slider.round:before {
border-radius: 50%;
}

.opt-choice {
display: flex;
flex-direction: row;
}
.opt-choice p {
margin-right: 10px;
}
8 changes: 2 additions & 6 deletions src/components/layout/Header.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { NavLink } from 'react-router-dom';
import { Navbar } from 'react-bootstrap';
import LoggedInLinks from './LoggedInLinks';
import LoggedOutLinks from './LoggedOutLinks';
import { isLoggedIn } from '../../helpers';
import { Navbar } from 'react-bootstrap';
import '../../assets/styles/header.scss';

class Header extends Component {
Expand All @@ -15,11 +15,7 @@ class Header extends Component {
*/
const links = login.isAuthentincated || isLoggedIn() ? <LoggedInLinks /> : <LoggedOutLinks />;
return (
<Navbar
fixed="top"
className="navbar navbar-expand-lg navbar-light"
data-set="nav-bar-test"
>
<Navbar fixed="top" className="navbar navbar-expand-lg navbar-light" data-set="nav-bar-test">
<NavLink className="navbar-brand" to="/" data-set="nav-bar-brand-test">
Authors Haven
</NavLink>
Expand Down
42 changes: 42 additions & 0 deletions src/components/layout/LoggedInLinks.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import React from 'react';
import { NavLink, withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { logoutUser } from '../../redux/actions/loginActions';
import OptInOutView from '../../views/OptInOutView';
import '../../assets/styles/header.scss';
import {
Popover, OverlayTrigger, Dropdown, Col, Row,
} from 'react-bootstrap';

/*
*@return {jsx} to display in the Header component
Expand All @@ -17,6 +21,43 @@ class LoggedInLinks extends React.Component {
};

render() {
const popover = (
<Popover id="popover-basic">
<OverlayTrigger
trigger="click"
placement="bottom"
overlay={(
<Popover id="popover-basic">
<div className="container">
<Col>
<Row>
<div className="col text-left">
<strong>Choose what you want to opt in/out of</strong>
<hr />
<OptInOutView />
</div>
</Row>
</Col>
</div>
</Popover>
)}
rootClose
>
<Dropdown.Toggle variant=" focus-click" id="dropdown-basic">
Notification Settings
</Dropdown.Toggle>
</OverlayTrigger>
<br />
<br />
</Popover>
);

const OptInOut = () => (
<OverlayTrigger trigger="click" placement="bottom" overlay={popover}>
<i className="fa fa-bell-o nav-link" aria-hidden="true" />
</OverlayTrigger>
);

const username = localStorage.getItem('user')
? JSON.parse(localStorage.getItem('user')).username
: '';
Expand All @@ -26,6 +67,7 @@ class LoggedInLinks extends React.Component {
return (
<div className="row">
<div className="col-md-10 navbar-custom">
<OptInOut />
<NavLink className="nav-link" to="/search">
<span role="img" aria-label="search">
&#128269;
Expand Down
3 changes: 1 addition & 2 deletions src/redux/actions/CreateCommentAction.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import axios from 'axios';
import { fetchCommentsAction } from './FetchCommentsAction';
import { BASE_URL, CREATE_COMMENT } from '../constants';
import { successToast, errorToast } from '../../helpers';
import { successToast } from '../../helpers';

/*
*Defines the create_comment actions and dispatches the right
Expand All @@ -19,7 +19,6 @@ export const createComment = (data, slug, history) => async dispatch => {
.then(response => {
dispatch(createCommentSuccess(response.data));
successToast('Comment posted successfully');

dispatch(fetchCommentsAction(slug, history));
})
.catch(error => {
Expand Down
41 changes: 41 additions & 0 deletions src/redux/actions/NotificationsOptinAction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import axios from 'axios';
import { BASE_URL, OPTINOUT, headers } from '../constants';
import { successToast, errorToast } from '../../helpers';

const config = {
headers: { Authorization: localStorage.getItem('token') },
};

export const OptInOutAction = data => async dispatch => {
dispatch({ type: OPTINOUT });
return axios
.put(`${BASE_URL}/notifications/subscriptions`, data, config)
.then(response => {
dispatch(optInOutSuccess(response.data));
})
.catch(error => {
dispatch(optInOutFailure(error));
});
};

export const NotifyStatusAction = () => async dispatch => {
dispatch({ type: OPTINOUT });
return axios
.get(`${BASE_URL}/notifications/subscriptions`, config)
.then(response => {
dispatch(optInOutSuccess(response.data));
})
.catch(error => {
dispatch(optInOutFailure(error));
});
};

export const optInOutSuccess = response => ({
type: `${OPTINOUT}_SUCCESS`,
response,
});

export const optInOutFailure = error => ({
type: `${OPTINOUT}_FAILURE`,
error,
});
1 change: 1 addition & 0 deletions src/redux/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ export const CREATE_REPORT = 'CREATE_REPORT';
export const FETCH_REPORT = 'FETCH_REPORT';
export const DELETE_REPORT = 'DELETE_REPORT';
export const UPDATE_REPORT = 'UPDATE_REPORT';
export const OPTINOUT = 'OPTINOUT';
57 changes: 57 additions & 0 deletions src/redux/reducers/NotificationsOptInOutReducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { OPTINOUT } from '../constants';

const initialState = {
isLoading: false,
data: {},
error: {},
};

export const OptInOutReducer = (state = initialState, action) => {
switch (action.type) {
case `${OPTINOUT}`:
return {
...state,
isLoading: true,
};
case `${OPTINOUT}_SUCCESS`:
return {
...state,
isLoading: false,
data: action.response,
};
case `${OPTINOUT}_FAILURE`:
return {
...state,
isLoading: false,
error: action.error,
};

default:
return { ...state };
}
};

export const NotifyStatusReducer = (state = initialState, action) => {
switch (action.type) {
case `${OPTINOUT}`:
return {
...state,
isLoading: true,
};
case `${OPTINOUT}_SUCCESS`:
return {
...state,
isLoading: false,
data: action.response,
};
case `${OPTINOUT}_FAILURE`:
return {
...state,
isLoading: false,
error: action.error,
};

default:
return { ...state };
}
};
3 changes: 3 additions & 0 deletions src/redux/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import FetchCommentsReducer from './FetchCommentsReducer';
import bookmarkReducer from './bookmarkReducer';
import TagsReducer from './TagsReducer';
import ReportsReducer from './ CreateReportReducer';
import { OptInOutReducer, NotifyStatusReducer } from './NotificationsOptInOutReducer.js';

const rootReducer = combineReducers({
signup: SignUpReducer,
Expand All @@ -29,6 +30,8 @@ const rootReducer = combineReducers({
bookmarkReducer,
TagsReducer,
ReportsReducer,
OptInOutReducer,
NotifyStatusReducer,
});

export default rootReducer;
75 changes: 75 additions & 0 deletions src/views/OptInOutView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { OptInOutAction, NotifyStatusAction } from '../redux/actions/NotificationsOptinAction';

class OptInOutView extends Component {

componentDidMount(){
const { NotifyStatusAction } = this.props;
NotifyStatusAction();
}

onClick = (e, isSubscribed) => {
const { OptInOutAction } = this.props;
const data = {
[e.target.name]: isSubscribed,
}
OptInOutAction(data)
};

render() {
const { data, status } = this.props;
const { isLoading } = status

return (
<>
<div className="opt-choice">
<p>Email</p>
{data.data.subscriptions && data.data.subscriptions.email === true ? (
<label className="switch">
<input type="checkbox" name="email" checked onClick={() => this.onClick(event, false)} />
<span className="slider round" />
</label>
):(
<label className="switch">
<input type="checkbox" id="email" name="email" onClick={() => this.onClick(event, true)} />
<span className="slider round" />
</label>
)}

</div>
<div className="opt-choice">
<p>In App</p>
{data.data.subscriptions && data.data.subscriptions.app === true ? (
<label className="switch">
<input type="checkbox" name="app" checked onClick={() => this.onClick(event, false)} />
<span className="slider round" />
</label>
):(
<label className="switch">
<input type="checkbox" name="app" onClick={() => this.onClick(event, true)} />
<span className="slider round" />
</label>
)}

</div>
</>
);
}
}

export const mapStateToProps = state => ({
data: state.OptInOutReducer,
status: state.NotifyStatusReducer,
});

export const mapDispatchToProps = () => ({
OptInOutAction,
NotifyStatusAction,
});

export default connect(
mapStateToProps,
mapDispatchToProps(),
)(OptInOutView);
Loading

0 comments on commit 6322379

Please sign in to comment.