AdminCoursePage added..... admin routes protection added
This commit is contained in:
@@ -17,6 +17,7 @@ import TokenRefresher from "./components/TokenRefresher";
|
||||
import DepartmentConsolidated from "./Pages/DepartmentConsolidated";
|
||||
import PanelConsolidated from "./Pages/PanelConsolidated";
|
||||
import { AdminFacultyPage } from "./Pages/AdminFacultyPage";
|
||||
import AdminCoursePage from "./Pages/AdminCoursePage";
|
||||
|
||||
|
||||
function App() {
|
||||
@@ -37,6 +38,7 @@ function App() {
|
||||
<Route path="/departmentConsolidated" element={<PrivateRoute element={<DepartmentConsolidated />} />} />
|
||||
<Route path="/panelConsolidated" element={<PrivateRoute element={<PanelConsolidated/>} />} />
|
||||
<Route path="/AdminFacultyPage" element={<PrivateRoute element={<AdminFacultyPage/>} />} />
|
||||
<Route path="/AdminCoursePage" element={<PrivateRoute element={<AdminCoursePage/>} />} />
|
||||
</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 { toast, ToastContainer } from "react-toastify";
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
|
||||
export const AdminFacultyPage = () => {
|
||||
@@ -10,6 +11,7 @@ export const AdminFacultyPage = () => {
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [currentFaculty, setCurrentFaculty] = useState({
|
||||
facultyId: '',
|
||||
name: '',
|
||||
@@ -19,13 +21,51 @@ export const AdminFacultyPage = () => {
|
||||
courses: [''],
|
||||
});
|
||||
const [courses, setcourses] = useState({});
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Fetch all faculties
|
||||
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();
|
||||
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 res = await axios.get('http://localhost:8080/api/faculty');
|
||||
setFaculties(res.data);
|
||||
@@ -125,6 +165,10 @@ export const AdminFacultyPage = () => {
|
||||
faculty.name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return <p style={{ textAlign: 'center', marginTop: '50px', fontSize: '18px' }}>Loading...</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
@@ -211,7 +255,7 @@ export const AdminFacultyPage = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{currentFaculty.courses.map((course, index) => (
|
||||
<div key={index} style={courseContainerStyle}>
|
||||
|
||||
|
||||
@@ -98,6 +98,13 @@ const Navbar = () => {
|
||||
Faculty
|
||||
</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>
|
||||
<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;
|
||||
Reference in New Issue
Block a user