Skip to content

Commit

Permalink
feat: add reCaptcha to ContactForm
Browse files Browse the repository at this point in the history
feat: fix reCaptcha server logic

feat: reset reCaptcha after submitting
  • Loading branch information
martapanc committed Sep 17, 2023
1 parent e947904 commit 9a66a86
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 7 deletions.
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ NEXT_PUBLIC_SHOW_LOGGER="false"

GRAPHQL_URL=https://some-url.com/graphql

SENDGRID_API_KEY=Abcde
SENDGRID_API_KEY=Abcde

RECAPTCHA_SITE_KEY=6LcXXX
RECAPTCHA_SECRET_KEY=6LcYYY
4 changes: 4 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ const nextConfig = {
},
];
},

experimental: {
serverActions: true,
},
};

module.exports = nextConfig;
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@heroicons/react": "^2.0.18",
"@mui/material": "^5.13.5",
"@sendgrid/mail": "^7.7.0",
"@types/react-google-recaptcha": "^2.1.5",
"@types/styled-components": "^5.1.0",
"classnames": "^2.3.2",
"clsx": "^2.0.0",
Expand All @@ -43,6 +44,7 @@
"plop": "^3.1.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-google-recaptcha": "^3.1.0",
"react-headroom": "^3.2.1",
"react-i18next": "^13.2.0",
"react-icons": "^4.10.1",
Expand Down
46 changes: 42 additions & 4 deletions src/components/molecules/ContactForm/ContactForm.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
'use client';

import { Form, Formik } from 'formik';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useEffect, useRef, useState } from 'react';
import ReCAPTCHA from 'react-google-recaptcha';
import { Trans, useTranslation } from 'react-i18next';
import * as Yup from 'yup';

import { verifyCaptcha } from '@/lib/verifyCaptcha';

import { Input } from '@/components/atoms/Input/Input';
import { Select } from '@/components/atoms/select/Select';
import { TextArea } from '@/components/atoms/TextArea/TextArea';
Expand All @@ -23,6 +26,25 @@ const ContactForm = () => {
const [success, setSuccess] = useState(false);
const [error, setError] = useState(false);

const recaptchaRef = useRef<ReCAPTCHA>(null);
const [isVerified, setIsVerified] = useState<boolean>(false);

const reCaptchaSiteKey = '6LcSyzAoAAAAAC7JTJ6gtOWW3cjTK_vKRm2WjEtC';

async function handleCaptchaSubmission(token: string | null) {
// Server function to verify captcha
await verifyCaptcha(token)
.then(() => {
setIsVerified(true);
})
.catch(() => {
setIsVerified(false);
setError(true);
});
}

useEffect(() => {}, [isVerified]);

const { t } = useTranslation();

const subjects = [
Expand Down Expand Up @@ -78,6 +100,7 @@ const ContactForm = () => {
setSubmitting(false);
setSuccess(true);
resetForm();
recaptchaRef.current?.reset();
};

return (
Expand All @@ -104,7 +127,10 @@ const ContactForm = () => {
)}
{error && (
<div className='rounded-md bg-red-100 px-4 py-2 text-red-600 ring-1 ring-red-600 font-semibold'>
{t('contacts.error')}
<Trans t={t} i18nKey='contacts.error' />
<a className='ms-1 underline' href='mailto:info@martacodes.it'>
info@martacodes.it
</a>
</div>
)}

Expand Down Expand Up @@ -139,7 +165,19 @@ const ContactForm = () => {
/>

<div className='mt-6 flex justify-end'>
<Button type='submit' disabled={isSubmitting} className='group'>
<ReCAPTCHA
sitekey={reCaptchaSiteKey}
ref={recaptchaRef}
onChange={handleCaptchaSubmission}
/>
</div>

<div className='mt-2 flex justify-end'>
<Button
type='submit'
disabled={!isVerified || isSubmitting}
className='group'
>
{isSubmitting
? t('contacts.button.loading')
: t('contacts.button.send')}
Expand Down
2 changes: 1 addition & 1 deletion src/data/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
}
},
"success": "Thanks for your message! 🤗 I will get back to you as soon as possible.",
"error": "Whoops, something went wrong on our side! 😟 <br /> Please try again - if the issue persists, reach out directly at <a className='ms-1 underline' href='mailto:info@martacodes.it'>info@martacodes.it</a>",
"error": "Whoops, something went wrong on our side! 😟 <br /> Please try again - if the issue persists, reach out directly at",
"button": {
"send": "Send message",
"loading": "Working on it..."
Expand Down
43 changes: 43 additions & 0 deletions src/lib/verifyCaptcha.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use server';

export interface ReCaptchaResp {
success: boolean;
challenge_ts?: string;
hostname?: string;
}

export async function verifyCaptcha(token: string | null) {
const key = process.env.RECAPTCHA_SECRET_KEY;

if (!key) {
throw new Error('Error retrieving reCaptcha secret key.');
}

if (token === null) {
throw new Error('reCaptcha Token is null');
}

const url = 'https://www.google.com/recaptcha/api/siteverify';

const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `secret=${key}&response=${token}`,
});

if (!response.ok) {
throw new Error('Failed to verify reCAPTCHA: ' + response.json());
}

const data = await response.json();

const reCaptchaResp: ReCaptchaResp = {
success: data.success,
challenge_ts: data['challenge_ts'],
hostname: data['hostname'],
};

return reCaptchaResp;
}
25 changes: 24 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4219,6 +4219,13 @@
dependencies:
"@types/react" "*"

