Skip to content

Commit fff36bc

Browse files
sexta13bbmoz
authored andcommitted
feature/#154303604 - Mary receives notification that Kevin applied to her projects (#173)
* mary notifs - initial commit * create application create a new application when applying to a project. * fix lint * remove white space * mark as seen applications * show application info * remove unused class
1 parent d2e94ba commit fff36bc

File tree

16 files changed

+211
-14
lines changed

16 files changed

+211
-14
lines changed

client/App.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ const CreateProjectRestrictedLoadable = restricted(Loadable({
2727
loading: Loading
2828
}))
2929

30+
const ApplicationsRestrictedLoadable = restricted(Loadable({
31+
loader: () => import('./components/Applications/ApplicationsList'),
32+
loading: Loading
33+
}))
34+
3035
class App extends Component {
3136
componentDidMount () {
3237
this.props.appLoad()
@@ -46,6 +51,7 @@ class App extends Component {
4651
<Route path='/signup' component={SignupForm}/>
4752
<Route path='/landing-a' component={LandingA} />
4853
<Route path='/landing-c' component={LandingC} />
54+
<Route path='/show-applications' component={ApplicationsRestrictedLoadable} />
4955
</Switch>
5056
</div>
5157
)

client/actions/application/index.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import apiOptionsFromState from '../../api/lib/apiOptionsFromState'
2+
import applicationsApiClient from '../../api/applications'
3+
import * as actionTypes from './types'
4+
5+
export default class ApplicationActionCreator {
6+
static getNotifications (user) {
7+
return (dispatch, getState) => {
8+
dispatch({type: actionTypes.FETCH_NOTIFICATIONS_START})
9+
try {
10+
const state = getState()
11+
const apiOptions = apiOptionsFromState(state)
12+
const applications = applicationsApiClient.getNotifications(apiOptions)
13+
dispatch({type: actionTypes.FETCH_NOTIFICATIONS_SUCCESS, payload: applications})
14+
} catch (error) {
15+
dispatch({type: actionTypes.FETCH_NOTIFICATIONS_ERROR, payload: error, error: true})
16+
}
17+
}
18+
}
19+
20+
static markAsSeen (applicationId) {
21+
return (dispatch, getState) => {
22+
try {
23+
const state = getState()
24+
const apiOptions = apiOptionsFromState(state)
25+
const application = applicationsApiClient.markAsSeenApplication(apiOptions, applicationId)
26+
dispatch({type: actionTypes.MARK_AS_SEEN_SUCCESS, payload: application})
27+
} catch (error) {
28+
dispatch({type: actionTypes.MARK_AS_SEEN_ERROR, payload: error, error: true})
29+
}
30+
}
31+
}
32+
33+
static createApplication (project, user) {
34+
return (dispatch, getState) => {
35+
try {
36+
const state = getState()
37+
const apiOptions = apiOptionsFromState(state)
38+
const applicationToCreate = { project: project.id, volunteer: user.id }
39+
const application = applicationsApiClient.createApplication(apiOptions, applicationToCreate)
40+
dispatch({type: actionTypes.CREATE_APPLICATION_SUCCESS, payload: application})
41+
} catch (error) {
42+
dispatch({type: actionTypes.CREATE_APPLICATION_ERROR, payload: error, error: true})
43+
}
44+
}
45+
}
46+
}

client/actions/application/types.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
export const FETCH_NOTIFICATIONS_START = 'FETCH_NOTIFICATIONS_START'
3+
export const FETCH_NOTIFICATIONS_SUCCESS = 'FETCH_NOTIFICATIONS_SUCCESS'
4+
export const FETCH_NOTIFICATIONS_ERROR = 'FETCH_NOTIFICATIONS_ERROR'
5+
export const CREATE_APPLICATION_SUCCESS = 'CREATE_APPLICATION_SUCCESS'
6+
export const CREATE_APPLICATION_ERROR = 'CREATE_APPLICATION_ERROR'
7+
export const MARK_AS_SEEN_SUCCESS = 'MARK_AS_SEEN_SUCCESS'
8+
export const MARK_AS_SEEN_ERROR = 'MARK_AS_SEEN_ERROR'

client/api/applications.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ const applicationsApiClient = {
2222

2323
rejectApplication (apiOptions, applicationId) {
2424
return apiRequest.post(`/applications/${applicationId}/reject`, apiOptions)
25+
},
26+
27+
getNotifications (apiOptions) {
28+
return apiRequest.get('/applications/notifications', apiOptions)
29+
},
30+
31+
markAsSeenApplication (apiOptions, applicationId) {
32+
return apiRequest.post(`/applications/${applicationId}/markAsSeen`, apiOptions)
2533
}
2634
}
2735

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React, { Component } from 'react'
2+
import PropTypes from 'prop-types'
3+
import { connect } from 'react-redux'
4+
import ApplicationActionCreator from '../../actions/application/'
5+
6+
class ApplicationsList extends Component {
7+
componentWillReceiveProps (nextProps) {
8+
this._markAsSeenApplications(nextProps.applications)
9+
}
10+
11+
componentDidMount () {
12+
this._markAsSeenApplications(this.props.applications)
13+
}
14+
15+
_markAsSeenApplications (applications) {
16+
if (Array.isArray(applications)) {
17+
applications.map(application => {
18+
this.props.markAsSeen(application.id)
19+
})
20+
}
21+
}
22+
renderApplicationInfo (application) {
23+
return (<div> Volunteer email: {application.volunteer.email} - Project name: {application.project.name} </div>)
24+
}
25+
render () {
26+
return (
27+
Array.isArray(this.props.applications) && this.props.applications.map(this.renderApplicationInfo)
28+
)
29+
}
30+
}
31+
32+
const mapStateToProps = (state) => ({
33+
applications: state.application.applications
34+
})
35+
36+
const mapDispatchToProps = {
37+
markAsSeen: ApplicationActionCreator.markAsSeen
38+
}
39+
40+
ApplicationsList.propTypes = {
41+
applications: PropTypes.any,
42+
markAsSeen: PropTypes.func
43+
}
44+
45+
export default connect(mapStateToProps, mapDispatchToProps)(ApplicationsList)

client/components/Header/Header.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,33 @@ import PropTypes from 'prop-types'
55
import AppBar from 'material-ui/AppBar'
66
import Toolbar from 'material-ui/Toolbar'
77
import Typography from 'material-ui/Typography'
8+
import Badge from 'material-ui/Badge'
89

910
import AuthActionCreator from '../../actions/auth'
1011
import styles from './Header.scss'
12+
import ApplicationActionCreator from '../../actions/application'
1113

1214
class Header extends Component {
1315
constructor (props) {
1416
super(props)
1517

1618
this.renderHeaderButtons = this.renderHeaderButtons.bind(this)
1719
this.getLinkStyles = this.getLinkStyles.bind(this)
20+
this.renderNotificationBadge = this.renderNotificationBadge.bind(this)
21+
}
22+
23+
componentDidMount () {
24+
this.props.getNotifications()
25+
}
26+
27+
renderNotificationBadge () {
28+
const { applications } = this.props
29+
return applications &&
30+
<Link key="show-applications" to="/show-applications" className={this.getLinkStyles('show-project')}>
31+
{applications.notSeenCounter > 0 ? <Badge badgeContent={applications.notSeenCounter} color="primary" >
32+
<span className={styles.applicationBadgeText}>APPLICATIONS</span>
33+
</Badge> : <span className={styles.applicationBadgeText}>APPLICATIONS</span>}
34+
</Link>
1835
}
1936

2037
renderHeaderButtons () {
@@ -25,6 +42,7 @@ class Header extends Component {
2542
]
2643
if (this.props.user.usertype === 'contact') {
2744
return [
45+
this.renderNotificationBadge(),
2846
<Link key="create-project" to="/create-project" className={this.getLinkStyles('create-project')}>CREATE PROJECT</Link>,
2947
...authButtons
3048
]
@@ -65,6 +83,7 @@ class Header extends Component {
6583

6684
function mapStateToProps (state) {
6785
return {
86+
applications: state.application,
6887
authenticated: state.auth.authenticated,
6988
currentPage: state.routing.location.pathname,
7089
user: state.user,
@@ -73,15 +92,23 @@ function mapStateToProps (state) {
7392
}
7493

7594
const mapDispatchToProps = {
76-
logout: AuthActionCreator.logout
95+
logout: AuthActionCreator.logout,
96+
getNotifications: ApplicationActionCreator.getNotifications
7797
}
7898

7999
Header.propTypes = {
80100
authenticated: PropTypes.bool.isRequired,
81101
currentPage: PropTypes.string.isRequired,
82102
logout: PropTypes.func.isRequired,
83103
user: PropTypes.object.isRequired,
84-
login: PropTypes.object
104+
login: PropTypes.object,
105+
applications: PropTypes.shape({
106+
applications: PropTypes.any,
107+
fetching: PropTypes.bool,
108+
notSeenCounter: PropTypes.number
109+
}),
110+
getNotifications: PropTypes.func
111+
85112
}
86113

87114
export default connect(mapStateToProps, mapDispatchToProps)(Header)

client/components/Header/Header.scss

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,6 @@
3737
pointer-events: none;
3838
opacity: 0.6;
3939
}
40+
.applicationBadgeText {
41+
padding: 0 15px;
42+
}

client/components/Home/Home.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,13 @@ function mapStateToProps (state) {
7272

7373
const mapDispatchToProps = {
7474
onLoad: ProjectActionCreator.fetchAllProjects
75+
7576
}
7677

7778
Home.propTypes = {
7879
onLoad: PropTypes.func,
7980
projects: PropTypes.array
81+
8082
}
8183

8284
export default connect(mapStateToProps, mapDispatchToProps)(Home)

client/components/ListOfProjects/ListOfProjects.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import GridList, { GridListTile } from 'material-ui/GridList'
55
import Chip from 'material-ui/Chip'
66

77
import UserActionCreator from '../../actions/user'
8+
import ApplicationActionCreator from '../../actions/application'
89
import Project from '../Project/Project'
910
import styles from './ListOfProjects.scss'
1011

@@ -13,6 +14,12 @@ class ListOfProjects extends Component {
1314
super(props)
1415

1516
this.renderListOfProjects = this.renderListOfProjects.bind(this)
17+
this.applyToProject = this.applyToProject.bind(this)
18+
}
19+
20+
applyToProject (project, user) {
21+
this.props.applyForProject(project, user)
22+
this.props.createApplication(project, user)
1623
}
1724

1825
renderListOfProjects () {
@@ -22,7 +29,7 @@ class ListOfProjects extends Component {
2229
<Project
2330
project={project}
2431
authenticated={this.props.authenticated}
25-
applyForProject={this.props.applyForProject} />
32+
applyForProject={this.applyToProject} />
2633

2734
<div className={styles.causesContainer}>
2835
{ project.causes.map((cause, chipIndex) => {
@@ -60,14 +67,16 @@ function mapStateToProps (state) {
6067
}
6168

6269
const mapDispatchToProps = {
63-
applyForProject: UserActionCreator.applyForProject
70+
applyForProject: UserActionCreator.applyForProject,
71+
createApplication: ApplicationActionCreator.createApplication
6472
}
6573

6674
ListOfProjects.propTypes = {
6775
applyForProject: PropTypes.func,
6876
authenticated: PropTypes.bool.isRequired,
6977
projects: PropTypes.array,
70-
title: PropTypes.string
78+
title: PropTypes.string,
79+
createApplication: PropTypes.func
7180
}
7281

7382
export default connect(mapStateToProps, mapDispatchToProps)(ListOfProjects)

client/index.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react'
22
import ReactDOM from 'react-dom'
33
import { MuiThemeProvider, createMuiTheme } from 'material-ui/styles'
44
import createHistory from 'history/createBrowserHistory'
5-
import { createStore, combineReducers, applyMiddleware } from 'redux'
5+
import { createStore, combineReducers, applyMiddleware, compose } from 'redux'
66
import { createLogger } from 'redux-logger'
77
import { Provider } from 'react-redux'
88
import { ConnectedRouter, routerReducer, routerMiddleware } from 'react-router-redux'
@@ -16,6 +16,7 @@ import './scss/main.scss'
1616
import App from './App'
1717
import reducers from './reducers'
1818

19+
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
1920
const browserHistory = createHistory()
2021
const navigationMiddleware = routerMiddleware(browserHistory)
2122
const loggerMiddleware = createLogger({
@@ -27,12 +28,14 @@ const store = createStore(
2728
...reducers,
2829
routing: routerReducer
2930
}),
30-
applyMiddleware(
31-
navigationMiddleware,
32-
loggerMiddleware,
33-
promiseMiddleware,
34-
thunkMiddleware,
35-
localStorageMiddleware
31+
composeEnhancers(
32+
applyMiddleware(
33+
navigationMiddleware,
34+
loggerMiddleware,
35+
promiseMiddleware,
36+
thunkMiddleware,
37+
localStorageMiddleware
38+
)
3639
)
3740
)
3841

0 commit comments

Comments
 (0)