AdminCoursePage added..... admin routes protection added
This commit is contained in:
89
client/package-lock.json
generated
89
client/package-lock.json
generated
@@ -14,6 +14,7 @@
|
|||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
"jspdf": "^2.5.2",
|
"jspdf": "^2.5.2",
|
||||||
"jspdf-autotable": "^3.8.4",
|
"jspdf-autotable": "^3.8.4",
|
||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
@@ -6091,6 +6092,11 @@
|
|||||||
"node": ">= 0.4.0"
|
"node": ">= 0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/buffer-equal-constant-time": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
|
||||||
|
},
|
||||||
"node_modules/buffer-from": {
|
"node_modules/buffer-from": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
@@ -7528,6 +7534,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
|
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/ecdsa-sig-formatter": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ee-first": {
|
"node_modules/ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
@@ -12694,6 +12708,27 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jsonwebtoken": {
|
||||||
|
"version": "9.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
|
||||||
|
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"jws": "^3.2.2",
|
||||||
|
"lodash.includes": "^4.3.0",
|
||||||
|
"lodash.isboolean": "^3.0.3",
|
||||||
|
"lodash.isinteger": "^4.0.4",
|
||||||
|
"lodash.isnumber": "^3.0.3",
|
||||||
|
"lodash.isplainobject": "^4.0.6",
|
||||||
|
"lodash.isstring": "^4.0.1",
|
||||||
|
"lodash.once": "^4.0.0",
|
||||||
|
"ms": "^2.1.1",
|
||||||
|
"semver": "^7.5.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12",
|
||||||
|
"npm": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jspdf": {
|
"node_modules/jspdf": {
|
||||||
"version": "2.5.2",
|
"version": "2.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz",
|
||||||
@@ -12746,6 +12781,25 @@
|
|||||||
"pako": "~0.2.5"
|
"pako": "~0.2.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jwa": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-equal-constant-time": "1.0.1",
|
||||||
|
"ecdsa-sig-formatter": "1.0.11",
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jws": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
|
||||||
|
"dependencies": {
|
||||||
|
"jwa": "^1.4.1",
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/kareem": {
|
"node_modules/kareem": {
|
||||||
"version": "2.6.3",
|
"version": "2.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
|
||||||
@@ -12889,6 +12943,36 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
|
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.includes": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isboolean": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isinteger": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isnumber": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isplainobject": {
|
||||||
|
"version": "4.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||||
|
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isstring": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
|
||||||
|
},
|
||||||
"node_modules/lodash.memoize": {
|
"node_modules/lodash.memoize": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||||
@@ -12899,6 +12983,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
|
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.once": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
|
||||||
|
},
|
||||||
"node_modules/lodash.sortby": {
|
"node_modules/lodash.sortby": {
|
||||||
"version": "4.7.0",
|
"version": "4.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
"jspdf": "^2.5.2",
|
"jspdf": "^2.5.2",
|
||||||
"jspdf-autotable": "^3.8.4",
|
"jspdf-autotable": "^3.8.4",
|
||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import TokenRefresher from "./components/TokenRefresher";
|
|||||||
import DepartmentConsolidated from "./Pages/DepartmentConsolidated";
|
import DepartmentConsolidated from "./Pages/DepartmentConsolidated";
|
||||||
import PanelConsolidated from "./Pages/PanelConsolidated";
|
import PanelConsolidated from "./Pages/PanelConsolidated";
|
||||||
import { AdminFacultyPage } from "./Pages/AdminFacultyPage";
|
import { AdminFacultyPage } from "./Pages/AdminFacultyPage";
|
||||||
|
import AdminCoursePage from "./Pages/AdminCoursePage";
|
||||||
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
@@ -37,6 +38,7 @@ function App() {
|
|||||||
<Route path="/departmentConsolidated" element={<PrivateRoute element={<DepartmentConsolidated />} />} />
|
<Route path="/departmentConsolidated" element={<PrivateRoute element={<DepartmentConsolidated />} />} />
|
||||||
<Route path="/panelConsolidated" element={<PrivateRoute element={<PanelConsolidated/>} />} />
|
<Route path="/panelConsolidated" element={<PrivateRoute element={<PanelConsolidated/>} />} />
|
||||||
<Route path="/AdminFacultyPage" element={<PrivateRoute element={<AdminFacultyPage/>} />} />
|
<Route path="/AdminFacultyPage" element={<PrivateRoute element={<AdminFacultyPage/>} />} />
|
||||||
|
<Route path="/AdminCoursePage" element={<PrivateRoute element={<AdminCoursePage/>} />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
313
client/src/Pages/AdminCoursePage.jsx
Normal file
313
client/src/Pages/AdminCoursePage.jsx
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import axios from 'axios';
|
||||||
|
import Navbar from './Navbar';
|
||||||
|
import { toast, ToastContainer } from "react-toastify";
|
||||||
|
import 'react-toastify/dist/ReactToastify.css';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
export const AdminCoursePage = () => {
|
||||||
|
const [courses, setCourses] = useState([]);
|
||||||
|
const [showForm, setShowForm] = useState(false);
|
||||||
|
const [isEditMode, setIsEditMode] = useState(false);
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [currentCourse, setCurrentCourse] = useState({
|
||||||
|
courseId: '',
|
||||||
|
name: '',
|
||||||
|
department: '',
|
||||||
|
program: '',
|
||||||
|
scheme: '',
|
||||||
|
semester: '',
|
||||||
|
status: 'not submitted',
|
||||||
|
});
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
// Fetch all courses
|
||||||
|
useEffect(() => {
|
||||||
|
const checkAdmin = async () => {
|
||||||
|
try {
|
||||||
|
const res = await axios.get("http://localhost:8080/api/me", { withCredentials: true });
|
||||||
|
if (!res.data.isAdmin) {
|
||||||
|
handleUnauthorizedAccess();
|
||||||
|
} else {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Unauthorized access:", error);
|
||||||
|
handleUnauthorizedAccess();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchCourses();
|
||||||
|
checkAdmin();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleUnauthorizedAccess = async () => {
|
||||||
|
try {
|
||||||
|
toast.warning("Unauthorized access. Logging out...", { position: "top-center" });
|
||||||
|
|
||||||
|
// Attempt to log out
|
||||||
|
await axios.get("http://localhost:8080/auth/logout", { withCredentials: true });
|
||||||
|
|
||||||
|
// Delay redirection to show the toast message
|
||||||
|
setTimeout(() => {
|
||||||
|
navigate("/"); // Redirect to login
|
||||||
|
}, 1500);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error during unauthorized access handling:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Fetch all courses after confirming admin status
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isLoading) {
|
||||||
|
fetchCourses();
|
||||||
|
}
|
||||||
|
}, [isLoading]);
|
||||||
|
|
||||||
|
const fetchCourses = async () => {
|
||||||
|
try {
|
||||||
|
const res = await axios.get('http://localhost:8080/api/courses');
|
||||||
|
setCourses(res.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching courses: ", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add or Update Course
|
||||||
|
const handleAddOrUpdateCourse = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (isEditMode) {
|
||||||
|
await axios.put(`http://localhost:8080/api/courses/${currentCourse.courseId}`, currentCourse);
|
||||||
|
toast.success('Course updated successfully!', { position: 'top-center' });
|
||||||
|
} else {
|
||||||
|
await axios.post('http://localhost:8080/api/courses', currentCourse);
|
||||||
|
toast.success('Course added successfully!', { position: 'top-center' });
|
||||||
|
}
|
||||||
|
resetForm();
|
||||||
|
fetchCourses();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error adding/updating course:", error);
|
||||||
|
toast.error("An error occurred!", { position: 'top-center' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Delete Course
|
||||||
|
const handleDeleteCourse = async (courseId) => {
|
||||||
|
const confirm = window.confirm('⚠️ Are you sure you want to delete this course?');
|
||||||
|
if (confirm) {
|
||||||
|
try {
|
||||||
|
await axios.delete(`http://localhost:8080/api/courses/${courseId}`);
|
||||||
|
fetchCourses();
|
||||||
|
toast.success('Course deleted successfully!', { position: 'top-center' });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting course:", error);
|
||||||
|
toast.error("An error occurred!", { position: 'top-center' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open Form for Edit
|
||||||
|
const handleOpenEditForm = (course) => {
|
||||||
|
setCurrentCourse(course);
|
||||||
|
setIsEditMode(true);
|
||||||
|
setShowForm(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open Form for Add
|
||||||
|
const handleOpenAddForm = () => {
|
||||||
|
resetForm();
|
||||||
|
setIsEditMode(false);
|
||||||
|
setShowForm(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reset form
|
||||||
|
const resetForm = () => {
|
||||||
|
setCurrentCourse({
|
||||||
|
courseId: '',
|
||||||
|
name: '',
|
||||||
|
department: '',
|
||||||
|
program: '',
|
||||||
|
scheme: '',
|
||||||
|
semester: '',
|
||||||
|
status: 'not submitted',
|
||||||
|
});
|
||||||
|
setShowForm(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredCourses = courses.filter((course) =>
|
||||||
|
course.name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <p style={{ textAlign: 'center', marginTop: '50px', fontSize: '18px' }}>Loading...</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Navbar />
|
||||||
|
<ToastContainer />
|
||||||
|
<div style={{ fontFamily: 'Arial, sans-serif', backgroundColor: '#f4f6f9', padding: '20px' }}>
|
||||||
|
<h2 style={{ textAlign: 'center', color: '#333', marginBottom: '20px' }}>Course Management</h2>
|
||||||
|
|
||||||
|
<div style={{ textAlign: 'center', marginBottom: '20px' }}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search course by name..."
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
style={inputStyle}
|
||||||
|
/>
|
||||||
|
<button onClick={handleOpenAddForm} style={addButtonStyle}>
|
||||||
|
+ Add New Course
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showForm && (
|
||||||
|
<div style={formStyle}>
|
||||||
|
<h3>{isEditMode ? 'Edit Course' : 'Add New Course'}</h3>
|
||||||
|
<form onSubmit={handleAddOrUpdateCourse}>
|
||||||
|
<input
|
||||||
|
placeholder="Course ID"
|
||||||
|
value={currentCourse.courseId}
|
||||||
|
onChange={(e) => setCurrentCourse({ ...currentCourse, courseId: e.target.value })}
|
||||||
|
style={inputStyle}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
placeholder="Course Name"
|
||||||
|
value={currentCourse.name}
|
||||||
|
onChange={(e) => setCurrentCourse({ ...currentCourse, name: e.target.value })}
|
||||||
|
style={inputStyle}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
placeholder="Department"
|
||||||
|
value={currentCourse.department}
|
||||||
|
onChange={(e) => setCurrentCourse({ ...currentCourse, department: e.target.value })}
|
||||||
|
style={inputStyle}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
placeholder="Program"
|
||||||
|
value={currentCourse.program}
|
||||||
|
onChange={(e) => setCurrentCourse({ ...currentCourse, program: e.target.value })}
|
||||||
|
style={inputStyle}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
placeholder="Scheme"
|
||||||
|
value={currentCourse.scheme}
|
||||||
|
onChange={(e) => setCurrentCourse({ ...currentCourse, scheme: e.target.value })}
|
||||||
|
style={inputStyle}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
placeholder="Semester"
|
||||||
|
value={currentCourse.semester}
|
||||||
|
onChange={(e) => setCurrentCourse({ ...currentCourse, semester: e.target.value })}
|
||||||
|
style={inputStyle}
|
||||||
|
/>
|
||||||
|
<select
|
||||||
|
value={currentCourse.status}
|
||||||
|
onChange={(e) => setCurrentCourse({ ...currentCourse, status: e.target.value })}
|
||||||
|
style={inputStyle}
|
||||||
|
>
|
||||||
|
<option value="submitted">Submitted</option>
|
||||||
|
<option value="not submitted">Not Submitted</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<div >
|
||||||
|
<button type="submit" style={submitButtonStyle}>
|
||||||
|
{isEditMode ? 'Update Course' : 'Add Course'}
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => setShowForm(false)} style={cancelButtonStyle}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* TABLE DATA */}
|
||||||
|
<table style={tableStyle}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Course ID</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Department</th>
|
||||||
|
<th>Program</th>
|
||||||
|
<th>Scheme</th>
|
||||||
|
<th>Semester</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{filteredCourses.length > 0 ? (
|
||||||
|
filteredCourses.map((c) => (
|
||||||
|
<tr key={c.courseId}>
|
||||||
|
<td>{c.courseId}</td>
|
||||||
|
<td>{c.name}</td>
|
||||||
|
<td>{c.department}</td>
|
||||||
|
<td>{c.program}</td>
|
||||||
|
<td>{c.scheme}</td>
|
||||||
|
<td>{c.semester}</td>
|
||||||
|
<td>{c.status}</td>
|
||||||
|
<td>
|
||||||
|
<button onClick={() => handleOpenEditForm(c)} style={editButtonStyle}>Edit</button>
|
||||||
|
<button onClick={() => handleDeleteCourse(c.courseId)} style={deleteButtonStyle}>Delete</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td colSpan="5" style={{ textAlign: 'center', color: 'red' }}>
|
||||||
|
No courses found
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const tableStyle = {
|
||||||
|
width: '100%',
|
||||||
|
borderCollapse: 'collapse',
|
||||||
|
marginTop: '20px',
|
||||||
|
};
|
||||||
|
|
||||||
|
const inputStyle = {
|
||||||
|
padding: '10px',
|
||||||
|
width: '250px',
|
||||||
|
borderRadius: '5px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
fontSize: '16px',
|
||||||
|
marginRight: '10px',
|
||||||
|
};
|
||||||
|
|
||||||
|
const formStyle = {
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
padding: '20px',
|
||||||
|
borderRadius: '10px',
|
||||||
|
maxWidth: '500px',
|
||||||
|
margin: '0 auto',
|
||||||
|
boxShadow: '0 5px 20px rgba(0,0,0,0.1)',
|
||||||
|
};
|
||||||
|
|
||||||
|
const addButtonStyle = {
|
||||||
|
backgroundColor: '#4CAF50',
|
||||||
|
color: '#fff',
|
||||||
|
padding: '10px 15px',
|
||||||
|
borderRadius: '5px',
|
||||||
|
border: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitButtonStyle = { backgroundColor: '#4CAF50', color: 'white', padding: '10px 20px', borderRadius: '5px' };
|
||||||
|
const cancelButtonStyle = { backgroundColor: '#f44336', color: 'white', padding: '10px 20px', borderRadius: '5px' };
|
||||||
|
const editButtonStyle = { backgroundColor: '#4CAF50', color: 'white', padding: '5px 10px', borderRadius: '5px' };
|
||||||
|
const deleteButtonStyle = { backgroundColor: '#f44336', color: 'white', padding: '5px 10px', borderRadius: '5px' };
|
||||||
|
|
||||||
|
export default AdminCoursePage;
|
||||||
@@ -3,6 +3,7 @@ import axios from 'axios';
|
|||||||
import Navbar from './Navbar';
|
import Navbar from './Navbar';
|
||||||
import { toast, ToastContainer } from "react-toastify";
|
import { toast, ToastContainer } from "react-toastify";
|
||||||
import 'react-toastify/dist/ReactToastify.css';
|
import 'react-toastify/dist/ReactToastify.css';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
|
||||||
export const AdminFacultyPage = () => {
|
export const AdminFacultyPage = () => {
|
||||||
@@ -10,6 +11,7 @@ export const AdminFacultyPage = () => {
|
|||||||
const [showForm, setShowForm] = useState(false);
|
const [showForm, setShowForm] = useState(false);
|
||||||
const [isEditMode, setIsEditMode] = useState(false);
|
const [isEditMode, setIsEditMode] = useState(false);
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [currentFaculty, setCurrentFaculty] = useState({
|
const [currentFaculty, setCurrentFaculty] = useState({
|
||||||
facultyId: '',
|
facultyId: '',
|
||||||
name: '',
|
name: '',
|
||||||
@@ -19,13 +21,51 @@ export const AdminFacultyPage = () => {
|
|||||||
courses: [''],
|
courses: [''],
|
||||||
});
|
});
|
||||||
const [courses, setcourses] = useState({});
|
const [courses, setcourses] = useState({});
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
// Fetch all faculties
|
// Fetch all faculties
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const checkAdmin = async () => {
|
||||||
|
try {
|
||||||
|
const res = await axios.get("http://localhost:8080/api/me", { withCredentials: true });
|
||||||
|
console.log(res.data);
|
||||||
|
if (!res.data.isAdmin) {
|
||||||
|
handleUnauthorizedAccess();
|
||||||
|
} else {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Unauthorized access:", error);
|
||||||
|
handleUnauthorizedAccess();
|
||||||
|
}
|
||||||
|
};
|
||||||
fetchFaculties();
|
fetchFaculties();
|
||||||
fetchCourses();
|
fetchCourses();
|
||||||
|
checkAdmin();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleUnauthorizedAccess = async () => {
|
||||||
|
try {
|
||||||
|
toast.warning("Unauthorized access. Logging out...", { position: "top-center" });
|
||||||
|
|
||||||
|
// Attempt to log out
|
||||||
|
await axios.get("http://localhost:8080/auth/logout", { withCredentials: true });
|
||||||
|
|
||||||
|
// Delay redirection to show the toast message
|
||||||
|
setTimeout(() => {
|
||||||
|
navigate("/"); // Redirect to login
|
||||||
|
}, 1500);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error during unauthorized access handling:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isLoading) {
|
||||||
|
fetchCourses();
|
||||||
|
}
|
||||||
|
}, [isLoading]);
|
||||||
|
|
||||||
const fetchFaculties = async () => {
|
const fetchFaculties = async () => {
|
||||||
const res = await axios.get('http://localhost:8080/api/faculty');
|
const res = await axios.get('http://localhost:8080/api/faculty');
|
||||||
setFaculties(res.data);
|
setFaculties(res.data);
|
||||||
@@ -125,6 +165,10 @@ export const AdminFacultyPage = () => {
|
|||||||
faculty.name.toLowerCase().includes(searchQuery.toLowerCase())
|
faculty.name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <p style={{ textAlign: 'center', marginTop: '50px', fontSize: '18px' }}>Loading...</p>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
|
|||||||
@@ -98,6 +98,13 @@ const Navbar = () => {
|
|||||||
Faculty
|
Faculty
|
||||||
</NavLink>
|
</NavLink>
|
||||||
)}
|
)}
|
||||||
|
{isAdmin && (
|
||||||
|
<NavLink to="/AdminCoursePage" style={NavlinkStyle}
|
||||||
|
onMouseEnter={(e) => e.target.style.backgroundColor = "#660000"}
|
||||||
|
onMouseLeave={(e) => e.target.style.backgroundColor = "transparent"}>
|
||||||
|
Courses
|
||||||
|
</NavLink>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button className="logout-button" onClick={handleLogout} style={{
|
<button className="logout-button" onClick={handleLogout} style={{
|
||||||
|
|||||||
22
client/src/components/verifyAdmin.js
Normal file
22
client/src/components/verifyAdmin.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
const jwt = require("jsonwebtoken");
|
||||||
|
|
||||||
|
const verifyAdmin = (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const token = req.cookies.token; // Ensure you are using cookies for auth
|
||||||
|
if (!token) {
|
||||||
|
return res.status(401).json({ message: "Access denied. No token provided." });
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||||
|
if (decoded.role !== "admin") {
|
||||||
|
return res.status(403).json({ message: "Access denied. Admins only." });
|
||||||
|
}
|
||||||
|
|
||||||
|
req.user = decoded; // Attach user data to the request
|
||||||
|
next();
|
||||||
|
} catch (error) {
|
||||||
|
res.status(401).json({ message: "Invalid or expired token" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = verifyAdmin;
|
||||||
@@ -121,4 +121,18 @@ router.post("/reset-password", async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// router.get("/me", (req, res) => {
|
||||||
|
// try {
|
||||||
|
// const token = req.cookies.token;
|
||||||
|
// console.log("token recieved",token);
|
||||||
|
// if (!token) return res.status(401).json({ message: "Unauthorized" });
|
||||||
|
|
||||||
|
// const user = jwt.verify(token, process.env.JWT_SECRET);
|
||||||
|
// res.json(user);
|
||||||
|
// } catch (error) {
|
||||||
|
// res.status(401).json({ message: "Invalid token" });
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const express = require("express");
|
const express = require("express");
|
||||||
const Course = require("../models/Course");
|
const Course = require("../models/Course");
|
||||||
|
const verifyAdmin = require("../../client/src/components/verifyAdmin");
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ router.get("/:id", async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Update course status route
|
// Update course status route
|
||||||
router.patch("/api/courses/:courseId", async (req, res) => {
|
router.patch(":courseId", async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { courseId } = req.params; // Extract courseId from params
|
const { courseId } = req.params; // Extract courseId from params
|
||||||
const { status } = req.body; // Extract status from body
|
const { status } = req.body; // Extract status from body
|
||||||
@@ -69,5 +70,67 @@ router.patch("/api/courses/:courseId", async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//update a course
|
||||||
|
router.put("/:courseId", verifyAdmin, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const updatedCourse = await Course.findOneAndUpdate(
|
||||||
|
{ courseId: req.params.courseId },
|
||||||
|
req.body,
|
||||||
|
{ new: true }
|
||||||
|
);
|
||||||
|
if (!updatedCourse) {
|
||||||
|
return res.status(404).json({ error: "Course not found" });
|
||||||
|
}
|
||||||
|
res.json(updatedCourse);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating course:", error);
|
||||||
|
res.status(500).json({ error: "Failed to update course" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//delete
|
||||||
|
router.delete("/:courseId", verifyAdmin, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const deletedCourse = await Course.findOneAndDelete({ courseId: req.params.courseId });
|
||||||
|
if (!deletedCourse) {
|
||||||
|
return res.status(404).json({ error: "Course not found" });
|
||||||
|
}
|
||||||
|
res.json({ message: "Course deleted successfully" });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting course:", error);
|
||||||
|
res.status(500).json({ error: "Failed to delete course" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// add a new course
|
||||||
|
router.post("/", verifyAdmin, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { courseId, name, department, program, scheme, semester, status } = req.body;
|
||||||
|
|
||||||
|
// Check if a course with the same courseId already exists
|
||||||
|
const existingCourse = await Course.findOne({ courseId });
|
||||||
|
if (existingCourse) {
|
||||||
|
return res.status(400).json({ error: "Course ID already exists" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const newCourse = new Course({
|
||||||
|
courseId,
|
||||||
|
name,
|
||||||
|
department,
|
||||||
|
program,
|
||||||
|
scheme,
|
||||||
|
semester,
|
||||||
|
status
|
||||||
|
});
|
||||||
|
|
||||||
|
await newCourse.save();
|
||||||
|
res.status(201).json({ message: "Course added successfully", course: newCourse });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error adding course:", error);
|
||||||
|
res.status(500).json({ error: "Failed to add course" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const optionsRoutes = require("./routes/optionsRoutes");
|
|||||||
const consolidatedRoutes = require("./routes/consolidatedRoutes");
|
const consolidatedRoutes = require("./routes/consolidatedRoutes");
|
||||||
const emailRoutes = require("./routes/emailRoutes");
|
const emailRoutes = require("./routes/emailRoutes");
|
||||||
const Course = require("./models/Course");
|
const Course = require("./models/Course");
|
||||||
|
const User = require("./models/User");
|
||||||
|
|
||||||
// MongoDB Connection
|
// MongoDB Connection
|
||||||
mongoose
|
mongoose
|
||||||
@@ -195,6 +196,7 @@ app.get("/api/auth-check", (req, res) => {
|
|||||||
const token = req.cookies.token; // Retrieve the httpOnly cookie
|
const token = req.cookies.token; // Retrieve the httpOnly cookie
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
|
console.log("Tehehe");
|
||||||
return res.status(401).json({ message: "Unauthorized" });
|
return res.status(401).json({ message: "Unauthorized" });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,6 +208,30 @@ app.get("/api/auth-check", (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get("/api/me", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const token = req.cookies.token; // ✅ Get token from request cookies
|
||||||
|
if (!token) return res.status(401).json({ message: "Unauthorized - No Token" });
|
||||||
|
|
||||||
|
const decoded = jwt.verify(token, process.env.JWT_SECRET); // ✅ Verify token
|
||||||
|
|
||||||
|
// ✅ Fetch user from DB to ensure latest `isAdmin` value
|
||||||
|
const user = await User.findById(decoded.userId);
|
||||||
|
if (!user) return res.status(404).json({ message: "User not found" });
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
userId: user._id,
|
||||||
|
isAdmin: user.isAdmin, // ✅ Return actual `isAdmin` value
|
||||||
|
exp: decoded.exp,
|
||||||
|
iat: decoded.iat
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("JWT Verification Error:", error.message);
|
||||||
|
res.status(401).json({ message: "Invalid token" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// User Profile Route
|
// User Profile Route
|
||||||
app.get("/api/user/profile", async (req, res) => {
|
app.get("/api/user/profile", async (req, res) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user