"@types/react-google-recaptcha@^2.1.5":
version "2.1.5"
resolved "https://verdaccio.mein-recycling.de/@types%2freact-google-recaptcha/-/react-google-recaptcha-2.1.5.tgz#af157dc2e4bde3355f9b815a64f90e85cfa9df8b"
integrity sha512-iWTjmVttlNgp0teyh7eBXqNOQzVq2RWNiFROWjraOptRnb1OcHJehQnji0sjqIRAk9K0z8stjyhU+OLpPb0N6w==
dependencies:
"@types/react" "*"

"@types/react-headroom@^3.2.0":
version "3.2.0"
resolved "https://verdaccio.mein-recycling.de/@types%2freact-headroom/-/react-headroom-3.2.0.tgz#8d6ae82f5b647581533c3e640d42d85120ea00af"
Expand Down Expand Up @@ -11887,7 +11894,7 @@ prompts@^2.0.1, prompts@^2.4.0:
kleur "^3.0.3"
sisteransi "^1.0.5"

prop-types@^15.0.0, prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
prop-types@^15.0.0, prop-types@^15.5.0, prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://verdaccio.mein-recycling.de/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
Expand Down Expand Up @@ -12082,6 +12089,14 @@ raw-body@2.5.1:
iconv-lite "0.4.24"
unpipe "1.0.0"

react-async-script@^1.2.0:
version "1.2.0"
resolved "https://verdaccio.mein-recycling.de/react-async-script/-/react-async-script-1.2.0.tgz#ab9412a26f0b83f5e2e00de1d2befc9400834b21"
integrity sha512-bCpkbm9JiAuMGhkqoAiC0lLkb40DJ0HOEJIku+9JDjxX3Rcs+ztEOG13wbrOskt3n2DTrjshhaQ/iay+SnGg5Q==
dependencies:
hoist-non-react-statics "^3.3.0"
prop-types "^15.5.0"

react-colorful@^5.1.2:
version "5.6.1"
resolved "https://verdaccio.mein-recycling.de/react-colorful/-/react-colorful-5.6.1.tgz#7dc2aed2d7c72fac89694e834d179e32f3da563b"
Expand Down Expand Up @@ -12130,6 +12145,14 @@ react-fast-compare@^2.0.1:
resolved "https://verdaccio.mein-recycling.de/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==

react-google-recaptcha@^3.1.0:
version "3.1.0"
resolved "https://verdaccio.mein-recycling.de/react-google-recaptcha/-/react-google-recaptcha-3.1.0.tgz#44aaab834495d922b9d93d7d7a7fb2326315b4ab"
integrity sha512-cYW2/DWas8nEKZGD7SCu9BSuVz8iOcOLHChHyi7upUuVhkpkhYG/6N3KDiTQ3XAiZ2UAZkfvYKMfAHOzBOcGEg==
dependencies:
prop-types "^15.5.0"
react-async-script "^1.2.0"

react-headroom@^3.2.1:
version "3.2.1"
resolved "https://verdaccio.mein-recycling.de/react-headroom/-/react-headroom-3.2.1.tgz#30a3e27cc6d21d464665ccedb94f62a698031de3"
Expand Down

0 comments on commit 9a66a86

Please sign in to comment.