- add recaptcha boilerplate for contact form
- still need to add api key to both project and ci/cd
This commit is contained in:
parent
6cd3a9deba
commit
8b97fcb73e
@ -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)
|
||||
|
||||
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>
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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,6 +53,15 @@ export default function handler(
|
||||
secure: true,
|
||||
});
|
||||
|
||||
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!,
|
||||
@ -55,11 +72,26 @@ export default function handler(
|
||||
}
|
||||
|
||||
transporter.sendMail(mailData, function (err, info) {
|
||||
if(err)
|
||||
console.log(err)
|
||||
else
|
||||
console.log(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');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
34
yarn.lock
34
yarn.lock
@ -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==
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user