From 16d21161b627f6815918ac5f7e2f9b4a8be54afc Mon Sep 17 00:00:00 2001 From: TheJob21 Date: Wed, 16 Apr 2025 22:40:47 -0500 Subject: [PATCH 01/22] tokenExpiryChecks and reminder emails implemented --- client/package.json | 3 +- client/src/pages/TokenRenewal.jsx | 73 +++++++++++++++++++++++++++++++ client/src/router.js | 5 +++ server/jobs/cronJobsConfig.js | 2 + server/jobs/tokenExpiryCheck.js | 57 ++++++++++++++++++++++++ server/package.json | 3 +- server/routes/token.js | 34 ++++++++++++++ 7 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 client/src/pages/TokenRenewal.jsx create mode 100644 server/jobs/tokenExpiryCheck.js diff --git a/client/package.json b/client/package.json index 75898d4e..1884b6fa 100644 --- a/client/package.json +++ b/client/package.json @@ -9,6 +9,7 @@ "@testing-library/user-event": "^13.5.0", "axios": "^1.8.2", "bootstrap": "^5.3.5", + "client": "file:", "react": "^19.0.0", "react-bootstrap": "^2.10.9", "react-dom": "^19.0.0", @@ -42,6 +43,6 @@ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" - ] + ] } } diff --git a/client/src/pages/TokenRenewal.jsx b/client/src/pages/TokenRenewal.jsx new file mode 100644 index 00000000..f83b15a3 --- /dev/null +++ b/client/src/pages/TokenRenewal.jsx @@ -0,0 +1,73 @@ +import React, { useState } from 'react'; + +const TokenRenewal = () => { + const [token, setToken] = useState(''); + const [responseMessage, setResponseMessage] = useState(''); + const [loading, setLoading] = useState(false); + + const handleInputChange = (event) => { + setToken(event.target.value); + }; + + const handleFormSubmit = async (event) => { + event.preventDefault(); + setLoading(true); + setResponseMessage(''); + + try { + const response = await fetch(`${process.env.REACT_APP_API_URL}/api/token/renew`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ token }), + }); + + const data = await response.json(); + + if (response.ok) { + setResponseMessage({ text: 'Success: Your token has been renewed!', success: true }); + } else { + setResponseMessage({ text: `Error: ${data.message}`, success: false }); + } + } catch (error) { + setResponseMessage({ text: 'Error: Unable to process your request.', success: false }); + } finally { + setLoading(false); + } + }; + + return ( +
+

Token Renewal

+
+
+ + +
+ +
+ + {responseMessage && ( +
+ {responseMessage.text} +
+ )} +
+ ); +}; + +export default TokenRenewal; diff --git a/client/src/router.js b/client/src/router.js index c1bfe4ef..c1e9bbe6 100644 --- a/client/src/router.js +++ b/client/src/router.js @@ -17,6 +17,7 @@ import A4PresentationEvaluationForm from "./pages/A4PresentationEvaluationForm"; import SupervisorDashboard from "./pages/SupervisorDashboard"; import CoordinatorDashboard from "./pages/CoordinatorDashboard"; import CoordinatorRequestDetailView from "./pages/CoordinatorRequestDetailView"; +import TokenRenewal from "./pages/TokenRenewal"; // Create and export the router configuration const router = createBrowserRouter([ @@ -65,6 +66,10 @@ const router = createBrowserRouter([ path: "coordinator/request/:id", element: , }, + { + path: "renew-token", + element: , + }, ], }, ]); diff --git a/server/jobs/cronJobsConfig.js b/server/jobs/cronJobsConfig.js index 2cb9e5c6..ae83c923 100644 --- a/server/jobs/cronJobsConfig.js +++ b/server/jobs/cronJobsConfig.js @@ -1,10 +1,12 @@ const CronJob = require("../models/CronJob"); const { coordinatorReminder, supervisorReminder } = require("./reminderEmail"); +const { checkAndSendReminders } = require("./tokenExpiryCheck"); // Map of job names to their corresponding functions const jobFunctions = { coordinatorApprovalReminder: coordinatorReminder, supervisorApprovalReminder: supervisorReminder, + tokenExpiryReminder: checkAndSendReminders, // Add more job functions here as needed }; diff --git a/server/jobs/tokenExpiryCheck.js b/server/jobs/tokenExpiryCheck.js new file mode 100644 index 00000000..3607ccd2 --- /dev/null +++ b/server/jobs/tokenExpiryCheck.js @@ -0,0 +1,57 @@ +const mongoose = require("mongoose"); +const UserTokenRequest = require("../models/TokenRequest"); // Adjust the path if needed +const emailService = require("../services/emailService"); +require("dotenv").config(); + +async function checkAndSendReminders() { + try { + await mongoose.connect(process.env.MONGO_URI); + const now = new Date(); + + // Tokens expiring in 3 and 7 days + const daysToCheck = [3, 7]; + + for (const days of daysToCheck) { + const targetDate = new Date(now); + targetDate.setDate(now.getDate() + days); + + const expiringTokens = await UserTokenRequest.find({ + isActivated: true, + status: "activated", + expiresAt: { + $gte: new Date(targetDate.setHours(0, 0, 0, 0)), + $lt: new Date(targetDate.setHours(23, 59, 59, 999)), + }, + }); + + for (const token of expiringTokens) { + const subject = `Reminder: Your Internship Token Expires in ${days} Day${days > 1 ? "s" : ""}`; + const renewalLink = `${process.env.FRONTEND_URL}/renew-token/${token.token}`; // Update with actual renewal path + + const html = ` +

Hello ${token.fullName},

+

This is a reminder that your internship access token will expire in ${days} day${days > 1 ? "s" : ""} on ${token.expiresAt.toDateString()}.

+

If your token expires, you will lose access to the internship management system.

+

Click here to renew your token securely.

+

Thank you,
Internship Program Management Team

+ `; + + const emailResponse = await emailService.sendEmail({ + to: token.ouEmail, + subject, + html, + }); + + console.log(`Email sent to ${token.ouEmail} for ${days}-day reminder:`, emailResponse); + } + } + + await mongoose.disconnect(); + } catch (error) { + console.error("Error during token expiry check:", error); + } +} + +module.exports = { + checkAndSendReminders, +}; diff --git a/server/package.json b/server/package.json index 20f54234..2e950867 100644 --- a/server/package.json +++ b/server/package.json @@ -24,7 +24,8 @@ "mongodb": "^6.14.2", "mongoose": "^8.13.2", "node-cron": "^3.0.3", - "nodemailer": "^6.10.0" + "nodemailer": "^6.10.0", + "server": "file:" }, "devDependencies": { "jest": "^29.7.0" diff --git a/server/routes/token.js b/server/routes/token.js index 247fda86..eb5e2173 100644 --- a/server/routes/token.js +++ b/server/routes/token.js @@ -233,4 +233,38 @@ router.delete("/deactivate", async (req, res) => { } }); +router.post('/renew', async (req, res) => { + const { token } = req.body; + + try { + const userToken = await UserTokenRequest.findOne({ token }); + + if (!userToken) { + return res.status(404).json({ message: 'Token not found or invalid.' }); + } + + if (userToken.status !== 'activated') { + return res.status(400).json({ message: 'Token is not activated, cannot renew.' }); + } + + // Renew the token logic (extend expiry, etc.) + const currentDate = new Date(); + userToken.expiresAt = new Date(currentDate.setDate(currentDate.getDate() + 30)); + await userToken.save(); + + // Send confirmation email (optional) + await emailService.sendEmail({ + to: userToken.ouEmail, + subject: 'Your Token Has Been Renewed', + html: `

Your token has been successfully renewed and will now expire on ${userToken.expiresAt.toLocaleDateString()}.

