Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds email capture to bottom of blog #3333

Merged
merged 10 commits into from
Jan 15, 2018
1 change: 1 addition & 0 deletions www/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"gatsby-transformer-yaml": "^1.5.7",
"graphql-request": "^1.4.0",
"gray-percentage": "^2.0.0",
"jsonp": "^0.2.1",
"limax": "^1.5.0",
"lodash": "^4.16.6",
"mitt": "^1.1.2",
Expand Down
160 changes: 160 additions & 0 deletions www/src/components/email-capture-form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import React from "react"
import { rhythm } from "../utils/typography"
import presets from "../utils/presets"
import jsonp from "jsonp"

// Mailchimp endpoint
// From: https://us17.admin.mailchimp.com/lists/integration/embeddedcode?id=XXXXXX
// Where `XXXXXX` is the MC list ID
// Note: we change `/post` to `/post-json`
const MAILCHIMP_URL = `https://gatsbyjs.us17.list-manage.com/subscribe/post-json?u=1dc33f19eb115f7ebe4afe5ee&id=f366064ba7`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should i make these env vars?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideally this should be a prop passed into the component named mailChimpUrl

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought about that too but after getting this merged, I was thinking of adding the form on other non-blog pages. then each parent component will need to pass the MAILCHIMP_URL as a prop, which gets super messy.

thoughts on env var? or just keeping this URL in this component? imagine having this component in a bunch of different templates in the future.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd just leave it hard-coded. It's not really a secret nor are we trying to make this component reusable for multiple lists atm. It's trivial to refactor later.


class EmailCaptureForm extends React.Component {
constructor() {
super()
this.state = {
email: ``,
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although React docs do say to initialize state within the constructor, I've been reading a lot about how that's not necessary if you don't have props, like here. https://stackoverflow.com/a/37788410 and this post touches on this concept as well: https://babeljs.io/blog/2015/06/07/react-on-es6-plus

But happy to refactor using the constructor. Just thought I'd share anyway.


// Update state each time user edits their email address
_handleEmailChange = e => {
this.setState({ email: e.target.value })
}

// Check whether the email address is valid:
// - not an empty string,
// - greater than 5 characters,
// - includes both `@` and `.`
_isValidEmailAddress = email =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to off-load common algorithms like this to an NPM module e.g. https://github.com/Sembiance/email-validator

!!email && email.length > 5 && (email.includes(`@`) && email.includes(`.`))

// Using jsonp, post to MC server & handle its response
_postEmailToMailchimp = url => {
// jsonp lib takes an `endpoint`, {options}, & callback
jsonp(url, { param: `c` }, (err, data) => {
// network failures, timeouts, etc
if (err) {
this.setState({
status: `error`,
msg: err,
})

// Mailchimp errors & failures
} else if (data.result !== `success`) {
this.setState({
status: `error`,
msg: data.msg,
})

// Posted email successfully to Mailchimp
} else {
this.setState({
status: `success`,
msg: data.msg,
})
}
})
}

// On form submit, validate email
// then jsonp to Mailchimp, and update state
_handleFormSubmit = e => {
e.preventDefault()
e.stopPropagation()

// If email is not valid, break early
if (!this._isValidEmailAddress(this.state.email)) {
this.setState({
status: `error`,
msg: `"${this.state.email}" is not a valid email address`,
})
return
}

// Construct the url for our jsonp request
// Query params must be in CAPS
// Capture pathname for better email targeting
const url = `${MAILCHIMP_URL}
&EMAIL=${encodeURIComponent(this.state.email)}
&PATHNAME=${window.location.pathname}
`

this.setState(
{
msg: null,
status: `sending`,
},
// jsonp request as setState callback
this._postEmailToMailchimp(url)
)
}

render() {
return (
<div
css={{
border: `2px solid ${presets.brand}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the "rhythm" function for spacings

backgroundColor: presets.veryLightPurple,
borderRadius: `4px`,
padding: `${rhythm(0.75)}`,
}}
>
{this.state.status === `success` ? (
<div>Thank you! Youʼll receive your first email shortly.</div>
) : (
<div>
Enjoyed this post? Receive the next one in your inbox!
<br />
<form
id="email-capture"
method="post"
noValidate
css={{ margin: 0 }}
>
<div>
<input
type="email"
name="email"
placeholder="[email protected]"
onChange={this._handleEmailChange}
css={{
marginTop: rhythm(0.3),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use divisions of 2 for rhythm units e.g. 1/4, 1/2, 1, 2, etc.

padding: `${rhythm(0.3)} ${rhythm(0.3)} ${rhythm(
0.3
)} ${rhythm(0.7)}`,
width: `250px`,
color: presets.bodyColor,
}}
/>
<button
type="submit"
onClick={this._handleFormSubmit}
css={{
borderRadius: `2px`,
border: `2px solid ${presets.brand}`,
backgroundColor: presets.brand,
height: `43px`,
cursor: `pointer`,
padding: `0 ${rhythm(0.75)} 0 ${rhythm(0.75)}`,
margin: `${rhythm(0.75)} 0 0 ${rhythm(0.75)}`,
}}
>
Subscribe
</button>
{this.state.status === `error` && (
<div
dangerouslySetInnerHTML={{ __html: this.state.msg }}
css={{ marginTop: `${rhythm(0.5)}` }}
/>
)}
</div>
</form>
</div>
)}
</div>
)
}
}

export default EmailCaptureForm
2 changes: 2 additions & 0 deletions www/src/templates/template-blog-post.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Img from "gatsby-image"
import presets from "../utils/presets"
import typography, { rhythm, scale, options } from "../utils/typography"
import Container from "../components/container"
import EmailCaptureForm from '../components/email-capture-form'

class BlogPostTemplate extends React.Component {
render() {
Expand Down Expand Up @@ -200,6 +201,7 @@ class BlogPostTemplate extends React.Component {
__html: this.props.data.markdownRemark.html,
}}
/>
<EmailCaptureForm />
</Container>
<div
css={{
Expand Down