Skip to content

Commit 5e4d73f

Browse files
authored
Merge pull request #99 from Web-Dev-Path/feature/add-recaptcha
add reCaptcha
2 parents f53ccbc + 48a3da2 commit 5e4d73f

File tree

8 files changed

+246
-103
lines changed

8 files changed

+246
-103
lines changed

.env-template

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
To get rid of the `console.log` errors related to MailChimp and reCapctha:
12

2-
# When updating the Subscribe MailChimp functionality present on the footer:
3-
# 1 Ask for the url link on Slack
4-
# 2 Create your local `.env` with the url credentials that will not be tracked into our repository for security reasons
3+
- 1 Ask for the the credentials on Slack
4+
- 2 Create your local `.env` with the credentials that will not be tracked into our repository for security reasons
55

66
NEXT_PUBLIC_MAILCHIMP_URL={Add the url link}
7-
7+
NEXT_PUBLIC_RECAPTCHA_SITE={Add the key}
8+
RECAPTCHA_SECRET_KEY={Add the key}

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2323
- CardsColumns.js and Card.js
2424
- footer copyright
2525
- Header.js component
26+
- reCAPTCHA
2627

2728
### Fixed
2829

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ After talking to us on Slack and deciding this project is your "cup of tea", you
2828
This is how to get started locally:
2929

3030
- [Git clone this repository](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository-from-github/cloning-a-repository)
31+
- Ask on Slack for the `.env` file credentials with MailChimp and reCaptcha keys and add it to your local copy.
3132
- Run `yarn install`
3233
- Run `yarn run dev` (for Windows)
3334
- Run `yarn run dev-mac` (for macOS)

components/mailchimp/NewsletterForm.js

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,51 @@
1-
import { useState } from 'react';
1+
import { createRef, useState } from 'react';
22
import { decode } from 'html-entities';
33
import Container from '../Container';
44
import newsletterStyles from '../../styles/Newsletter.module.scss';
5+
import ReCAPTCHA from 'react-google-recaptcha';
6+
7+
const SITE_KEY = process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY;
58