`, + }); + + return res.status(200).json({ message: 'Token successfully renewed!' }); + + } catch (error) { + console.error('Error renewing token:', error); + return res.status(500).json({ message: 'An error occurred while renewing the token.' }); + } +}); + module.exports = router; From a7a64cd48a34255512d2e4f64d9c815b80e6ef4f Mon Sep 17 00:00:00 2001 From: TheJob21 Date: Thu, 17 Apr 2025 20:16:38 -0500 Subject: [PATCH 02/22] Fixed some bugs, Tested functionality --- client/src/pages/TokenRenewal.jsx | 88 +++++++++++++------------------ client/src/router.js | 2 +- server/jobs/tokenExpiryCheck.js | 2 +- server/routes/token.js | 10 ++-- 4 files changed, 46 insertions(+), 56 deletions(-) diff --git a/client/src/pages/TokenRenewal.jsx b/client/src/pages/TokenRenewal.jsx index f83b15a3..8db7edeb 100644 --- a/client/src/pages/TokenRenewal.jsx +++ b/client/src/pages/TokenRenewal.jsx @@ -1,64 +1,50 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; const TokenRenewal = () => { - const [token, setToken] = useState(''); + const { token } = useParams(); const [responseMessage, setResponseMessage] = useState(''); - const [loading, setLoading] = useState(false); - - const handleInputChange = (event) => { - setToken(event.target.value); - }; - - const handleFormSubmit = async (event) => { - event.preventDefault(); - setLoading(true); - setResponseMessage(''); - - try { - const response = await fetch(`${process.env.REACT_APP_API_URL}/api/token/renew`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ token }), - }); - - const data = await response.json(); - - if (response.ok) { - setResponseMessage({ text: 'Success: Your token has been renewed!', success: true }); - } else { - setResponseMessage({ text: `Error: ${data.message}`, success: false }); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const renewToken = async () => { + try { + const response = await fetch(`${process.env.REACT_APP_API_URL}/api/token/renew`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ token }), + }); + + const data = await response.json(); + + if (response.ok) { + setResponseMessage({ text: '✅ Success: Your token has been renewed!', success: true }); + } else { + setResponseMessage({ text: `❌ Error: ${data.message}`, success: false }); + } + } catch (error) { + setResponseMessage({ text: '❌ Error: Unable to process your request.', success: false }); + } finally { + setLoading(false); } - } catch (error) { - setResponseMessage({ text: 'Error: Unable to process your request.', success: false }); - } finally { + }; + + if (token) { + renewToken(); + } else { + setResponseMessage({ text: '❌ Error: No token found in the URL.', success: false }); setLoading(false); } - }; + }, [token]); return (

Token Renewal

-
-
- - -
- -
- - {responseMessage && ( + {loading ? ( +

⏳ Processing your token renewal...

+ ) : (
, }, { - path: "renew-token", + path: "renew-token/:token", element: , }, ], diff --git a/server/jobs/tokenExpiryCheck.js b/server/jobs/tokenExpiryCheck.js index 3607ccd2..ebdc6374 100644 --- a/server/jobs/tokenExpiryCheck.js +++ b/server/jobs/tokenExpiryCheck.js @@ -3,7 +3,7 @@ const UserTokenRequest = require("../models/TokenRequest"); // Adjust the path i const emailService = require("../services/emailService"); require("dotenv").config(); -async function checkAndSendReminders() { +const checkAndSendReminders = async () => { try { await mongoose.connect(process.env.MONGO_URI); const now = new Date(); diff --git a/server/routes/token.js b/server/routes/token.js index eb5e2173..e9bbc8bb 100644 --- a/server/routes/token.js +++ b/server/routes/token.js @@ -237,7 +237,10 @@ router.post('/renew', async (req, res) => { const { token } = req.body; try { - const userToken = await UserTokenRequest.findOne({ token }); + + if (!token) return res.status(400).json({ error: "Token is missing." }); + console.log("Received token:", token); + const userToken = await TokenRequest.findOne({ token: token }); if (!userToken) { return res.status(404).json({ message: 'Token not found or invalid.' }); @@ -249,7 +252,7 @@ router.post('/renew', async (req, res) => { // Renew the token logic (extend expiry, etc.) const currentDate = new Date(); - userToken.expiresAt = new Date(currentDate.setDate(currentDate.getDate() + 30)); + userToken.expiresAt = new Date(currentDate.setMonth(currentDate.getMonth() + 6)); await userToken.save(); // Send confirmation email (optional) @@ -258,7 +261,8 @@ router.post('/renew', async (req, res) => { subject: 'Your Token Has Been Renewed', html: `

Your token has been successfully renewed and will now expire on ${userToken.expiresAt.toLocaleDateString()}.

