diff --git a/app/(default)/pbctf/page.tsx b/app/(default)/pbctf/page.tsx new file mode 100644 index 0000000..60c8079 --- /dev/null +++ b/app/(default)/pbctf/page.tsx @@ -0,0 +1,41 @@ +"use client"; +import PBCTFForm from "@/components/forms/pbctfForm"; +import DotPattern from "@/components/magicui/dot-pattern"; +import { cn } from "@/lib/utils"; +import { onAuthStateChanged } from "firebase/auth"; +import { auth } from "@/Firebase"; +import { useEffect } from "react"; +import { useRouter } from "next/navigation"; + +const PBCTFRegisterPage = () => { + const router = useRouter(); + + useEffect(() => { + onAuthStateChanged(auth, (user) => { + if (!user) { + router.push("/login"); + } + }); + }, [router]); + + return ( +
+
+ +
+ +
+ ); +}; + +export default PBCTFRegisterPage; + diff --git a/app/css/additional-styles/theme.css b/app/css/additional-styles/theme.css index bba8017..226afa8 100644 --- a/app/css/additional-styles/theme.css +++ b/app/css/additional-styles/theme.css @@ -178,4 +178,7 @@ html { -webkit-transform: translate3d(10px, 0, 0) scale(1.2); transform: translate3d(10px, 0, 0) scale(1.2); } -} \ No newline at end of file +} + + + diff --git a/components/forms/pbctfForm.tsx b/components/forms/pbctfForm.tsx new file mode 100644 index 0000000..4027470 --- /dev/null +++ b/components/forms/pbctfForm.tsx @@ -0,0 +1,364 @@ +"use client"; +import "../../app/css/additional-styles/utility-patterns.css"; +import "../../app/css/additional-styles/theme.css"; +import { useEffect, useState } from "react"; +import { useForm, SubmitHandler } from "react-hook-form"; +import { collection, addDoc, query, where, getDocs } from "firebase/firestore"; +import { db } from "@/Firebase"; +import { branches } from "@/lib/constants/dropdownOptions"; +import { Press_Start_2P } from "next/font/google"; + +const pressStart2P = Press_Start_2P({ + weight: "400", + subsets: ["latin"], +}); + +type ParticipantData = { + name: string; + year: string; + branch: string; + usn: string; + email: string; + phone: string; +}; + +type FormData = { + participationType: "solo" | "duo"; + participant1: ParticipantData; + participant2?: ParticipantData; +}; + +const PBCTFForm: React.FC = () => { + const [isSuccess, setSuccess] = useState(false); + const [participationType, setParticipationType] = useState<"solo" | "duo">("solo"); + const [isSubmitting, setIsSubmitting] = useState(false); + const [usnError, setUsnError] = useState(null); + + const [headingText, setHeadingText] = useState(""); + const heading = "Be a Part of PBCTF Register Now!"; + + useEffect(() => { + let currentIndex = 0; + let timeoutId: NodeJS.Timeout; + + const typeEffect = () => { + if (currentIndex <= heading.length) { + setHeadingText(heading.substring(0, currentIndex)); + currentIndex++; + timeoutId = setTimeout(typeEffect, 200); // Typing speed + } + }; + + typeEffect(); + + return () => clearTimeout(timeoutId); + }, []); + + const { + register, + handleSubmit, + formState: { errors }, + watch, + } = useForm(); + + const watchYear1 = watch("participant1.year"); + const watchYear2 = watch("participant2.year"); + const watchUsn1 = watch("participant1.usn"); + const watchUsn2 = watch("participant2.usn"); + + const checkUsnUniqueness = async (usn: string): Promise => { + const q = query(collection(db, "pbctf_registrations"), + where("participant1.usn", "==", usn)); + const querySnapshot = await getDocs(q); + + if (!querySnapshot.empty) { + return false; + } + + const q2 = query(collection(db, "pbctf_registrations"), + where("participant2.usn", "==", usn)); + const querySnapshot2 = await getDocs(q2); + + return querySnapshot2.empty; + }; + + const onSubmit: SubmitHandler = async (data) => { + if (isSubmitting) return; + setIsSubmitting(true); + setUsnError(null); + + try { + // Check if USNs are the same for duo participation + if (data.participationType === "duo" && data.participant2 && data.participant1.usn === data.participant2.usn) { + setUsnError("USNs for Participant 1 and Participant 2 cannot be the same"); + setIsSubmitting(false); + return; + } + + // Check USN uniqueness for participant1 + const isUnique1 = await checkUsnUniqueness(data.participant1.usn); + if (!isUnique1) { + setUsnError("USN for Participant 1 already exists"); + setIsSubmitting(false); + return; + } + + // Check USN uniqueness for participant2 if it exists + if (data.participationType === "duo" && data.participant2) { + const isUnique2 = await checkUsnUniqueness(data.participant2.usn); + if (!isUnique2) { + setUsnError("USN for Participant 2 already exists"); + setIsSubmitting(false); + return; + } + } + + // If all checks pass, submit the form + await addDoc(collection(db, "pbctf_registrations"), data); + setSuccess(true); + } catch (error) { + console.error("Error submitting form:", error); + } finally { + setIsSubmitting(false); + } + }; + + if (isSuccess) { + return ( +
+
+
+
+ + + +
+

+ Registration Successful! +

+

+ You have successfully registered for the PBCTF! +

+

+ Join the WhatsApp Group for further updates immediately. +

+
+ +
+
+ ); + } + + const renderParticipantFields = (participantNumber: 1 | 2) => ( +
+

Participant {participantNumber}

+
+
+ + {errors[`participant${participantNumber}`]?.name && ( +

+ {errors[`participant${participantNumber}`]?.name?.message} +

+ )} +
+
+
+ + {errors[`participant${participantNumber}`]?.year && ( +

+ {errors[`participant${participantNumber}`]?.year?.message} +

+ )} +
+
+ + {errors[`participant${participantNumber}`]?.branch && ( +

+ {errors[`participant${participantNumber}`]?.branch?.message} +

+ )} +
+
+
+ + {errors[`participant${participantNumber}`]?.usn && ( +

+ {errors[`participant${participantNumber}`]?.usn?.message} +

+ )} +
+
+ + {errors[`participant${participantNumber}`]?.email && ( +

+ {errors[`participant${participantNumber}`]?.email?.message} +

+ )} +
+
+ + {errors[`participant${participantNumber}`]?.phone && ( +

+ {errors[`participant${participantNumber}`]?.phone?.message} +

+ )} +
+
+
+ ); + + return ( +
+

+ {headingText} +

+
+
+ + +
+ + {renderParticipantFields(1)} + {participationType === "duo" && renderParticipantFields(2)} + + {usnError && ( +

{usnError}

+ )} + +
+ +
+
+
+ ); +}; + +export default PBCTFForm; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f525820..8e16e97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,8 +24,9 @@ "clsx": "^2.1.1", "cobe": "^0.6.3", "firebase": "^10.12.5", - "framer-motion": "^11.3.21", + "framer-motion": "^11.5.6", "headlessui": "^0.0.0", + "lodash": "^4.17.21", "lucide-react": "^0.419.0", "next": "^14.0.4", "next-rate-limit": "^0.0.3", @@ -8980,9 +8981,10 @@ } }, "node_modules/framer-motion": { - "version": "11.3.24", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.3.24.tgz", - "integrity": "sha512-kl0YI7HwAtyV0VOAWuU/rXoOS8+z5qSkMN6rZS+a9oe6fIha6SC3vjJN6u/hBpvjrg5MQNdSnqnjYxm0WYTX9g==", + "version": "11.5.6", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.5.6.tgz", + "integrity": "sha512-JMwUpAxv/DWgul9vPgX0ElKn0G66sUc6O9tOXsYwn3zxwvhxFljSXC0XT2QCzuTYBshwC8nyDAa1SYcV0Ldbhw==", + "license": "MIT", "dependencies": { "tslib": "^2.4.0" }, @@ -9928,6 +9930,12 @@ "node": ">=8" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -10154,6 +10162,7 @@ "version": "0.419.0", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.419.0.tgz", "integrity": "sha512-YkOHuc1uGH2A4G0NRZyeCW6mMFGb8z3amep0fARuKIri68nveAT5C8OuXOPJXpb/iIgSfsjdMjjII7bnEtGkvw==", + "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } diff --git a/package.json b/package.json index 15d8352..70d2518 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,9 @@ "clsx": "^2.1.1", "cobe": "^0.6.3", "firebase": "^10.12.5", - "framer-motion": "^11.3.21", + "framer-motion": "^11.5.6", "headlessui": "^0.0.0", + "lodash": "^4.17.21", "lucide-react": "^0.419.0", "next": "^14.0.4", "next-rate-limit": "^0.0.3",