69
const NewsletterForm = ({ status, message, onValidated }) => {
710
const [error, setError] = useState(null);
8-
const [email, setEmail] = useState(null);
11+
const [name, setName] = useState('');
12+
const [email, setEmail] = useState('');
13+
const recaptchaRef = createRef();
14+
15+
const onReCAPTCHAChange = async captchaCode => {
16+
// If the reCAPTCHA code is null or undefined indicating that
17+
// the reCAPTCHA was expired then return early
18+
if (!captchaCode) {
19+
return;
20+
}
21+
try {
22+
const response = await fetch('/api/register', {
23+
method: 'POST',
24+
body: JSON.stringify({ email, name, captcha: captchaCode }),
25+
headers: {
26+
'Content-Type': 'application/json',
27+
},
28+
});
29+
if (response.ok) {
30+
// If the response is ok than show the success console.log
31+
console.log('Email registered successfully');
32+
} else {
33+
// Else throw an error with the message returned
34+
const error = await response.json();
35+
throw new Error(error.message);
36+
}
37+
} catch (error) {
38+
console.log(error?.message || 'Something went wrong');
39+
} finally {
40+
// Reset the reCAPTCHA when the request has failed or succeeded
41+
if (recaptchaRef?.current) {
42+
recaptchaRef.current.reset();
43+
}
44+
45+
setEmail('');
46+
setName('');
47+
}
48+
};
949

1050
/**
1151
* Handle form submit.
@@ -17,17 +57,24 @@ const NewsletterForm = ({ status, message, onValidated }) => {
1757

1858
setError(null);
1959

60+
if (!name) {
61+
setError('Please enter a name');
62+
return null;
63+
}
64+
2065
if (!email) {
2166
setError('Please enter a valid email address');
2267
return null;
2368
}
2469

70+
recaptchaRef.current.execute();
71+
2572
const isFormValidated = onValidated({ EMAIL: email });
2673

2774
event.target.reset();
2875

2976
// On success return true
30-
return email && email.indexOf('@') > -1 && isFormValidated;
77+
return name && email && email.indexOf('@') > -1 && isFormValidated;
3178
};
3279

3380
/**
@@ -77,22 +124,36 @@ const NewsletterForm = ({ status, message, onValidated }) => {
77124
>
78125
<input
79126
className={`${newsletterStyles.newsletter__input} ${newsletterStyles.newsletter__name}`}
127+
onChange={event => setName(event?.target?.value ?? '')}
80128
type="text"
81129
name="name"
130+
value={name}
82131
placeholder="name"
83132
/>
84133
<input
85134
className={`${newsletterStyles.newsletter__input} ${newsletterStyles.newsletter__email}`}
86135
onChange={event => setEmail(event?.target?.value ?? '')}
87136
type="email"
88137
name="email"
138+
value={email}
89139
placeholder="email"
90140
onKeyUp={event => handleInputKeyEvent(event)}
91141
/>
92-
<button className={newsletterStyles.newsletter__button}>
142+
<button
143+
className={newsletterStyles.newsletter__button}
144+
type="submit"
145+
>
93146
Subscribe
94147
</button>
148+
149+
<ReCAPTCHA
150+
ref={recaptchaRef}
151+
size="invisible"
152+
sitekey={SITE_KEY}
153+
onChange={onReCAPTCHAChange}
154+
/>
95155
</form>
156+
96157
<div className={newsletterStyles.newsletterFormInfo}>
97158
{status === 'sending' && (
98159
<div className={newsletterStyles.newsletterFormSending}>

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"next": "^12.0.10",
1616
"react": "17.0.1",
1717
"react-dom": "17.0.1",
18+
"react-google-recaptcha": "^2.1.0",
1819
"react-mailchimp-subscribe": "^2.1.3",
1920
"sass": "^1.35.1"
2021
}

pages/api/register.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
const sleep = () =>
2+
new Promise(resolve => {
3+
setTimeout(() => {
4+
resolve();
5+
}, 350);
6+
});
7+
8+
export default async function handler(req, res) {
9+
const SECRET_KEY = process.env.RECAPTCHA_SECRET_KEY;
10+
11+
const { body, method } = req;
12+
13+
// Extract the email, name, and captcha code from the request body
14+
const { email, name, captcha } = body;
15+
16+
if (method === 'POST') {
17+
// If email or captcha are missing return an error
18+
if (!email || !name || !captcha) {
19+
return res.status(422).json({
20+
message: 'Unprocessable request, please provide the required fields',
21+
});
22+
}
23+
24+
try {
25+
// Ping the google recaptcha verify API to verify the captcha code you received
26+
const response = await fetch(
27+
`https://www.google.com/recaptcha/api/siteverify?secret=${SECRET_KEY}&response=${captcha}`,
28+
{
29+
headers: {
30+
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
31+
},
32+
method: 'POST',
33+
}
34+
);
35+
const captchaValidation = await response.json();
36+
37+
if (captchaValidation.success) {
38+
// Replace this with the API that will save the data received
39+
// to your backend
40+
await sleep();
41+
// Return 200 if everything is successful
42+
return res.status(200).send('OK');
43+
}
44+
45+
return res.status(422).json({
46+
message: 'Unprocessable request, Invalid captcha code',
47+
});
48+
} catch (error) {
49+
console.log(error);
50+
return res.status(422).json({ message: 'Something went wrong' });
51+
}
52+
}
53+
// Return 404 if someone pings the API with a method other than
54+
// POST
55+
return res.status(404).send('Not found');
56+
}

styles/Newsletter.module.scss

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,9 @@
1111
color: $primary-content-color;
1212
margin-top: 1rem;
1313

14-
@include tablet {
15-
margin: 0;
16-
}
17-
1814
@include large-desktop {
1915
font-size: 1.2rem;
16+
margin: 0;
2017
}
2118
}
2219
}
@@ -34,13 +31,15 @@
3431
display: flex;
3532
justify-content: space-between;
3633
align-items: center;
34+
gap: 2rem;
3735
}
3836
}
3937

4038
&__title {
4139
font-size: 1.5rem;
4240
color: $primary-content-color;
4341
margin: 0;
42+
white-space: nowrap;
4443

4544
@include medium-desktop {
4645
font-size: 1.75rem;

0 commit comments

Comments
 (0)