`, }); - + + console.log('Token successfully renewed!') return res.status(200).json({ message: 'Token successfully renewed!' }); } catch (error) { From be80fffd29b3377b41e3e1bd173e7920225c10cd Mon Sep 17 00:00:00 2001 From: DHANUSHWI Date: Thu, 17 Apr 2025 22:36:06 -0500 Subject: [PATCH 03/22] Token Renewal API with secure validation and expiry extension --- package.json | 5 ++ server/routes/token.js | 106 +++++++++++++++++++++++++++-------------- 2 files changed, 74 insertions(+), 37 deletions(-) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 00000000..1bb6abe9 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "node-cron": "^3.0.3" + } +} diff --git a/server/routes/token.js b/server/routes/token.js index 247fda86..b6809181 100644 --- a/server/routes/token.js +++ b/server/routes/token.js @@ -16,8 +16,7 @@ const hashToken = (token) => { router.post("/request", async (req, res) => { try { - const { fullName, ouEmail, password, semester, academicAdvisor, role } = - req.body; + const { fullName, ouEmail, password, semester, academicAdvisor, role } = req.body; if (!fullName || !ouEmail || !password || !semester) { return res.status(400).json({ error: "All fields are required." }); @@ -25,9 +24,7 @@ router.post("/request", async (req, res) => { const existing = await TokenRequest.findOne({ ouEmail }); if (existing) { - return res - .status(400) - .json({ error: "Token request already exists for this email." }); + return res.status(400).json({ error: "Token request already exists for this email." }); } const plainToken = jwt.sign({ ouEmail }, JWT_SECRET, { expiresIn: "180d" }); @@ -35,7 +32,7 @@ router.post("/request", async (req, res) => { const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS); const requestedAt = new Date(); - const expiresAt = new Date(requestedAt.getTime() + 5 * 24 * 60 * 60 * 1000); // 5 days + const expiresAt = new Date(requestedAt.getTime() + 5 * 24 * 60 * 60 * 1000); const request = new TokenRequest({ fullName, @@ -85,16 +82,12 @@ router.post("/activate", async (req, res) => { const { token } = req.body; if (!token) return res.status(400).json({ error: "Token is missing." }); const hashedToken = hashToken(token); - console.log("Received token:", token); const user = await TokenRequest.findOne({ token: hashedToken }); if (!user) return res.status(404).json({ error: "Token not found." }); - if (user.deletedAt) - return res.status(403).json({ error: "Token has been deactivated." }); - if (user.isActivated) - return res.status(400).json({ error: "Token already activated." }); - if (new Date() > user.expiresAt) - return res.status(400).json({ error: "Token has expired." }); + if (user.deletedAt) return res.status(403).json({ error: "Token has been deactivated." }); + if (user.isActivated) return res.status(400).json({ error: "Token already activated." }); + if (new Date() > user.expiresAt) return res.status(400).json({ error: "Token has expired." }); user.isActivated = true; user.activatedAt = new Date(); @@ -120,10 +113,8 @@ router.post("/login", async (req, res) => { const user = await TokenRequest.findOne({ token: hashedToken }); if (!user) return res.status(404).json({ error: "Invalid token." }); - if (user.deletedAt) - return res.status(403).json({ error: "Token is deactivated." }); - if (!user.isActivated) - return res.status(403).json({ error: "Token not activated." }); + if (user.deletedAt) return res.status(403).json({ error: "Token is deactivated." }); + if (!user.isActivated) return res.status(403).json({ error: "Token not activated." }); res.json({ message: "Login successful", user }); } catch (err) { @@ -131,40 +122,32 @@ router.post("/login", async (req, res) => { } }); -// login api router.post("/user-login", async (req, res) => { const { ouEmail, password, role } = req.body; - console.log(role); + if (!ouEmail || !password || !role) { return res.status(400).json({ message: "All fields are required" }); } + try { const user = await TokenRequest.findOne({ ouEmail }); if (!user) { - return res - .status(401) - .json({ message: "Email or password is incorrect" }); + return res.status(401).json({ message: "Email or password is incorrect" }); } const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) { - return res - .status(401) - .json({ message: "Email or password is incorrect" }); + return res.status(401).json({ message: "Email or password is incorrect" }); } - // First, check if the entered role matches the user's actual role if (user.role.toLowerCase() !== role.toLowerCase()) { return res.status(403).json({ message: "User role mismatch." }); } - // If the role is student, do additional token checks if (role.toLowerCase() === "student") { if (!user.isStudent) { - return res - .status(403) - .json({ message: "User is not registered as a student." }); + return res.status(403).json({ message: "User is not registered as a student." }); } if (!user.token || user.token === "") { @@ -179,9 +162,9 @@ router.post("/user-login", async (req, res) => { const tokenExpiry = new Date(user.expiresAt); if (tokenExpiry < now) { - return res - .status(403) - .json({ message: "Token has expired. Please request a new one." }); + return res.status(403).json({ + message: "Token has expired. Please request a new one.", + }); } } @@ -196,14 +179,11 @@ router.delete("/deactivate", async (req, res) => { try { const { token, ouEmail } = req.body; if (!token && !ouEmail) { - return res - .status(400) - .json({ error: "Token or Email is required for deactivation." }); + return res.status(400).json({ error: "Token or Email is required for deactivation." }); } let filter = {}; - // Only hash the token if it exists if (token) { if (typeof token !== "string") { return res.status(400).json({ error: "Token must be a string." }); @@ -213,6 +193,7 @@ router.delete("/deactivate", async (req, res) => { } else { filter = { ouEmail }; } + const user = await TokenRequest.findOne(filter); if (!user) { return res.status(404).json({ error: "Token not found." }); @@ -233,4 +214,55 @@ router.delete("/deactivate", async (req, res) => { } }); +router.post("/renew", async (req, res) => { + try { + const { token } = req.body; + + if (!token) { + return res.status(400).json({ error: "Token is required." }); + } + + const hashedToken = hashToken(token); + const user = await TokenRequest.findOne({ token: hashedToken }); + + if (!user) { + return res.status(404).json({ error: "Token not found." }); + } + + if (user.deletedAt || user.status === "deleted") { + return res.status(403).json({ error: "Token has been deactivated." }); + } + + if (!user.isActivated || user.status !== "activated") { + return res.status(403).json({ error: "Token is not activated." }); + } + + if (new Date() > user.expiresAt) { + return res.status(403).json({ error: "Token has already expired." }); + } + + const newToken = jwt.sign({ ouEmail: user.ouEmail }, JWT_SECRET, { expiresIn: "180d" }); + const hashedNewToken = hashToken(newToken); + + const newExpiresAt = new Date(); + newExpiresAt.setMonth(newExpiresAt.getMonth() + 6); + + user.token = hashedNewToken; + user.expiresAt = newExpiresAt; + user.status = "activated"; + + await user.save(); + + res.status(200).json({ + message: "Your token has been updated. You can now securely login.", + redirectUrl: `${FRONTEND_URL}/renewal-success`, + token: newToken, + expiresAt: newExpiresAt, + }); + } catch (error) { + console.error("Token renewal error:", error); + res.status(500).json({ error: "Internal server error." }); + } +}); + module.exports = router; From 22902b7da1dfd23ea34ab108df826c27b1ead4dd Mon Sep 17 00:00:00 2001 From: Aakash452 Date: Fri, 18 Apr 2025 11:21:57 -0500 Subject: [PATCH 04/22] Implemented student dashboard with conditional UI and navigation --- client/src/pages/Home.js | 82 +++++++++++------- client/src/pages/ProtectedRouteStudent.jsx | 16 ++++ client/src/pages/StudentDashboard.jsx | 96 ++++++++++++++++++++++ client/src/router.js | 15 +++- client/src/styles/StudentDashboard.css | 71 ++++++++++++++++ server/index.js | 2 + server/routes/studentRoutes.js | 30 +++++++ server/routes/token.js | 4 + 8 files changed, 283 insertions(+), 33 deletions(-) create mode 100644 client/src/pages/ProtectedRouteStudent.jsx create mode 100644 client/src/pages/StudentDashboard.jsx create mode 100644 client/src/styles/StudentDashboard.css create mode 100644 server/routes/studentRoutes.js diff --git a/client/src/pages/Home.js b/client/src/pages/Home.js index c1fdf9e6..8512f969 100644 --- a/client/src/pages/Home.js +++ b/client/src/pages/Home.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React from "react"; import { useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import "../styles/App.css"; @@ -7,7 +7,7 @@ import "../styles/login.css"; import StudentIcon from "../Icons/StudentIcon"; import CoordinatorIcon from "../Icons/CoordinatorIcon"; import SupervisorIcon from "../Icons/SupervisorIcon"; -import Swal from 'sweetalert2'; +import Swal from "sweetalert2"; function Home() { const navigate = useNavigate(); @@ -17,7 +17,6 @@ function Home() { role: "", }); const [showPassword, setShowPassword] = useState(false); - const handleInputChange = (e) => { const { name, value } = e.target; @@ -29,9 +28,9 @@ function Home() { const handleSubmit = async (e) => { e.preventDefault(); - + const { email: ouEmail, password, role } = formData; - + if (!ouEmail || !password || !role) { return Swal.fire({ icon: "warning", @@ -39,24 +38,40 @@ function Home() { text: "Please fill in all fields to sign in 💫", }); } - + try { - const response = await fetch(`${process.env.REACT_APP_API_URL}/api/token/user-login`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ ouEmail, password, role }), - }); - + const response = await fetch( + `${process.env.REACT_APP_API_URL}/api/token/user-login`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ ouEmail, password, role }), + } + ); + const data = await response.json(); - + if (response.ok) { - Swal.fire({ - icon: "success", - title: "Login Successful 🌟", - text: `Welcome back, ${role}!`, - }); + const user = data.user; + + // Store only required fields + const limitedUserInfo = { + fullName: user.fullName, + id: user._id, + email:user.ouEmail + }; + + localStorage.setItem("ipmsUser", JSON.stringify(limitedUserInfo)); + + // Swal.fire({ + // icon: "success", + // title: "Login Successful", + // text: `Welcome back, `, + // }); + + navigate("/student-dashboard"); } else { Swal.fire({ icon: "error", @@ -73,8 +88,6 @@ function Home() { }); } }; - - return (
@@ -101,11 +114,15 @@ function Home() { ].map(({ role: r, Icon }) => (
setFormData({ - ...formData, - role: r, - })} + className={`role-card ${ + formData.role === r ? "selected" : "" + }`} + onClick={() => + setFormData({ + ...formData, + role: r, + }) + } >

@@ -179,17 +196,20 @@ function Home() { marginBottom: "1rem", }} > -

+
+ +
+
+

Weekly Report (Form A2)

+ {!approvalStatus && ( +

+ Finish your Form A1 first +

+ )} +
+ +
+
+
+ + ); +}; + +export default StudentDashboard; diff --git a/client/src/router.js b/client/src/router.js index 5461fdc0..0289dea1 100644 --- a/client/src/router.js +++ b/client/src/router.js @@ -16,9 +16,11 @@ import A3JobEvaluationForm from "./pages/A3JobEvaluationForm"; import ActivateAccount from "./pages/ActivateAccount"; import SupervisorDashboard from "./pages/SupervisorDashboard"; import CoordinatorDashboard from "./pages/CoordinatorDashboard"; +import StudentDashboard from "./pages/StudentDashboard"; +import ProtectedRouteStudent from "./pages/ProtectedRouteStudent"; // Create and export the router configuration -const router = createBrowserRouter([ +const router = createBrowserRouter([ { path: "/", element: , @@ -31,11 +33,20 @@ const router = createBrowserRouter([ { path: "signup", element: , - }, + }, { path: "weekly-report", element: , }, + { + path: "/student-dashboard", + element: ( + + + + ) + }, + { path: "a1-form", element: , diff --git a/client/src/styles/StudentDashboard.css b/client/src/styles/StudentDashboard.css new file mode 100644 index 00000000..71975b3e --- /dev/null +++ b/client/src/styles/StudentDashboard.css @@ -0,0 +1,71 @@ +.student-dashboard { + display: flex; + flex-direction: column; + align-items: center; + padding: 2rem 1rem; + background-color: #f7f7f7; + min-height: 90vh; + } + + .dashboard-header { + width: 100%; + max-width: 900px; + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; + } + + .user-role { + font-weight: bold; + font-size: 1rem; + color: #5c0a0a; + } + + .dashboard-card { + background-color: white; + border-radius: 10px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08); + padding: 2rem; + width: 100%; + max-width: 900px; + } + + .card-section { + background-color: #842020; + border-radius: 10px; + padding: 1.5rem; + margin-bottom: 1.5rem; + display: flex; + justify-content: space-between; + align-items: center; + color: white; + box-shadow: 0 6px 18px rgba(0, 0, 0, 0.1); + } + + .card-content h3 { + margin: 0; + font-size: 1.2rem; + font-weight: 600; + } + + .card-content p { + margin: 5px 0 0; + font-size: 0.9rem; + } + + .card-button { + background-color: white; + color: #842020; + border: none; + padding: 8px 16px; + border-radius: 6px; + font-weight: 500; + cursor: pointer; + transition: 0.3s ease-in-out; + } + + .card-button:hover { + background-color: #e6e6e6; + } + \ No newline at end of file diff --git a/server/index.js b/server/index.js index 26854aa6..6e4e69cf 100644 --- a/server/index.js +++ b/server/index.js @@ -11,6 +11,7 @@ require("dotenv").config(); const emailRoutes = require("./routes/emailRoutes"); const tokenRoutes = require("./routes/token"); const approvalRoutes = require("./routes/approvalRoutes"); +const studentRoutes = require("./routes/studentRoutes") // Import cron job manager and register jobs const cronJobManager = require("./utils/cronUtils"); @@ -74,6 +75,7 @@ app.use("/api/email", emailRoutes); app.use("/api/token", tokenRoutes); app.use("/api", approvalRoutes); app.use("/api/reports", weeklyReportRoutes); +app.use("/api/student",studentRoutes) app.post("/api/createUser", async (req, res) => { try { diff --git a/server/routes/studentRoutes.js b/server/routes/studentRoutes.js new file mode 100644 index 00000000..55714ef8 --- /dev/null +++ b/server/routes/studentRoutes.js @@ -0,0 +1,30 @@ +const express = require("express"); +const router = express.Router(); +const InternshipRequest = require("../models/InternshipRequest"); +const User = require("../models/User"); + +// GET internship request by student's ouEmail +router.post("/", async (req, res) => { + const { ouEmail } = req.body; + console.log(ouEmail) + try { + const studentUser = await User.findOne({ email: ouEmail }); + if (!studentUser) { + return res.status(404).json({ message: "Student not found" }); + } + + const internshipData = await InternshipRequest.findOne({ student: studentUser._id }); + + if (!internshipData) { + return res.status(404).json({ message: "No internship request found for this student" }); + } + const approvalStatus = internshipData.status == "approved" ? true : false + return res.status(200).json({ message: "Success" , approvalStatus }); + } catch (error) { + console.error("Error fetching internship request:", error); + return res.status(500).json({ message: "Server error" }); + } + }); + + +module.exports = router; \ No newline at end of file diff --git a/server/routes/token.js b/server/routes/token.js index 247fda86..dd619e89 100644 --- a/server/routes/token.js +++ b/server/routes/token.js @@ -5,6 +5,7 @@ const crypto = require("crypto"); const bcrypt = require("bcrypt"); const TokenRequest = require("../models/TokenRequest"); const emailService = require("../services/emailService"); +const User = require("../models/User") const JWT_SECRET = process.env.JWT_SECRET; const FRONTEND_URL = process.env.FRONTEND_URL; @@ -51,7 +52,10 @@ router.post("/request", async (req, res) => { activationLinkSentAt: new Date(), }); + + await request.save(); + const activationLink = `${FRONTEND_URL}/activate/${plainToken}`; const emailBody = ` From fbd3186b6616fc1f6a2db8c8016388215f12f5ed Mon Sep 17 00:00:00 2001 From: kushi-3 <84432650+kushi-3@users.noreply.github.com> Date: Fri, 18 Apr 2025 12:35:52 -0500 Subject: [PATCH 05/22] Add page for redirection after successful token renewal --- client/src/pages/RenewalSuccess.jsx | 24 ++++++++++++++++++++++++ client/src/router.js | 6 +++++- server/routes/token.js | 6 ++++-- 3 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 client/src/pages/RenewalSuccess.jsx diff --git a/client/src/pages/RenewalSuccess.jsx b/client/src/pages/RenewalSuccess.jsx new file mode 100644 index 00000000..e07ccd66 --- /dev/null +++ b/client/src/pages/RenewalSuccess.jsx @@ -0,0 +1,24 @@ +import React from "react"; +import { useNavigate } from "react-router-dom"; + + +const RenewalSuccess = () => { + const navigate = useNavigate(); + + const handleGoHome = () => { + navigate("/"); + }; + + return ( +
+
+

Success!

+

+ Your token has been updated. You can now securely login. +

+
+
+ ); +}; + +export default RenewalSuccess; diff --git a/client/src/router.js b/client/src/router.js index c1bfe4ef..c7d8f656 100644 --- a/client/src/router.js +++ b/client/src/router.js @@ -17,7 +17,7 @@ import A4PresentationEvaluationForm from "./pages/A4PresentationEvaluationForm"; import SupervisorDashboard from "./pages/SupervisorDashboard"; import CoordinatorDashboard from "./pages/CoordinatorDashboard"; import CoordinatorRequestDetailView from "./pages/CoordinatorRequestDetailView"; - +import RenewalSuccess from "./pages/RenewalSuccess"; // Create and export the router configuration const router = createBrowserRouter([ { @@ -65,6 +65,10 @@ const router = createBrowserRouter([ path: "coordinator/request/:id", element: , }, + { + path: "renewal-success", + element: , + } ], }, ]); diff --git a/server/routes/token.js b/server/routes/token.js index b6809181..8c5eab9c 100644 --- a/server/routes/token.js +++ b/server/routes/token.js @@ -222,8 +222,8 @@ router.post("/renew", async (req, res) => { return res.status(400).json({ error: "Token is required." }); } - const hashedToken = hashToken(token); - const user = await TokenRequest.findOne({ token: hashedToken }); + // const hashedToken = hashToken(token); + const user = await TokenRequest.findOne({ token: token }); if (!user) { return res.status(404).json({ error: "Token not found." }); @@ -259,6 +259,8 @@ router.post("/renew", async (req, res) => { token: newToken, expiresAt: newExpiresAt, }); + + res.redirect(`${FRONTEND_URL}/renewal-success`); } catch (error) { console.error("Token renewal error:", error); res.status(500).json({ error: "Internal server error." }); From 05a2bd3fdff5f5f8f1afcb6fd60eb1fef5c9349d Mon Sep 17 00:00:00 2001 From: kushi-3 <84432650+kushi-3@users.noreply.github.com> Date: Fri, 18 Apr 2025 12:49:41 -0500 Subject: [PATCH 06/22] Remove unused variable --- client/src/pages/RenewalSuccess.jsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/client/src/pages/RenewalSuccess.jsx b/client/src/pages/RenewalSuccess.jsx index e07ccd66..947e2bf4 100644 --- a/client/src/pages/RenewalSuccess.jsx +++ b/client/src/pages/RenewalSuccess.jsx @@ -1,13 +1,6 @@ import React from "react"; -import { useNavigate } from "react-router-dom"; - const RenewalSuccess = () => { - const navigate = useNavigate(); - - const handleGoHome = () => { - navigate("/"); - }; return (
From ed1458ec12b67d06861457b2c4aaaac4da6f1d55 Mon Sep 17 00:00:00 2001 From: Srija Repudi Date: Sat, 19 Apr 2025 00:11:20 -0500 Subject: [PATCH 07/22] auto-deactivation changes --- server/jobs/autoDeactivateCronjobs.js | 26 ++++++++++++++++ server/jobs/cronJobsConfig.js | 3 ++ server/routes/token.js | 44 +++++++++++++++++++++++++-- 3 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 server/jobs/autoDeactivateCronjobs.js diff --git a/server/jobs/autoDeactivateCronjobs.js b/server/jobs/autoDeactivateCronjobs.js new file mode 100644 index 00000000..1c09bfc3 --- /dev/null +++ b/server/jobs/autoDeactivateCronjobs.js @@ -0,0 +1,26 @@ +const TokenRequest = require("../models/TokenRequest"); + +const autoDeactivateExpiredTokens = async () => { + try { + const now = new Date(); + + const result = await TokenRequest.updateMany( + { + expiryDate: { $lt: now }, + status: "activated", + }, + { + $set: { + status: "deactivated", + deactivationReason: "token_expired", + }, + } + ); + + console.log(`Auto-deactivated ${result.modifiedCount} users`); + } catch (error) { + console.error("Error in auto-deactivation:", error); + } +}; + +module.exports = autoDeactivateExpiredTokens; diff --git a/server/jobs/cronJobsConfig.js b/server/jobs/cronJobsConfig.js index 9aef87af..96817121 100644 --- a/server/jobs/cronJobsConfig.js +++ b/server/jobs/cronJobsConfig.js @@ -1,9 +1,12 @@ const CronJob = require("../models/CronJob"); const coordinatorReminder = require("./reminderEmail"); +const autoDeactivateCronjobs = require("./autoDeactivateCronjobs"); + // Map of job names to their corresponding functions const jobFunctions = { coordinatorApprovalReminder: coordinatorReminder, + autoDeactivateCronjobs: autoDeactivateCronjobs, // Add more job functions here as needed }; diff --git a/server/routes/token.js b/server/routes/token.js index f14797ad..4e5cd08d 100644 --- a/server/routes/token.js +++ b/server/routes/token.js @@ -178,9 +178,13 @@ router.post("/user-login", async (req, res) => { const tokenExpiry = new Date(user.expiresAt); if (tokenExpiry < now) { - return res - .status(403) - .json({ message: "Token has expired. Please request a new one." }); + user.status = "deactivated"; + await user.save(); + + return res.status(403).json({ + message: "Your account is deactivated due to token expiry.", + renewalLink: `${FRONTEND_URL}/renew-token?email=${user.ouEmail}` + }); } } @@ -217,4 +221,38 @@ router.delete("/deactivate", async (req, res) => { } }); +router.post("/renew-token", async (req, res) => { + try { + const { ouEmail } = req.body; + const user = await TokenRequest.findOne({ ouEmail }); + + if (!user) return res.status(404).json({ error: "User not found." }); + + const newToken = jwt.sign({ ouEmail }, JWT_SECRET, { expiresIn: "180d" }); + const hashedToken = crypto.createHash("sha256").update(newToken).digest("hex"); + + const now = new Date(); + const newExpiry = new Date(now.setMonth(now.getMonth() + 6)); + + user.token = hashedToken; + user.expiresAt = newExpiry; + user.status = "activated"; + user.isActivated = true; + user.activatedAt = new Date(); + user.deletedAt = null; + user.deactivationReason = null; + + await user.save(); + + res.status(200).json({ + message: "Token renewed and account reactivated.", + token: newToken + }); + } catch (err) { + console.error("Renew token error:", err); + res.status(500).json({ error: "Internal server error" }); + } +}); + + module.exports = router; From 7649a53f925af3b0c1dd15d87d6be1d420b1e074 Mon Sep 17 00:00:00 2001 From: Chinni Sree Addagalla Date: Sat, 19 Apr 2025 01:51:20 -0500 Subject: [PATCH 08/22] Implemented Auto-Deactivation after token expiry --- server/jobs/autoDeactivateCronjobs.js | 6 +++--- server/models/TokenRequest.js | 2 +- server/routes/token.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/server/jobs/autoDeactivateCronjobs.js b/server/jobs/autoDeactivateCronjobs.js index 1c09bfc3..5ce739d5 100644 --- a/server/jobs/autoDeactivateCronjobs.js +++ b/server/jobs/autoDeactivateCronjobs.js @@ -1,12 +1,12 @@ const TokenRequest = require("../models/TokenRequest"); -const autoDeactivateExpiredTokens = async () => { +const autoDeactivateCronjobs = async () => { try { const now = new Date(); const result = await TokenRequest.updateMany( { - expiryDate: { $lt: now }, + expiresAt: { $lt: now }, status: "activated", }, { @@ -23,4 +23,4 @@ const autoDeactivateExpiredTokens = async () => { } }; -module.exports = autoDeactivateExpiredTokens; +module.exports = autoDeactivateCronjobs; diff --git a/server/models/TokenRequest.js b/server/models/TokenRequest.js index 32eeb74d..dcae8a0a 100644 --- a/server/models/TokenRequest.js +++ b/server/models/TokenRequest.js @@ -98,7 +98,7 @@ const userTokenRequestSchema = new mongoose.Schema( }, status: { type: String, - enum: ['pending', 'activated', 'expired', 'deleted'], + enum: ['pending', 'activated', 'expired', 'deleted','deactivated'], default: 'pending', }, }, diff --git a/server/routes/token.js b/server/routes/token.js index 4e5cd08d..889754e0 100644 --- a/server/routes/token.js +++ b/server/routes/token.js @@ -170,7 +170,7 @@ router.post("/user-login", async (req, res) => { return res.status(403).json({ message: "Token not issued yet." }); } - if (user.status !== "activated") { + if (!user.isActivated) { return res.status(403).json({ message: "Token is not activated yet." }); } From d7abafad89b8c03c22ac0c77d887da7c83b68ef9 Mon Sep 17 00:00:00 2001 From: kushi-3 <84432650+kushi-3@users.noreply.github.com> Date: Sat, 19 Apr 2025 10:02:46 -0500 Subject: [PATCH 09/22] Revert "Remove unused variable" This reverts commit 05a2bd3fdff5f5f8f1afcb6fd60eb1fef5c9349d. --- client/src/pages/RenewalSuccess.jsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/client/src/pages/RenewalSuccess.jsx b/client/src/pages/RenewalSuccess.jsx index 947e2bf4..e07ccd66 100644 --- a/client/src/pages/RenewalSuccess.jsx +++ b/client/src/pages/RenewalSuccess.jsx @@ -1,6 +1,13 @@ import React from "react"; +import { useNavigate } from "react-router-dom"; + const RenewalSuccess = () => { + const navigate = useNavigate(); + + const handleGoHome = () => { + navigate("/"); + }; return (
From 86868e634ae185bfebf32a8bf0be05d66463b478 Mon Sep 17 00:00:00 2001 From: kushi-3 <84432650+kushi-3@users.noreply.github.com> Date: Sat, 19 Apr 2025 10:05:10 -0500 Subject: [PATCH 10/22] Revert "Add page for redirection after successful token renewal" This reverts commit fbd3186b6616fc1f6a2db8c8016388215f12f5ed. --- client/src/pages/RenewalSuccess.jsx | 24 ------------------------ client/src/router.js | 6 +----- server/routes/token.js | 6 ++---- 3 files changed, 3 insertions(+), 33 deletions(-) delete mode 100644 client/src/pages/RenewalSuccess.jsx diff --git a/client/src/pages/RenewalSuccess.jsx b/client/src/pages/RenewalSuccess.jsx deleted file mode 100644 index e07ccd66..00000000 --- a/client/src/pages/RenewalSuccess.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from "react"; -import { useNavigate } from "react-router-dom"; - - -const RenewalSuccess = () => { - const navigate = useNavigate(); - - const handleGoHome = () => { - navigate("/"); - }; - - return ( -
-
-

Success!

-

- Your token has been updated. You can now securely login. -

-
-
- ); -}; - -export default RenewalSuccess; diff --git a/client/src/router.js b/client/src/router.js index c7d8f656..c1bfe4ef 100644 --- a/client/src/router.js +++ b/client/src/router.js @@ -17,7 +17,7 @@ import A4PresentationEvaluationForm from "./pages/A4PresentationEvaluationForm"; import SupervisorDashboard from "./pages/SupervisorDashboard"; import CoordinatorDashboard from "./pages/CoordinatorDashboard"; import CoordinatorRequestDetailView from "./pages/CoordinatorRequestDetailView"; -import RenewalSuccess from "./pages/RenewalSuccess"; + // Create and export the router configuration const router = createBrowserRouter([ { @@ -65,10 +65,6 @@ const router = createBrowserRouter([ path: "coordinator/request/:id", element: , }, - { - path: "renewal-success", - element: , - } ], }, ]); diff --git a/server/routes/token.js b/server/routes/token.js index 8c5eab9c..b6809181 100644 --- a/server/routes/token.js +++ b/server/routes/token.js @@ -222,8 +222,8 @@ router.post("/renew", async (req, res) => { return res.status(400).json({ error: "Token is required." }); } - // const hashedToken = hashToken(token); - const user = await TokenRequest.findOne({ token: token }); + const hashedToken = hashToken(token); + const user = await TokenRequest.findOne({ token: hashedToken }); if (!user) { return res.status(404).json({ error: "Token not found." }); @@ -259,8 +259,6 @@ router.post("/renew", async (req, res) => { token: newToken, expiresAt: newExpiresAt, }); - - res.redirect(`${FRONTEND_URL}/renewal-success`); } catch (error) { console.error("Token renewal error:", error); res.status(500).json({ error: "Internal server error." }); From a039d99eee30ec972e9b571b258d43718930f69b Mon Sep 17 00:00:00 2001 From: Srija Repudi Date: Sat, 19 Apr 2025 14:12:40 -0500 Subject: [PATCH 11/22] Implemented Auto deactivation after token expiry with 24-hour window --- server/jobs/autoDeactivateCronjobs.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/jobs/autoDeactivateCronjobs.js b/server/jobs/autoDeactivateCronjobs.js index 5ce739d5..1a27f2a7 100644 --- a/server/jobs/autoDeactivateCronjobs.js +++ b/server/jobs/autoDeactivateCronjobs.js @@ -3,10 +3,14 @@ const TokenRequest = require("../models/TokenRequest"); const autoDeactivateCronjobs = async () => { try { const now = new Date(); + const startOfToday = new Date(now.setHours(0, 0, 0, 0)); + const endOfToday = new Date(now.setHours(23, 59, 59, 999)); const result = await TokenRequest.updateMany( { - expiresAt: { $lt: now }, + expiresAt: { + $gte: startOfToday, + $lt: endOfToday, }, status: "activated", }, { From 47b3407d7548499f3646faa91dfa7352e79f9b29 Mon Sep 17 00:00:00 2001 From: Srija Repudi Date: Sat, 19 Apr 2025 14:23:01 -0500 Subject: [PATCH 12/22] Implemented Auto deactivation after token expiry with 24-hour window and removed renew-token logic --- server/routes/token.js | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/server/routes/token.js b/server/routes/token.js index 889754e0..30160320 100644 --- a/server/routes/token.js +++ b/server/routes/token.js @@ -221,38 +221,4 @@ router.delete("/deactivate", async (req, res) => { } }); -router.post("/renew-token", async (req, res) => { - try { - const { ouEmail } = req.body; - const user = await TokenRequest.findOne({ ouEmail }); - - if (!user) return res.status(404).json({ error: "User not found." }); - - const newToken = jwt.sign({ ouEmail }, JWT_SECRET, { expiresIn: "180d" }); - const hashedToken = crypto.createHash("sha256").update(newToken).digest("hex"); - - const now = new Date(); - const newExpiry = new Date(now.setMonth(now.getMonth() + 6)); - - user.token = hashedToken; - user.expiresAt = newExpiry; - user.status = "activated"; - user.isActivated = true; - user.activatedAt = new Date(); - user.deletedAt = null; - user.deactivationReason = null; - - await user.save(); - - res.status(200).json({ - message: "Token renewed and account reactivated.", - token: newToken - }); - } catch (err) { - console.error("Renew token error:", err); - res.status(500).json({ error: "Internal server error" }); - } -}); - - module.exports = router; From cc2672f213e14c2f2add9743bf364844416a2bf3 Mon Sep 17 00:00:00 2001 From: Srija Repudi Date: Sat, 19 Apr 2025 14:31:41 -0500 Subject: [PATCH 13/22] Implemented Auto deactivation after token expiry with 24-hour window and removed renew-token logic --- server/jobs/cronJobsConfig.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/jobs/cronJobsConfig.js b/server/jobs/cronJobsConfig.js index 96817121..51072399 100644 --- a/server/jobs/cronJobsConfig.js +++ b/server/jobs/cronJobsConfig.js @@ -1,11 +1,14 @@ const CronJob = require("../models/CronJob"); -const coordinatorReminder = require("./reminderEmail"); +const { coordinatorReminder, supervisorReminder } = require("./reminderEmail"); +const { checkAndSendReminders } = require("./tokenExpiryCheck"); const autoDeactivateCronjobs = require("./autoDeactivateCronjobs"); // Map of job names to their corresponding functions const jobFunctions = { coordinatorApprovalReminder: coordinatorReminder, + supervisorApprovalReminder: supervisorReminder, + tokenExpiryReminder: checkAndSendReminders, autoDeactivateCronjobs: autoDeactivateCronjobs, // Add more job functions here as needed }; From 9205dc3b16a8b335c112a921f36591f5b21857e2 Mon Sep 17 00:00:00 2001 From: kushi-3 <84432650+kushi-3@users.noreply.github.com> Date: Sat, 19 Apr 2025 16:35:14 -0500 Subject: [PATCH 14/22] Update code to search for given token --- server/routes/token.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/server/routes/token.js b/server/routes/token.js index bfbdb217..17dc8b7c 100644 --- a/server/routes/token.js +++ b/server/routes/token.js @@ -227,26 +227,25 @@ router.post("/renew", async (req, res) => { const { token } = req.body; if (!token) { - return res.status(400).json({ error: "Token is required." }); + return res.status(400).json({ message: "Token is required." }); } - const hashedToken = hashToken(token); - const user = await TokenRequest.findOne({ token: hashedToken }); + const user = await TokenRequest.findOne({ token: token }); if (!user) { - return res.status(404).json({ error: "Token not found." }); + return res.status(404).json({ message: "Token not found." }); } if (user.deletedAt || user.status === "deleted") { - return res.status(403).json({ error: "Token has been deactivated." }); + return res.status(403).json({ message: "Token has been deactivated." }); } if (!user.isActivated || user.status !== "activated") { - return res.status(403).json({ error: "Token is not activated." }); + return res.status(403).json({ message: "Token is not activated." }); } if (new Date() > user.expiresAt) { - return res.status(403).json({ error: "Token has already expired." }); + return res.status(403).json({ message: "Token has already expired." }); } const newToken = jwt.sign({ ouEmail: user.ouEmail }, JWT_SECRET, { expiresIn: "180d" }); @@ -269,7 +268,7 @@ router.post("/renew", async (req, res) => { }); } catch (error) { console.error("Token renewal error:", error); - res.status(500).json({ error: "Internal server error." }); + res.status(500).json({ message: "Internal server error." }); } }); From 72e6c5f8d14598aaf8969af9aef8df382cef60b7 Mon Sep 17 00:00:00 2001 From: kushi-3 <84432650+kushi-3@users.noreply.github.com> Date: Sat, 19 Apr 2025 17:41:34 -0500 Subject: [PATCH 15/22] Add sooner id to the sign up --- client/src/pages/SignUp.js | 24 ++++++++++++++++++++++++ server/models/TokenRequest.js | 11 ++++++++--- server/routes/token.js | 5 +++-- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/client/src/pages/SignUp.js b/client/src/pages/SignUp.js index 34a9ac73..ec417986 100644 --- a/client/src/pages/SignUp.js +++ b/client/src/pages/SignUp.js @@ -15,6 +15,7 @@ function SignUp() { const [step, setStep] = useState(1); const [fullName, setFullName] = useState(""); const [ouEmail, setOuEmail] = useState(""); + const [soonerId, setSoonerId] = useState(""); const [password, setPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); const [showPassword, setShowPassword] = useState(false); @@ -61,12 +62,22 @@ function SignUp() { return; } + if (!/^\d{9}$/.test(soonerId)) { + Swal.fire({ + icon: "error", + title: "Invalid Sooner ID", + text: "Sooner ID must be a 9-digit number.", + }); + return; + } + try { const response = await axios.post( `${process.env.REACT_APP_API_URL}/api/token/request`, { fullName, ouEmail, + soonerId, password, semester, academicAdvisor: role === "student" ? academicAdvisor : "", @@ -255,6 +266,19 @@ function SignUp() { required />
+ +
+ + setSoonerId(e.target.value)} + placeholder="Enter your 9-digit Sooner ID" + required + /> +
+
diff --git a/server/models/TokenRequest.js b/server/models/TokenRequest.js index dcae8a0a..78cb6b1e 100644 --- a/server/models/TokenRequest.js +++ b/server/models/TokenRequest.js @@ -1,5 +1,3 @@ -// models/UserTokenRequest.js - const mongoose = require('mongoose'); /** @@ -12,6 +10,7 @@ const mongoose = require('mongoose'); * - fullName: Student's full name. * - password: Encrypted password for login authentication. * - ouEmail: Unique OU email for login. + * - soonerId: Unique 9-character ID assigned to the student. * - semester: The semester in which the internship is active. * - academicAdvisor: Reference to the academic advisor (if using a separate collection). * - token: Unique access token used for login. @@ -23,7 +22,7 @@ const mongoose = require('mongoose'); * - status: Optional string enum for tracking token state. * - activationLinkSentAt: Timestamp when the activation email was sent. * - password: Encrypted password for login authentication. - * + * Additional Features: * - Automatically sets `expiresAt` to 6 months from `requestedAt`. * - Uses `timestamps` to auto-generate `createdAt` and `updatedAt`. @@ -51,6 +50,12 @@ const userTokenRequestSchema = new mongoose.Schema( lowercase: true, match: [/^[\w-.]+@ou\.edu$/, 'Email must be a valid OU address'], }, + soonerId: { + type: String, + required: [true, 'Sooner ID is required'], + unique: true, + match: [/^\d{9}$/, 'Sooner ID must be exactly 9 digits'], + }, role: { type: String, required: true, diff --git a/server/routes/token.js b/server/routes/token.js index 17dc8b7c..d7270f44 100644 --- a/server/routes/token.js +++ b/server/routes/token.js @@ -17,9 +17,9 @@ const hashToken = (token) => { router.post("/request", async (req, res) => { try { - const { fullName, ouEmail, password, semester, academicAdvisor, role } = req.body; + const { fullName, ouEmail, soonerId, password, semester, academicAdvisor, role } = req.body; - if (!fullName || !ouEmail || !password || !semester) { + if (!fullName || !ouEmail || !soonerId || !password || !semester) { return res.status(400).json({ error: "All fields are required." }); } @@ -38,6 +38,7 @@ router.post("/request", async (req, res) => { const request = new TokenRequest({ fullName, ouEmail, + soonerId, password: hashedPassword, semester, role, From f2423623803819ee9563ae5df9e1bddb9f71d195 Mon Sep 17 00:00:00 2001 From: Aakash452 Date: Sat, 19 Apr 2025 22:22:27 -0500 Subject: [PATCH 16/22] a1form scheme reference changed --- client/src/pages/StudentDashboard.jsx | 174 +++++++++++++++----------- server/models/InternshipRequest.js | 2 +- server/routes/studentRoutes.js | 47 ++++--- 3 files changed, 127 insertions(+), 96 deletions(-) diff --git a/client/src/pages/StudentDashboard.jsx b/client/src/pages/StudentDashboard.jsx index af9f4614..0a276075 100644 --- a/client/src/pages/StudentDashboard.jsx +++ b/client/src/pages/StudentDashboard.jsx @@ -3,93 +3,115 @@ import { useNavigate } from "react-router-dom"; import "../styles/StudentDashboard.css"; // Make sure you create this CSS const StudentDashboard = () => { - const navigate = useNavigate(); + const navigate = useNavigate(); - const user = JSON.parse(localStorage.getItem("ipmsUser")); - const backendUrl = process.env.REACT_APP_API_URL; - const ouEmail = user?.email; - const [approvalStatus, setApprovalStatus] = useState(false) - - - useEffect(() => { - const fetchData = async () => { - try { - const res = await fetch(`${backendUrl}/api/student`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ ouEmail }), - }); - - const data = await res.json(); - setApprovalStatus(data.approvalStatus); - - } catch (err) { - console.error("Error fetching internship data", err); - } - }; - - if (ouEmail) { - fetchData(); - } - }, [ouEmail]); - + const user = JSON.parse(localStorage.getItem("ipmsUser")); + const backendUrl = process.env.REACT_APP_API_URL; + const ouEmail = user?.email; + const [approvalStatus, setApprovalStatus] = useState("draft"); + useEffect(() => { + const fetchData = async () => { + try { + const res = await fetch(`${backendUrl}/api/student`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ ouEmail }), + }); + const data = await res.json(); + setApprovalStatus(data.approvalStatus); + } catch (err) { + console.error("Error fetching internship data", err); + } + }; + if (ouEmail) { + fetchData(); + } + }, [ouEmail]); + console.log(approvalStatus); return (
-
-

