diff --git a/app/(default)/api/registration/recruitment/route.ts b/app/(default)/api/registration/recruitment/route.ts
index 94175db..aaee02e 100644
--- a/app/(default)/api/registration/recruitment/route.ts
+++ b/app/(default)/api/registration/recruitment/route.ts
@@ -1,77 +1,153 @@
import { db } from "@/Firebase";
-import { recruitValidate } from "@/lib/utils";
-import { addDoc, collection, getDocs } from "firebase/firestore";
+
+import { recruitValidate } from "@/lib/server/utils";
+
+import {
+ addDoc,
+ collection,
+ getDocs,
+ limit,
+ query,
+ where,
+} from "firebase/firestore";
import { NextResponse } from "next/server";
-// Verify reCAPTCHA Token
-async function verifyRecaptcha(token: string) {
- const secretKey = process.env.RECAPTCHA_SECRET_KEY;
- const response = await fetch(
- `https://www.google.com/recaptcha/api/siteverify`,
+// Add a new registration
+export async function POST(request: Request) {
+ const formData = await request.json();
+ const { recaptcha_token, ...data } = formData;
+ const { email, whatsapp_number, college_id } = formData;
+
+ // Only one Registration per person
+
+ // Query for email
+const emailQuery = query(
+ collection(db, "recruitment2024"),
+ where("email", "==", email)
+);
+
+// Query for phone number
+const phoneQuery = query(
+ collection(db, "recruitment2024"),
+ where("whatsapp_number", "==", whatsapp_number)
+);
+
+// Query for college ID
+const collegeIdQuery = query(
+ collection(db, "recruitment2024"),
+ where("college_id", "==", college_id)
+);
+
+// Fetch results from all queries
+const [emailSnapshot, phoneSnapshot, collegeIdSnapshot] = await Promise.all([
+ getDocs(emailQuery),
+ getDocs(phoneQuery),
+ getDocs(collegeIdQuery)
+]);
+
+
+ console.log(!emailSnapshot.empty);
+
+ if (!emailSnapshot.empty) {
+ return NextResponse.json(
+ {
+ message: "Email is already registered!",
+ error: "Email is already registered!",
+ },
+
+ {
+ status: 500,
+ }
+ );
+ }
+ if (!phoneSnapshot.empty) {
+ return NextResponse.json(
+ {
+ message: "Whatsapp No. is already registered!",
+ error: "Whatsapp No. is already registered!",
+ },
+
+ {
+ status: 500,
+ }
+ );
+ }
+ if (!collegeIdSnapshot.empty) {
+ return NextResponse.json(
+ {
+ message: "College Id is already registered!",
+ error: "College Id is already registered!",
+ },
+
+ {
+ status: 500,
+ }
+ );
+ }
+
+ const recaptchaToken = recaptcha_token;
+
+ const details = {
+ event: {
+ token: recaptchaToken,
+ siteKey: process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY,
+ },
+ };
+
+
+ if (!recaptchaToken) {
+ return NextResponse.json(
+ {
+ message: "reCAPTCHA token not found! Try again",
+ error: "reCAPTCHA token not found!",
+ },
+ {
+ status: 500,
+ }
+ );
+ }
+
+ // Verify the reCATPTCHA token
+
+ const recaptchaResponse = await fetch(
+ `https://recaptchaenterprise.googleapis.com/v1/projects/${process.env.RECAPTCHA_PROJECT}/assessments?key=${process.env.RECAPTCHA_API_KEY}`,
{
method: "POST",
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
- body: new URLSearchParams({
- secret: secretKey || "",
- response: token,
- }),
+ body: JSON.stringify(details),
}
);
- const data = await response.json();
- return data.success;
-}
-// Add a new registration
-export async function POST(request: Request) {
- const data = await request.json();
- const recaptchaToken = data.recaptchaToken; // Extract the reCAPTCHA token from the request
-
- // Verify the reCAPTCHA token
- const isRecaptchaValid = await verifyRecaptcha(recaptchaToken);
-
- if (!isRecaptchaValid) {
+ const recaptchaResult = await recaptchaResponse.json();
+ console.log(recaptchaResult.riskAnalysis.score);
+ if (recaptchaResult.riskAnalysis.score < 0.7) {
return NextResponse.json({
- message: "reCAPTCHA verification failed",
- error: true,
+ message: "reCAPTCHA validation failed",
+ error: recaptchaResult["error-codes"],
});
}
// Validate the data
const val = recruitValidate(data);
- if (!Array.isArray(data)) {
- return NextResponse.json({
- message: "Expected an array of JSON objects",
- error: true,
- });
- }
-
if (val.error) {
- return NextResponse.json({ message: "Validation error", error: val.error });
+ return NextResponse.json(
+ { message: "Validation error", error: val.error },
+ { status: 500 }
+ );
}
+ // Save to Firebase
+
try {
- // Save to Firebase
const docRef = await addDoc(collection(db, "recruitment2024"), data);
console.log("Document written with ID: ", docRef.id);
} catch (error) {
console.error(error);
- return NextResponse.json({ message: "An error occurred", error });
+ return NextResponse.json(
+ { message: "An error occurred", error },
+ { status: 500 }
+ );
}
// Return a response
- return NextResponse.json({ message: "Registration successful", data });
-}
-
-// Get all registrations
-export async function GET() {
- try {
- // Get all registrations in recruitment2024 collection
- const querySnapshot = await getDocs(collection(db, "recruitment2024"));
- // Map the data to get only the data
- const data = querySnapshot.docs.map((doc) => doc.data());
- return NextResponse.json({ data });
- } catch (error) {
- console.error(error);
- return NextResponse.json({ message: "An error occurred", error });
- }
+ return NextResponse.json({ message: "Registration successful" });
}
diff --git a/app/(default)/api/registration/sih/route.ts b/app/(default)/api/registration/sih/route.ts
index b368caa..60c8e71 100644
--- a/app/(default)/api/registration/sih/route.ts
+++ b/app/(default)/api/registration/sih/route.ts
@@ -1,32 +1,91 @@
-import { db } from '@/Firebase';
-import { sihValidate } from '@/lib/utils';
-import { addDoc, collection, getDocs } from 'firebase/firestore';
-import { NextResponse } from 'next/server';
+import { db } from "@/Firebase";
+import {
+ createCSRFToken,
+ getSessionIdFromRequest,
+ verifyCSRFToken,
+} from "@/lib/server/csrf";
+import { sihValidate } from "@/lib/server/utils";
+import { addDoc, collection, getDocs, query, where } from "firebase/firestore";
+import { NextResponse } from "next/server";
+// Add a new registration
+export async function POST(request: Request) {
+ const formData = await request.json();
+ const { recaptcha_token, ...data } = formData;
-// Verify reCAPTCHA Token
-// async function verifyRecaptcha(token: string) {
-// const secretKey = process.env.RECAPTCHA_SECRET_KEY;
-// const response = await fetch(`https://www.google.com/recaptcha/api/siteverify`, {
-// method: 'POST',
-// headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
-// body: new URLSearchParams({
-// secret: secretKey || '',
-// response: token,
-// }),
-// });
-// const data = await response.json();
-// return data.success;
-// }
+ console.log(formData);
+
+ // Only one Registration per Email
-// Add a new registration
-export async function POST(request: Request) {
- const data = await request.json();
- const recaptchaToken = data.recaptchaToken; // Extract the reCAPTCHA token from the request
+ const { team_info: { team_leader: { email } } } = data;
+ const q = query(
+ collection(db, "sih2024"),
+ where("team_info.team_leader.email", "==", email)
+ );
+ const querySnapshot = await getDocs(q);
+
+ console.log(!querySnapshot.empty);
+
+ if (!querySnapshot.empty) {
+ return NextResponse.json(
+ {
+ message: "Email is already registered!",
+ error: "Email is already registered!",
+ },
+
+ {
+ status: 500,
+ }
+ );
+ }
- // Verify the reCAPTCHA token
- // const isRecaptchaValid = await verifyRecaptcha(recaptchaToken);
+ const recaptchaToken = recaptcha_token;
+ if (!recaptchaToken) {
+ return NextResponse.json(
+ {
+ message: "reCAPTCHA token not found! Refresh and try again",
+ error: "reCAPTCHA token not found!",
+ },
+ {
+ status: 500,
+ }
+ );
+ }
+ const recaptchaSecretKey = process.env.RECAPTCHA_SECRET_KEY;
+
+ // Verify reCAPTCHA token
+ const recaptchaResponse = await fetch(
+ `https://www.google.com/recaptcha/api/siteverify?secret=${recaptchaSecretKey}&response=${recaptchaToken}`,
+ { method: "POST" }
+ );
+ const recaptchaResult = await recaptchaResponse.json();
+
+ console.log(recaptchaResult);
+ if (!recaptchaResult.success) {
+ return NextResponse.json(
+ {
+ message: "reCAPTCHA validation failed",
+ error: recaptchaResult["error-codes"],
+ },
+ {
+ status: 500,
+ }
+ );
+ }
+
+ const sessionId = getSessionIdFromRequest(request);
+ const csrfToken = createCSRFToken(sessionId);
+
+ // Verify the CSRF token
+ if (!verifyCSRFToken(sessionId, csrfToken)) {
+ return NextResponse.json(
+ { message: "Invalid CSRF token" },
+ {
+ status: 403,
+ }
+ );
+ }
// if (!isRecaptchaValid) {
// return NextResponse.json({ message: 'reCAPTCHA verification failed', error: true });
@@ -35,33 +94,14 @@ export async function POST(request: Request) {
// Validate the data
const val = sihValidate(data);
- if (val.error) {
- return NextResponse.json({ message: 'Validation error', error: val.error });
- }
-
- try {
- console.log('Data:', data);
- // Save to Firebase
- const docRef = await addDoc(collection(db, "sih2024"), data);
- console.log("Document written with ID: ", docRef.id);
- } catch (error) {
- console.error(error);
- return NextResponse.json({ message: 'An error occurred', error });
- }
- // Return a response
- return NextResponse.json({ message: 'Registration successful', data });
+ try {
+ // Save to Firebase
+ const docRef = await addDoc(collection(db, "sih2024"), data);
+ console.log("Document written with ID: ", docRef.id);
+ } catch (error) {
+ console.error(error);
+ return NextResponse.json({ message: "An error occurred", error });
+ }
+ // Return a response
+ return NextResponse.json({ message: "Registration successful" });
}
-
-//get all registrations
-export async function GET() {
- try {
- // Get all registrations in sih2024 collection
- const querySnapshot = await getDocs(collection(db, "sih2024"));
- // Map the data to get only the data
- const data = querySnapshot.docs.map((doc) => doc.data());
- return NextResponse.json({ data });
- } catch (error) {
- console.error(error);
- return NextResponse.json({ message: 'An error occurred', error });
- }
-}
\ No newline at end of file
diff --git a/app/(default)/layout.tsx b/app/(default)/layout.tsx
index 83a7959..9ac7068 100644
--- a/app/(default)/layout.tsx
+++ b/app/(default)/layout.tsx
@@ -6,6 +6,8 @@ import AOS from 'aos'
import 'aos/dist/aos.css'
import Footer from '@/components/ui/footer'
+import { Toaster } from 'react-hot-toast'
+import Script from 'next/script'
export default function DefaultLayout({
children,
@@ -24,10 +26,11 @@ export default function DefaultLayout({
return (
<>
+
{children}
-
+
diff --git a/app/(default)/recruitment/page.tsx b/app/(default)/recruitment/page.tsx
index 88058ba..e5d8869 100644
--- a/app/(default)/recruitment/page.tsx
+++ b/app/(default)/recruitment/page.tsx
@@ -1,8 +1,8 @@
-'use client'
+"use client";
import RecruitmentForm from "@/components/forms/recruitmentForm";
import DotPattern from "@/components/magicui/dot-pattern";
import "../../css/additional-styles/form.css";
-import { cn } from "@/lib/utils";
+import { cn } from "@/lib/server/utils";
import { onAuthStateChanged } from "firebase/auth";
import { auth } from "@/Firebase";
import { useEffect } from "react";
@@ -18,28 +18,25 @@ const RegisterPage = () => {
// }
// });
// });\
- useEffect(() => {
- router.push("/");
- })
+ // useEffect(() => {
+ // router.push("/");
+ // })
return (
-
-
-
-
-
-
-
+
);
};
diff --git a/app/(default)/sihregistration/page.tsx b/app/(default)/sihregistration/page.tsx
index 7a26964..4435bb4 100644
--- a/app/(default)/sihregistration/page.tsx
+++ b/app/(default)/sihregistration/page.tsx
@@ -4,7 +4,7 @@ import { FormProvider } from "@/components/forms/formContext";
import DotPattern from "@/components/magicui/dot-pattern";
import { useEffect } from "react";
import "../../css/additional-styles/form.css";
-import { cn } from "@/lib/utils";
+import { cn } from "@/lib/server/utils";
import { onAuthStateChanged } from "firebase/auth";
import { auth } from "@/Firebase";
import { useRouter } from "next/navigation";
@@ -25,11 +25,11 @@ const RegisterPage = () => {
-
-
-
- Register for Smart India Hackathon!
-
+
+
+
+ Register for Smart India Hackathon!
+
diff --git a/app/css/additional-styles/form.css b/app/css/additional-styles/form.css
index 972d134..3236161 100644
--- a/app/css/additional-styles/form.css
+++ b/app/css/additional-styles/form.css
@@ -25,26 +25,21 @@
justify-content: center;
align-items: center;
}
+
.container .title {
width: 100%;
position: relative;
display: flex;
align-items: center;
- height: 50px;
-}
-.container .title .block {
- height: inherit;
- background: #52c264;
- position: absolute;
- animation: mainBlock 2s cubic-bezier(0.74, 0.06, 0.4, 0.92) forwards;
- display: flex;
+ height: fit-content;
}
+
.container .title h1 {
color: #fff;
-webkit-animation: mainFadeIn 2s forwards;
-o-animation: mainFadeIn 2s forwards;
animation: mainFadeIn 2s forwards;
- animation-delay: 1.6s;
+ animation-delay: 1s;
opacity: 0;
display: flex;
align-items: baseline;
diff --git a/app/layout.tsx b/app/layout.tsx
index 2595646..d34e65f 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,32 +1,40 @@
+import "./css/style.css";
+import { Inter } from "next/font/google";
+import Header from "@/components/ui/header";
+import "@fortawesome/fontawesome-svg-core/styles.css";
+import { config } from "@fortawesome/fontawesome-svg-core";
+import { ThemeProvider as NextThemesProvider } from "next-themes";
+import Script from "next/script";
-import './css/style.css'
-import { Inter } from 'next/font/google'
-import Header from '@/components/ui/header'
-import '@fortawesome/fontawesome-svg-core/styles.css'
-import { config } from '@fortawesome/fontawesome-svg-core'
-import { ThemeProvider as NextThemesProvider } from 'next-themes'
-
-config.autoAddCss = false
+config.autoAddCss = false;
const inter = Inter({
- subsets: ['latin'],
- variable: '--font-inter',
- display: 'swap'
-})
+ subsets: ["latin"],
+ variable: "--font-inter",
+ display: "swap",
+});
export const metadata = {
- title: 'Point Blank',
- description: 'Hello World',
-}
+ title: "Point Blank",
+ description: "Hello World",
+};
export default function RootLayout({
children,
}: {
- children: React.ReactNode
+ children: React.ReactNode;
}) {
return (
-
-
+
+
+
+
+
@@ -35,5 +43,5 @@ export default function RootLayout({
- )
+ );
}
diff --git a/components/domains.tsx b/components/domains.tsx
index b4e5652..95722b9 100644
--- a/components/domains.tsx
+++ b/components/domains.tsx
@@ -1,63 +1,55 @@
-import { cn } from "@/lib/utils";
+import { cn } from "@/lib/server/utils";
import Marquee from "@/components/magicui/marquee";
const domains = [
- {
- name: "ACM - ICPC",
- img: "https://icpc.global/regionals/abouticpc/foundationlogo.png",
- },
- {
- name: "Kaggle",
- img: "https://img.icons8.com/?size=100&id=Omk4fWoSmCHm&format=png&color=000000",
- },
- {
- name: "IOT-ML",
- img: "https://img.icons8.com/?size=100&id=Ih6zOUuHwOOs&format=png&color=000000",
- },
- {
- name: "ML-Research",
- img: "https://img.icons8.com/?size=100&id=114322&format=png&color=000000",
- },
- {
- name: "DevOps",
- img: "https://img.icons8.com/?size=100&id=13816&format=png&color=000000",
- },
- {
- name: "Flutter Development",
- img: "https://img.icons8.com/?size=100&id=7I3BjCqe9rjG&format=png&color=000000",
- },
- {
- name: "React Development",
- img: "https://img.icons8.com/?size=100&id=123603&format=png&color=000000",
- },
- {
- name: "Open Source Hackathon",
- img: "https://img.icons8.com/?size=100&id=63655&format=png&color=000000",
- },
- {
- name: "Interview Prep",
- img: "https://img.icons8.com/?size=100&id=13724&format=png&color=000000",
- },
+ {
+ name: "ACM - ICPC",
+ img: "https://icpc.global/regionals/abouticpc/foundationlogo.png",
+ },
+ {
+ name: "Kaggle",
+ img: "https://img.icons8.com/?size=100&id=Omk4fWoSmCHm&format=png&color=000000",
+ },
+ {
+ name: "IOT-ML",
+ img: "https://img.icons8.com/?size=100&id=Ih6zOUuHwOOs&format=png&color=000000",
+ },
+ {
+ name: "ML-Research",
+ img: "https://img.icons8.com/?size=100&id=114322&format=png&color=000000",
+ },
+ {
+ name: "DevOps",
+ img: "https://img.icons8.com/?size=100&id=13816&format=png&color=000000",
+ },
+ {
+ name: "Flutter Development",
+ img: "https://img.icons8.com/?size=100&id=7I3BjCqe9rjG&format=png&color=000000",
+ },
+ {
+ name: "React Development",
+ img: "https://img.icons8.com/?size=100&id=123603&format=png&color=000000",
+ },
+ {
+ name: "Open Source Hackathon",
+ img: "https://img.icons8.com/?size=100&id=63655&format=png&color=000000",
+ },
+ {
+ name: "Interview Prep",
+ img: "https://img.icons8.com/?size=100&id=13724&format=png&color=000000",
+ },
];
-const Card = ({
- img,
- name,
-}: {
- img: string;
- name: string;
-}) => {
+const Card = ({ img, name }: { img: string; name: string }) => {
return (
-
- {name}
-
+
{name}
);
@@ -66,8 +58,13 @@ const Card = ({
export default function Domains() {
return (
-
Domains We Explore
-
Our club covers a wide range of interests and fields, bringing unique perspectives and expertise to every project!
+
+ Domains We Explore
+
+
+ Our club covers a wide range of interests and fields, bringing unique
+ perspectives and expertise to every project!
+
{domains.map((domain) => (
@@ -77,4 +74,4 @@ export default function Domains() {
);
-}
\ No newline at end of file
+}
diff --git a/components/forms/reCaptcha.tsx b/components/forms/reCaptcha.tsx
deleted file mode 100644
index d6381cf..0000000
--- a/components/forms/reCaptcha.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { useEffect } from "react";
-
-const Recaptcha = ({ onChange }: { onChange: (token: string | null) => void }) => {
- useEffect(() => {
- const loadRecaptcha = () => {
- if (window.grecaptcha) {
- window.grecaptcha.ready(() => {
- window.grecaptcha.execute(process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY || '', { action: 'submit' }).then((token: string) => {
- onChange(token);
- });
- });
- }
- };
-
- const script = document.createElement("script");
- script.src = `https://www.google.com/recaptcha/api.js?render=${process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY}`;
- script.async = true;
- script.defer = true;
- script.onload = loadRecaptcha;
- document.head.appendChild(script);
-
- return () => {
- document.head.removeChild(script);
- };
- }, [onChange]);
-
- return null;
-};
-
-export default Recaptcha;
-
-
-
diff --git a/components/forms/recruitmentForm.tsx b/components/forms/recruitmentForm.tsx
index 7f997a5..ce789ba 100644
--- a/components/forms/recruitmentForm.tsx
+++ b/components/forms/recruitmentForm.tsx
@@ -4,13 +4,16 @@ import "../../app/css/additional-styles/theme.css";
import { useState } from "react";
import { useForm, SubmitHandler } from "react-hook-form";
import { years, branches } from "@/lib/constants/dropdownOptions";
-import Recaptcha from "./reCaptcha";
+import Success from "./success";
+import toast from "react-hot-toast";
+import { getErrorMessage, fetchCsrfToken } from "@/lib/client/clientUtils";
const RecruitmentForm: React.FC = () => {
const [isSuccess, setSuccess] = useState
(false);
const [mode, setMode] = useState(false);
const [display, setDisplay] = useState(false);
- const [recaptchaToken, setRecaptchaToken] = useState(null);
+ const [token, setToken] = useState("");
+
const {
register,
@@ -28,97 +31,52 @@ const RecruitmentForm: React.FC = () => {
},
});
+ const setTokenFunc = (getToken: string) => {
+ setToken(getToken);
+ };
const changeMode = (e: any) => {
- console.log(e.target.value);
if (e.target.value === "1st year") setMode(true);
else setMode(false);
setDisplay(true);
};
const onSubmit: SubmitHandler = async (data: any) => {
- if (!recaptchaToken) {
- alert("Please complete the reCAPTCHA");
- return;
- }
-
-
try {
- let response = await fetch("/api/registration/recruitment", {
+ grecaptcha.enterprise.ready(async () => {
+ const token = await grecaptcha.enterprise.execute(
+ process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY
+ );
+ setTokenFunc(token);
+ });
+
+ data.recaptcha_token = token;
+
+ const response = await fetch("/api/registration/recruitment", {
method: "POST",
- headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
- if (response.status == 200) {
- setSuccess(true);
+
+ const res = await response.json();
+
+ if (!response.ok || res.error) {
+ console.log(response.json);
+ toast.error(res.message);
+ return;
}
- response = await response.json();
+
+ setSuccess(true);
} catch (error) {
console.error("Error submitting form:", error);
+ toast.error(getErrorMessage(error));
}
};
if (isSuccess) {
return (
-
-
-
-
-
- Registration Successfull!
-
-
- Data Saved ! Good Luck for the Test!
-
-
- Join the Whatsapp Group for further updates immediately.
-
-
-
-
-
-
+
);
}
@@ -306,10 +264,9 @@ const RecruitmentForm: React.FC = () => {
)}
-
-
Submit
diff --git a/components/forms/sihForm.tsx b/components/forms/sihForm.tsx
index 503244a..c3d0671 100644
--- a/components/forms/sihForm.tsx
+++ b/components/forms/sihForm.tsx
@@ -5,7 +5,9 @@ import { useEffect, useState } from "react";
import Accordion from "./accordion";
import { useForm, SubmitHandler } from "react-hook-form";
import { useFormContext } from "../forms/formContext";
-import Recaptcha from "./reCaptcha";
+import toast from "react-hot-toast";
+import { getErrorMessage, fetchCsrfToken } from "@/lib/client/clientUtils";
+
import {
years,
courses,
@@ -13,6 +15,11 @@ import {
problems,
} from "@/lib/constants/dropdownOptions";
import { useRouter } from "next/navigation";
+import {
+ GoogleReCaptcha,
+ GoogleReCaptchaProvider,
+} from "react-google-recaptcha-v3";
+import Success from "./success";
import { onAuthStateChanged } from "firebase/auth";
import { auth } from "@/Firebase";
@@ -20,6 +27,14 @@ const SIHMultiStepForm: React.FC = () => {
const { formData, setFormData } = useFormContext();
const [step, setStep] = useState(1);
const [isSuccess, setSuccess] = useState(false);
+ const [token, setToken] = useState("");
+ const [refreshReCaptcha, setRefreshReCaptcha] = useState(false);
+ const [csrfToken, setCsrfToken] = useState("");
+
+ const setTokenFunc = (getToken: string) => {
+ setToken(getToken);
+ };
+
const [recaptchaToken, setRecaptchaToken] = useState(null);
const router = useRouter();
@@ -45,12 +60,15 @@ const SIHMultiStepForm: React.FC = () => {
} = useForm({
defaultValues: formData,
});
- const onSubmit: SubmitHandler = async (data : any) => {
+ const onSubmit: SubmitHandler = async (data: any) => {
+ data.recaptcha_token = token;
+
+ const csrftoken = await fetchCsrfToken();
+ setCsrfToken(csrftoken);
setFormData({ ...formData, ...data });
if (step < 3) {
setStep(step + 1);
- console.log(step);
} else {
if (!recaptchaToken) {
alert("Please complete the reCAPTCHA");
@@ -60,81 +78,38 @@ const SIHMultiStepForm: React.FC = () => {
try {
let response = await fetch("/api/registration/sih", {
method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "X-CSRF-Token": csrfToken,
+ },
body: JSON.stringify(data),
});
- if (response.status == 200) {
- setSuccess(true);
+
+ const res = await response.json();
+
+ if (!response.ok || res.error) {
+ console.log(response.json);
+ toast.error(res.message);
+ return;
}
- response = await response.json();
+
+ setSuccess(true);
} catch (error) {
+ setRefreshReCaptcha(!refreshReCaptcha);
console.error("Error submitting form:", error);
+
+ toast.error(getErrorMessage(error));
}
}
};
+
if (isSuccess) {
- const router = useRouter();
return (
-
-
-
-
-
- Registration Successfull!
-
-
- You have successfully registered for Smart India Hackathon.
-
-
- Join the Whatsapp Group for further updates immediately.
-
-
-
-
-
-
+
);
}
@@ -669,9 +644,6 @@ const SIHMultiStepForm: React.FC = () => {
>
Previous
-
-
-
+
+
+
+
+ Registration Successfull!
+
+
+ {props.message}
+
+
+ Join the Whatsapp Group for further updates immediately.
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/components/hero.tsx b/components/hero.tsx
index d38e533..6c49251 100644
--- a/components/hero.tsx
+++ b/components/hero.tsx
@@ -1,11 +1,10 @@
-import logo from '@/public/images/logo.svg'
-import Link from 'next/link'
-import Image from 'next/image'
+import logo from "@/public/images/logo.svg";
+import Link from "next/link";
+import Image from "next/image";
import FlickeringGrid from "@/components/magicui/flickering-grid";
-import { cn } from "@/lib/utils";
+import { cn } from "@/lib/server/utils";
import "../app/css/additional-styles/landing.css";
-
export default function Hero() {
return (
@@ -15,9 +14,20 @@ export default function Hero() {
-
-
- We are a student run tech community at Dayananda Sagar College of Engineering, Bangalore. We aim to provide a platform for students to learn, grow and innovate.
+
+
+ We are a student run tech community at Dayananda Sagar
+ College of Engineering, Bangalore. We aim to provide a
+ platform for students to learn, grow and innovate.
@@ -34,5 +44,5 @@ export default function Hero() {
flickerChance={0.5}
/>
- )
-}
\ No newline at end of file
+ );
+}
diff --git a/components/magicui/animated-grid-pattern.tsx b/components/magicui/animated-grid-pattern.tsx
index ce9201d..a541c28 100644
--- a/components/magicui/animated-grid-pattern.tsx
+++ b/components/magicui/animated-grid-pattern.tsx
@@ -3,7 +3,7 @@
import { useEffect, useId, useRef, useState } from "react";
import { motion } from "framer-motion";
-import { cn } from "@/lib/utils";
+import { cn } from "@/lib/server/utils";
interface GridPatternProps {
width?: number;
@@ -57,11 +57,11 @@ export function GridPattern({
currentSquares.map((sq) =>
sq.id === id
? {
- ...sq,
- pos: getPos(),
- }
- : sq,
- ),
+ ...sq,
+ pos: getPos(),
+ }
+ : sq
+ )
);
};
@@ -100,7 +100,7 @@ export function GridPattern({
aria-hidden="true"
className={cn(
"pointer-events-none absolute inset-0 h-full w-full fill-gray-400/30 stroke-gray-400/30",
- className,
+ className
)}
{...props}
>
diff --git a/components/magicui/dot-pattern.tsx b/components/magicui/dot-pattern.tsx
index ea42a5e..679b09e 100644
--- a/components/magicui/dot-pattern.tsx
+++ b/components/magicui/dot-pattern.tsx
@@ -1,6 +1,6 @@
import { useId } from "react";
-import { cn } from "@/lib/utils";
+import { cn } from "@/lib/server/utils";
interface DotPatternProps {
width?: any;
@@ -31,7 +31,7 @@ export function DotPattern({
aria-hidden="true"
className={cn(
"pointer-events-none absolute inset-0 h-full w-full fill-neutral-400/80",
- className,
+ className
)}
{...props}
>
diff --git a/components/magicui/gradual-spacing.tsx b/components/magicui/gradual-spacing.tsx
index 188d148..69d8c1e 100644
--- a/components/magicui/gradual-spacing.tsx
+++ b/components/magicui/gradual-spacing.tsx
@@ -2,7 +2,7 @@
import { AnimatePresence, motion, Variants } from "framer-motion";
-import { cn } from "@/lib/utils";
+import { cn } from "@/lib/server/utils";
interface GradualSpacingProps {
text: string;
diff --git a/components/magicui/hyper-text.tsx b/components/magicui/hyper-text.tsx
index e50bc43..7cb8424 100644
--- a/components/magicui/hyper-text.tsx
+++ b/components/magicui/hyper-text.tsx
@@ -3,7 +3,7 @@
import { useEffect, useRef, useState } from "react";
import { AnimatePresence, motion, Variants } from "framer-motion";
-import { cn } from "@/lib/utils";
+import { cn } from "@/lib/server/utils";
interface HyperTextProps {
text: string;
@@ -39,31 +39,28 @@ export default function HyperText({
};
useEffect(() => {
- const interval = setInterval(
- () => {
- if (!animateOnLoad && isFirstRender.current) {
- clearInterval(interval);
- isFirstRender.current = false;
- return;
- }
- if (interations.current < text.length) {
- setDisplayText((t) =>
- t.map((l, i) =>
- l === " "
- ? l
- : i <= interations.current
- ? text[i]
- : alphabets[getRandomInt(26)],
- ),
- );
- interations.current = interations.current + 0.1;
- } else {
- setTrigger(false);
- clearInterval(interval);
- }
- },
- duration / (text.length * 10),
- );
+ const interval = setInterval(() => {
+ if (!animateOnLoad && isFirstRender.current) {
+ clearInterval(interval);
+ isFirstRender.current = false;
+ return;
+ }
+ if (interations.current < text.length) {
+ setDisplayText((t) =>
+ t.map((l, i) =>
+ l === " "
+ ? l
+ : i <= interations.current
+ ? text[i]
+ : alphabets[getRandomInt(26)]
+ )
+ );
+ interations.current = interations.current + 0.1;
+ } else {
+ setTrigger(false);
+ clearInterval(interval);
+ }
+ }, duration / (text.length * 10));
// Clean up interval on unmount
return () => clearInterval(interval);
}, [text, duration, trigger, animateOnLoad]);
diff --git a/components/magicui/marquee.tsx b/components/magicui/marquee.tsx
index 6f50a86..c60fa0b 100644
--- a/components/magicui/marquee.tsx
+++ b/components/magicui/marquee.tsx
@@ -1,4 +1,4 @@
-import { cn } from "@/lib/utils";
+import { cn } from "@/lib/server/utils";
interface MarqueeProps {
className?: string;
@@ -28,7 +28,7 @@ export default function Marquee({
"flex-row": !vertical,
"flex-col": vertical,
},
- className,
+ className
)}
>
{Array(repeat)
diff --git a/components/magicui/sparkles-text.tsx b/components/magicui/sparkles-text.tsx
index 945daed..751a3fa 100644
--- a/components/magicui/sparkles-text.tsx
+++ b/components/magicui/sparkles-text.tsx
@@ -3,7 +3,7 @@
import { CSSProperties, ReactElement, useEffect, useState } from "react";
import { motion } from "framer-motion";
-import { cn } from "@/lib/utils";
+import { cn } from "@/lib/server/utils";
interface Sparkle {
id: string;
@@ -94,7 +94,7 @@ const SparklesText: React.FC
= ({
} else {
return { ...star, lifespan: star.lifespan - 0.1 };
}
- }),
+ })
);
};
diff --git a/global.d.ts b/global.d.ts
new file mode 100644
index 0000000..3ee164f
--- /dev/null
+++ b/global.d.ts
@@ -0,0 +1 @@
+declare var grecaptcha: any;
\ No newline at end of file
diff --git a/lib/client/clientUtils.ts b/lib/client/clientUtils.ts
new file mode 100644
index 0000000..6a8eaf4
--- /dev/null
+++ b/lib/client/clientUtils.ts
@@ -0,0 +1,34 @@
+import { NextResponse } from "next/server";
+
+export const getErrorMessage = (error: unknown): string => {
+ let message: string;
+ if (error instanceof Error) {
+ message = error.message;
+ } else if (error && typeof error === "object" && "message" in error) {
+ message = String(error.message);
+ } else if (typeof error === "string") {
+ message = error;
+ } else {
+ }
+ message = "Something went wrong! Try refreshing and submitting again.";
+ return message;
+ };
+
+ export async function fetchCsrfToken() {
+ try {
+ const response = await fetch('/api/csrf-token', {
+ method: 'GET',
+ credentials: 'include',
+ });
+
+ if (!response.ok) {
+ return NextResponse.json({message: 'Failed to fetch CSRF token'});
+ }
+
+ const data = await response.json();
+ return data.csrfToken;
+ } catch (error) {
+ console.error('Error fetching CSRF token:', error);
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/lib/utils.ts b/lib/server/utils.ts
similarity index 97%
rename from lib/utils.ts
rename to lib/server/utils.ts
index 2a2acd5..410dbcb 100644
--- a/lib/utils.ts
+++ b/lib/server/utils.ts
@@ -43,7 +43,7 @@ export const recruitValidate = (data: any) => {
if (!data.year_of_study) {
return { error: 'year of study is required' }
}
- if (!data.admission_number) {
+ if (!data.college_id) {
return { error: 'admission_number is required' }
}
if (!data.about) {
diff --git a/package-lock.json b/package-lock.json
index 8e16e97..24fc338 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,23 +18,28 @@
"@types/react": "^18.2.42",
"@types/react-dom": "^18.2.17",
"aos": "^3.0.0-beta.6",
- "axios": "^1.7.4",
+ "axios": "^1.7.7",
"canvas-confetti": "^1.9.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cobe": "^0.6.3",
+ "cookie": "^0.6.0",
+ "csrf": "^3.1.0",
+ "edge-csrf": "^2.0.0-rc7",
"firebase": "^10.12.5",
"framer-motion": "^11.5.6",
"headlessui": "^0.0.0",
"lodash": "^4.17.21",
"lucide-react": "^0.419.0",
"next": "^14.0.4",
+ "next-csrf": "^0.2.1",
"next-rate-limit": "^0.0.3",
"next-themes": "^0.3.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-google-recaptcha-v3": "^1.10.1",
"react-hook-form": "^7.53.0",
+ "react-hot-toast": "^2.4.1",
"react-icons": "^5.3.0",
"react-spinners": "^0.14.1",
"react-spring": "^9.7.4",
@@ -46,6 +51,7 @@
"@tailwindcss/forms": "^0.5.7",
"@types/aos": "^3.0.7",
"@types/canvas-confetti": "^1.6.4",
+ "@types/cookie": "^0.6.0",
"autoprefixer": "^10.4.16",
"postcss": "^8.4.32",
"tailwindcss": "^3.3.6"
@@ -3070,17 +3076,19 @@
}
},
"node_modules/@next/env": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.5.tgz",
- "integrity": "sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA=="
+ "version": "14.2.13",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.13.tgz",
+ "integrity": "sha512-s3lh6K8cbW1h5Nga7NNeXrbe0+2jIIYK9YaA9T7IufDWnZpozdFUp6Hf0d5rNWUKu4fEuSX2rCKlGjCrtylfDw==",
+ "license": "MIT"
},
"node_modules/@next/swc-darwin-arm64": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz",
- "integrity": "sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==",
+ "version": "14.2.13",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.13.tgz",
+ "integrity": "sha512-IkAmQEa2Htq+wHACBxOsslt+jMoV3msvxCn0WFSfJSkv/scy+i/EukBKNad36grRxywaXUYJc9mxEGkeIs8Bzg==",
"cpu": [
"arm64"
],
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
@@ -3090,12 +3098,13 @@
}
},
"node_modules/@next/swc-darwin-x64": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz",
- "integrity": "sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==",
+ "version": "14.2.13",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.13.tgz",
+ "integrity": "sha512-Dv1RBGs2TTjkwEnFMVL5XIfJEavnLqqwYSD6LXgTPdEy/u6FlSrLBSSfe1pcfqhFEXRAgVL3Wpjibe5wXJzWog==",
"cpu": [
"x64"
],
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
@@ -3105,12 +3114,13 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz",
- "integrity": "sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==",
+ "version": "14.2.13",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.13.tgz",
+ "integrity": "sha512-yB1tYEFFqo4ZNWkwrJultbsw7NPAAxlPXURXioRl9SdW6aIefOLS+0TEsKrWBtbJ9moTDgU3HRILL6QBQnMevg==",
"cpu": [
"arm64"
],
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -3120,12 +3130,13 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz",
- "integrity": "sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==",
+ "version": "14.2.13",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.13.tgz",
+ "integrity": "sha512-v5jZ/FV/eHGoWhMKYrsAweQ7CWb8xsWGM/8m1mwwZQ/sutJjoFaXchwK4pX8NqwImILEvQmZWyb8pPTcP7htWg==",
"cpu": [
"arm64"
],
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -3135,12 +3146,13 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz",
- "integrity": "sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==",
+ "version": "14.2.13",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.13.tgz",
+ "integrity": "sha512-aVc7m4YL7ViiRv7SOXK3RplXzOEe/qQzRA5R2vpXboHABs3w8vtFslGTz+5tKiQzWUmTmBNVW0UQdhkKRORmGA==",
"cpu": [
"x64"
],
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -3150,12 +3162,13 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz",
- "integrity": "sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==",
+ "version": "14.2.13",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.13.tgz",
+ "integrity": "sha512-4wWY7/OsSaJOOKvMsu1Teylku7vKyTuocvDLTZQq0TYv9OjiYYWt63PiE1nTuZnqQ4RPvME7Xai+9enoiN0Wrg==",
"cpu": [
"x64"
],
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -3165,12 +3178,13 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz",
- "integrity": "sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==",
+ "version": "14.2.13",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.13.tgz",
+ "integrity": "sha512-uP1XkqCqV2NVH9+g2sC7qIw+w2tRbcMiXFEbMihkQ8B1+V6m28sshBwAB0SDmOe0u44ne1vFU66+gx/28RsBVQ==",
"cpu": [
"arm64"
],
+ "license": "MIT",
"optional": true,
"os": [
"win32"
@@ -3180,12 +3194,13 @@
}
},
"node_modules/@next/swc-win32-ia32-msvc": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz",
- "integrity": "sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==",
+ "version": "14.2.13",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.13.tgz",
+ "integrity": "sha512-V26ezyjPqQpDBV4lcWIh8B/QICQ4v+M5Bo9ykLN+sqeKKBxJVDpEc6biDVyluTXTC40f5IqCU0ttth7Es2ZuMw==",
"cpu": [
"ia32"
],
+ "license": "MIT",
"optional": true,
"os": [
"win32"
@@ -3195,12 +3210,13 @@
}
},
"node_modules/@next/swc-win32-x64-msvc": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz",
- "integrity": "sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==",
+ "version": "14.2.13",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.13.tgz",
+ "integrity": "sha512-WwzOEAFBGhlDHE5Z73mNU8CO8mqMNLqaG+AO9ETmzdCQlJhVtWZnOl2+rqgVQS+YHunjOWptdFmNfbpwcUuEsw==",
"cpu": [
"x64"
],
+ "license": "MIT",
"optional": true,
"os": [
"win32"
@@ -7267,12 +7283,23 @@
"integrity": "sha512-fNyZ/Fdw/Y92X0vv7B+BD6ysHL4xVU5dJcgzgxLdGbn8O3PezZNIJpml44lKM0nsGur+o/6+NZbZeNTt00U1uA==",
"dev": true
},
+ "node_modules/@types/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
+ "dev": true
+ },
"node_modules/@types/debounce": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.4.tgz",
"integrity": "sha512-jBqiORIzKDOToaF63Fm//haOCHuwQuLa2202RK4MozpA6lh93eCBc+/8+wZn5OzjJt3ySdc+74SXWXB55Ewtyw==",
"peer": true
},
+ "node_modules/@types/http-errors": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.2.tgz",
+ "integrity": "sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w=="
+ },
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
@@ -7605,9 +7632,10 @@
}
},
"node_modules/axios": {
- "version": "1.7.4",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
- "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
+ "version": "1.7.7",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
+ "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
+ "license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
@@ -8373,6 +8401,22 @@
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"peer": true
},
+ "node_modules/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.1.tgz",
+ "integrity": "sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
"node_modules/core-js-compat": {
"version": "3.38.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.0.tgz",
@@ -8420,6 +8464,19 @@
"node": ">= 8"
}
},
+ "node_modules/csrf": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/csrf/-/csrf-3.1.0.tgz",
+ "integrity": "sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==",
+ "dependencies": {
+ "rndm": "1.2.0",
+ "tsscmp": "1.0.6",
+ "uid-safe": "2.1.5"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -8512,6 +8569,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.8"
@@ -8521,6 +8579,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.8",
@@ -8547,6 +8606,12 @@
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
},
+ "node_modules/edge-csrf": {
+ "version": "2.0.0-rc7",
+ "resolved": "https://registry.npmjs.org/edge-csrf/-/edge-csrf-2.0.0-rc7.tgz",
+ "integrity": "sha512-9+r9jnWStACk8sHUFGyCJtPLgUPah+cWeaT5kHGjVqCrcRmlgm5xPDhckwoYoqonJMAgQtieuNEC5e5H56vteQ==",
+ "deprecated": "This package has been split into separate integration packages located at @edge-csrf/"
+ },
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -8667,6 +8732,7 @@
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.6"
@@ -9009,6 +9075,7 @@
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.6"
@@ -9034,6 +9101,20 @@
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"peer": true
},
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -9118,6 +9199,15 @@
"node": ">=4"
}
},
+ "node_modules/goober": {
+ "version": "2.1.14",
+ "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz",
+ "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "csstype": "^3.0.10"
+ }
+ },
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -9187,6 +9277,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "license": "MIT",
"peer": true,
"dependencies": {
"depd": "2.0.0",
@@ -9203,6 +9294,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.8"
@@ -10645,9 +10737,10 @@
}
},
"node_modules/micromatch": {
- "version": "4.0.7",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
- "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "license": "MIT",
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
@@ -10806,11 +10899,12 @@
"peer": true
},
"node_modules/next": {
- "version": "14.2.5",
- "resolved": "https://registry.npmjs.org/next/-/next-14.2.5.tgz",
- "integrity": "sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==",
+ "version": "14.2.13",
+ "resolved": "https://registry.npmjs.org/next/-/next-14.2.13.tgz",
+ "integrity": "sha512-BseY9YNw8QJSwLYD7hlZzl6QVDoSFHL/URN5K64kVEVpCsSOWeyjbIGK+dZUaRViHTaMQX8aqmnn0PHBbGZezg==",
+ "license": "MIT",
"dependencies": {
- "@next/env": "14.2.5",
+ "@next/env": "14.2.13",
"@swc/helpers": "0.5.5",
"busboy": "1.6.0",
"caniuse-lite": "^1.0.30001579",
@@ -10825,15 +10919,15 @@
"node": ">=18.17.0"
},
"optionalDependencies": {
- "@next/swc-darwin-arm64": "14.2.5",
- "@next/swc-darwin-x64": "14.2.5",
- "@next/swc-linux-arm64-gnu": "14.2.5",
- "@next/swc-linux-arm64-musl": "14.2.5",
- "@next/swc-linux-x64-gnu": "14.2.5",
- "@next/swc-linux-x64-musl": "14.2.5",
- "@next/swc-win32-arm64-msvc": "14.2.5",
- "@next/swc-win32-ia32-msvc": "14.2.5",
- "@next/swc-win32-x64-msvc": "14.2.5"
+ "@next/swc-darwin-arm64": "14.2.13",
+ "@next/swc-darwin-x64": "14.2.13",
+ "@next/swc-linux-arm64-gnu": "14.2.13",
+ "@next/swc-linux-arm64-musl": "14.2.13",
+ "@next/swc-linux-x64-gnu": "14.2.13",
+ "@next/swc-linux-x64-musl": "14.2.13",
+ "@next/swc-win32-arm64-msvc": "14.2.13",
+ "@next/swc-win32-ia32-msvc": "14.2.13",
+ "@next/swc-win32-x64-msvc": "14.2.13"
},
"peerDependencies": {
"@opentelemetry/api": "^1.1.0",
@@ -10854,6 +10948,26 @@
}
}
},
+ "node_modules/next-csrf": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/next-csrf/-/next-csrf-0.2.1.tgz",
+ "integrity": "sha512-6KN0tQSUltAttX2uA18YPMe0/dkIpofvOeTXGXlmHtXqhtNke60KVryWVKS1ovjLlBGkjBea9eRmXlA/Hm4PDQ==",
+ "dependencies": {
+ "@types/http-errors": "^1.8.0",
+ "cookie": "^0.4.1",
+ "cookie-signature": "^1.1.0",
+ "csrf": "^3.1.0",
+ "querystring": "^0.2.0"
+ }
+ },
+ "node_modules/next-csrf/node_modules/cookie": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
+ "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/next-rate-limit": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/next-rate-limit/-/next-rate-limit-0.0.3.tgz",
@@ -11668,7 +11782,6 @@
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz",
"integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==",
"deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.",
- "peer": true,
"engines": {
"node": ">=0.4.x"
}
@@ -11701,10 +11814,19 @@
}
]
},
+ "node_modules/random-bytes": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
+ "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.6"
@@ -11791,6 +11913,22 @@
"react": "^16.8.0 || ^17 || ^18 || ^19"
}
},
+ "node_modules/react-hot-toast": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz",
+ "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==",
+ "license": "MIT",
+ "dependencies": {
+ "goober": "^2.1.10"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": ">=16",
+ "react-dom": ">=16"
+ }
+ },
"node_modules/react-icons": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.3.0.tgz",
@@ -12376,6 +12514,11 @@
"node": "*"
}
},
+ "node_modules/rndm": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz",
+ "integrity": "sha512-fJhQQI5tLrQvYIYFpOnFinzv9dwmR7hRnUz1XqP3OJ1jIweTNOd6aTO4jwQSgcBSFUB+/KHJxuGneime+FdzOw=="
+ },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -12442,9 +12585,10 @@
}
},
"node_modules/send": {
- "version": "0.18.0",
- "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
- "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+ "version": "0.19.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
+ "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
+ "license": "MIT",
"peer": true,
"dependencies": {
"debug": "2.6.9",
@@ -12469,6 +12613,7 @@
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
"peer": true,
"dependencies": {
"ms": "2.0.0"
@@ -12478,12 +12623,14 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT",
"peer": true
},
"node_modules/send/node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "license": "MIT",
"peer": true,
"bin": {
"mime": "cli.js"
@@ -12496,12 +12643,14 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT",
"peer": true
},
"node_modules/send/node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
"peer": true,
"dependencies": {
"ee-first": "1.1.1"
@@ -12514,6 +12663,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.8"
@@ -12529,20 +12679,31 @@
}
},
"node_modules/serve-static": {
- "version": "1.15.0",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
- "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+ "version": "1.16.2",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+ "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+ "license": "MIT",
"peer": true,
"dependencies": {
- "encodeurl": "~1.0.2",
+ "encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
- "send": "0.18.0"
+ "send": "0.19.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
+ "node_modules/serve-static/node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@@ -12553,6 +12714,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC",
"peer": true
},
"node_modules/shallow-clone": {
@@ -13268,6 +13430,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
"peer": true,
"engines": {
"node": ">=0.6"
@@ -13289,6 +13452,14 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="
},
+ "node_modules/tsscmp": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz",
+ "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==",
+ "engines": {
+ "node": ">=0.6.x"
+ }
+ },
"node_modules/type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
@@ -13319,6 +13490,17 @@
"node": ">=14.17"
}
},
+ "node_modules/uid-safe": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
+ "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
+ "dependencies": {
+ "random-bytes": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/undici": {
"version": "5.28.4",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz",
diff --git a/package.json b/package.json
index 70d2518..665e154 100644
--- a/package.json
+++ b/package.json
@@ -19,23 +19,28 @@
"@types/react": "^18.2.42",
"@types/react-dom": "^18.2.17",
"aos": "^3.0.0-beta.6",
- "axios": "^1.7.4",
+ "axios": "^1.7.7",
"canvas-confetti": "^1.9.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cobe": "^0.6.3",
+ "cookie": "^0.6.0",
+ "csrf": "^3.1.0",
+ "edge-csrf": "^2.0.0-rc7",
"firebase": "^10.12.5",
"framer-motion": "^11.5.6",
"headlessui": "^0.0.0",
"lodash": "^4.17.21",
"lucide-react": "^0.419.0",
"next": "^14.0.4",
+ "next-csrf": "^0.2.1",
"next-rate-limit": "^0.0.3",
"next-themes": "^0.3.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-google-recaptcha-v3": "^1.10.1",
"react-hook-form": "^7.53.0",
+ "react-hot-toast": "^2.4.1",
"react-icons": "^5.3.0",
"react-spinners": "^0.14.1",
"react-spring": "^9.7.4",
@@ -47,6 +52,7 @@
"@tailwindcss/forms": "^0.5.7",
"@types/aos": "^3.0.7",
"@types/canvas-confetti": "^1.6.4",
+ "@types/cookie": "^0.6.0",
"autoprefixer": "^10.4.16",
"postcss": "^8.4.32",
"tailwindcss": "^3.3.6"