diff --git a/apps/web/package.json b/apps/web/package.json index 4e35ad6..44cc7bf 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -14,6 +14,7 @@ "@repo/db": "*", "@repo/ui": "*", "@tabler/icons-react": "^3.6.0", + "@types/react-google-recaptcha": "^2.1.9", "clsx": "^2.1.1", "framer-motion": "^11.2.12", "mini-svg-data-uri": "^1.4.4", @@ -21,6 +22,7 @@ "next-themes": "^0.3.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-google-recaptcha": "^3.1.0", "tailwind-merge": "^2.3.0" }, "devDependencies": { @@ -31,6 +33,7 @@ "@types/node": "^20.11.24", "@types/react": "^18.2.61", "@types/react-dom": "^18.2.19", + "@types/react-google-recaptcha": "^2.1.9", "autoprefixer": "^10.4.18", "postcss": "^8.4.35", "tailwindcss": "^3.4.1", diff --git a/apps/web/src/app/api/route.ts b/apps/web/src/app/api/pre-register/route.ts similarity index 100% rename from apps/web/src/app/api/route.ts rename to apps/web/src/app/api/pre-register/route.ts diff --git a/apps/web/src/app/api/validate-recaptcha/route.ts b/apps/web/src/app/api/validate-recaptcha/route.ts new file mode 100644 index 0000000..67f1c32 --- /dev/null +++ b/apps/web/src/app/api/validate-recaptcha/route.ts @@ -0,0 +1,22 @@ +import { NextRequest, NextResponse } from "next/server"; + +export async function POST(req: NextRequest, res: NextResponse) { + const { recaptchaResponse } = req.body; + const secretKey = process.env.RECAPTCHA_SECRET; + + const response = await fetch( + `https://www.google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${recaptchaResponse}`, + { + method: "POST", + } + ); + const data = await response.json(); + + console.log("---------------------------------------------") + console.log(data) + + if (data.success) { + return NextResponse.json({success: true}) + } + return NextResponse.json({success: false}) + } \ No newline at end of file diff --git a/apps/web/src/components/pre-register-form.tsx b/apps/web/src/components/pre-register-form.tsx index b0cebd3..756f8cb 100644 --- a/apps/web/src/components/pre-register-form.tsx +++ b/apps/web/src/components/pre-register-form.tsx @@ -1,27 +1,79 @@ "use client"; -import React, { useState } from "react"; +import React, { useState, useRef, FormEvent } from "react"; import { Label } from "@repo/ui/label"; import { Input } from "@repo/ui/input"; -import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@repo/ui/components/ui/dialog"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@repo/ui/components/ui/dialog"; import { Button } from "@repo/ui/components/ui/button"; +import ReCAPTCHA from "react-google-recaptcha"; + +interface FormData { + firstName: string; + lastName: string; + email: string; +} export function PreRegForm() { - const [formData, setFormData] = useState({ + const [formData, setFormData] = useState({ firstName: "", lastName: "", - email: "" - }) - const [isOpen, setIsOpen] = useState(false) - const handleSubmit = async () => { - await fetch("/api", { - method: "POST", body: JSON.stringify(formData) - }) - setIsOpen(false) + email: "", + }); + const [isOpen, setIsOpen] = useState(false); + const [recaptchaToken, setRecaptchaToken] = useState(null); + + const recaptchaRef = useRef(null); + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + if (!recaptchaToken) { + alert("Please complete the reCAPTCHA."); + return; + } + + const recaptchaResponse = recaptchaToken; + + const response = await fetch("/api/validate-recaptcha", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ recaptchaResponse }), + }); + + const data = await response.json(); + console.log(data); + + if (response.ok) { + // reCAPTCHA validation passed + await fetch("/api/pre-register", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + }); + alert("Success"); + } else { + // reCAPTCHA validation failed + alert("reCAPTCHA validation failed. Please try again."); + } + setIsOpen(false); }; + return ( - setIsOpen(prev => !prev)}> + setIsOpen((prev) => !prev)}> - + @@ -30,42 +82,63 @@ export function PreRegForm() { Make changes to your profile here. Click save when you're done. -
-
- - { - setFormData(prev => { - return { - ...prev, - firstName: e.target.value - } - }) - }} /> +
+
+
+ + { + setFormData((prev) => ({ + ...prev, + firstName: e.target.value, + })); + }} + /> +
+
+ + { + setFormData((prev) => ({ + ...prev, + lastName: e.target.value, + })); + }} + /> +
-
- - { - setFormData(prev => { - return { - ...prev, - lastName: e.target.value - } - }) - }} /> + + { + setFormData((prev) => ({ + ...prev, + email: e.target.value, + })); + }} + /> +
+ setRecaptchaToken(token)} + />
-
- - { - setFormData(prev => { - return { - ...prev, - email: e.target.value - } - }) - }} /> - - - + + + +
); diff --git a/yarn.lock b/yarn.lock index c40f70d..515e42b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1226,6 +1226,13 @@ dependencies: "@types/react" "*" +"@types/react-google-recaptcha@^2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@types/react-google-recaptcha/-/react-google-recaptcha-2.1.9.tgz#cd1ffe571fe738473b66690a86dad6c9d3648427" + integrity sha512-nT31LrBDuoSZJN4QuwtQSF3O89FVHC4jLhM+NtKEmVF5R1e8OY0Jo4//x2Yapn2aNHguwgX5doAq8Zo+Ehd0ug== + dependencies: + "@types/react" "*" + "@types/react@*", "@types/react@^18.2.61": version "18.3.2" resolved "https://registry.npmjs.org/@types/react/-/react-18.3.2.tgz" @@ -2840,6 +2847,13 @@ hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: dependencies: function-bind "^1.1.2" +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" + hosted-git-info@^2.1.4: version "2.8.9" resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz" @@ -3780,7 +3794,7 @@ prisma@^5.10.2: dependencies: "@prisma/engines" "5.14.0" -prop-types@^15.8.1: +prop-types@^15.5.0, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -3799,6 +3813,14 @@ queue-microtask@^1.2.2: resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +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@^18.2.0: version "18.3.1" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz" @@ -3807,7 +3829,15 @@ react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.2" -react-is@^16.13.1: +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-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==