Skip to content

Commit

Permalink
Add Profile page and icon to NavBar
Browse files Browse the repository at this point in the history
  • Loading branch information
Sagargupta16 committed Feb 2, 2024
1 parent 8563345 commit 8e909bd
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 10 deletions.
6 changes: 6 additions & 0 deletions client/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import NavBar from './components/NavBar/NavBar';
import Authentication from './pages/Auth/Authentication';
import Companies from './pages/Companies/Companies';
import Home from './pages/Home/Home';
import Profile from './pages/Profile/Profile';
import Stats from './pages/Stats/Stats';
import Students from './pages/Students/Students';
import Teams from './pages/Teams/Teams';
Expand Down Expand Up @@ -55,6 +56,11 @@ const App = () => {
element: <Companies />,
loader: checkAuthAction
},
{
path: 'profile',
element: <Profile />,
loader: checkAuthAction
},
{
path: 'auth',
element: <Authentication />
Expand Down
6 changes: 6 additions & 0 deletions client/src/components/NavBar/NavBar.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import axios from 'axios';
import { useEffect, useState } from 'react';
import { BiStats } from 'react-icons/bi';
import { CgProfile } from 'react-icons/cg';
import { FaHome, FaSignInAlt, FaUsers } from 'react-icons/fa';
import { GoOrganization } from 'react-icons/go';
import { PiSignOutBold } from 'react-icons/pi';
Expand Down Expand Up @@ -65,6 +66,11 @@ const NavBar = () => {
to: 'companies',
label: 'Companies',
icon: <GoOrganization />
},
{
to: 'profile',
label: 'Profile',
icon: <CgProfile />
}
]);
} else {
Expand Down
78 changes: 78 additions & 0 deletions client/src/pages/Profile/Profile.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
.profile {
border: 2px solid var(--color-primary);
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
justify-content: space-around;
gap: 20px;
padding: 20px;
}

.item-group {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
background: var(--color-bg);
padding: 20px;
border-radius: 10px;
width: 100%;
height: 100%;
}

.item-group h2 {
color: var(--color-primary);
font-size: 1.5rem;
margin-bottom: 10px;
}

.item {
display: flex;
flex-direction: row;
align-items: center;
gap: 10px;
width: 100%;
justify-content: space-between;
padding: 10px;
border-radius: 10px;
background: var(--color-bg);
}

.item label {
color: var(--color-primary);
font-size: 1.2rem;
}

.item input {
padding: 10px;
border-radius: 5px;
border: 1px solid var(--color-primary);
width: 100%;
}

.item input:focus {
outline: none;
border: 1px solid var(--color-primary-variant);
}

.item input:disabled {
background: var(--color-bg);
}

.item button {
padding: 10px;
border-radius: 5px;
border: none;
background: var(--color-primary);
color: var(--color-bg);
font-size: 1.2rem;
cursor: pointer;
display: flex;
align-items: center;
gap: 10px;
}

.item button:hover {
background: var(--color-primary-variant);
}
108 changes: 108 additions & 0 deletions client/src/pages/Profile/Profile.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { useEffect, useState } from 'react';
import { GiCheckMark } from 'react-icons/gi';
import { MdOutlineModeEdit } from 'react-icons/md';
import { RxCross1 } from 'react-icons/rx';
import { getStudent, updateStudent } from '../../api/studentApi';
import getUser from '../../utils/user.js';
import './Profile.css';

const Profile = () => {
const userDetails = getUser();
const [user, setUser] = useState({});
const [isEditing, setIsEditing] = useState('');

useEffect(() => {
const fetchStudentDetails = async () => {
try {
const student = await getStudent(userDetails.id);
setUser(student.data);
} catch (error) {
console.log(error);
}
};

fetchStudentDetails();
}, [userDetails.id]);

const handleInputChange = (name, value) => {
setUser((prevState) => ({
...prevState,
[name]: value
}));
};

const handleSubmit = async (e) => {
e.preventDefault();
try {
await updateStudent(userDetails.id, user);
setUser(user);
setIsEditing('');
} catch (error) {
console.log(error.response.data.message);
}
};

const onCancel = () => {
const input = document.getElementById(isEditing);
input.disabled = true;
setIsEditing('');
};

const item = (label, name, value) => {
return (
<div className="item">
<label htmlFor={name}>{label}</label>
<input
type="text"
name={name}
value={value}
id={name}
onChange={(e) => handleInputChange(name, e.target.value)}
disabled={isEditing !== name}
/>
{isEditing === name ? (
<>
<button onClick={handleSubmit}>
<GiCheckMark />
</button>
<button onClick={onCancel}>
<RxCross1 />
</button>
</>
) : (
<button
onClick={() => {
document.getElementById(name).disabled = false;
setIsEditing(name);
}}
>
<MdOutlineModeEdit />
</button>
)}
</div>
);
};

return (
<div className="container">
<h1>My Profile</h1>
{Object.keys(user).length !== 0 && (
<div className="profile">
<div className="item-group">
<h2>Personal Information</h2>
{item('Name', 'Name', user.name)}
{item('Email', 'email', user.email)}
{item('Roll No', 'rollNo', user.rollNo)}
</div>
<div className="item-group">
<h2>Company Information</h2>
{item('Company', 'company', user.placedAt.companyName)}
{item('Location', 'location', user.placedAt.location)}
</div>
</div>
)}
</div>
);
};

export default Profile;
2 changes: 1 addition & 1 deletion server/controllers/authController.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ exports.postResetPassword = async (req, res) => {
const user = await User.findOne({ email: email.toString() });
if (!user) return res.status(401).json({ status: false, errors: ['User Not Found'] });

const existing = await Otp.findOne({ email: email.toString()});
const existing = await Otp.findOne({ email: email.toString() });
if (!existing) return res.status(401).json({ status: false, errors: ['OTP not generated'] });

if (existing.otp !== otp) return res.status(401).json({ status: false, errors: ['Incorrect OTP'] });
Expand Down
2 changes: 1 addition & 1 deletion server/controllers/statsController.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ exports.getStudentStats = async (req, res) => {
const companies = await Company.find();
const filterCompanies = filterValidCompanies(companies);
const totalStudents = students.length;
const totalEligibleStudents = students.filter((student) => student.pg.cgpa >= 6.5 && student.backlogs === 0).length;
const totalEligibleStudents = students.filter((student) => (student.pg.cgpa >= 6.5 && student.backlogs === 0) || student.placed).length;
const totalPlacedStudents = calculateTotalPlacedStudents(filterCompanies);
const totalVerifiedStudents = students.filter((student) => student.isVerified === true).length;
const totalUnverifiedStudents = students.filter((student) => student.isVerified === false).length;
Expand Down
18 changes: 14 additions & 4 deletions server/controllers/userController.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ exports.viewSingleUser = async (req, res) => {
}
logger.info(`User Viewed: ${user.name}`);
user.password = null;
res.status(200).json({ user });
res.status(200).json(user);
} catch (error) {
logger.error(error);
res.status(500).json({ error });
Expand All @@ -53,13 +53,22 @@ exports.updateUser = async (req, res) => {
return res.status(400).json({ message: 'Invalid user ID' });
}

const validationError = validateUser(req.body);
if (validationError.length > 0) return res.status(400).json({ errors: validationError });
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({ message: 'User not found' });
}

const { password, ...updatedData } = req.body;

const updatedUser = await User.findByIdAndUpdate(req.params.id, req.body, { new: true });
if(req.user.role === 'student' && req.params.id !== req.user.id) {
return res.status(403).json({ message: 'Forbidden' });
}

const updatedUser = await User.findByIdAndUpdate(req.params.id, { ...user.toObject(), ...updatedData }, { new: true });
if (!updatedUser) {
return res.status(404).json({ message: 'User not found' });
}

logger.info(`User updated: ${updatedUser.name}`);
res.status(200).json(updatedUser);
} catch (error) {
Expand Down Expand Up @@ -229,3 +238,4 @@ exports.updateCompanyLocation = async (req, res) => {
res.status(500).json({ message: 'Internal server error' });
}
};

4 changes: 2 additions & 2 deletions server/models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ const userSchema = new mongoose.Schema(
email: {
type: String,
required: true,
match: /^((?=(?:[^\s@]+@[^\s@]+\.[^\s@]+))\2)+$/,
match: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
unique: true
},
password: { type: String, required: true },
rollNo: {
type: String,
match: /^((?=(\d{2}MCF1R\d{2,})))\2$/,
match: /^\d{2}MCF1R\d{2,}$/,
unique: true
},
role: {
Expand Down
4 changes: 2 additions & 2 deletions server/routes/userRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ const limiter = require('../utils/limiter');
router.get('/view', authenticateUser, checkUserRole(['student', 'admin', 'placementCoordinator']), userController.viewAllUsers);

// View a single user by ID with rate limiting
router.get('/view/:id', authenticateUser, checkUserRole(['admin', 'placementCoordinator']), userController.viewSingleUser);
router.get('/view/:id', authenticateUser, checkUserRole(['student', 'admin', 'placementCoordinator']), userController.viewSingleUser);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
a database access
, but is not rate-limited.

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a database access
, but is not rate-limited.

// Update a User with rate limiting
router.put('/update/:id', authenticateUser, checkUserRole(['admin', 'placementCoordinator']), limiter, userController.updateUser);
router.put('/update/:id', authenticateUser, checkUserRole(['student','admin', 'placementCoordinator']), limiter, userController.updateUser);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
a database access
, but is not rate-limited.

// Update Verification Status of a User with rate limiting
router.put('/verify/:id', authenticateUser, checkUserRole(['admin', 'placementCoordinator']), limiter, userController.verify);
Expand Down

0 comments on commit 8e909bd

Please sign in to comment.