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..8db7edeb
--- /dev/null
+++ b/client/src/pages/TokenRenewal.jsx
@@ -0,0 +1,59 @@
+import React, { useEffect, useState } from 'react';
+import { useParams } from 'react-router-dom';
+
+const TokenRenewal = () => {
+ const { token } = useParams();
+ const [responseMessage, setResponseMessage] = useState('');
+ 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);
+ }
+ };
+
+ if (token) {
+ renewToken();
+ } else {
+ setResponseMessage({ text: '❌ Error: No token found in the URL.', success: false });
+ setLoading(false);
+ }
+ }, [token]);
+
+ return (
+
+
Token Renewal
+ {loading ? (
+
⏳ Processing your token renewal...
+ ) : (
+
+ {responseMessage.text}
+
+ )}
+
+ );
+};
+
+export default TokenRenewal;
diff --git a/client/src/router.js b/client/src/router.js
index c1bfe4ef..db8aaf77 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/: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..ebdc6374
--- /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();
+
+const checkAndSendReminders = async () => {
+ 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..e9bbc8bb 100644
--- a/server/routes/token.js
+++ b/server/routes/token.js
@@ -233,4 +233,42 @@ router.delete("/deactivate", async (req, res) => {
}
});
+router.post('/renew', async (req, res) => {
+ const { token } = req.body;
+
+ try {
+
+ 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.' });
+ }
+
+ 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.setMonth(currentDate.getMonth() + 6));
+ 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()}.
`,
+ });
+
+ console.log('Token successfully renewed!')
+ 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;