5 Commits
1.0.0 ... 1.2.0

Author SHA1 Message Date
cdc6614c63 Create 2 RSVP end dates 2017-03-29 21:59:05 +01:00
75740699f1 Add link to gift list site 2017-03-14 16:12:14 +00:00
6d75606134 Create gift list page
Add generic vegetarian option to mains
Add additional content about Offley Place
2017-03-07 22:52:34 +00:00
a66bc1cedc Add less transparent bg to scrolled nav
Reposition the background images
2017-03-06 23:39:09 +00:00
3f068dfcec Fix bug that prevented sending of email
Add api key for export route
2017-03-06 22:33:32 +00:00
20 changed files with 247 additions and 45 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "wedding", "name": "wedding",
"version": "1.0.0", "version": "1.2.0",
"private": true, "private": true,
"engines": { "engines": {
"node": ">=6.5", "node": ">=6.5",

BIN
public/img/moors_2560.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 KiB

View File

@@ -13,6 +13,10 @@ html {
line-height: 1.375; /* ~22px */ line-height: 1.375; /* ~22px */
} }
body {
overflow-x: hidden;
}
a { a {
color: #0074c2; color: #0074c2;
} }

View File

@@ -4,8 +4,14 @@
right: 0; right: 0;
left: 0; left: 0;
height: 40px; height: 40px;
background: rgba(0, 0, 0, 0.1);
overflow: hidden; overflow: hidden;
z-index: 100;
background: rgba(0, 0, 0, 0.1);
transition: background 0.3s, ease-in;
}
.navDark {
background: rgba(60, 60, 60, 0.95);
} }
.container { .container {

View File

@@ -8,18 +8,47 @@ class Navigation extends React.Component {
current: PropTypes.string, current: PropTypes.string,
}; };
constructor(props) {
super(props);
this.state = {
scrollAtTop: true,
};
}
componentDidMount() {
window.addEventListener('scroll', this.handleScroll);
}
componentWillUnmount() {
window.removeEventListener('scroll', this.handleScroll);
}
handleScroll = (event) => {
const scrollTop = event.srcElement.body.scrollTop;
this.setState({
scrollAtTop: scrollTop < 10,
});
};
render() { render() {
if (!this.props.current) { if (!this.props.current) {
return null; return null;
} }
return ( return (
<div className={s.root} role="navigation"> <div
className={[
s.root,
this.state.scrollAtTop ? null : s.navDark,
].join(' ')}
role="navigation"
>
<div className={s.container}> <div className={s.container}>
<Link className={[s.link, this.props.current === 'home' ? s.active : null].join(' ')} to="/">RSVP</Link> <Link className={[s.link, this.props.current === 'home' ? s.active : null].join(' ')} to="/">RSVP</Link>
<Link className={[s.link, this.props.current === 'ceremony' ? s.active : null].join(' ')} to="/church">Church</Link> <Link className={[s.link, this.props.current === 'ceremony' ? s.active : null].join(' ')} to="/church">Church</Link>
<Link className={[s.link, this.props.current === 'reception' ? s.active : null].join(' ')} to="/venue">Venue</Link> <Link className={[s.link, this.props.current === 'reception' ? s.active : null].join(' ')} to="/venue">Venue</Link>
<Link className={[s.link, this.props.current === 'accomodation' ? s.active : null].join(' ')} to="/hotels">Hotels</Link> <Link className={[s.link, this.props.current === 'accomodation' ? s.active : null].join(' ')} to="/hotels">Hotels</Link>
<Link className={[s.link, this.props.current === 'transport' ? s.active : null].join(' ')} to="/transport">Transport</Link> <Link className={[s.link, this.props.current === 'transport' ? s.active : null].join(' ')} to="/transport">Transport</Link>
<Link className={[s.link, this.props.current === 'giftlist' ? s.active : null].join(' ')} to="/giftlist">Gift list</Link>
</div> </div>
</div> </div>
); );

View File

@@ -1,6 +1,6 @@
{ {
"name": "Navigation", "name": "Navigation",
"version": "1.0.0", "version": "1.1.1",
"private": true, "private": true,
"main": "./Navigation.js" "main": "./Navigation.js"
} }

View File

@@ -18,7 +18,7 @@ function getEmailText(person) {
return `Dear ${person.firstname}.\n\n return `Dear ${person.firstname}.\n\n
Thanks for your RSVP, ${person.attending ? 'we\'re delighted to hear you will be attending' : 'we\'re sorry to hear you will not be attending'}.\n Thanks for your RSVP, ${person.attending ? 'we\'re delighted to hear you will be attending' : 'we\'re sorry to hear you will not be attending'}.\n
${person.attending ? `Your meal choices are ${starters[person.starter]} and ${mains[person.main]}.\n` : ''} ${person.attending ? `Your meal choices are ${starters[person.starter]} and ${mains[person.main]}.\n` : ''}
${person.attending && person.dietary.length > 0 ? `Your dietary requirements are: ${person.dietary}.\n` : ''} ${person.attending && person.dietary && person.dietary.trim().length > 0 ? `Your dietary requirements are: ${person.dietary}.\n` : ''}
You can modify your RSVP up until ${getRsvpEnd()} here: https://danielandhana.com/${person.password}.\n You can modify your RSVP up until ${getRsvpEnd()} here: https://danielandhana.com/${person.password}.\n
${person.attending ? 'See you at our wedding,' : 'Hope to see you soon,'}\n ${person.attending ? 'See you at our wedding,' : 'Hope to see you soon,'}\n
Daniel and Hana Daniel and Hana
@@ -29,7 +29,7 @@ function getEmailHTML(person) {
return `<p>Dear ${person.firstname}.</p> return `<p>Dear ${person.firstname}.</p>
<p>Thanks for your RSVP, ${person.attending ? 'we\'re delighted to hear you will be attending' : 'we\'re sorry to hear you will not be attending'}.</p> <p>Thanks for your RSVP, ${person.attending ? 'we\'re delighted to hear you will be attending' : 'we\'re sorry to hear you will not be attending'}.</p>
${person.attending ? `<p>Your meal choices are <b>${starters[person.starter]}</b> and <b>${mains[person.main]}</b>.</p>` : ''} ${person.attending ? `<p>Your meal choices are <b>${starters[person.starter]}</b> and <b>${mains[person.main]}</b>.</p>` : ''}
${person.attending && person.dietary.length > 0 ? `<p>Your dietary requirements are: <b>${person.dietary}</b>.</p>` : ''} ${person.attending && person.dietary && person.dietary.trim().length > 0 ? `<p>Your dietary requirements are: <b>${person.dietary}</b>.</p>` : ''}
<p>You can modify your RSVP up until <b>${getRsvpEnd()}</b> <a href="https://danielandhana.com/${person.password}">here</a>.</p> <p>You can modify your RSVP up until <b>${getRsvpEnd()}</b> <a href="https://danielandhana.com/${person.password}">here</a>.</p>
<p>${person.attending ? 'See you at our wedding,' : 'Hope to see you soon,'}</p> <p>${person.attending ? 'See you at our wedding,' : 'Hope to see you soon,'}</p>
<p>Daniel and Hana</p> <p>Daniel and Hana</p>

View File

@@ -12,6 +12,7 @@
background: url('/img/offley_2560.jpg'); background: url('/img/offley_2560.jpg');
background-size: cover; background-size: cover;
background-position-x: 54%; background-position-x: 54%;
background-position-y: 66%;
background-color: #c1c1c1; background-color: #c1c1c1;
filter: grayscale(1) brightness(0.4); filter: grayscale(1) brightness(0.4);
z-index: -1; z-index: -1;

View File

@@ -12,6 +12,7 @@
background: url('/img/church_2560.jpg'); background: url('/img/church_2560.jpg');
background-size: cover; background-size: cover;
background-position-x: 54%; background-position-x: 54%;
background-position-y: 66%;
background-color: #c1c1c1; background-color: #c1c1c1;
filter: grayscale(1) brightness(0.4); filter: grayscale(1) brightness(0.4);
z-index: -1; z-index: -1;

View File

@@ -0,0 +1,75 @@
@import '../../components/variables.css';
.root {
width: 100vw;
height: 100vh;
}
.bgImage {
position: fixed;
width: 100vw;
height: 100vh;
background: url('/img/moors_2560.jpg');
background-size: cover;
background-position-x: 78%;
background-position-y: 66%;
background-color: #c1c1c1;
filter: grayscale(1) brightness(0.4);
z-index: -1;
transition: filter ease-in-out 0.5s;
}
.container {
padding-top: 50px;
margin: 0 20px;
}
.headings {
max-width: 640px;
margin: 0 auto 40px;
text-align: center;
color: #fff;
text-shadow: 3px 3px 8px var(--colour-nearblack);
font-size: 1.3em;
}
.headings h1 {
margin: 0 0 10px;
}
.sig {
font-family: var(--font-family-cursive);
font-size: 1.4em;
}
.content {
max-width: 960px;
margin: 0 auto;
color: #fff;
text-shadow: 1px 1px 4px var(--colour-nearblack);
}
.button {
display: inline-block;
box-sizing: border-box;
margin: 0;
padding: 10px 20px;
opacity: 0.75;
outline: 0;
border: 4px solid #fff;
border-radius: 14px;
background: var(--colour-nearblack);
color: #fff;
text-align: center;
text-decoration: none;
font-size: 18px;
line-height: 1.3333333;
cursor: pointer;
transition: opacity ease-in-out 0.25s, box-shadow ease-in-out 0.25s;
}
.button:hover,
.button:focus {
opacity: 0.9;
box-shadow: 0 0 4px #fff;
}

View File

@@ -0,0 +1,44 @@
import React from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from './Giftlist.css';
class Giftlist extends React.Component {
render() {
return (
<div className={s.root}>
<div className={s.bgImage} />
<div className={s.container}>
<div className={s.headings}>
<h1>Gift list</h1>
</div>
<div className={s.content}>
<p>We&#8217;ve been asked what we&#8217;d like for a gift and the answer
is simple: you, at our wedding, having a great time.</p>
<p>However, if you insist then we&#8217;ll say what we&#8217;d also love
is the most amazing honeymoon possible. We have the basics covered but
we&#8217;re hoping for the holiday of a lifetime. We would like to see
and do everything we&#8217;ve dreamed about!</p>
<p>Here&#8217;s how our gift list works. Just pick the item you&#8217;d
most like to give us. You&#8217;ll then be asked to leave your name
and any message you have for us. There is the option to use the PayPal
system to send your gift (you can use a credit card &ndash; you
don&#8217;t need a PayPal account). Or, on the day, you could bring
the value of your gift in a card.</p>
<p>We&#8217;ll send you a link to our online photo gallery when we get
back and we&#8217;ll be sure to tell (bore) you all about it in great
detail.</p>
<p className={s.sig}>Dan and Hana</p>
<a
className={s.button}
href="https://www.our-wedding-list.co.uk/danielandhana/list"
>
View our list
</a>
</div>
</div>
</div>
);
}
}
export default withStyles(s)(Giftlist);

View File

@@ -0,0 +1,15 @@
import React from 'react';
import Giftlist from './Giftlist';
import Layout from '../../components/Layout';
export default {
path: '/giftlist',
action() {
return {
title: 'Gift list',
component: <Layout current="giftlist"><Giftlist /></Layout>,
};
},
};

View File

@@ -12,6 +12,7 @@
background: url('/img/prague_2560.jpg'); background: url('/img/prague_2560.jpg');
background-size: cover; background-size: cover;
background-position-x: 54%; background-position-x: 54%;
background-position-y: 66%;
background-color: #c1c1c1; background-color: #c1c1c1;
filter: grayscale(1) brightness(0.6); filter: grayscale(1) brightness(0.6);
z-index: -1; z-index: -1;

View File

@@ -13,6 +13,7 @@ export default {
require('./reception').default, require('./reception').default,
require('./accomodation').default, require('./accomodation').default,
require('./transport').default, require('./transport').default,
require('./giftlist').default,
require('./login').default, require('./login').default,
require('./admin').default, require('./admin').default,
require('./adminPeople').default, require('./adminPeople').default,

View File

@@ -12,6 +12,7 @@
background: url('/img/offley_2560.jpg'); background: url('/img/offley_2560.jpg');
background-size: cover; background-size: cover;
background-position-x: 54%; background-position-x: 54%;
background-position-y: 66%;
background-color: #c1c1c1; background-color: #c1c1c1;
filter: grayscale(1) brightness(0.4); filter: grayscale(1) brightness(0.4);
z-index: -1; z-index: -1;

View File

@@ -39,6 +39,23 @@ class Reception extends React.Component {
defaultCenter={offley.position} defaultCenter={offley.position}
markers={[offley]} markers={[offley]}
/> />
<h3>About Offley Place</h3>
<p>Legend has it that in the 8th century, King Offa discovered the bones of
Saint Alban and set about creating St Albans Abbey in a tribute to him.
While it was being completed he looked for a place to call home and
discovered the local area. After building a grand palace the village is
thought to have become known as Great Off Lee.</p>
<p>Great Offley Place was first built in the 17th century. Rebuilt in 1810
but still retaining a Tudor porch and a 17th century wing, the grand house
has a long history which has seen the same family line keep ownership for
an impressive 350 years.</p>
<p>In 1929 the family ownership was brought to an end when the last owner,
Guy George, sold the estate to a director of WH Smith who was well liked
in the village and built a cricket pavilion and pitch.</p>
<p>When World War II broke out it was sold again, this time to become a
teacher training college, and then finally in 2003 it was taken over again
and transformed into the luxury hotel, wedding venue and restaurant it
is today.</p>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -12,6 +12,7 @@
background: url('/img/cervenalhota_2560.jpg'); background: url('/img/cervenalhota_2560.jpg');
background-size: cover; background-size: cover;
background-position-x: 30%; background-position-x: 30%;
background-position-y: 66%;
background-color: #c1c1c1; background-color: #c1c1c1;
filter: grayscale(1) brightness(0.6); filter: grayscale(1) brightness(0.6);
z-index: -1; z-index: -1;

View File

@@ -6,13 +6,13 @@ import NotFound from '../notFound/NotFound';
import { rsvpEndDate, getRsvpEnd } from '../../config'; import { rsvpEndDate, getRsvpEnd } from '../../config';
let title = 'RSVP'; let title = 'RSVP';
let attending = 'day';
export default { export default {
path: '/rsvp/:password', path: '/rsvp/:password',
isEditable() { isEditable() {
return new Date() < rsvpEndDate; return new Date() < rsvpEndDate[attending];
}, },
async action({ params }) { async action({ params }) {
@@ -35,6 +35,9 @@ export default {
}; };
} }
// Assuming all connected guests have the same invite
attending = people[0].ceremony ? 'day' : 'evening';
return { return {
title, title,
component: ( component: (
@@ -42,7 +45,7 @@ export default {
<Rsvp <Rsvp
title={title} title={title}
people={people} people={people}
rsvpEnd={getRsvpEnd()} rsvpEnd={getRsvpEnd(attending)}
isEditable={this.isEditable()} isEditable={this.isEditable()}
/> />
</Layout> </Layout>

View File

@@ -12,6 +12,7 @@
background: url('/img/offley_2560.jpg'); background: url('/img/offley_2560.jpg');
background-size: cover; background-size: cover;
background-position-x: 54%; background-position-x: 54%;
background-position-y: 66%;
background-color: #c1c1c1; background-color: #c1c1c1;
filter: grayscale(1) brightness(0.4); filter: grayscale(1) brightness(0.4);
z-index: -1; z-index: -1;

View File

@@ -21,7 +21,7 @@ import models from './data/models';
import schema from './data/schema'; import schema from './data/schema';
import routes from './routes'; import routes from './routes';
import assets from './assets'; // eslint-disable-line import/no-unresolved import assets from './assets'; // eslint-disable-line import/no-unresolved
import { port, auth, starters, mains, rsvpEndDate } from './config'; import { port, auth, starters, mains, rsvpEndDate, exportApiKey } from './config';
import Person from './data/models/Person'; import Person from './data/models/Person';
import Settings from './data/models/Settings'; import Settings from './data/models/Settings';
import Rollbar from './core/rollbar'; import Rollbar from './core/rollbar';
@@ -149,12 +149,6 @@ app.get('/rsvp/:code', bouncer.block, (req, res, next) => {
app.post('/rsvp/save', (req, res) => { app.post('/rsvp/save', (req, res) => {
try { try {
if (new Date() > rsvpEndDate) {
res.json({
success: false,
err: 'RSVP end date has elapsed',
});
} else {
const update = {}; const update = {};
update[req.body.prop] = req.body.value; update[req.body.prop] = req.body.value;
Person.update(update, { Person.update(update, {
@@ -165,7 +159,13 @@ app.post('/rsvp/save', (req, res) => {
Person.findOne({ Person.findOne({
where: { key: req.body.key }, where: { key: req.body.key },
}).then(data => { }).then(data => {
if (data.completed) { const endDate = rsvpEndDate[(data.ceremony ? 'day' : 'evening')];
if (new Date() > endDate) {
return res.json({
success: false,
err: 'RSVP end date has elapsed',
});
} else if (data.completed) {
const attending = `\nAttending: ${data.attending ? 'Yes' : 'No'}`; const attending = `\nAttending: ${data.attending ? 'Yes' : 'No'}`;
const starter = (data.attending && data.starter !== -1) ? `\nStarter: ${starters[data.starter]}` : ''; const starter = (data.attending && data.starter !== -1) ? `\nStarter: ${starters[data.starter]}` : '';
const main = (data.attending && data.main !== -1) ? `\nMain: ${mains[data.main]}` : ''; const main = (data.attending && data.main !== -1) ? `\nMain: ${mains[data.main]}` : '';
@@ -174,7 +174,7 @@ app.post('/rsvp/save', (req, res) => {
sendSlackMsgWithDebounce(`${data.firstname} ${data.lastname} has saved their RSVP:${attending}${starter}${main}${dietary}`, '#wedding-rsvps', req.body.key); sendSlackMsgWithDebounce(`${data.firstname} ${data.lastname} has saved their RSVP:${attending}${starter}${main}${dietary}`, '#wedding-rsvps', req.body.key);
sendRSVPEmailWithDebounce(data); sendRSVPEmailWithDebounce(data);
} }
res.json({ return res.json({
success: true, success: true,
prop: req.body.prop, prop: req.body.prop,
}); });
@@ -192,7 +192,6 @@ app.post('/rsvp/save', (req, res) => {
err, err,
}); });
}); });
}
} catch (err) { } catch (err) {
Rollbar.handleError(err); Rollbar.handleError(err);
res.json({ res.json({
@@ -222,7 +221,10 @@ app.get('/login', (req, res, next) => {
app.use('/admin*', (req, res, next) => { app.use('/admin*', (req, res, next) => {
try { try {
if (!req.user || !admin(req.user.email)) { // Allow if api key is valid and path is /admin/people/export
if (req.headers['x-api-key'] === exportApiKey && req.originalUrl === '/admin/people/export') {
next();
} else if (!req.user || !admin(req.user.email)) {
res.redirect(401, '/login'); res.redirect(401, '/login');
} else { } else {
next(); next();