Welcome, {user.fullName}

-
+
+

Welcome, {user.fullName}

+
-
-
-
-

Request Internship (FORM A1)

-

Track your internship journey

-
- -
+
+ {/* ------ FORM A1 Card ------ */} +
+
+

Request Internship (FORM A1)

+

Track your internship journey

+ + {(approvalStatus === "submitted" || + approvalStatus === "pending manual review") && ( +

+ Your form is submitted and under review +

+ )} -
-
-

Weekly Report (Form A2)

- {!approvalStatus && ( -

- Finish your Form A1 first -

- )} + {approvalStatus === "approved" && ( +

Approved

+ )} +
+ + +
+ + {/* ------ FORM A2 Card ------ */} +
+
+

Weekly Report (Form A2)

+ {approvalStatus === "draft" && ( +

+ Finish your Form A1 first +

+ )} + + {(approvalStatus === "submitted" || + approvalStatus === "pending manual review") && ( +

+ Wait for your Form A1 to be approved +

+ )} +
+ + +
-
-
-
- ); }; diff --git a/server/models/InternshipRequest.js b/server/models/InternshipRequest.js index 3732f04d..b9470465 100644 --- a/server/models/InternshipRequest.js +++ b/server/models/InternshipRequest.js @@ -16,7 +16,7 @@ const formA1 = new mongoose.Schema({ student: { // get student's name, email, id from User type: ObjectId, required: true, - ref: 'User' + ref: 'UserTokenRequest' }, workplace: { name: { diff --git a/server/routes/studentRoutes.js b/server/routes/studentRoutes.js index 55714ef8..a49b2dd8 100644 --- a/server/routes/studentRoutes.js +++ b/server/routes/studentRoutes.js @@ -2,29 +2,38 @@ const express = require("express"); const router = express.Router(); const InternshipRequest = require("../models/InternshipRequest"); const User = require("../models/User"); +const TokenRequest = require("../models/TokenRequest"); + // GET internship request by student's ouEmail router.post("/", async (req, res) => { - const { ouEmail } = req.body; - console.log(ouEmail) - try { - const studentUser = await User.findOne({ email: ouEmail }); - if (!studentUser) { - return res.status(404).json({ message: "Student not found" }); - } - - const internshipData = await InternshipRequest.findOne({ student: studentUser._id }); - - if (!internshipData) { - return res.status(404).json({ message: "No internship request found for this student" }); - } - const approvalStatus = internshipData.status == "approved" ? true : false - return res.status(200).json({ message: "Success" , approvalStatus }); - } catch (error) { - console.error("Error fetching internship request:", error); - return res.status(500).json({ message: "Server error" }); + const { ouEmail } = req.body; + console.log("Received email:", ouEmail); + + try { + const studentUser = await TokenRequest.findOne({ ouEmail }); + + if (!studentUser) { + return res.status(404).json({ message: "Student not found in TokenRequest" }); } - }); + + const internshipData = await InternshipRequest.findOne({ student: studentUser._id }); + + if (!internshipData) { + return res.status(404).json({ message: "No internship request found for this student" }); + } + + + + const approvalStatus = internshipData.status + + + return res.status(200).json({ message: "Success", approvalStatus }); + } catch (error) { + console.error("Error fetching internship request:", error); + return res.status(500).json({ message: "Server error" }); + } +}); module.exports = router; \ No newline at end of file From d52df8af0239476788c91730fe429ac21f68575b Mon Sep 17 00:00:00 2001 From: kushi-3 <84432650+kushi-3@users.noreply.github.com> Date: Sun, 20 Apr 2025 15:17:54 -0500 Subject: [PATCH 17/22] Update to make sooner id field mandatory only to studnets --- client/src/pages/SignUp.js | 22 ++++++++++++++++------ server/models/TokenRequest.js | 4 +++- server/routes/token.js | 14 ++++++++++---- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/client/src/pages/SignUp.js b/client/src/pages/SignUp.js index ec417986..3346fac1 100644 --- a/client/src/pages/SignUp.js +++ b/client/src/pages/SignUp.js @@ -62,7 +62,7 @@ function SignUp() { return; } - if (!/^\d{9}$/.test(soonerId)) { + if (role=== "student" && !/^\d{9}$/.test(soonerId)) { Swal.fire({ icon: "error", title: "Invalid Sooner ID", @@ -77,7 +77,7 @@ function SignUp() { { fullName, ouEmail, - soonerId, + soonerId : role === "student" ? soonerId : "", password, semester, academicAdvisor: role === "student" ? academicAdvisor : "", @@ -99,6 +99,7 @@ function SignUp() { // Clear form setFullName(""); setOuEmail(""); + setSoonerId(""); setPassword(""); setConfirmPassword(""); setSemester(""); @@ -111,13 +112,22 @@ function SignUp() { } catch (error) { console.error("Error creating user:", error); - if (error.response && error.response.status === 400) { + if (error.response && error.response.status === 401) { Swal.fire({ icon: "error", title: "Email Already Exists", text: "The provided email ID is already registered. Try logging in.", }); - } else { + } + else if(role=== "student" && error.response && error.response.status === 402){ + Swal.fire({ + icon: "error", + title: "Sooner ID Already Exists", + text: "The provided Sooner ID is already registered.", + }); + } + else { + console.log("Error response:", error.response); Swal.fire({ icon: "error", title: "Something went wrong", @@ -267,7 +277,7 @@ function SignUp() { />
-
+ {role === "student" &&
-
+
}
diff --git a/server/models/TokenRequest.js b/server/models/TokenRequest.js index 78cb6b1e..d7b52670 100644 --- a/server/models/TokenRequest.js +++ b/server/models/TokenRequest.js @@ -52,7 +52,9 @@ const userTokenRequestSchema = new mongoose.Schema( }, soonerId: { type: String, - required: [true, 'Sooner ID is required'], + required: function () { + return this.role === 'student'; + }, unique: true, match: [/^\d{9}$/, 'Sooner ID must be exactly 9 digits'], }, diff --git a/server/routes/token.js b/server/routes/token.js index d7270f44..7587aa85 100644 --- a/server/routes/token.js +++ b/server/routes/token.js @@ -18,16 +18,22 @@ const hashToken = (token) => { router.post("/request", async (req, res) => { try { const { fullName, ouEmail, soonerId, password, semester, academicAdvisor, role } = req.body; - - if (!fullName || !ouEmail || !soonerId || !password || !semester) { + if (!fullName || !ouEmail || !password || !semester) { return res.status(400).json({ error: "All fields are required." }); } const existing = await TokenRequest.findOne({ ouEmail }); if (existing) { - return res.status(400).json({ error: "Token request already exists for this email." }); + return res.status(401).json({ error: "Token request already exists for this email." }); } + if(role==="student"){ + const existingSoonerId = await TokenRequest.findOne({ soonerId }); + if(existingSoonerId){ + return res.status(402).json({ error: "Token request already exists for this Sooner ID." }); + } + } + const plainToken = jwt.sign({ ouEmail }, JWT_SECRET, { expiresIn: "180d" }); const hashedToken = hashToken(plainToken); const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS); @@ -38,7 +44,7 @@ router.post("/request", async (req, res) => { const request = new TokenRequest({ fullName, ouEmail, - soonerId, + soonerId: role === "student" ? soonerId : "", password: hashedPassword, semester, role, From 15f6fa3f010e04690270d9f9d30d86f375c0881e Mon Sep 17 00:00:00 2001 From: Aakash452 Date: Sun, 20 Apr 2025 16:51:53 -0500 Subject: [PATCH 18/22] Student Dashboard logic updated --- client/src/pages/Home.js | 16 ++++++++++--- client/src/pages/StudentDashboard.jsx | 34 +++++++++++++++++++++++---- server/routes/studentRoutes.js | 9 ++++--- 3 files changed, 46 insertions(+), 13 deletions(-) diff --git a/client/src/pages/Home.js b/client/src/pages/Home.js index 331a14cb..49b83cd5 100644 --- a/client/src/pages/Home.js +++ b/client/src/pages/Home.js @@ -62,8 +62,8 @@ function Home() { if (response.ok) { const user = data.user; - - // Store only required fields + if(role === "student"){ + // Store only required fields const limitedUserInfo = { fullName: user.fullName, id: user._id, @@ -71,6 +71,16 @@ function Home() { }; localStorage.setItem("ipmsUser", JSON.stringify(limitedUserInfo)); + navigate("/student-dashboard"); + }else{ + Swal.fire({ + icon: "success", + title: "Login Successfull", + text: `Welcome , ${user.fullName}`, + }); + } + + // Swal.fire({ // icon: "success", @@ -78,7 +88,7 @@ function Home() { // text: `Welcome back, `, // }); - navigate("/student-dashboard"); + } else { Swal.fire({ icon: "error", diff --git a/client/src/pages/StudentDashboard.jsx b/client/src/pages/StudentDashboard.jsx index 0a276075..e3713ab0 100644 --- a/client/src/pages/StudentDashboard.jsx +++ b/client/src/pages/StudentDashboard.jsx @@ -8,7 +8,7 @@ const StudentDashboard = () => { const user = JSON.parse(localStorage.getItem("ipmsUser")); const backendUrl = process.env.REACT_APP_API_URL; const ouEmail = user?.email; - const [approvalStatus, setApprovalStatus] = useState("draft"); + const [approvalStatus, setApprovalStatus] = useState("not_submitted"); useEffect(() => { const fetchData = async () => { @@ -47,6 +47,12 @@ const StudentDashboard = () => {

Request Internship (FORM A1)

Track your internship journey

+ {approvalStatus === "not_submitted" && ( +

+ You have not submitted the form yet +

+ )} + {(approvalStatus === "submitted" || approvalStatus === "pending manual review") && (

@@ -62,14 +68,25 @@ const StudentDashboard = () => {