-
+
+
© {new Date().getFullYear()} PrepEdge AI. All rights reserved.
-
-
- Privacy
-
-
- Terms
-
-
- Cookies
-
+
+ Privacy
+ Terms
+ Cookies
diff --git a/client/src/components/InterviewList.jsx b/client/src/components/InterviewList.jsx
index 253680b..71a5051 100644
--- a/client/src/components/InterviewList.jsx
+++ b/client/src/components/InterviewList.jsx
@@ -3,105 +3,97 @@ import { useState } from "react";
import { FaChevronLeft, FaChevronRight } from "react-icons/fa";
function InterviewCard({ title, date, interviewId, className = "" }) {
- const navigate = useNavigate();
- return (
-
-
-
-
-
-
- );
+ const navigate = useNavigate();
+ return (
+
+
+
+
+
+
+ );
}
-export default function InterviewList({ interviews, itemsPerPage = 3 }) {
- const [currentPage, setCurrentPage] = useState(1);
- const totalPages = Math.ceil(interviews.length / itemsPerPage);
+export default function InterviewList({ interviews = [], itemsPerPage = 3 }) {
+ // ✅ Always force interviews to be an array
+ const safeInterviews = Array.isArray(interviews) ? interviews : [];
- const startIndex = (currentPage - 1) * itemsPerPage;
- const endIndex = startIndex + itemsPerPage;
- const currentInterviews = interviews.slice(startIndex, endIndex);
+ const [currentPage, setCurrentPage] = useState(1);
+ const totalPages = Math.ceil(safeInterviews.length / itemsPerPage);
- const goToPage = (page) => {
- setCurrentPage(page);
- };
+ const startIndex = (currentPage - 1) * itemsPerPage;
+ const endIndex = startIndex + itemsPerPage;
+ const currentInterviews = safeInterviews.slice(startIndex, endIndex);
- const goToPrevious = () => {
- if (currentPage > 1) {
- setCurrentPage(currentPage - 1);
- }
- };
+ const goToPage = (page) => setCurrentPage(page);
+ const goToPrevious = () => currentPage > 1 && setCurrentPage(currentPage - 1);
+ const goToNext = () =>
+ currentPage < totalPages && setCurrentPage(currentPage + 1);
- const goToNext = () => {
- if (currentPage < totalPages) {
- setCurrentPage(currentPage + 1);
- }
- };
+ return (
+
+
+ Past Interviews
+
- return (
-
-
- Past Interviews
-
+
+ {currentInterviews.length > 0 ? (
+ currentInterviews.map((interview) => (
+
+ ))
+ ) : (
+
No interviews found.
+ )}
+
-
- {currentInterviews.map((interview) => (
-
- ))}
-
+ {totalPages > 1 && (
+
+
- {totalPages > 1 && (
-
-
+ {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
+
+ ))}
- {Array.from({ length: totalPages }, (_, i) => i + 1).map(
- (page) => (
-
- )
- )}
-
-
-
- )}
-
- );
+
+
+ )}
+
+ );
}
diff --git a/client/src/pages/CreateInterview.jsx b/client/src/pages/CreateInterview.jsx
index b7b8a9a..a5953ff 100644
--- a/client/src/pages/CreateInterview.jsx
+++ b/client/src/pages/CreateInterview.jsx
@@ -4,595 +4,324 @@ import axios from "axios";
import LoadingScreen from "../components/LoadingScreen";
import { useAuth } from "../context/AuthContext";
import {
- FaArrowRight,
- FaArrowLeft,
- FaUpload,
- FaFileAlt,
- FaTimes,
+ FaArrowRight,
+ FaArrowLeft,
+ FaUpload,
+ FaFileAlt,
+ FaTimes,
} from "react-icons/fa";
import Toast from "../components/Toast";
export default function SetupForm() {
- const { user, loading, setLoading } = useAuth();
- const [currentStep, setCurrentStep] = useState(1);
- const [isTransitioning, setIsTransitioning] = useState(false);
- const [drag, setDrag] = useState(false);
- const [toast, setToast] = useState({
- show: false,
- message: "",
- type: "success",
- });
- const fileInputRef = useRef(null);
-
- useEffect(() => {
- console.log("--------\nCurrent user from context:", user, "\n--------");
- }, [user]);
- const navigate = useNavigate();
-
- const [formData, setFormData] = useState({
- interviewName: "",
- numOfQuestions: 3,
- interviewType: "Technical",
- role: "",
- experienceLevel: "Fresher",
- companyName: "",
- companyDescription: "",
- jobDescription: "",
- resume: null,
- focusAt: "",
- });
-
- const totalSteps = 3;
- const progressPercentage = ((currentStep - 1) / (totalSteps - 1)) * 100;
-
- const handleNext = () => {
- if (currentStep < totalSteps) {
- setIsTransitioning(true);
- setTimeout(() => {
- setCurrentStep(currentStep + 1);
- setIsTransitioning(false);
- }, 150);
- }
- };
-
- const handleBack = () => {
- if (currentStep > 1) {
- setIsTransitioning(true);
- setTimeout(() => {
- setCurrentStep(currentStep - 1);
- setIsTransitioning(false);
- }, 150);
- }
- };
-
- const handleInputChange = (field, value) => {
- setFormData((prev) => ({ ...prev, [field]: value }));
- };
-
- const handleDrag = (e) => {
- e.preventDefault();
- e.stopPropagation();
- if (e.type === "dragenter" || e.type === "dragover") {
- setDrag(true);
- } else if (e.type === "dragleave") {
- setDrag(false);
- }
- };
-
- const handleDrop = (e) => {
- e.preventDefault();
- e.stopPropagation();
- setDrag(false);
-
- if (e.dataTransfer.files && e.dataTransfer.files[0]) {
- const file = e.dataTransfer.files[0];
- if (file.type === "application/pdf") {
- handleInputChange("resume", file);
- }
- }
- };
-
- const handleFileSelect = (e) => {
- if (e.target.files && e.target.files[0]) {
- const file = e.target.files[0];
- if (file.type === "application/pdf") {
- handleInputChange("resume", file);
- }
- }
- };
-
- const removeFile = () => {
- handleInputChange("resume", null);
- 1;
- if (fileInputRef.current) {
- fileInputRef.current.value = "";
- }
- };
-
- const showToast = (message, type) => {
- setToast({ show: true, message, type });
- };
-
- const hideToast = () => {
- setToast((prev) => ({ ...prev, show: false }));
- };
-
- const handleSubmit = async (e) => {
- e.preventDefault();
- if (!user) {
- console.error("❌ User is null in CreateInterview.jsx");
- return;
- }
- setLoading(true);
- const token = await user.getIdToken();
- console.log("-------\nToken being sent to backend:", token, "\n-------");
-
- const data = new FormData();
- Object.keys(formData).forEach((key) => {
- if (formData[key]) {
- data.append(key, formData[key]);
- }
- console.log("-------\nForm Data: \n", [...data.entries()], "\n-------");
- });
-
- try {
- const res = await axios.post(
- `${import.meta.env.VITE_API_URL}/api/interview/setup`,
- data,
- {
- headers: {
- Authorization: `Bearer ${token}`,
- },
- }
- );
- console.log("-------\nResponse from interview setup:", res.data, "\n-------");
-
- showToast("Interview setup complete!", "success");
- const interviewId = res.data.interview._id;
- navigate(`/interview/${interviewId}`);
- } catch (err) {
- console.error(
- "------\nError in interview setup:",
- err.response?.data || err,
- "\n------"
- );
- showToast("Something went wrong", "error");
- } finally {
- setLoading(false);
- }
- };
-
- if (loading) {
- return (
-
- );
- }
-
- return (
- <>
- {toast.show && (
-
- )}
-
-
-
- Step {currentStep} of {totalSteps}
-
-
- {/* Progress Bar */}
-
-
-
- Interview Setup
-
-
- {Math.round(progressPercentage)}% Complete
-
-
-
-
-
- {/* Form Container */}
-
-
- {/* Step 1: Interview Basics */}
- {currentStep === 1 && (
-
-
-
- Interview Setup
-
-
-
-
-
-
-
- handleInputChange(
- "interviewName",
- e.target.value
- )
- }
- placeholder="e.g. Frontend Role at Google"
- className={`w-full px-4 py-3 border ${formData.interviewName!==""? "border-gray-300" : "border-red-500"} rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all`}
- />
-
-
-
-
-
-
-
-
-
-
- {[
- "Technical",
- "Behavioral",
- "Mixed",
- ].map((type) => (
-
- ))}
-
-
-
-
-
-
-
-
- )}
-
- {/* Step 2: Job Details */}
- {currentStep === 2 && (
-
-
-
- Job Details
-
-
- Help us understand the context of
- the role
-
-
-
-
-
-
-
- handleInputChange(
- "role",
- e.target.value
- )
- }
- placeholder="e.g. Software Engineer"
- className={`w-full px-4 py-3 border ${formData.role!==""? "border-gray-300" : "border-red-500"} rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all`}
- />
-
-
-
-
-
- {[
- "Fresher",
- "Junior",
- "Mid",
- "Senior",
- ].map((type) => (
-
- ))}
-
-
-
-
-
-
- handleInputChange(
- "companyName",
- e.target.value
- )
- }
- placeholder="e.g. Tech Innovations Inc"
- className={`w-full px-4 py-3 border ${formData.companyName!==""? "border-gray-300" : "border-red-500"} rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all`}
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )}
-
- {/* Step 3: Resume & Focus */}
- {currentStep === 3 && (
-
-
-
- Almost there!
-
-
- Just a few finishing touches
-
-
-
-
-
-
-
- {!formData.resume ? (
-
-
-
-
-
- {" "}
- or drag and drop
-
-
-
- PDF only, up to 5MB
-
-
-
- ) : (
-
-
-
-
-
- {
- formData
- .resume
- .name
- }
-
-
- {(
- formData
- .resume
- .size /
- 1024 /
- 1024
- ).toFixed(
- 2
- )}{" "}
- MB
-
-
-
-
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
- )}
-
-
-
-
- >
- );
+ const { user, loading, setLoading } = useAuth();
+ const [currentStep, setCurrentStep] = useState(1);
+ const [isTransitioning, setIsTransitioning] = useState(false);
+ const [drag, setDrag] = useState(false);
+ const [toast, setToast] = useState({
+ show: false,
+ message: "",
+ type: "success",
+ });
+ const fileInputRef = useRef(null);
+
+ const MAX_FILE_SIZE_MB = 5;
+
+ const navigate = useNavigate();
+
+ const [formData, setFormData] = useState({
+ interviewName: "",
+ numOfQuestions: 3,
+ interviewType: "Technical",
+ role: "",
+ experienceLevel: "Fresher",
+ companyName: "",
+ companyDescription: "",
+ jobDescription: "",
+ resume: null,
+ focusAt: "",
+ });
+
+ const totalSteps = 3;
+ const progressPercentage = ((currentStep - 1) / (totalSteps - 1)) * 100;
+
+ const handleNext = () => {
+ if (currentStep < totalSteps) {
+ setIsTransitioning(true);
+ setTimeout(() => {
+ setCurrentStep(currentStep + 1);
+ setIsTransitioning(false);
+ }, 150);
+ }
+ };
+
+ const handleBack = () => {
+ if (currentStep > 1) {
+ setIsTransitioning(true);
+ setTimeout(() => {
+ setCurrentStep(currentStep - 1);
+ setIsTransitioning(false);
+ }, 150);
+ }
+ };
+
+ const handleInputChange = (field, value) => {
+ setFormData((prev) => ({ ...prev, [field]: value }));
+ };
+
+ const handleDrag = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (e.type === "dragenter" || e.type === "dragover") {
+ setDrag(true);
+ } else if (e.type === "dragleave") {
+ setDrag(false);
+ }
+ };
+
+ const handleDrop = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ setDrag(false);
+
+ if (e.dataTransfer.files && e.dataTransfer.files[0]) {
+ const file = e.dataTransfer.files[0];
+ const fileSizeMB = file.size / 1024 / 1024;
+
+ if (file.type !== "application/pdf") {
+ showToast("Only PDF files are allowed.", "error");
+ return;
+ }
+
+ if (fileSizeMB > MAX_FILE_SIZE_MB) {
+ showToast("File size exceeds 5MB.", "error");
+ return;
+ }
+
+ handleInputChange("resume", file);
+ } else {
+ showToast("No file detected.", "error");
+ }
+ };
+
+ const handleFileSelect = (e) => {
+ if (e.target.files && e.target.files[0]) {
+ const file = e.target.files[0];
+ const fileSizeMB = file.size / 1024 / 1024;
+
+ if (file.type !== "application/pdf") {
+ showToast("Only PDF files are allowed", "error");
+ return;
+ }
+
+ if (fileSizeMB > MAX_FILE_SIZE_MB) {
+ showToast("File size exceeds 5MB.", "error");
+ return;
+ }
+
+ handleInputChange("resume", file);
+ } else {
+ showToast("No file selected", "error");
+ }
+ };
+
+ const removeFile = () => {
+ handleInputChange("resume", null);
+ if (fileInputRef.current) fileInputRef.current.value = "";
+ };
+
+ const showToast = (message, type) => {
+ setToast({ show: true, message, type });
+ };
+
+ const hideToast = () => {
+ setToast((prev) => ({ ...prev, show: false }));
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+
+ if (!user) {
+ showToast("User not logged in", "error");
+ return;
+ }
+
+
+ if (!formData.interviewName || !formData.role || !formData.resume) {
+ showToast("Please fill all required fields and upload your resume.", "error");
+ return;
+ }
+
+ setLoading(true);
+ const token = await user.getIdToken();
+
+ const data = new FormData();
+ Object.keys(formData).forEach((key) => {
+ if (formData[key]) data.append(key, formData[key]);
+ });
+
+ try {
+ const res = await axios.post(
+ `${import.meta.env.VITE_API_URL}/api/interview/setup`,
+ data,
+ { headers: { Authorization: `Bearer ${token}` } }
+ );
+
+ showToast("Interview setup complete!", "success");
+ const interviewId = res.data.interview._id;
+ navigate(`/interview/${interviewId}`);
+ } catch (err) {
+ console.error("Error in interview setup:", err.response?.data || err);
+ showToast("Something went wrong", "error");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ if (loading) return
;
+
+ return (
+ <>
+ {toast.show &&
}
+
+
+
+ Step {currentStep} of {totalSteps}
+
+
+
+
+
+ Interview Setup
+ {Math.round(progressPercentage)}% Complete
+
+
+
+
+
+
+
+
+ >
+ );
}
diff --git a/client/src/pages/Dashboard.jsx b/client/src/pages/Dashboard.jsx
index 4cd0126..57e65b4 100644
--- a/client/src/pages/Dashboard.jsx
+++ b/client/src/pages/Dashboard.jsx
@@ -6,104 +6,102 @@ import InterviewList from "../components/InterviewList";
import { useAuth } from "../context/AuthContext";
export default function Dashboard() {
- const [interviews, setInterviews] = useState([]);
- const [summaryData, setSummaryData] = useState([]);
- const [chartData, setChartData] = useState([]);
- const { user } = useAuth();
-
- useEffect(() => {
- const fetchData = async () => {
- try {
- const token = await user.getIdToken();
- const response = await axios.get(
- `${import.meta.env.VITE_API_URL}/api/interview`,
- {
- headers: {
- Authorization: `Bearer ${token}`,
- },
- }
- );
- setInterviews(response.data);
-
- const reportResponse = await axios.get(
- `${import.meta.env.VITE_API_URL}/api/report`,
- {
- headers: {
- Authorization: `Bearer ${token}`,
- },
- }
- );
-
- const chartData = reportResponse.data.map((report) => ({
- date: new Date(report.createdAt).toLocaleDateString(),
- score: Number(report.finalScore) || 0,
- interviewName: report.interviewId?.interview_name || "N/A",
- }));
- setChartData(chartData);
-
- const bestScore = Math.max(
- ...reportResponse.data.map((i) => i.finalScore || 0)
- );
-
- const totalInterviews = response.data.length;
- const roleCounts = response.data
- .map((i) => i.role)
- .reduce((acc, role) => {
- acc[role] = (acc[role] || 0) + 1;
- return acc;
- }, {});
- const mostCommonRole =
- Object.entries(roleCounts).sort(
- (a, b) => b[1] - a[1]
- )[0]?.[0] || "N/A";
-
- setSummaryData([
- { title: "Total Interviews", value: totalInterviews },
- { title: "Best Score", value: bestScore.toFixed(2) },
- { title: "Most Common Role", value: mostCommonRole },
- ]);
- } catch (error) {
- console.error("Failed to fetch interviews:", error);
- }
- };
-
- fetchData();
- }, [user]);
-
- return (
-
-
-
-
-
- Interview Performance Dashboard
-
-
-
-
-
-
-
- Summary
-
-
- {summaryData.map((item, index) => (
-
- ))}
-
-
-
-
-
-
-
-
-
- );
+ const [interviews, setInterviews] = useState([]);
+ const [summaryData, setSummaryData] = useState([]);
+ const [chartData, setChartData] = useState([]);
+ const { user } = useAuth();
+
+ useEffect(() => {
+ const fetchData = async () => {
+ try {
+ const token = await user.getIdToken();
+
+ // ---- Fetch Interviews ----
+ const response = await axios.get(
+ `${import.meta.env.VITE_API_URL}/api/interview`,
+ {
+ headers: { Authorization: `Bearer ${token}` },
+ }
+ );
+
+ // ✅ Ensure array
+ const interviewsData = Array.isArray(response.data)
+ ? response.data
+ : response.data.interviews || [];
+ setInterviews(interviewsData);
+
+ // ---- Fetch Reports ----
+ const reportResponse = await axios.get(
+ `${import.meta.env.VITE_API_URL}/api/report`,
+ {
+ headers: { Authorization: `Bearer ${token}` },
+ }
+ );
+
+ const reports = Array.isArray(reportResponse.data)
+ ? reportResponse.data
+ : reportResponse.data.reports || [];
+
+ // ✅ Chart Data
+ const chartDataFormatted = reports.map((report) => ({
+ date: new Date(report.createdAt).toLocaleDateString(),
+ score: Number(report.finalScore) || 0,
+ interviewName: report.interviewId?.interview_name || "N/A",
+ }));
+ setChartData(chartDataFormatted);
+
+ // ✅ Summary Data
+ const bestScore = Math.max(...reports.map((i) => i.finalScore || 0), 0);
+ const totalInterviews = interviewsData.length;
+
+ const roleCounts = interviewsData
+ .map((i) => i.role)
+ .reduce((acc, role) => {
+ acc[role] = (acc[role] || 0) + 1;
+ return acc;
+ }, {});
+
+ const mostCommonRole =
+ Object.entries(roleCounts).sort((a, b) => b[1] - a[1])[0]?.[0] ||
+ "N/A";
+
+ setSummaryData([
+ { title: "Total Interviews", value: totalInterviews },
+ { title: "Best Score", value: bestScore.toFixed(2) },
+ { title: "Most Common Role", value: mostCommonRole },
+ ]);
+ } catch (error) {
+ console.error("Failed to fetch interviews:", error);
+ }
+ };
+
+ fetchData();
+ }, [user]);
+
+ return (
+
+
+
+
+ Interview Performance Dashboard
+
+
+
+
+
+
+
Summary
+
+ {summaryData.map((item, index) => (
+
+ ))}
+
+
+
+
+
+
+
+
+ );
}
diff --git a/client/src/pages/Login.jsx b/client/src/pages/Login.jsx
index ffa06b6..fea439c 100644
--- a/client/src/pages/Login.jsx
+++ b/client/src/pages/Login.jsx
@@ -1,351 +1,261 @@
import { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
-import { FaGithub, FaGoogle, FaEyeSlash, FaEye } from "react-icons/fa";
-import Toast from "../components/Toast";
-import {useAuth} from "../context/AuthContext"
+import { FaGithub, FaGoogle, FaEye, FaEyeSlash } from "react-icons/fa";
+import { useAuth } from "../context/AuthContext";
import {
- auth,
- googleProvider,
- signInWithPopup,
- signInWithEmailAndPassword,
+ auth,
+ googleProvider,
+ signInWithPopup,
+ signInWithEmailAndPassword,
} from "../firebase";
import { GithubAuthProvider } from "firebase/auth";
import axios from "axios";
+import toast from "react-hot-toast"; // ✅ import toast
export default function Login() {
- const githubProvider = new GithubAuthProvider();
- const { user, setUser } = useAuth();
+ const githubProvider = new GithubAuthProvider();
+ const { setUser } = useAuth();
- const [email, setEmail] = useState("");
- const [password, setPassword] = useState("");
- const [showPassword, setShowPassword] = useState(false);
- const [validation, setValidation] = useState({
- name: "untouched",
- email: "untouched",
- password: "untouched",
- });
- const [toast, setToast] = useState({
- show: false,
- message: "",
- type: "success",
- });
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [showPassword, setShowPassword] = useState(false);
- const navigate = useNavigate();
+ const navigate = useNavigate();
- const validateEmail = (value) => {
- if (value.trim() === "") return "empty";
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
- return emailRegex.test(value) ? "valid" : "invalid";
- };
+ // ✅ Email + Password login
+ const handleEmailLogin = async (e) => {
+ e.preventDefault();
- const validatePassword = (value) => {
- if (value === "") return "empty";
- return "valid";
- };
+ if (!email || !password) {
+ toast.error("⚠️ Please enter email and password", {
+ style: {
+ borderRadius: "10px",
+ background: "#ef4444", // red
+ color: "#fff",
+ fontWeight: "600",
+ },
+ iconTheme: {
+ primary: "#fff",
+ secondary: "#ef4444",
+ },
+ });
+ return;
+ }
- const handleEmailChange = (value) => {
- setEmail(value);
- setValidation((prev) => ({
- ...prev,
- email: validateEmail(value),
- }));
- };
+ try {
+ const userCredential = await signInWithEmailAndPassword(auth, email, password);
+ const idToken = await userCredential.user.getIdToken();
- const handlePasswordChange = (value) => {
- setPassword(value);
- setValidation((prev) => ({
- ...prev,
- password: validatePassword(value),
- }));
- };
+ await axios.post(
+ `${import.meta.env.VITE_API_URL}/api/auth/login`,
+ {},
+ { headers: { Authorization: `Bearer ${idToken}` } }
+ );
- const getRingColor = (fieldValidation) => {
- switch (fieldValidation) {
- case "empty":
- return "focus:ring-orange-500 focus:border-orange-500";
- case "invalid":
- return "focus:ring-red-500 focus:border-red-500 ring-2 ring-red-500 border-red-500";
- case "valid":
- return "focus:ring-blue-500 focus:border-blue-500";
- default:
- return "focus:ring-blue-500 focus:border-blue-500";
- }
- };
+ setUser(userCredential.user);
+ toast.success("🎉 Login successful", {
+ style: {
+ borderRadius: "10px",
+ background: "#22c55e", // green
+ color: "#fff",
+ fontWeight: "600",
+ },
+ iconTheme: {
+ primary: "#fff",
+ secondary: "#22c55e",
+ },
+ });
- const showToast = (message, type) => {
- setToast({ show: true, message, type });
- };
+ setTimeout(() => navigate("/"), 800);
+ } catch (err) {
+ toast.error(err.message || "❌ Login failed", {
+ style: {
+ borderRadius: "10px",
+ background: "#f97316", // orange
+ color: "#fff",
+ fontWeight: "600",
+ },
+ iconTheme: {
+ primary: "#fff",
+ secondary: "#f97316",
+ },
+ });
+ }
+ };
- const hideToast = () => {
- setToast((prev) => ({ ...prev, show: false }));
- };
+ // ✅ Google login
+ const handleGoogleLogin = async () => {
+ try {
+ const result = await signInWithPopup(auth, googleProvider);
+ const idToken = await result.user.getIdToken();
- const handleEmailLogin = async () => {
- const emailValidation = validateEmail(email);
- const passwordValidation = validatePassword(password);
+ await axios.post(
+ `${import.meta.env.VITE_API_URL}/api/auth/login`,
+ {},
+ { headers: { Authorization: `Bearer ${idToken}` } }
+ );
- setValidation({
- email: emailValidation,
- password: passwordValidation,
- });
+ setUser(result.user);
+ toast.success("🚀 Signed in with Google", {
+ style: {
+ borderRadius: "10px",
+ background: "#3b82f6", // blue
+ color: "#fff",
+ fontWeight: "600",
+ },
+ iconTheme: {
+ primary: "#fff",
+ secondary: "#3b82f6",
+ },
+ });
- if (emailValidation !== "valid" || passwordValidation !== "valid") {
- showToast("Please fill in all fields correctly", "error");
- return;
- }
+ setTimeout(() => navigate("/"), 800);
+ } catch (err) {
+ toast.error(err.message || "❌ Google login failed", {
+ style: {
+ borderRadius: "10px",
+ background: "#ef4444",
+ color: "#fff",
+ fontWeight: "600",
+ },
+ });
+ }
+ };
- try {
- const userCredential = await signInWithEmailAndPassword(
- auth,
- email,
- password
- );
- const idToken = await userCredential.user.getIdToken();
- await axios.post(
- `${import.meta.env.VITE_API_URL}/api/auth/login`,
- {},
- {
- headers: {
- Authorization: `Bearer ${idToken}`,
- },
- }
- );
+ // ✅ GitHub login
+ const handleGithub = async () => {
+ try {
+ const result = await signInWithPopup(auth, githubProvider);
+ const idToken = await result.user.getIdToken();
- setUser(userCredential.user);
- console.log("User logged in:", user);
- showToast("Logged in successfully!", "success");
- setTimeout(() => {
- navigate("/");
- }, 500);
- } catch (err) {
- showToast(err.message || "An error occurred during login", "error");
- }
- };
+ await axios.post(
+ `${import.meta.env.VITE_API_URL}/api/auth/login`,
+ {},
+ { headers: { Authorization: `Bearer ${idToken}` } }
+ );
- const handleGoogleLogin = async () => {
- try {
- const result = await signInWithPopup(auth, googleProvider);
- const idToken = await result.user.getIdToken();
- await axios.post(
- `${import.meta.env.VITE_API_URL}/api/auth/login`,
- {},
- {
- headers: {
- Authorization: `Bearer ${idToken}`,
- },
- }
- );
+ setUser(result.user);
+ toast.success("🐙 Signed in with GitHub", {
+ style: {
+ borderRadius: "10px",
+ background: "#111827", // black/gray
+ color: "#fff",
+ fontWeight: "600",
+ },
+ });
- setUser(result.user);
- console.log("User logged in:", user);
- showToast("Logged in successfully!", "success");
- setTimeout(() => {
- navigate("/");
- }, 2000);
- } catch (err) {
- showToast(
- err.message || "An error occurred during Google login",
- "error"
- );
- }
- };
+ setTimeout(() => navigate("/dashboard"), 800);
+ } catch (err) {
+ toast.error(err.message || "❌ GitHub login failed", {
+ style: {
+ borderRadius: "10px",
+ background: "#6b7280", // gray
+ color: "#fff",
+ fontWeight: "600",
+ },
+ });
+ }
+ };
- //for github login
- const handleGithubLogin = async () => {
- try {
- const result = await signInWithPopup(auth, githubProvider);
- const credential = GithubAuthProvider.credentialFromResult(result);
- const token = credential.accessToken;
+ return (
+
+
+
+
+
+
Login
+
- const idToken = await result.user.getIdToken();
- await axios.post(
- `${import.meta.env.VITE_API_URL}/api/auth/login`,
- {},
- {
- headers: {
- Authorization: `Bearer ${idToken}`,
- },
- }
- );
+ {/* ✅ Email + password form */}
+
- return (
-
- {toast.show && (
-
- )}
+ {/* ✅ Social login buttons */}
+
+
+
+
+ Or continue with
+
+
-
-
-
-
-
- Login to your account
-
-
+
+
+
+
+
-
-
-
-
-
-
-
- Or continue with
-
-
-
-
-
-
-
-
-
-
-
-
- {"Don't have an account? "}
-
- Sign Up
-
-
-
-
-
-
-
- );
+
+
+ {"Don’t have an account? "}
+
+ Sign up
+
+
+
+
+
+
+
+ );
}
diff --git a/client/src/pages/SignUp.jsx b/client/src/pages/SignUp.jsx
index 90fca59..6e4f690 100644
--- a/client/src/pages/SignUp.jsx
+++ b/client/src/pages/SignUp.jsx
@@ -1,432 +1,383 @@
import { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { FaGithub, FaGoogle, FaEye, FaEyeSlash } from "react-icons/fa";
-import Toast from "../components/Toast";
import { useAuth } from "../context/AuthContext";
import {
- auth,
- googleProvider,
- signInWithPopup,
- createUserWithEmailAndPassword,
- updateProfile,
+ auth,
+ googleProvider,
+ signInWithPopup,
+ createUserWithEmailAndPassword,
+ updateProfile,
} from "../firebase";
import { GithubAuthProvider } from "firebase/auth";
import axios from "axios";
+import toast from "react-hot-toast"; // ✅ Import hot-toast
export default function SignUp() {
- const githubProvider = new GithubAuthProvider();
- const { user, setUser } = useAuth();
-
- const [name, setName] = useState("");
- const [email, setEmail] = useState("");
- const [password, setPassword] = useState("");
- const [showPassword, setShowPassword] = useState(false);
- const [validation, setValidation] = useState({
- name: "untouched",
- email: "untouched",
- password: "untouched",
- });
- const [toast, setToast] = useState({
- show: false,
- message: "",
- type: "success",
- });
- const [passwordStrength, setPasswordStrength] = useState("");
-
- const navigate = useNavigate();
-
- const validateName = (value) => {
- if (value.trim() === "") return "empty";
- if (value.trim().length < 2) return "invalid";
- return "valid";
- };
-
- const validateEmail = (value) => {
- if (value.trim() === "") return "empty";
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
- return emailRegex.test(value) ? "valid" : "invalid";
- };
-
- const validatePassword = (value) => {
- if (value === "") return "empty";
- if (value.length < 6) return "invalid";
- return "valid";
- };
-
- const calculatePasswordStrength = (value) => {
- let strength = "Weak";
- let strengthScore = 0;
-
- if (value.length >= 6) strengthScore++;
- if (/[A-Z]/.test(value)) strengthScore++;
- if (/[0-9]/.test(value)) strengthScore++;
- if (/[^A-Za-z0-9]/.test(value)) strengthScore++;
-
- if (strengthScore >= 4) strength = "Strong";
- else if (strengthScore >= 2) strength = "Medium";
-
- return strength;
- };
-
- const handleNameChange = (value) => {
- setName(value);
- setValidation((prev) => ({
- ...prev,
- name: validateName(value),
- }));
- };
-
- const handleEmailChange = (value) => {
- setEmail(value);
- setValidation((prev) => ({
- ...prev,
- email: validateEmail(value),
- }));
- };
-
- const handlePasswordChange = (value) => {
- setPassword(value);
- setValidation((prev) => ({
- ...prev,
- password: validatePassword(value),
- }));
- setPasswordStrength(calculatePasswordStrength(value));
- };
-
- const getRingColor = (fieldValidation) => {
- switch (fieldValidation) {
- case "empty":
- return "focus:ring-orange-500 focus:border-orange-500";
- case "invalid":
- return "focus:ring-red-500 focus:border-red-500 ring-2 ring-red-500 border-red-500";
- case "valid":
- return "focus:ring-blue-500 focus:border-blue-500";
- default:
- return "focus:ring-blue-500 focus:border-blue-500";
- }
- };
-
- const showToast = (message, type) => {
- setToast({ show: true, message, type });
- };
-
- const hideToast = () => {
- setToast((prev) => ({ ...prev, show: false }));
- };
-
- const handleEmailSignUp = async () => {
- const nameValidation = validateName(name);
- const emailValidation = validateEmail(email);
- const passwordValidation = validatePassword(password);
-
- setValidation({
- name: nameValidation,
- email: emailValidation,
- password: passwordValidation,
- });
-
- if (
- nameValidation !== "valid" ||
- emailValidation !== "valid" ||
- passwordValidation !== "valid"
- ) {
- showToast("Please fill in all fields correctly", "error");
- return;
- }
-
- try {
- const userCredential = await createUserWithEmailAndPassword(
- auth,
- email,
- password
- );
- await updateProfile(userCredential.user, { displayName: name });
-
- const idToken = await userCredential.user.getIdToken();
- await axios.post(
- `${import.meta.env.VITE_API_URL}/api/auth/register`,
- {},
- {
- headers: {
- Authorization: `Bearer ${idToken}`,
- },
- }
- );
-
- showToast("Account created successfully!", "success");
- setTimeout(() => {
- navigate("/login");
- }, 500);
- } catch (err) {
- showToast(
- err.message || "An error occurred during sign up",
- "error"
- );
- }
- };
-
- //for google
- const handleGoogleLogin = async () => {
- try {
- const result = await signInWithPopup(auth, googleProvider);
- const idToken = await result.user.getIdToken();
- await axios.post(
- `${import.meta.env.VITE_API_URL}/api/auth/register`,
- {},
- {
- headers: {
- Authorization: `Bearer ${idToken}`,
- },
- }
- );
-
- setUser(result.user);
- console.log("User signed in:", user);
-
- showToast("Successfully signed in with Google!", "success");
- setTimeout(() => {
- navigate("/");
- }, 2000);
- } catch (err) {
- showToast(
- err.message || "An error occurred during Google sign in",
- "error"
- );
- }
- };
-
- //for Github
- const handleGithub = async () => {
- try {
- const result = await signInWithPopup(auth, githubProvider);
- const credential = GithubAuthProvider.credentialFromResult(result);
- const token = credential.accessToken;
-
- const idToken = await result.user.getIdToken();
- await axios.post(
- `${import.meta.env.VITE_API_URL}/api/auth/register`,
- {},
- {
- headers: {
- Authorization: `Bearer ${idToken}`,
- },
- }
- );
-
- setUser(result.user); // Set the authenticated user
- showToast("Successfully signed in with GitHub!", "success");
-
- setTimeout(() => {
- navigate("/");
- }, 2000);
- } catch (error) {
- console.error("GitHub login failed:", error);
- // showToast(error.message || "GitHub login failed", "error");
- setTimeout(() => {
- navigate("/");
- }, 2000);
- }
- };
-
- return (
-
- {toast.show && (
-
- )}
-
-
-
-
-
-
- Create a new account
-
-
-
-
-
-
-
-
-
-
- Or continue with
-
-
-
-
-
-
-
-
-
-
-
-
- {"Already have an account? "}
-
- Login
-
-
-
-
-
-
-
- );
+ const githubProvider = new GithubAuthProvider();
+ const { setUser } = useAuth();
+
+ const [name, setName] = useState("");
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [showPassword, setShowPassword] = useState(false);
+ const [validation, setValidation] = useState({
+ name: "untouched",
+ email: "untouched",
+ password: "untouched",
+ });
+ const [passwordStrength, setPasswordStrength] = useState("");
+
+ const navigate = useNavigate();
+
+ const validateName = (value) => {
+ if (value.trim() === "") return "empty";
+ if (value.trim().length < 2) return "invalid";
+ return "valid";
+ };
+
+ const validateEmail = (value) => {
+ if (value.trim() === "") return "empty";
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ return emailRegex.test(value) ? "valid" : "invalid";
+ };
+
+ const validatePassword = (value) => {
+ if (value === "") return "empty";
+ if (value.length < 6) return "invalid";
+ return "valid";
+ };
+
+ const calculatePasswordStrength = (value) => {
+ let strength = "Weak";
+ let score = 0;
+
+ if (value.length >= 6) score++;
+ if (/[A-Z]/.test(value)) score++;
+ if (/[0-9]/.test(value)) score++;
+ if (/[^A-Za-z0-9]/.test(value)) score++;
+
+ if (score >= 4) strength = "Strong";
+ else if (score >= 2) strength = "Medium";
+
+ return strength;
+ };
+
+ const handleNameChange = (value) => {
+ setName(value);
+ setValidation((prev) => ({
+ ...prev,
+ name: validateName(value),
+ }));
+ };
+
+ const handleEmailChange = (value) => {
+ setEmail(value);
+ setValidation((prev) => ({
+ ...prev,
+ email: validateEmail(value),
+ }));
+ };
+
+ const handlePasswordChange = (value) => {
+ setPassword(value);
+ setValidation((prev) => ({
+ ...prev,
+ password: validatePassword(value),
+ }));
+ setPasswordStrength(calculatePasswordStrength(value));
+ };
+
+ const getRingColor = (fieldValidation) => {
+ switch (fieldValidation) {
+ case "empty":
+ return "focus:ring-orange-500 focus:border-orange-500";
+ case "invalid":
+ return "focus:ring-red-500 focus:border-red-500 ring-2 ring-red-500 border-red-500";
+ case "valid":
+ return "focus:ring-blue-500 focus:border-blue-500";
+ default:
+ return "focus:ring-blue-500 focus:border-blue-500";
+ }
+ };
+
+ const handleEmailSignUp = async (e) => {
+ e.preventDefault();
+
+ const nameValidation = validateName(name);
+ const emailValidation = validateEmail(email);
+ const passwordValidation = validatePassword(password);
+
+ setValidation({
+ name: nameValidation,
+ email: emailValidation,
+ password: passwordValidation,
+ });
+
+ if (
+ nameValidation !== "valid" ||
+ emailValidation !== "valid" ||
+ passwordValidation !== "valid"
+ ) {
+ toast.error("Please fill in all fields correctly");
+ return;
+ }
+
+ try {
+ const userCredential = await createUserWithEmailAndPassword(
+ auth,
+ email,
+ password
+ );
+ await updateProfile(userCredential.user, { displayName: name });
+
+ const idToken = await userCredential.user.getIdToken();
+ await axios.post(
+ `${import.meta.env.VITE_API_URL}/api/auth/register`,
+ {},
+ {
+ headers: {
+ Authorization: `Bearer ${idToken}`,
+ },
+ }
+ );
+
+ toast.success("Account created successfully!");
+ setTimeout(() => {
+ navigate("/login");
+ }, 500);
+ } catch (err) {
+ toast.error(err.message || "An error occurred during sign up");
+ }
+ };
+
+ const handleGoogleLogin = async () => {
+ try {
+ const result = await signInWithPopup(auth, googleProvider);
+ const idToken = await result.user.getIdToken();
+ await axios.post(
+ `${import.meta.env.VITE_API_URL}/api/auth/register`,
+ {},
+ {
+ headers: {
+ Authorization: `Bearer ${idToken}`,
+ },
+ }
+ );
+
+ setUser(result.user);
+ toast.success("Successfully signed in with Google!");
+ setTimeout(() => {
+ navigate("/");
+ }, 2000);
+ } catch (err) {
+ toast.error(err.message || "An error occurred during Google sign in");
+ }
+ };
+
+ const handleGithub = async () => {
+ try {
+ const result = await signInWithPopup(auth, githubProvider);
+ const idToken = await result.user.getIdToken();
+ await axios.post(
+ `${import.meta.env.VITE_API_URL}/api/auth/register`,
+ {},
+ {
+ headers: {
+ Authorization: `Bearer ${idToken}`,
+ },
+ }
+ );
+
+ setUser(result.user);
+ toast.success("Successfully signed in with GitHub!");
+ setTimeout(() => {
+ navigate("/");
+ }, 2000);
+ } catch (error) {
+ toast.error(error.message || "GitHub login failed");
+ }
+ };
+
+ return (
+
+ {/* 🔥 No need for
component */}
+
+
+
+
+
+
+ Create a new account
+
+
+
+
+
+
+
+
+
+
+ Or continue with
+
+
+
+
+
+
+
+
+
+
+
+
+ {"Already have an account? "}
+
+ Login
+
+
+
+
+
+
+
+ );
}
diff --git a/server/firebase.js b/server/firebase.js
new file mode 100644
index 0000000..4ed3532
--- /dev/null
+++ b/server/firebase.js
@@ -0,0 +1,12 @@
+import admin from "firebase-admin";
+import dotenv from "dotenv";
+
+dotenv.config();
+
+const serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT_KEY);
+
+admin.initializeApp({
+ credential: admin.credential.cert(serviceAccount),
+});
+
+export default admin;
diff --git a/server/index.js b/server/index.js
index 016aa75..02be98c 100644
--- a/server/index.js
+++ b/server/index.js
@@ -7,28 +7,44 @@ import express from "express";
import cors from "cors";
import dotenv from "dotenv";
import connectDB from "./config/db.js";
-import admin from "firebase-admin";
import multer from "multer";
+// Import Firebase Admin instance (already initialized in firebase.js)
+import admin from "./firebase.js";
+
+// Load environment variables
dotenv.config();
+
const app = express();
const upload = multer({ dest: "uploads/" });
+
+// --- Middlewares ---
app.use(cors({
origin: 'https://prepedgeai.vercel.app',
credentials: true,
}));
app.use(express.json());
-connectDB()
- .then(() => console.log("Database connected successfully"))
- .catch((error) => console.error("Database connection failed:", error));
-
-// Initializing Firebase Admin SDK
-var serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT);
-admin.initializeApp({
- credential: admin.credential.cert(serviceAccount)
+
+// --- Protected route middleware ---
+app.use("/api/protected", async (req, res, next) => {
+ const token = req.headers.authorization?.split(" ")[1];
+ if (!token) return res.status(401).json({ error: "Unauthorized" });
+
+ try {
+ const decoded = await admin.auth().verifyIdToken(token);
+ req.user = decoded;
+ next();
+ } catch (err) {
+ res.status(401).json({ error: "Invalid token" });
+ }
});
-// Routing
+// --- Connect to database ---
+connectDB()
+ .then(() => console.log("Database connected successfully"))
+ .catch((error) => console.error("Database connection failed:", error));
+
+// --- Routes ---
import authRoutes from "./routes/authRoutes.js";
import interviewRoutes from "./routes/interviewRoutes.js";
import reportRoutes from "./routes/reportRoutes.js";
@@ -39,6 +55,6 @@ app.use("/api/interview", interviewRoutes);
app.use("/api/report", reportRoutes);
app.use("/api/contact", contactRoutes);
-// Starting the server
+// --- Start server ---
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
diff --git a/server/package-lock.json b/server/package-lock.json
index a9be8ac..49903f3 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -22,11 +22,11 @@
"mongoose": "^8.16.0",
"multer": "^2.0.1",
"nodemailer": "^7.0.4",
- "nodemon": "^3.1.10",
"pdf-parse": "^1.1.1",
"streamifier": "^0.1.1"
},
"devDependencies": {
+ "nodemon": "^3.1.10",
"patch-package": "^8.0.0",
"postinstall-postinstall": "^2.1.0"
}
@@ -1058,6 +1058,7 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
@@ -1118,7 +1119,8 @@
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
},
"node_modules/base64-arraybuffer": {
"version": "1.0.2",
@@ -1160,6 +1162,7 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
"engines": {
"node": ">=8"
},
@@ -1190,6 +1193,7 @@
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -1199,6 +1203,7 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
"dependencies": {
"fill-range": "^7.1.1"
},
@@ -1370,6 +1375,7 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
@@ -1460,7 +1466,8 @@
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
},
"node_modules/concat-stream": {
"version": "2.0.0",
@@ -1857,6 +1864,7 @@
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -2039,6 +2047,7 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
@@ -2170,6 +2179,7 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
"dependencies": {
"is-glob": "^4.0.1"
},
@@ -2283,6 +2293,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
"engines": {
"node": ">=4"
}
@@ -2450,7 +2461,8 @@
"node_modules/ignore-by-default": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
- "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA=="
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
+ "dev": true
},
"node_modules/inflight": {
"version": "1.0.6",
@@ -2480,6 +2492,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
"dependencies": {
"binary-extensions": "^2.0.0"
},
@@ -2506,6 +2519,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -2522,6 +2536,7 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
@@ -2533,6 +2548,7 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
"engines": {
"node": ">=0.12.0"
}
@@ -2902,6 +2918,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -3223,6 +3240,7 @@
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz",
"integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^3.5.2",
@@ -3251,6 +3269,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -3445,6 +3464,7 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
"engines": {
"node": ">=8.6"
},
@@ -3509,7 +3529,8 @@
"node_modules/pstree.remy": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
- "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w=="
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
+ "dev": true
},
"node_modules/punycode": {
"version": "2.3.1",
@@ -3591,6 +3612,7 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
"dependencies": {
"picomatch": "^2.2.1"
},
@@ -3862,6 +3884,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
"integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
+ "dev": true,
"dependencies": {
"semver": "^7.5.3"
},
@@ -3988,6 +4011,7 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
"dependencies": {
"has-flag": "^3.0.0"
},
@@ -4083,6 +4107,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
"dependencies": {
"is-number": "^7.0.0"
},
@@ -4102,6 +4127,7 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
"integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
+ "dev": true,
"bin": {
"nodetouch": "bin/nodetouch.js"
}
@@ -4137,7 +4163,8 @@
"node_modules/undefsafe": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
- "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA=="
+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
+ "dev": true
},
"node_modules/undici-types": {
"version": "7.8.0",
diff --git a/server/package.json b/server/package.json
index c8c0160..d71c326 100644
--- a/server/package.json
+++ b/server/package.json
@@ -8,7 +8,8 @@
"test": "echo \"Error: no test specified\" && exit 1",
"postinstall": "patch-package",
"start": "node index.js",
- "dev": "nodemon index.js"
+ "dev": "nodemon index.js",
+
},
"keywords": [],
"author": "",
@@ -26,11 +27,11 @@
"mongoose": "^8.16.0",
"multer": "^2.0.1",
"nodemailer": "^7.0.4",
- "nodemon": "^3.1.10",
"pdf-parse": "^1.1.1",
"streamifier": "^0.1.1"
},
"devDependencies": {
+ "nodemon": "^3.1.10",
"patch-package": "^8.0.0",
"postinstall-postinstall": "^2.1.0"
}