Add validation to RSVP field
Add fade in/out of bg image Clean up layout
This commit is contained in:
@@ -21,7 +21,7 @@ class Html extends React.Component {
|
|||||||
<meta name="description" content={description} />
|
<meta name="description" content={description} />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="apple-touch-icon" href="apple-touch-icon.png" />
|
<link rel="apple-touch-icon" href="apple-touch-icon.png" />
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Poppins:500|Yesteryear" />
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Palanquin+Dark|Yesteryear" />
|
||||||
{style && <style id="css" dangerouslySetInnerHTML={{ __html: style }} />}
|
{style && <style id="css" dangerouslySetInnerHTML={{ __html: style }} />}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rsvpGroup {
|
.rsvpGroup {
|
||||||
width: 60%;
|
width: 65%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rsvpButtonGroup {
|
.rsvpButtonGroup {
|
||||||
@@ -64,6 +64,27 @@
|
|||||||
box-shadow: 0 0 4px #fff;
|
box-shadow: 0 0 4px #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inputInvalid {
|
||||||
|
animation: shake 0.5s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invalidMsg {
|
||||||
|
display: none;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border: 4px solid var(--colour-red);
|
||||||
|
border-radius: 14px;
|
||||||
|
background: transparent;
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.3333333;
|
||||||
|
transition: opacity ease-in-out 0.25s, box-shadow ease-in-out 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invalidMsgVisible {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
display: block;
|
display: block;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -89,3 +110,28 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
box-shadow: 0 0 4px #fff;
|
box-shadow: 0 0 4px #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes shake {
|
||||||
|
8%,
|
||||||
|
41% {
|
||||||
|
transform: translateX(-10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
25%,
|
||||||
|
58% {
|
||||||
|
transform: translateX(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: translateX(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
92% {
|
||||||
|
transform: translateX(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import React from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import withStyles from 'isomorphic-style-loader/lib/withStyles';
|
import withStyles from 'isomorphic-style-loader/lib/withStyles';
|
||||||
import s from './RsvpButton.css';
|
import s from './RsvpButton.css';
|
||||||
|
import fetch from '../../core/fetch';
|
||||||
|
|
||||||
class RsvpButton extends React.Component {
|
class RsvpButton extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
focusCallback: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -13,7 +17,45 @@ class RsvpButton extends React.Component {
|
|||||||
this.setState({ showRsvpForm: true });
|
this.setState({ showRsvpForm: true });
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.rsvpInput.focus();
|
this.rsvpInput.focus();
|
||||||
}, 250);
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRsvpFocus = () => {
|
||||||
|
if (this.props.focusCallback) {
|
||||||
|
this.props.focusCallback(true);
|
||||||
|
}
|
||||||
|
this.setState({ inputError: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRsvpBlur = () => {
|
||||||
|
if (this.props.focusCallback) {
|
||||||
|
this.props.focusCallback(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFormSubmit = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
fetch(`rsvp/${this.rsvpInput.value}`)
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 200) {
|
||||||
|
location.href = data.url;
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
inputInvalid: true,
|
||||||
|
inputInvalidMsg: 'Sorry, that RSVP code is invalid.',
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
this.setState({
|
||||||
|
inputInvalid: false,
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
this.setState({
|
||||||
|
inputInvalid: true,
|
||||||
|
inputInvalidMsg: 'Sorry, there was a problem validating your RSVP code, please try again later.',
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -35,14 +77,19 @@ class RsvpButton extends React.Component {
|
|||||||
this.state.showRsvpForm ? s.rsvpFormVisible : null,
|
this.state.showRsvpForm ? s.rsvpFormVisible : null,
|
||||||
].join(' ')}
|
].join(' ')}
|
||||||
>
|
>
|
||||||
<form method="post" action="/rsvp">
|
<form method="post" action="/rsvp" onSubmit={this.handleFormSubmit}>
|
||||||
<div className={[s.formGroup, s.rsvpGroup].join(' ')}>
|
<div className={[s.formGroup, s.rsvpGroup].join(' ')}>
|
||||||
<label className={s.label} htmlFor="code">
|
<label className={s.label} htmlFor="code">
|
||||||
Enter your RSVP code
|
Enter your RSVP code
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
className={s.input}
|
className={[
|
||||||
|
s.input,
|
||||||
|
this.state.inputInvalid ? s.inputInvalid : null,
|
||||||
|
].join(' ')}
|
||||||
ref={(input) => { this.rsvpInput = input; }}
|
ref={(input) => { this.rsvpInput = input; }}
|
||||||
|
onFocus={this.handleRsvpFocus}
|
||||||
|
onBlur={this.handleRsvpBlur}
|
||||||
id="code"
|
id="code"
|
||||||
type="text"
|
type="text"
|
||||||
name="code"
|
name="code"
|
||||||
@@ -55,6 +102,15 @@ class RsvpButton extends React.Component {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<div
|
||||||
|
className={[
|
||||||
|
s.invalidMsg,
|
||||||
|
this.state.inputInvalidMsg ? s.invalidMsgVisible : null,
|
||||||
|
].join(' ')}
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
{this.state.inputInvalidMsg}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,11 +9,17 @@
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background-image: url('/img/church_2500.jpg');
|
background: url('/img/church_2500.jpg');
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position-x: 40%;
|
background-position-x: 50%;
|
||||||
|
background-color: #c1c1c1;
|
||||||
filter: grayscale(1) brightness(0.4);
|
filter: grayscale(1) brightness(0.4);
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
|
transition: filter ease-in-out 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.churchImageFocus {
|
||||||
|
filter: grayscale(0.4) brightness(0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
@@ -25,19 +31,23 @@
|
|||||||
max-width: 640px;
|
max-width: 640px;
|
||||||
margin: 0 auto 40px;
|
margin: 0 auto 40px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--colour-nearblack);
|
color: #fff;
|
||||||
|
font-size: 1.3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.headings > * {
|
.headings h1 {
|
||||||
margin: 0 0 20px;
|
margin: 0 0 10px;
|
||||||
font-size: 2.5em;
|
}
|
||||||
|
|
||||||
|
.headings h2 {
|
||||||
|
margin: 0 0 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.headings h3 {
|
.headings h3 {
|
||||||
margin-top: 40px;
|
margin: 0 0 20px;
|
||||||
font-family: var(--font-family-cursive);
|
font-family: var(--font-family-cursive);
|
||||||
}
|
}
|
||||||
|
|
||||||
.rsvp {
|
.rsvp {
|
||||||
padding-top: 15vh;
|
padding-top: 10vh;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,25 @@ import s from './Home.css';
|
|||||||
import RsvpButton from '../../components/RsvpButton';
|
import RsvpButton from '../../components/RsvpButton';
|
||||||
|
|
||||||
class Home extends React.Component {
|
class Home extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
focusChurchImage = (focus) => {
|
||||||
|
this.setState({ focusChurch: focus });
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className={s.root}>
|
<div className={s.root}>
|
||||||
<div className={s.churchImage} />
|
<div
|
||||||
|
className={[
|
||||||
|
s.churchImage,
|
||||||
|
this.state.focusChurch ? s.churchImageFocus : null,
|
||||||
|
].join(' ')}
|
||||||
|
/>
|
||||||
<div className={s.container}>
|
<div className={s.container}>
|
||||||
<div className={s.headings}>
|
<div className={s.headings}>
|
||||||
<h1>Daniel and Hana</h1>
|
<h1>Daniel and Hana</h1>
|
||||||
@@ -15,7 +30,9 @@ class Home extends React.Component {
|
|||||||
<h3>- 27th July 2017 -</h3>
|
<h3>- 27th July 2017 -</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className={s.rsvp}>
|
<div className={s.rsvp}>
|
||||||
<RsvpButton />
|
<RsvpButton
|
||||||
|
focusCallback={this.focusChurchImage}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user