diff --git a/backend/controllers/studentController.js b/backend/controllers/studentController.js
index fcb209b..57614aa 100644
--- a/backend/controllers/studentController.js
+++ b/backend/controllers/studentController.js
@@ -155,6 +155,18 @@ export const deleteStudent = asyncHandler(async (req, res) => {
const student = await Student.findById(req.params.id);
if (student) {
+
+ // Find associated User and delete it
+ const associatedUser = await User.findOne({ email: student.email });
+ const associatedSchoolFees = await SchoolFees.findOne({student:student._id})
+
+ if (associatedUser) {
+ await associatedUser.remove();
+ }
+ if (associatedUser) {
+ await associatedSchoolFees.remove()
+ }
+
await student.remove();
res.json({ success: true, message: 'Student removed' });
} else {
diff --git a/backend/controllers/userController.js b/backend/controllers/userController.js
index c4dcbab..c42f0f2 100644
--- a/backend/controllers/userController.js
+++ b/backend/controllers/userController.js
@@ -310,6 +310,35 @@ const updateProfile = asyncHandler(async (req, res) => {
}
});
+// @desc Toggle user activation status
+// @route PUT /api/users/:id/toggle-active
+// @access Private/Admin
+const toggleUserActivation = asyncHandler(async (req, res) => {
+ const userId = req.params.id;
+
+ const user = await User.findById(userId);
+
+ if (!user) {
+ res.status(404);
+ throw new Error('User not found');
+ }
+
+ user.isActive = !user.isActive; // Toggle isActive status
+
+ const updatedUser = await user.save();
+
+ res.status(200).json({
+ _id: updatedUser._id,
+ firstName: updatedUser.firstName,
+ secondName: updatedUser.secondName,
+ email: updatedUser.email,
+ isAdmin: updatedUser.isAdmin,
+ isActive: updatedUser.isActive,
+ userType: updatedUser.userType,
+ verified: updatedUser.verified,
+ });
+});
+
export {
authUser,
registerUser,
@@ -317,5 +346,6 @@ export {
verifyResetPassword,
setNewPassword,
getAllUsers,
- updateProfile
+ updateProfile,
+ toggleUserActivation
}
diff --git a/backend/models/studentModel.js b/backend/models/studentModel.js
index 39ce3d6..456ff1d 100644
--- a/backend/models/studentModel.js
+++ b/backend/models/studentModel.js
@@ -80,6 +80,8 @@ const studentSchema = mongoose.Schema(
}
);
+
+
studentSchema.methods.matchPassword = async function (enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password);
};
diff --git a/backend/routes/studentRoutes.js b/backend/routes/studentRoutes.js
index 9ca2cb9..bf7b169 100644
--- a/backend/routes/studentRoutes.js
+++ b/backend/routes/studentRoutes.js
@@ -6,7 +6,7 @@ import { admitStudent, deleteStudent, getAllStudents, studentsByCourse } from '.
student_router.route('/').post(protect, admin,admitStudent)
student_router.route('/').get(protect,getAllStudents)
-student_router.route('/').delete(protect,deleteStudent)
+student_router.route('/:id').delete(protect,deleteStudent)
student_router.route('/course/:id').get(protect,studentsByCourse)
export default student_router
diff --git a/backend/routes/userRoutes.js b/backend/routes/userRoutes.js
index 757222c..17a8185 100644
--- a/backend/routes/userRoutes.js
+++ b/backend/routes/userRoutes.js
@@ -7,7 +7,8 @@ import {
authUser,
sendRestPassword,
getAllUsers,
- updateProfile
+ updateProfile,
+ toggleUserActivation
} from '../controllers/userController.js'
import { protect, admin } from '../middleware/authMiddleware.js'
@@ -17,7 +18,7 @@ router.route('/').get(getAllUsers)
router.route('/reset-password').post(sendRestPassword)
router.route('/change-password/:id/:token').post(setNewPassword)
router.route('/update-profile').put(protect,updateProfile)
-
+router.route('/:id/toggle-active').put(protect, admin, toggleUserActivation);
diff --git a/frontend/cypress/e2e/login.cy.js b/frontend/cypress/e2e/login.cy.js
deleted file mode 100644
index a0de60a..0000000
--- a/frontend/cypress/e2e/login.cy.js
+++ /dev/null
@@ -1,81 +0,0 @@
-describe('Login Test Suite', () => {
- beforeEach(() => {
-
- cy.visit('http://localhost:5173');
- });
-
- it('should perform the login action successfully', () => {
-
-
- cy.get('[data-cy="email"]').type('devngecu@gmail.com');
- cy.get('[data-cy="password"]').type('I@mrich254');
-
- cy.get('[data-cy="login-btn"]').click();
- cy.wait(2000);
- cy.location('pathname').should('eq', '/')
-
- });
-
- it('Returns an error if email or password is empty', () => {
-
-
- cy.get('[data-cy="email"]').type('caleb');
- // cy.get('[data-cy="password"]').type('');
-
- cy.get('[data-cy="login-btn"]').click();
- cy.wait(500);
- cy.contains('Form is invalid')
-
- });
-
- it('Returns an error if email or password is missing', () => {
-
-
- // cy.get('[data-cy="email"]').type('caleb');
- // cy.get('[data-cy="password"]').type('');
-
- cy.get('[data-cy="login-btn"]').click();
- cy.wait(500);
- cy.contains('Form is invalid')
-
- });
-
- it('Returns an error if email is not in database', () => {
-
-
- cy.get('[data-cy="email"]').type('kinuthia');
- cy.get('[data-cy="password"]').type('12345678');
-
- cy.get('[data-cy="login-btn"]').click();
- cy.wait(500);
- cy.contains('User not found')
-
- });
-
- it('Handles incorrect password scenario', () => {
-
-
- cy.get('[data-cy="email"]').type('caleb@gmail.com');
- cy.get('[data-cy="password"]').type('wrongPassword');
-
- cy.get('[data-cy="login-btn"]').click();
- cy.wait(500);
- cy.contains('Incorrect password')
-
- });
-
- it('Check deactivated account', () => {
-
-
- cy.get('[data-cy="email"]').type('caleb');
- cy.get('[data-cy="password"]').type('12345678');
-
- cy.get('[data-cy="login-btn"]').click();
- cy.wait(2000);
-
-
- cy.contains('Account deactivated, please contact admin')
-
-
- });
- });
\ No newline at end of file
diff --git a/frontend/cypress/e2e/spec.cy.js b/frontend/cypress/e2e/spec.cy.js
deleted file mode 100644
index 322992c..0000000
--- a/frontend/cypress/e2e/spec.cy.js
+++ /dev/null
@@ -1,5 +0,0 @@
-describe('template spec', () => {
- it('passes', () => {
- cy.visit('https://example.cypress.io')
- })
-})
\ No newline at end of file
diff --git a/frontend/cypress/fixtures/example.json b/frontend/cypress/fixtures/example.json
deleted file mode 100644
index 02e4254..0000000
--- a/frontend/cypress/fixtures/example.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "name": "Using fixtures to represent data",
- "email": "hello@cypress.io",
- "body": "Fixtures are a great way to mock data for responses to routes"
-}
diff --git a/frontend/cypress/support/commands.js b/frontend/cypress/support/commands.js
deleted file mode 100644
index 66ea16e..0000000
--- a/frontend/cypress/support/commands.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// ***********************************************
-// This example commands.js shows you how to
-// create various custom commands and overwrite
-// existing commands.
-//
-// For more comprehensive examples of custom
-// commands please read more here:
-// https://on.cypress.io/custom-commands
-// ***********************************************
-//
-//
-// -- This is a parent command --
-// Cypress.Commands.add('login', (email, password) => { ... })
-//
-//
-// -- This is a child command --
-// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
-//
-//
-// -- This is a dual command --
-// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
-//
-//
-// -- This will overwrite an existing command --
-// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
\ No newline at end of file
diff --git a/frontend/cypress/support/e2e.js b/frontend/cypress/support/e2e.js
deleted file mode 100644
index 0e7290a..0000000
--- a/frontend/cypress/support/e2e.js
+++ /dev/null
@@ -1,20 +0,0 @@
-// ***********************************************************
-// This example support/e2e.js is processed and
-// loaded automatically before your test files.
-//
-// This is a great place to put global configuration and
-// behavior that modifies Cypress.
-//
-// You can change the location of this file or turn off
-// automatically serving support files with the
-// 'supportFile' configuration option.
-//
-// You can read more here:
-// https://on.cypress.io/configuration
-// ***********************************************************
-
-// Import commands.js using ES2015 syntax:
-import './commands'
-
-// Alternatively you can use CommonJS syntax:
-// require('./commands')
\ No newline at end of file
diff --git a/frontend/src/actions/studentActions.jsx b/frontend/src/actions/studentActions.jsx
index 199fde2..86d4277 100644
--- a/frontend/src/actions/studentActions.jsx
+++ b/frontend/src/actions/studentActions.jsx
@@ -10,6 +10,9 @@ import {
STUDENT_DETAILS_REQUEST,
STUDENT_DETAILS_SUCCESS,
STUDENT_DETAILS_FAIL,
+ STUDENT_DELETE_REQUEST,
+ STUDENT_DELETE_SUCCESS,
+ STUDENT_DELETE_FAIL,
} from '../constants/studentConstants';
import { useHistory } from 'react-router-dom';
const base_url = `http://localhost:5000/api/students`;
diff --git a/frontend/src/actions/userActions.jsx b/frontend/src/actions/userActions.jsx
index d957f7d..b5649df 100644
--- a/frontend/src/actions/userActions.jsx
+++ b/frontend/src/actions/userActions.jsx
@@ -30,7 +30,10 @@ import {
USER_CHANGE_PASSWORD_REQUEST,
USER_CHANGE_PASSWORD_SUCCESS,
- USER_CHANGE_PASSWORD_FAIL
+ USER_CHANGE_PASSWORD_FAIL,
+ USER_TOGGLE_ACTIVE_REQUEST,
+ USER_TOGGLE_ACTIVE_SUCCESS,
+ USER_TOGGLE_ACTIVE_FAIL,
} from '../constants/userConstants'
import { useHistory } from 'react-router-dom';
@@ -303,4 +306,36 @@ export const listUsers = () => async (dispatch, getState) => {
: error.message,
});
}
+};
+
+export const toggleUserActive = (userId) => async (dispatch, getState) => {
+ try {
+ dispatch({ type: USER_TOGGLE_ACTIVE_REQUEST });
+
+ const {
+ userLogin: { userInfo },
+ } = getState();
+
+ const config = {
+ headers: {
+ Authorization: `Bearer ${userInfo.token}`,
+ },
+ };
+
+
+ const { data } = await axios.put(`${base_url}/${userId}/toggle-active`, {}, config);
+
+ dispatch({
+ type: USER_TOGGLE_ACTIVE_SUCCESS,
+ payload: data,
+ });
+ } catch (error) {
+ dispatch({
+ type: USER_TOGGLE_ACTIVE_FAIL,
+ payload:
+ error.response && error.response.data.message
+ ? error.response.data.message
+ : error.message,
+ });
+ }
};
\ No newline at end of file
diff --git a/frontend/src/constants/userConstants.jsx b/frontend/src/constants/userConstants.jsx
index 63b2c97..d85cac5 100644
--- a/frontend/src/constants/userConstants.jsx
+++ b/frontend/src/constants/userConstants.jsx
@@ -37,4 +37,8 @@ export const USER_RESET_PASSWORD_FAIL = 'USER_RESET_PASSWORD_FAIL'
export const USER_CHANGE_PASSWORD_REQUEST = 'USER_CHANGE_PASSWORD_REQUEST'
export const USER_CHANGE_PASSWORD_SUCCESS = 'USER_CHANGE_PASSWORD_SUCCESS'
-export const USER_CHANGE_PASSWORD_FAIL = 'USER_CHANGE_PASSWORD_FAIL'
\ No newline at end of file
+export const USER_CHANGE_PASSWORD_FAIL = 'USER_CHANGE_PASSWORD_FAIL'
+
+export const USER_TOGGLE_ACTIVE_REQUEST = 'USER_TOGGLE_ACTIVE_REQUEST';
+export const USER_TOGGLE_ACTIVE_SUCCESS = 'USER_TOGGLE_ACTIVE_SUCCESS';
+export const USER_TOGGLE_ACTIVE_FAIL = 'USER_TOGGLE_ACTIVE_FAIL';
diff --git a/frontend/src/reducers/userReducers.jsx b/frontend/src/reducers/userReducers.jsx
index 1ed8c52..29ef806 100644
--- a/frontend/src/reducers/userReducers.jsx
+++ b/frontend/src/reducers/userReducers.jsx
@@ -30,7 +30,10 @@ import {
USER_RESET_PASSWORD_FAIL,
USER_CHANGE_PASSWORD_REQUEST,
USER_CHANGE_PASSWORD_SUCCESS,
- USER_CHANGE_PASSWORD_FAIL
+ USER_CHANGE_PASSWORD_FAIL,
+ USER_TOGGLE_ACTIVE_REQUEST,
+ USER_TOGGLE_ACTIVE_SUCCESS,
+ USER_TOGGLE_ACTIVE_FAIL,
} from '../constants/userConstants'
export const userLoginReducer = (state = {}, action) => {
@@ -163,4 +166,20 @@ export const userChangePasswordReducer = (state = {}, action) => {
default:
return state
}
-}
\ No newline at end of file
+}
+
+export const userToggleActiveReducer = (state = {}, action) => {
+ switch (action.type) {
+ case USER_TOGGLE_ACTIVE_REQUEST:
+ return { loading: true };
+
+ case USER_TOGGLE_ACTIVE_SUCCESS:
+ return { loading: false, success: true, user: action.payload };
+
+ case USER_TOGGLE_ACTIVE_FAIL:
+ return { loading: false, error: action.payload };
+
+ default:
+ return state;
+ }
+};
\ No newline at end of file
diff --git a/frontend/src/screens/Admin/AllStudentsScreen.jsx b/frontend/src/screens/Admin/AllStudentsScreen.jsx
index 4c51a58..e5207a7 100644
--- a/frontend/src/screens/Admin/AllStudentsScreen.jsx
+++ b/frontend/src/screens/Admin/AllStudentsScreen.jsx
@@ -3,13 +3,12 @@ import { Table, Form, Button, Row, Col, ListGroup, Container, Card, Pagination }
import { useDispatch, useSelector } from 'react-redux';
import Message from '../../components/Message';
import Loader from '../../components/Loader';
-import { Link, useLocation } from 'react-router-dom';
-import { useRouteMatch } from 'react-router-dom';
-import { useHistory } from 'react-router-dom/cjs/react-router-dom.min';
-import { Collapse } from 'antd';
+import { useLocation } from 'react-router-dom';
+
import Sidebar from './components/Sidebar'
import { deleteStudent, listStudents } from '../../actions/studentActions';
import Topbar from './components/Topbar';
+import { Modal } from 'antd'
@@ -24,12 +23,12 @@ const AllStudents = () => {
const dispatch = useDispatch();
const [isModalOpen, setIsModalOpen] = useState(false);
- const [lecturerData, setLecturerData] = useState(null);
+ const [studentData, setStudentData] = useState(null);
const [searchQuery, setSearchQuery] = useState('');
-
-
- const showModal = (lecturerData) => {
- setLecturerData(lecturerData); // Assuming you have a state variable to store lecturer data
+ const [currentPage, setCurrentPage] = useState(1);
+ const itemsPerPage = 10;
+ const showModal = (data) => {
+ setStudentData(data);
setIsModalOpen(true);
};
@@ -58,12 +57,12 @@ const AllStudents = () => {
const studentList = useSelector((state) => state.studentList);
const { loading, error, students } = studentList;
- const lecturerDelete = useSelector((state) => state.lecturerDelete)
+ const studentDelete = useSelector((state) => state.studentDelete)
const {
loading: loadingDelete,
error: errorDelete,
success: successDelete,
- } = lecturerDelete
+ } = studentDelete
useEffect(() => {
dispatch(listStudents());
@@ -81,40 +80,62 @@ const AllStudents = () => {
const generateStudentData = () => {
-
- const filteredStudents = students.filter((student) =>
- student.firstName.toLowerCase().includes(searchQuery.toLowerCase()) ||
- student.lastName.toLowerCase().includes(searchQuery.toLowerCase()) ||
- student.email.toLowerCase().includes(searchQuery.toLowerCase())
- // Add more fields as needed for searching
- );
-
- return filteredStudents.map((student) => (
-
-
- {student.firstName} {student.lastName}
- {student.gender}
- {student.course.name}
- {student.email}
- {student.dob}
-
-
-
-
-
-
-
+
+ {student.firstName} {student.lastName}
+{student.gender}
+{student.course.name}
+{student.email}
+{student.dob}
+
+
+
+
+
+
+