- add recaptcha boilerplate for contact form

- still need to add api key to both project and ci/cd
This commit is contained in:
oonyeje 2023-11-29 09:35:12 -05:00
parent 6cd3a9deba
commit 8b97fcb73e
4 changed files with 127 additions and 28 deletions

View File

@ -1,31 +1,49 @@
import { useState } from 'react';
import { RefObject, useRef, useState } from 'react';
import { useForm, SubmitHandler, FieldValues } from 'react-hook-form';
import ReCAPTCHA from 'react-google-recaptcha';
export interface ContactFormData extends FieldValues {
firstName: string,
lastName: string,
email: string,
subject: string,
summary: string,
honeypot_xyz: string
};
const ContactForm = () => {
const {
register,
resetField,
handleSubmit,
setValue,
formState: { errors },
} = useForm();
const [submitted, setSubmitted] = useState(false);
const handleFormSubmission: SubmitHandler<FieldValues> = async (data) => {
console.log(data)
const handleFormSubmission: SubmitHandler<FieldValues> = async (data: FieldValues) => {
const submissionData = data as ContactFormData;
console.log(submissionData)
const res = await fetch('/api/contact', {
if (submissionData.honeypot_xyz !== "") {
// Form submission is spam
return;
}
const res = await fetch('/api/contact', {
method: 'POST',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
body: JSON.stringify(submissionData)
});
console.log('Response received')
if (res.status === 200) {
console.log('Response succeeded!')
setSubmitted(true)
resetField('captchaToken')
}
};
@ -33,6 +51,12 @@ const ContactForm = () => {
console.log(error)
};
const onCaptchaChange = (token: string | null) => {
// Set the captcha token when the user completes the reCAPTCHA
if (token) {
setValue('captchaToken', token);
}
};
return (
<div className='w-full flex flex-row justify-center'>
<form className=" w-1/2" onSubmit={handleSubmit(handleFormSubmission, handleFormError)}>
@ -62,11 +86,22 @@ const ContactForm = () => {
</select>
{errors.subject && <p>Subject is required.</p>}
</div>
<div className="flex flex-col w-full">
<input type='hidden' value="" {...register('honeypot_xyz', { required: true })}/>
</div>
<div className="flex flex-col w-full">
<textarea placeholder='Summary...' className="flex grow h-52" {...register('summary')} />
{errors.summary && <p>Please enter a message for your Project Inquiry.</p>}
</div>
<div className="pb-20px">
<ReCAPTCHA
size="normal"
sitekey={process.env.RECAPTCHA_SECRET_KEY ? process.env.RECAPTCHA_SECRET_KEY : 'ENTER_API_KEY'}
{...register('captchaToken', { required: true })}
onChange={onCaptchaChange}
/>
</div>
</div>
<input className="bg-blue-600 text-white rounded-sm cursor-pointer" type="submit" />
</div>

View File

@ -16,12 +16,14 @@
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"@types/nodemailer": "^6.4.14",
"@types/react-google-recaptcha": "^2.1.8",
"iframe-resizer-react": "^1.1.0",
"next": "latest",
"nodemailer": "^6.9.7",
"pdfjs-dist": "^4.0.269",
"react": "latest",
"react-dom": "latest",
"react-google-recaptcha": "^3.1.0",
"react-hook-form": "^7.47.0",
"react-modal": "^3.16.1",
"react-pdf": "^7.4.0",

View File

@ -18,7 +18,7 @@ type MailData = {
html?: string
}
export default function handler(
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
@ -29,12 +29,20 @@ export default function handler(
lastName,
subject,
email,
summary
summary,
captchaToken
} = req.body;
const {SMTP_PROXY_EMAIL, SMTP_RECIPIENT_EMAIL, SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD} = process.env;
const {
SMTP_PROXY_EMAIL,
SMTP_RECIPIENT_EMAIL,
SMTP_HOST,
SMTP_PORT,
SMTP_USERNAME,
SMTP_PASSWORD,
RECAPTCHA_SECRET_KEY
} = process.env;
console.log(JSON.stringify({SMTP_PROXY_EMAIL, SMTP_RECIPIENT_EMAIL, SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD}));
const transporter = nodemailer.createTransport({
port: parseInt(SMTP_PORT!),
host: SMTP_HOST!,
@ -45,21 +53,45 @@ export default function handler(
secure: true,
});
const mailData: MailData = {
from: SMTP_PROXY_EMAIL!,
to: SMTP_RECIPIENT_EMAIL!,
subject: `Message From ${firstName} ${lastName}: ${subject}`,
text: summary + " | Sent from: " + email,
html: `<div>${summary}</div><p>Sent from:
${email}</p>`
}
console.log(JSON.stringify({SMTP_PROXY_EMAIL, SMTP_RECIPIENT_EMAIL, SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD}));
try {
const response = await fetch(
`https://www.google.com/recaptcha/api/siteverify?secret=${RECAPTCHA_SECRET_KEY}&response=${captchaToken}`
);
if ((await response.json()).success) {
//reCaptcha verification successfull
const mailData: MailData = {
from: SMTP_PROXY_EMAIL!,
to: SMTP_RECIPIENT_EMAIL!,
subject: `Message From ${firstName} ${lastName}: ${subject}`,
text: summary + " | Sent from: " + email,
html: `<div>${summary}</div><p>Sent from:
${email}</p>`
}
transporter.sendMail(mailData, function (err, info) {
if(err) {
console.log(err);
res.status(500).send('Internal Server Error');
}
else {
console.log('successful');
console.log(info);
res.status(200).end();
}
})
res.status(200).json(req.body);
} else {
// reCAPTCHA verification failed
res.status(400).send('reCAPTCHA verification failed.');
}
} catch (error) {
console.error(error);
res.status(500).send('Internal server error');
}
transporter.sendMail(mailData, function (err, info) {
if(err)
console.log(err)
else
console.log(info)
})
res.status(200).json(req.body);
}

View File

@ -283,6 +283,13 @@
dependencies:
"@types/react" "*"
"@types/react-google-recaptcha@^2.1.8":
version "2.1.8"
resolved "https://registry.yarnpkg.com/@types/react-google-recaptcha/-/react-google-recaptcha-2.1.8.tgz#a76065be49b22851914b539c32601402fde17bbd"
integrity sha512-nYI3ZDoteZ0g4FYusyKWqz7AZqRdu70R3wDkosCcN0peb2WLn57i0Alm4IPiCRIx59yTUVPTiOELZH08gV1wXA==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@latest":
version "18.2.23"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.23.tgz#60ad6cf4895e93bed858db0e03bcc4ff97d0410e"
@ -1529,6 +1536,13 @@ has@^1.0.3:
dependencies:
function-bind "^1.1.1"
hoist-non-react-statics@^3.3.0:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
dependencies:
react-is "^16.7.0"
https-proxy-agent@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
@ -2386,7 +2400,7 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
prop-types@^15.5.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@ -2428,6 +2442,14 @@ rc@^1.2.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
react-async-script@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/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-dom@latest:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
@ -2436,12 +2458,20 @@ react-dom@latest:
loose-envify "^1.1.0"
scheduler "^0.23.0"
react-google-recaptcha@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/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-hook-form@^7.47.0:
version "7.47.0"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.47.0.tgz#a42f07266bd297ddf1f914f08f4b5f9783262f31"
integrity sha512-F/TroLjTICipmHeFlMrLtNLceO2xr1jU3CyiNla5zdwsGUGu2UOxxR4UyJgLlhMwLW/Wzp4cpJ7CPfgJIeKdSg==
react-is@^16.13.1:
react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==