Skip to content

Commit

Permalink
let a volunteer change its mission status #33
Browse files Browse the repository at this point in the history
  • Loading branch information
maxbeier committed Apr 28, 2017
1 parent 6ab63e3 commit ddaf5ff
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 2 deletions.
35 changes: 34 additions & 1 deletion client/components/Mission.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React from 'react';
import moment from 'moment';
import _ from 'lodash';
import { Card, Table, Button, Pill } from 'elemental';
import { Alert, Card, Table, ButtonGroup, Button, Pill } from 'elemental';
import { Map, TileLayer } from 'react-leaflet';
import MissionForm from './MissionForm';
import * as http from '../lib/http';

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

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

contextTypes: {
volunteers: React.PropTypes.object,
volunteer: React.PropTypes.object,
},

getDefaultProps() {
Expand Down Expand Up @@ -66,6 +68,21 @@ export default React.createClass({
this.props.onChange(mission);
},

setMessage(text, type) {
this.setState({ message: { text, type } });
_.delay(() => this.setState({ message: null }), 5000);
},

setMissionState(status) {
const mission = this.props.mission;
http.put(`/api/volunteer/missions/${mission.id}?status=${status}`)
.then(() => {
mission.crew.find(a => a.volunteer.id === this.context.volunteer.id).status = status;
this.props.onChange(mission);
})
.catch(({ error }) => this.setMessage(error, 'danger'));
},

toggleEdit() {
const isEditing = !this.state.isEditing;
this.setState({ isEditing });
Expand Down Expand Up @@ -104,8 +121,13 @@ export default React.createClass({
const position = this.state.position;
const right = { float: 'right' };

const isMyMission = this.context.volunteer && !!mission.crew.find(a => a.volunteer.id === this.context.volunteer.id);

return (
<Card>
{this.state.message &&
<Alert type={this.state.message.type}>{this.state.message.text}</Alert>
}
{this.props.isEditable
? <Button onClick={this.toggleEdit} style={right}>Edit</Button>
: <Pill label={mission.status} type="info" style={right} />
Expand All @@ -114,6 +136,17 @@ export default React.createClass({

{this.renderCrew(mission.crew)}

{isMyMission &&
<p style={{ float: 'right' }}>
Change your participation state:
<ButtonGroup style={{ marginLeft: '1em' }}>
<Button type="default-success" onClick={() => this.setMissionState('yes')}>Yes</Button>
<Button type="default" onClick={() => this.setMissionState('pending')}>Undecided</Button>
<Button type="default-danger" onClick={() => this.setMissionState('no')}>No</Button>
</ButtonGroup>
</p>
}

{position &&
<Map center={position} zoom={this.state.zoom}>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
Expand Down
19 changes: 18 additions & 1 deletion client/components/Volunteer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import * as http from '../lib/http';

export default React.createClass({

childContextTypes: {
volunteer: React.PropTypes.object,
},

getInitialState() {
return {
volunteer: null,
Expand All @@ -19,6 +23,12 @@ export default React.createClass({
};
},

getChildContext() {
return {
volunteer: this.state.volunteer,
};
},

componentDidMount() {
if (!this.state.hasVisitedBefore) {
window.localStorage.setItem('hasVisitedBefore', true);
Expand All @@ -42,6 +52,13 @@ export default React.createClass({
this.setState({ isEditing: !this.state.isEditing });
},

updateMission(newMission) {
const missions = this.state.missions;
const index = _.findIndex(missions, mission => mission.id === newMission.id);
missions[index] = newMission;
this.setState({ missions });
},

renderMissions() {
if (!this.state.missions) return null;

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

return (
<div>
{this.state.missions.map(mission => <Mission key={mission.id} mission={mission} />)}
{this.state.missions.map(mission => <Mission key={mission.id} mission={mission} onChange={this.updateMission} />)}
</div>
);
},
Expand Down
33 changes: 33 additions & 0 deletions routes/api/volunteers.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,39 @@ exports.changeToken = (req, res) => {
});
};

/**
* Change the Status of a Volunteer
*/
exports.changeMissionStatus = (req, res) => {
const token = req.token;
const missionID = req.params.id;
const newStatus = req.query.status;
const allowedStatus = ['pending', 'yes', 'no'];

if (!allowedStatus.includes(newStatus)) {
return res.apiError('not allowed');
}

Mission.model
.findById(missionID)
.populate('crew.volunteer', 'token')
.exec((err2, mission) => {
if (err2) return res.apiError(err2.detail.errmsg);
if (!mission) return res.apiError('not found');

const match = mission.crew.find(a => a.volunteer.token === token);

if (match) {
match.status = newStatus;
mission.save((err3) => {
if (err3) return res.apiError(err3.detail.errmsg);
res.apiResponse({ success: true });
});
}
else res.apiError('not found');
});
};

/**
* Delete Volunteer by ID
*/
Expand Down
1 change: 1 addition & 0 deletions routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ exports = module.exports = (app) => {
app.put('/api/volunteer', keystone.middleware.api, hasToken, api.volunteers.update);
app.post('/api/volunteer', keystone.middleware.api, api.volunteers.create);
app.post('/api/volunteer/token', keystone.middleware.api, api.volunteers.changeToken);
app.put('/api/volunteer/missions/:id', keystone.middleware.api, hasToken, api.volunteers.changeMissionStatus);

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

0 comments on commit ddaf5ff

Please sign in to comment.