Migrate RSVP end dates to per user

This commit is contained in:
2017-04-16 17:29:41 +01:00
parent cdc6614c63
commit 2e32b09a5c
11 changed files with 103 additions and 50 deletions

View File

@@ -66,6 +66,10 @@ const Person = Model.define('Person', {
defaultValue: -1,
},
enddate: {
type: DataType.DATE,
},
}, {
indexes: [

View File

@@ -20,6 +20,7 @@ const PersonItemType = new ObjectType({
dietary: { type: StringType },
starter: { type: StringType },
main: { type: StringType },
enddate: { type: StringType },
},
});

View File

@@ -3,6 +3,7 @@ import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from './AdminPeople.css';
import Link from '../../components/Link';
import Button from '../../components/Button';
import { prettifyDate } from '../../config';
class AdminPeople extends React.Component {
static propTypes = {
@@ -16,6 +17,7 @@ class AdminPeople extends React.Component {
email: PropTypes.string,
password: PropTypes.string,
attending: PropTypes.boolean,
enddate: PropTypes.string,
})).isRequired,
};
@@ -138,6 +140,22 @@ class AdminPeople extends React.Component {
>⬆</span>
<a href="#sortcolumn" onClick={event => this.sortPeople(event, 'ceremony')}>Ceremony</a>
</td>
<td>
<span
className={[
s.sortIcon,
this.state.sortedBy === 'enddate' && this.state.sortDirection ? s.showSortIcon : null,
].join(' ')}
>⬇</span>
<span
className={[
s.sortIcon,
this.state.sortedBy === 'enddate' && !this.state.sortDirection ? s.showSortIcon : null,
].join(' ')}
>⬆</span>
<a href="#sortcolumn" onClick={event => this.sortPeople(event, 'enddate')}>End date</a>
</td>
<td>
<span
className={[
@@ -163,6 +181,7 @@ class AdminPeople extends React.Component {
<td>{person.email}</td>
<td><Link to={`/rsvp/${person.password}`}>{person.password}</Link></td>
<td>{person.ceremony ? 'Full day' : 'Evening only'}</td>
<td>{prettifyDate(person.enddate)}</td>
<td>{person.completed ? 'Yes' : 'No'}</td>
<td>
<Button to={`/admin/person/${person.key}`}>Edit</Button>
@@ -239,6 +258,17 @@ class AdminPeople extends React.Component {
<option value="false">Evening only</option>
</select>
</div>
<div className={s.formGroup}>
<label className={s.label} htmlFor="enddate">
End date:
</label>
<input
className={s.input}
type="date"
id="enddate"
name="enddate"
/>
</div>
<div className={s.formGroup}>
<label className={s.label} htmlFor="password">
Password (blank will autogenerate):

View File

@@ -9,7 +9,7 @@ export default {
path: '/admin/people',
async action() {
const resp = await fetchQL('{getAllPersons{key,firstname,lastname,email,ceremony,completed,password}}');
const resp = await fetchQL('{getAllPersons{key,firstname,lastname,email,ceremony,completed,password,enddate}}');
let people = await resp.json();
if (people.data && people.data.getAllPersons) {

View File

@@ -15,6 +15,7 @@ class AdminPerson extends React.Component {
password: PropTypes.string,
attending: PropTypes.boolean,
ceremony: PropTypes.boolean,
enddate: PropTypes.string,
}).isRequired,
};
@@ -23,6 +24,14 @@ class AdminPerson extends React.Component {
lastname: this.props.person.lastname,
};
getISODate = (date) => {
const d = new Date(date);
const year = d.getFullYear();
const month = d.getMonth() + 1 < 10 ? `0${d.getMonth() + 1}` : d.getMonth() + 1;
const day = d.getDate() < 10 ? `0${d.getDate()}` : d.getDate();
return `${year}-${month}-${day}`;
}
handleFirstnameChange = (event) => {
this.setState({ firstname: event.target.value });
}
@@ -94,6 +103,18 @@ class AdminPerson extends React.Component {
<option value="false">Evening only</option>
</select>
</div>
<div className={s.formGroup}>
<label className={s.label} htmlFor="enddate">
End date:
</label>
<input
className={s.input}
type="date"
id="enddate"
name="enddate"
defaultValue={this.getISODate(this.props.person.enddate)}
/>
</div>
<div className={s.formGroup}>
<label className={s.label} htmlFor="password">
Password:

View File

@@ -11,7 +11,7 @@ export default {
return { redirect: '/admin' };
}
const resp = await fetchQL('{getPersonByKey{key,firstname,lastname,email,completed,password,ceremony,starter,main,dietary}}', {
const resp = await fetchQL('{getPersonByKey{key,firstname,lastname,email,completed,password,ceremony,starter,main,dietary,enddate}}', {
key: params.key,
});

View File

@@ -14,6 +14,7 @@ class Rsvp extends React.Component {
dietary: PropTypes.string,
starter: PropTypes.string,
main: PropTypes.string,
enddate: PropTypes.string,
})).isRequired,
rsvpEnd: PropTypes.string.isRequired,
isEditable: PropTypes.bool.isRequired,

View File

@@ -3,16 +3,15 @@ import Layout from '../../components/Layout';
import fetchQL from '../../core/fetchQL';
import Rsvp from './Rsvp';
import NotFound from '../notFound/NotFound';
import { rsvpEndDate, getRsvpEnd } from '../../config';
import { prettifyDate } from '../../config';
let title = 'RSVP';
let attending = 'day';
export default {
path: '/rsvp/:password',
isEditable() {
return new Date() < rsvpEndDate[attending];
isEditable(date) {
return new Date() < new Date(date);
},
async action({ params }) {
@@ -20,7 +19,7 @@ export default {
return { redirect: '/' };
}
const resp = await fetchQL('{getPersonsByPassword{key,firstname,lastname,attending,ceremony,dietary,starter,main}}', {
const resp = await fetchQL('{getPersonsByPassword{key,firstname,lastname,attending,ceremony,dietary,starter,main,enddate}}', {
password: params.password,
});
@@ -36,7 +35,7 @@ export default {
}
// Assuming all connected guests have the same invite
attending = people[0].ceremony ? 'day' : 'evening';
const firstDate = people[0].enddate;
return {
title,
@@ -45,8 +44,8 @@ export default {
<Rsvp
title={title}
people={people}
rsvpEnd={getRsvpEnd(attending)}
isEditable={this.isEditable()}
rsvpEnd={prettifyDate(firstDate)}
isEditable={this.isEditable(firstDate)}
/>
</Layout>
),

View File

@@ -21,7 +21,7 @@ import models from './data/models';
import schema from './data/schema';
import routes from './routes';
import assets from './assets'; // eslint-disable-line import/no-unresolved
import { port, auth, starters, mains, rsvpEndDate, exportApiKey } from './config';
import { port, auth, starters, mains, exportApiKey } from './config';
import Person from './data/models/Person';
import Settings from './data/models/Settings';
import Rollbar from './core/rollbar';
@@ -149,41 +149,41 @@ app.get('/rsvp/:code', bouncer.block, (req, res, next) => {
app.post('/rsvp/save', (req, res) => {
try {
const update = {};
update[req.body.prop] = req.body.value;
Person.update(update, {
where: {
key: req.body.key,
},
}).then(() => {
Person.findOne({
where: { key: req.body.key },
}).then(data => {
const endDate = rsvpEndDate[(data.ceremony ? 'day' : 'evening')];
if (new Date() > endDate) {
Person.findOne({
where: { key: req.body.key },
}).then(data => {
const endDate = new Date(data.enddate);
if (new Date() < endDate) {
const update = {};
update[req.body.prop] = req.body.value;
return Person.update(update, {
where: {
key: req.body.key,
},
}).then(() => {
if (data.completed) {
const attending = `\nAttending: ${data.attending ? 'Yes' : 'No'}`;
const starter = (data.attending && data.starter !== -1) ? `\nStarter: ${starters[data.starter]}` : '';
const main = (data.attending && data.main !== -1) ? `\nMain: ${mains[data.main]}` : '';
const dietary = (data.attending && data.dietary) ? `\nDietary requirements: ${data.dietary}` : '';
sendSlackMsgWithDebounce(`${data.firstname} ${data.lastname} has saved their RSVP:${attending}${starter}${main}${dietary}`, '#wedding-rsvps', req.body.key);
sendRSVPEmailWithDebounce(data);
}
return res.json({
success: false,
err: 'RSVP end date has elapsed',
success: true,
prop: req.body.prop,
});
}).catch(err => {
Rollbar.handleError(err);
res.json({
success: false,
err,
});
} else if (data.completed) {
const attending = `\nAttending: ${data.attending ? 'Yes' : 'No'}`;
const starter = (data.attending && data.starter !== -1) ? `\nStarter: ${starters[data.starter]}` : '';
const main = (data.attending && data.main !== -1) ? `\nMain: ${mains[data.main]}` : '';
const dietary = (data.attending && data.dietary) ? `\nDietary requirements: ${data.dietary}` : '';
sendSlackMsgWithDebounce(`${data.firstname} ${data.lastname} has saved their RSVP:${attending}${starter}${main}${dietary}`, '#wedding-rsvps', req.body.key);
sendRSVPEmailWithDebounce(data);
}
return res.json({
success: true,
prop: req.body.prop,
});
}).catch(err => {
Rollbar.handleError(err);
res.json({
success: false,
err,
});
}
return res.json({
success: false,
err: 'RSVP end date has elapsed',
});
}).catch(err => {
Rollbar.handleError(err);
@@ -242,6 +242,7 @@ app.post('/admin/person', (req, res, next) => {
lastname: req.body.lastname,
email: req.body.email,
ceremony: req.body.ceremony,
enddate: new Date(req.body.enddate),
};
if (req.body.password && req.body.password !== '') {