- 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 { 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 ContactForm = () => {
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
|
resetField,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
|
setValue,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm();
|
} = useForm();
|
||||||
|
|
||||||
const [submitted, setSubmitted] = useState(false);
|
const [submitted, setSubmitted] = useState(false);
|
||||||
|
|
||||||
const handleFormSubmission: SubmitHandler<FieldValues> = async (data) => {
|
const handleFormSubmission: SubmitHandler<FieldValues> = async (data: FieldValues) => {
|
||||||
console.log(data)
|
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',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json, text/plain, */*',
|
'Accept': 'application/json, text/plain, */*',
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify(data)
|
body: JSON.stringify(submissionData)
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Response received')
|
console.log('Response received')
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
console.log('Response succeeded!')
|
console.log('Response succeeded!')
|
||||||
setSubmitted(true)
|
setSubmitted(true)
|
||||||
|
resetField('captchaToken')
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -33,6 +51,12 @@ const ContactForm = () => {
|
|||||||
console.log(error)
|
console.log(error)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onCaptchaChange = (token: string | null) => {
|
||||||
|
// Set the captcha token when the user completes the reCAPTCHA
|
||||||
|
if (token) {
|
||||||
|
setValue('captchaToken', token);
|
||||||
|
}
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div className='w-full flex flex-row justify-center'>
|
<div className='w-full flex flex-row justify-center'>
|
||||||
<form className=" w-1/2" onSubmit={handleSubmit(handleFormSubmission, handleFormError)}>
|
<form className=" w-1/2" onSubmit={handleSubmit(handleFormSubmission, handleFormError)}>
|
||||||
@ -62,11 +86,22 @@ const ContactForm = () => {
|
|||||||
</select>
|
</select>
|
||||||
{errors.subject && <p>Subject is required.</p>}
|
{errors.subject && <p>Subject is required.</p>}
|
||||||
</div>
|
</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">
|
<div className="flex flex-col w-full">
|
||||||
<textarea placeholder='Summary...' className="flex grow h-52" {...register('summary')} />
|
<textarea placeholder='Summary...' className="flex grow h-52" {...register('summary')} />
|
||||||
{errors.summary && <p>Please enter a message for your Project Inquiry.</p>}
|
{errors.summary && <p>Please enter a message for your Project Inquiry.</p>}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
<input className="bg-blue-600 text-white rounded-sm cursor-pointer" type="submit" />
|
<input className="bg-blue-600 text-white rounded-sm cursor-pointer" type="submit" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -16,12 +16,14 @@
|
|||||||
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@types/nodemailer": "^6.4.14",
|
"@types/nodemailer": "^6.4.14",
|
||||||
|
"@types/react-google-recaptcha": "^2.1.8",
|
||||||
"iframe-resizer-react": "^1.1.0",
|
"iframe-resizer-react": "^1.1.0",
|
||||||
"next": "latest",
|
"next": "latest",
|
||||||
"nodemailer": "^6.9.7",
|
"nodemailer": "^6.9.7",
|
||||||
"pdfjs-dist": "^4.0.269",
|
"pdfjs-dist": "^4.0.269",
|
||||||
"react": "latest",
|
"react": "latest",
|
||||||
"react-dom": "latest",
|
"react-dom": "latest",
|
||||||
|
"react-google-recaptcha": "^3.1.0",
|
||||||
"react-hook-form": "^7.47.0",
|
"react-hook-form": "^7.47.0",
|
||||||
"react-modal": "^3.16.1",
|
"react-modal": "^3.16.1",
|
||||||
"react-pdf": "^7.4.0",
|
"react-pdf": "^7.4.0",
|
||||||
|
|||||||
@ -18,7 +18,7 @@ type MailData = {
|
|||||||
html?: string
|
html?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function handler(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse<Data>
|
res: NextApiResponse<Data>
|
||||||
) {
|
) {
|
||||||
@ -29,12 +29,20 @@ export default function handler(
|
|||||||
lastName,
|
lastName,
|
||||||
subject,
|
subject,
|
||||||
email,
|
email,
|
||||||
summary
|
summary,
|
||||||
|
captchaToken
|
||||||
|
|
||||||
} = req.body;
|
} = 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({
|
const transporter = nodemailer.createTransport({
|
||||||
port: parseInt(SMTP_PORT!),
|
port: parseInt(SMTP_PORT!),
|
||||||
host: SMTP_HOST!,
|
host: SMTP_HOST!,
|
||||||
@ -45,21 +53,45 @@ export default function handler(
|
|||||||
secure: true,
|
secure: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mailData: MailData = {
|
console.log(JSON.stringify({SMTP_PROXY_EMAIL, SMTP_RECIPIENT_EMAIL, SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD}));
|
||||||
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)
|
|
||||||
else
|
|
||||||
console.log(info)
|
|
||||||
})
|
|
||||||
|
|
||||||
res.status(200).json(req.body);
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
34
yarn.lock
34
yarn.lock
@ -283,6 +283,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@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":
|
"@types/react@*", "@types/react@latest":
|
||||||
version "18.2.23"
|
version "18.2.23"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.23.tgz#60ad6cf4895e93bed858db0e03bcc4ff97d0410e"
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.23.tgz#60ad6cf4895e93bed858db0e03bcc4ff97d0410e"
|
||||||
@ -1529,6 +1536,13 @@ has@^1.0.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind "^1.1.1"
|
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:
|
https-proxy-agent@^5.0.0:
|
||||||
version "5.0.1"
|
version "5.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
|
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"
|
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||||
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
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"
|
version "15.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||||
@ -2428,6 +2442,14 @@ rc@^1.2.7:
|
|||||||
minimist "^1.2.0"
|
minimist "^1.2.0"
|
||||||
strip-json-comments "~2.0.1"
|
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:
|
react-dom@latest:
|
||||||
version "18.2.0"
|
version "18.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
|
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"
|
loose-envify "^1.1.0"
|
||||||
scheduler "^0.23.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:
|
react-hook-form@^7.47.0:
|
||||||
version "7.47.0"
|
version "7.47.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.47.0.tgz#a42f07266bd297ddf1f914f08f4b5f9783262f31"
|
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.47.0.tgz#a42f07266bd297ddf1f914f08f4b5f9783262f31"
|
||||||
integrity sha512-F/TroLjTICipmHeFlMrLtNLceO2xr1jU3CyiNla5zdwsGUGu2UOxxR4UyJgLlhMwLW/Wzp4cpJ7CPfgJIeKdSg==
|
integrity sha512-F/TroLjTICipmHeFlMrLtNLceO2xr1jU3CyiNla5zdwsGUGu2UOxxR4UyJgLlhMwLW/Wzp4cpJ7CPfgJIeKdSg==
|
||||||
|
|
||||||
react-is@^16.13.1:
|
react-is@^16.13.1, react-is@^16.7.0:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user