feat(contact-us): Add hCaptcha verification to contact form
- Install @hcaptcha/react-hcaptcha dependency for CAPTCHA protection - Add hCaptcha component integration with site key configuration - Implement captcha token state management with verification callbacks - Add token validation before form submission with user feedback - Reset captcha after successful form submission - Include captcha in form submission payload as hcaptcha_token - Update Next.js to version 15.5.7 for compatibility - Enhance form security by requiring CAPTCHA verification before contact submission
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import api from "@/service/api";
|
import api from "@/service/api";
|
||||||
import { useState } from "react";
|
import HCaptcha from "@hcaptcha/react-hcaptcha";
|
||||||
|
import { useRef, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import InputGroup from "../_components/InputGroup";
|
import InputGroup from "../_components/InputGroup";
|
||||||
@@ -22,18 +23,37 @@ const ContactUsForm = ({
|
|||||||
columnsPerRow = 1,
|
columnsPerRow = 1,
|
||||||
}: IProps) => {
|
}: IProps) => {
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [hcaptchaToken, setHcaptchaToken] = useState<string | null>(null);
|
||||||
|
const captchaRef = useRef<HCaptcha>(null);
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
reset,
|
reset,
|
||||||
} = useForm<TContactUsForm>();
|
} = useForm<TContactUsForm>();
|
||||||
|
|
||||||
|
const onCaptchaVerify = (token: string) => {
|
||||||
|
setHcaptchaToken(token);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCaptchaExpire = () => {
|
||||||
|
setHcaptchaToken(null);
|
||||||
|
};
|
||||||
|
|
||||||
const onSubmit = async (data: TContactUsForm) => {
|
const onSubmit = async (data: TContactUsForm) => {
|
||||||
|
if (!hcaptchaToken) {
|
||||||
|
toast.error("Please complete the captcha verification");
|
||||||
|
return;
|
||||||
|
}
|
||||||
setIsLoading(() => true);
|
setIsLoading(() => true);
|
||||||
try {
|
try {
|
||||||
const response = (await api.post("contacts", { ...data })).data;
|
const response = (
|
||||||
|
await api.post("contacts", { ...data, hcaptcha_token: hcaptchaToken })
|
||||||
|
).data;
|
||||||
toast.success(response.message);
|
toast.success(response.message);
|
||||||
reset();
|
reset();
|
||||||
|
setHcaptchaToken(null);
|
||||||
|
captchaRef.current?.resetCaptcha();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
toast.error("Something went wrong");
|
toast.error("Something went wrong");
|
||||||
@@ -143,6 +163,14 @@ const ContactUsForm = ({
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<div className={`${colSpanClass}`}>
|
||||||
|
<HCaptcha
|
||||||
|
sitekey={process.env.NEXT_PUBLIC_HCAPTCHA_SITE_KEY || ""}
|
||||||
|
onVerify={onCaptchaVerify}
|
||||||
|
onExpire={onCaptchaExpire}
|
||||||
|
ref={captchaRef}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className={`${colSpanClass} flex justify-end`}>
|
<div className={`${colSpanClass} flex justify-end`}>
|
||||||
<button className="bg-(--primary) cursor-pointer w-fit text-white rounded-full py-4 px-6 flex justify-center items-center text-center">
|
<button className="bg-(--primary) cursor-pointer w-fit text-white rounded-full py-4 px-6 flex justify-center items-center text-center">
|
||||||
{isLoading ? <Loading /> : buttonText}
|
{isLoading ? <Loading /> : buttonText}
|
||||||
|
|||||||
Generated
+70
-40
@@ -8,13 +8,14 @@
|
|||||||
"name": "tm-landing",
|
"name": "tm-landing",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@hcaptcha/react-hcaptcha": "^1.16.0",
|
||||||
"@tanstack/react-query": "^5.90.7",
|
"@tanstack/react-query": "^5.90.7",
|
||||||
"@types/dompurify": "^3.2.0",
|
"@types/dompurify": "^3.2.0",
|
||||||
"apexcharts": "^5.3.5",
|
"apexcharts": "^5.3.5",
|
||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
"dompurify": "^3.3.0",
|
"dompurify": "^3.3.0",
|
||||||
"firebase": "^12.6.0",
|
"firebase": "^12.6.0",
|
||||||
"next": "15.5.5",
|
"next": "^15.5.7",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-apexcharts": "^1.8.0",
|
"react-apexcharts": "^1.8.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
@@ -43,6 +44,15 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/runtime": {
|
||||||
|
"version": "7.28.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.4.tgz",
|
||||||
|
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@emnapi/runtime": {
|
"node_modules/@emnapi/runtime": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.5.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.5.0.tgz",
|
||||||
@@ -692,6 +702,26 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@hcaptcha/loader": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@hcaptcha/loader/-/loader-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-i4lnNxKBe+COf3R1nFZEWaZoHIoJjvDgWqvcNrdZq8ehoSNMN6KVZ56dcQ02qKie2h3+BkbkwlJA9DOIuLlK/g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@hcaptcha/react-hcaptcha": {
|
||||||
|
"version": "1.16.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@hcaptcha/react-hcaptcha/-/react-hcaptcha-1.16.0.tgz",
|
||||||
|
"integrity": "sha512-ALRsAwQDCtGeR9QXrLjf7IQsWlaIg1dQWpPYdBR03KHkUrLgRjmqLKkOXaq8L+0qcicXRasHRuvXp2Vinun9aw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.17.9",
|
||||||
|
"@hcaptcha/loader": "^2.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.3.0",
|
||||||
|
"react-dom": ">= 16.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@img/colour": {
|
"node_modules/@img/colour": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@img/colour/-/colour-1.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@img/colour/-/colour-1.0.0.tgz",
|
||||||
@@ -1184,15 +1214,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/env": {
|
"node_modules/@next/env": {
|
||||||
"version": "15.5.5",
|
"version": "15.5.7",
|
||||||
"resolved": "https://registry.npmmirror.com/@next/env/-/env-15.5.5.tgz",
|
"resolved": "https://registry.npmmirror.com/@next/env/-/env-15.5.7.tgz",
|
||||||
"integrity": "sha512-2Zhvss36s/yL+YSxD5ZL5dz5pI6ki1OLxYlh6O77VJ68sBnlUrl5YqhBgCy7FkdMsp9RBeGFwpuDCdpJOqdKeQ==",
|
"integrity": "sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-arm64": {
|
"node_modules/@next/swc-darwin-arm64": {
|
||||||
"version": "15.5.5",
|
"version": "15.5.7",
|
||||||
"resolved": "https://registry.npmmirror.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.5.tgz",
|
"resolved": "https://registry.npmmirror.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.7.tgz",
|
||||||
"integrity": "sha512-lYExGHuFIHeOxf40mRLWoA84iY2sLELB23BV5FIDHhdJkN1LpRTPc1MDOawgTo5ifbM5dvAwnGuHyNm60G1+jw==",
|
"integrity": "sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1206,9 +1236,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-x64": {
|
"node_modules/@next/swc-darwin-x64": {
|
||||||
"version": "15.5.5",
|
"version": "15.5.7",
|
||||||
"resolved": "https://registry.npmmirror.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.5.tgz",
|
"resolved": "https://registry.npmmirror.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.7.tgz",
|
||||||
"integrity": "sha512-cacs/WQqa96IhqUm+7CY+z/0j9sW6X80KE07v3IAJuv+z0UNvJtKSlT/T1w1SpaQRa9l0wCYYZlRZUhUOvEVmg==",
|
"integrity": "sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1222,9 +1252,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||||
"version": "15.5.5",
|
"version": "15.5.7",
|
||||||
"resolved": "https://registry.npmmirror.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.5.tgz",
|
"resolved": "https://registry.npmmirror.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.7.tgz",
|
||||||
"integrity": "sha512-tLd90SvkRFik6LSfuYjcJEmwqcNEnVYVOyKTacSazya/SLlSwy/VYKsDE4GIzOBd+h3gW+FXqShc2XBavccHCg==",
|
"integrity": "sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1238,9 +1268,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-musl": {
|
"node_modules/@next/swc-linux-arm64-musl": {
|
||||||
"version": "15.5.5",
|
"version": "15.5.7",
|
||||||
"resolved": "https://registry.npmmirror.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.5.tgz",
|
"resolved": "https://registry.npmmirror.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.7.tgz",
|
||||||
"integrity": "sha512-ekV76G2R/l3nkvylkfy9jBSYHeB4QcJ7LdDseT6INnn1p51bmDS1eGoSoq+RxfQ7B1wt+Qa0pIl5aqcx0GLpbw==",
|
"integrity": "sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1254,9 +1284,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-gnu": {
|
"node_modules/@next/swc-linux-x64-gnu": {
|
||||||
"version": "15.5.5",
|
"version": "15.5.7",
|
||||||
"resolved": "https://registry.npmmirror.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.5.tgz",
|
"resolved": "https://registry.npmmirror.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.7.tgz",
|
||||||
"integrity": "sha512-tI+sBu+3FmWtqlqD4xKJcj3KJtqbniLombKTE7/UWyyoHmOyAo3aZ7QcEHIOgInXOG1nt0rwh0KGmNbvSB0Djg==",
|
"integrity": "sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1270,9 +1300,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-musl": {
|
"node_modules/@next/swc-linux-x64-musl": {
|
||||||
"version": "15.5.5",
|
"version": "15.5.7",
|
||||||
"resolved": "https://registry.npmmirror.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.5.tgz",
|
"resolved": "https://registry.npmmirror.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.7.tgz",
|
||||||
"integrity": "sha512-kDRh+epN/ulroNJLr+toDjN+/JClY5L+OAWjOrrKCI0qcKvTw9GBx7CU/rdA2bgi4WpZN3l0rf/3+b8rduEwrQ==",
|
"integrity": "sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1286,9 +1316,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||||
"version": "15.5.5",
|
"version": "15.5.7",
|
||||||
"resolved": "https://registry.npmmirror.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.5.tgz",
|
"resolved": "https://registry.npmmirror.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.7.tgz",
|
||||||
"integrity": "sha512-GDgdNPFFqiKjTrmfw01sMMRWhVN5wOCmFzPloxa7ksDfX6TZt62tAK986f0ZYqWpvDFqeBCLAzmgTURvtQBdgw==",
|
"integrity": "sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1302,9 +1332,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-x64-msvc": {
|
"node_modules/@next/swc-win32-x64-msvc": {
|
||||||
"version": "15.5.5",
|
"version": "15.5.7",
|
||||||
"resolved": "https://registry.npmmirror.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.5.tgz",
|
"resolved": "https://registry.npmmirror.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.7.tgz",
|
||||||
"integrity": "sha512-5kE3oRJxc7M8RmcTANP8RGoJkaYlwIiDD92gSwCjJY0+j8w8Sl1lvxgQ3bxfHY2KkHFai9tpy/Qx1saWV8eaJQ==",
|
"integrity": "sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2659,12 +2689,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/next": {
|
"node_modules/next": {
|
||||||
"version": "15.5.5",
|
"version": "15.5.7",
|
||||||
"resolved": "https://registry.npmmirror.com/next/-/next-15.5.5.tgz",
|
"resolved": "https://registry.npmmirror.com/next/-/next-15.5.7.tgz",
|
||||||
"integrity": "sha512-OQVdBPtpBfq7HxFN0kOVb7rXXOSIkt5lTzDJDGRBcOyVvNRIWFauMqi1gIHd1pszq1542vMOGY0HP4CaiALfkA==",
|
"integrity": "sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/env": "15.5.5",
|
"@next/env": "15.5.7",
|
||||||
"@swc/helpers": "0.5.15",
|
"@swc/helpers": "0.5.15",
|
||||||
"caniuse-lite": "^1.0.30001579",
|
"caniuse-lite": "^1.0.30001579",
|
||||||
"postcss": "8.4.31",
|
"postcss": "8.4.31",
|
||||||
@@ -2677,14 +2707,14 @@
|
|||||||
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@next/swc-darwin-arm64": "15.5.5",
|
"@next/swc-darwin-arm64": "15.5.7",
|
||||||
"@next/swc-darwin-x64": "15.5.5",
|
"@next/swc-darwin-x64": "15.5.7",
|
||||||
"@next/swc-linux-arm64-gnu": "15.5.5",
|
"@next/swc-linux-arm64-gnu": "15.5.7",
|
||||||
"@next/swc-linux-arm64-musl": "15.5.5",
|
"@next/swc-linux-arm64-musl": "15.5.7",
|
||||||
"@next/swc-linux-x64-gnu": "15.5.5",
|
"@next/swc-linux-x64-gnu": "15.5.7",
|
||||||
"@next/swc-linux-x64-musl": "15.5.5",
|
"@next/swc-linux-x64-musl": "15.5.7",
|
||||||
"@next/swc-win32-arm64-msvc": "15.5.5",
|
"@next/swc-win32-arm64-msvc": "15.5.7",
|
||||||
"@next/swc-win32-x64-msvc": "15.5.5",
|
"@next/swc-win32-x64-msvc": "15.5.7",
|
||||||
"sharp": "^0.34.3"
|
"sharp": "^0.34.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|||||||
+2
-1
@@ -8,13 +8,14 @@
|
|||||||
"start": "next start"
|
"start": "next start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@hcaptcha/react-hcaptcha": "^1.16.0",
|
||||||
"@tanstack/react-query": "^5.90.7",
|
"@tanstack/react-query": "^5.90.7",
|
||||||
"@types/dompurify": "^3.2.0",
|
"@types/dompurify": "^3.2.0",
|
||||||
"apexcharts": "^5.3.5",
|
"apexcharts": "^5.3.5",
|
||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
"dompurify": "^3.3.0",
|
"dompurify": "^3.3.0",
|
||||||
"firebase": "^12.6.0",
|
"firebase": "^12.6.0",
|
||||||
"next": "15.5.5",
|
"next": "^15.5.7",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-apexcharts": "^1.8.0",
|
"react-apexcharts": "^1.8.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user