Skip to content

Commit ddaf5ff

Browse files
committed
let a volunteer change its mission status #33
1 parent 6ab63e3 commit ddaf5ff

File tree

4 files changed

+86
-2
lines changed

4 files changed

+86
-2
lines changed

client/components/Mission.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import React from 'react';
22
import moment from 'moment';
33
import _ from 'lodash';
4-
import { Card, Table, Button, Pill } from 'elemental';
4+
import { Alert, Card, Table, ButtonGroup, Button, Pill } from 'elemental';
55
import { Map, TileLayer } from 'react-leaflet';
66
import MissionForm from './MissionForm';
7+
import * as http from '../lib/http';
78

89
const formatDate = date => moment(date).format(moment.localeData().longDateFormat('L'));
910

@@ -24,6 +25,7 @@ export default React.createClass({
2425

2526
contextTypes: {
2627
volunteers: React.PropTypes.object,
28+
volunteer: React.PropTypes.object,
2729
},
2830

2931
getDefaultProps() {
@@ -66,6 +68,21 @@ export default React.createClass({
6668
this.props.onChange(mission);
6769
},
6870

71+
setMessage(text, type) {
72+
this.setState({ message: { text, type } });
73+
_.delay(() => this.setState({ message: null }), 5000);
74+
},
75+
76+
setMissionState(status) {
77+
const mission = this.props.mission;
78+
http.put(`/api/volunteer/missions/${mission.id}?status=${status}`)
79+
.then(() => {
80+
mission.crew.find(a => a.volunteer.id === this.context.volunteer.id).status = status;
81+
this.props.onChange(mission);
82+
})
83+
.catch(({ error }) => this.setMessage(error, 'danger'));
84+
},
85+
6986
toggleEdit() {
7087
const isEditing = !this.state.isEditing;
7188
this.setState({ isEditing });
@@ -104,8 +121,13 @@ export default React.createClass({
104121
const position = this.state.position;
105122
const right = { float: 'right' };
106123

124+
const isMyMission = this.context.volunteer && !!mission.crew.find(a => a.volunteer.id === this.context.volunteer.id);
125+
107126
return (
108127
<Card>
128+
{this.state.message &&
129+
<Alert type={this.state.message.type}>{this.state.message.text}</Alert>
130+
}
109131
{this.props.isEditable
110132
? <Button onClick={this.toggleEdit} style={right}>Edit</Button>
111133
: <Pill label={mission.status} type="info" style={right} />
@@ -114,6 +136,17 @@ export default React.createClass({
114136

115137
{this.renderCrew(mission.crew)}
116138

139+
{isMyMission &&
140+
<p style={{ float: 'right' }}>
141+
Change your participation state:
142+
<ButtonGroup style={{ marginLeft: '1em' }}>
143+
<Button type="default-success" onClick={() => this.setMissionState('yes')}>Yes</Button>
144+
<Button type="default" onClick={() => this.setMissionState('pending')}>Undecided</Button>
145+
<Button type="default-danger" onClick={() => this.setMissionState('no')}>No</Button>
146+
</ButtonGroup>
147+
</p>
148+
}
149+
117150
{position &&
118151
<Map center={position} zoom={this.state.zoom}>
119152
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />

client/components/Volunteer.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import * as http from '../lib/http';
88

99
export default React.createClass({
1010

11+
childContextTypes: {
12+
volunteer: React.PropTypes.object,
13+
},
14+
1115
getInitialState() {
1216
return {
1317
volunteer: null,
@@ -19,6 +23,12 @@ export default React.createClass({
1923
};
2024
},
2125

26+
getChildContext() {
27+
return {
28+
volunteer: this.state.volunteer,
29+
};
30+
},
31+
2232
componentDidMount() {
2333
if (!this.state.hasVisitedBefore) {
2434
window.localStorage.setItem('hasVisitedBefore', true);
@@ -42,6 +52,13 @@ export default React.createClass({
4252
this.setState({ isEditing: !this.state.isEditing });
4353
},
4454

55+
updateMission(newMission) {
56+
const missions = this.state.missions;
57+
const index = _.findIndex(missions, mission => mission.id === newMission.id);
58+
missions[index] = newMission;
59+
this.setState({ missions });
60+
},
61+
4562
renderMissions() {
4663
if (!this.state.missions) return null;
4764

@@ -51,7 +68,7 @@ export default React.createClass({
5168

5269
return (
5370
<div>
54-
{this.state.missions.map(mission => <Mission key={mission.id} mission={mission} />)}
71+
{this.state.missions.map(mission => <Mission key={mission.id} mission={mission} onChange={this.updateMission} />)}
5572
</div>
5673
);
5774
},

routes/api/volunteers.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,39 @@ exports.changeToken = (req, res) => {
135135
});
136136
};
137137

138+
/**
139+
* Change the Status of a Volunteer
140+
*/
141+
exports.changeMissionStatus = (req, res) => {
142+
const token = req.token;
143+
const missionID = req.params.id;
144+
const newStatus = req.query.status;
145+
const allowedStatus = ['pending', 'yes', 'no'];
146+
147+
if (!allowedStatus.includes(newStatus)) {
148+
return res.apiError('not allowed');
149+
}
150+
151+
Mission.model
152+
.findById(missionID)
153+
.populate('crew.volunteer', 'token')
154+
.exec((err2, mission) => {
155+
if (err2) return res.apiError(err2.detail.errmsg);
156+
if (!mission) return res.apiError('not found');
157+
158+
const match = mission.crew.find(a => a.volunteer.token === token);
159+
160+
if (match) {
161+
match.status = newStatus;
162+
mission.save((err3) => {
163+
if (err3) return res.apiError(err3.detail.errmsg);
164+
res.apiResponse({ success: true });
165+
});
166+
}
167+
else res.apiError('not found');
168+
});
169+
};
170+
138171
/**
139172
* Delete Volunteer by ID
140173
*/

routes/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ exports = module.exports = (app) => {
3232
app.put('/api/volunteer', keystone.middleware.api, hasToken, api.volunteers.update);
3333
app.post('/api/volunteer', keystone.middleware.api, api.volunteers.create);
3434
app.post('/api/volunteer/token', keystone.middleware.api, api.volunteers.changeToken);
35+
app.put('/api/volunteer/missions/:id', keystone.middleware.api, hasToken, api.volunteers.changeMissionStatus);
3536

3637
// Uploaded images should not be publicly accessible
3738
app.use('/uploads', isAdminOrOwner, express.static('uploads', { redirect: false }));

0 commit comments

Comments
 (0)