Skip to content

Commit a9a028c

Browse files
authored
Merge pull request #24 from CS3219-AY2425S1/create-user-profile
Create user profile
2 parents d779f5c + cb3f31e commit a9a028c

File tree

6 files changed

+253
-7
lines changed

6 files changed

+253
-7
lines changed

Frontend/src/App.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'bootstrap/dist/css/bootstrap.min.css';
44
import Home from './components/Home';
55
import Login from './components/auth/Login';
66
import SignUp from './components/auth/SignUp';
7+
import EditProfile from "./components/user/EditProfile";
78
import ProtectedRoute from './components/routes/ProtectedRoute';
89

910
function App() {
@@ -22,6 +23,9 @@ function App() {
2223

2324
{/* Home page route */}
2425
<Route path='/home' element={<ProtectedRoute><Home /></ProtectedRoute>} />
26+
27+
{/* Edit Profile page route */}
28+
<Route path='/profile/:id' element={<ProtectedRoute><EditProfile /></ProtectedRoute>} />
2529
</Routes>
2630
</BrowserRouter>
2731
</div>

Frontend/src/components/NavigationBar.jsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
11
import Container from 'react-bootstrap/Container';
22
import Navbar from 'react-bootstrap/Navbar';
3-
import Button from 'react-bootstrap/esm/Button';
43
import { useNavigate } from 'react-router-dom';
4+
import UserAvatarBox from "./user/userAvatarBox";
55

66
function NavigationBar() {
77
const navigate = useNavigate();
88

99
// Add log out functionality
1010
const handleLogout = () => {
11+
sessionStorage.removeItem("jwt_token");
1112
navigate("/login");
1213
}
1314

1415
return (
1516
<Navbar className='bg-light' sticky="top">
1617
<Container>
1718
<Navbar.Brand>PeerPrep</Navbar.Brand>
18-
<Button className="ms-auto" variant="danger" onClick={handleLogout}>
19-
Logout
20-
</Button>
19+
<UserAvatarBox />
2120
</Container>
2221
</Navbar>
2322
);
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
// Page where users can change their profile information, including their username, email, and password.
2+
import React, { useEffect, useState } from 'react';
3+
import {useNavigate, useParams} from 'react-router-dom';
4+
import userService from "../../services/users"
5+
import InputField from "../auth/InputField";
6+
7+
function EditProfile() {
8+
const navigate = useNavigate();
9+
const { id } = useParams(); // Access the id from the route
10+
const [userData, setUserData] = useState(null);
11+
12+
const [errorMessage, setErrorMessage] = useState('');
13+
const [successMessage, setSuccessMessage] = useState('');
14+
const [usernameError, setUsernameError] = useState(false);
15+
const [emailError, setEmailError] = useState(false);
16+
const [passwordError, setPasswordError] = useState(false);
17+
18+
useEffect(() => {
19+
// Fetch the user data based on the id
20+
async function fetchUserData() {
21+
const response = await userService.getUser(id,
22+
{ headers: { Authorization: `Bearer ${sessionStorage.getItem('jwt_token')}` } });
23+
setUserData(response.data);
24+
}
25+
fetchUserData();
26+
}, [id]);
27+
28+
const [username, setUsername] = useState('');
29+
const [email, setEmail] = useState('');
30+
const [password, setPassword] = useState('');
31+
const [confirmPassword, setConfirmPassword ] = useState('')
32+
const handleEditProfile = async (e) => {
33+
e.preventDefault();
34+
// Handle the user profile edit
35+
const newUser = {};
36+
if (username) newUser.username = username;
37+
if (email) newUser.email = email;
38+
if (password) newUser.password = password;
39+
40+
// Reset error messages
41+
setErrorMessage('');
42+
setSuccessMessage('');
43+
setUsernameError(false);
44+
setEmailError(false);
45+
setPasswordError(false);
46+
47+
// Validate inputs
48+
if (!username && !email && !password) {
49+
setErrorMessage("Please fill in at least one field")
50+
return;
51+
}
52+
53+
if (password !== confirmPassword) {
54+
setPasswordError(true);
55+
setErrorMessage('Passwords do not match');
56+
return;
57+
}
58+
59+
// update user data
60+
try {
61+
await userService.updateUser(id, newUser,
62+
{ headers: { Authorization: `Bearer ${sessionStorage.getItem('jwt_token')}` } });
63+
setSuccessMessage('User data updated successfully!');
64+
navigate("/home")
65+
} catch (e) {
66+
console.log("Unable to update user data", e);
67+
if (e.response) {
68+
switch (e.response.status) {
69+
case 401:
70+
setErrorMessage('Unauthorized. Please login again.');
71+
break;
72+
case 409:
73+
setErrorMessage('Username or email already exists.');
74+
setUsernameError(true);
75+
setEmailError(true);
76+
break;
77+
case 500:
78+
setErrorMessage('Server error. Please try again later.');
79+
break;
80+
default:
81+
setErrorMessage('An unexpected error occurred.');
82+
break;
83+
}
84+
}
85+
}
86+
}
87+
88+
89+
const goHome = () => navigate('/home')
90+
// Render user edit form based on userData
91+
return userData ? (
92+
<div>
93+
<h2>Edit Profile</h2>
94+
<div className="right-side">
95+
<div className="input-container">
96+
<form onSubmit={handleEditProfile}>
97+
<InputField
98+
label="Username"
99+
type="text"
100+
placeholder="Enter your username"
101+
value={username}
102+
onChange={(e) => {
103+
setUsername(e.target.value)
104+
setUsernameError(false);
105+
}}
106+
error={usernameError}
107+
/>
108+
<InputField
109+
label="Email"
110+
type="email"
111+
placeholder="Enter your email"
112+
value={email}
113+
onChange={(e) => {
114+
setEmail(e.target.value)
115+
setEmailError(false);
116+
}}
117+
error={emailError}
118+
/>
119+
<InputField
120+
label="Password"
121+
type="password"
122+
placeholder="Enter your password"
123+
value={password}
124+
onChange={(e) => {
125+
setPassword(e.target.value)
126+
setPasswordError(false);
127+
}}
128+
error={passwordError}
129+
/>
130+
<InputField
131+
label="Confirm Password"
132+
type="password"
133+
placeholder="Confirm your password"
134+
value={confirmPassword}
135+
onChange={(e) => {
136+
setConfirmPassword(e.target.value)
137+
setPasswordError(false);
138+
}}
139+
error={passwordError}
140+
/>
141+
<div className='button-container'>
142+
<button type="submit" className="signup-button">
143+
Change Profile Details
144+
</button>
145+
</div>
146+
<div className='button-container'>
147+
<button type={'button'} onClick={goHome} className="signup-button">
148+
Cancel
149+
</button>
150+
</div>
151+
</form>
152+
<div className='notification'>
153+
{/* Success Message */}
154+
{successMessage && <p className="text-success mt-3">{successMessage}</p>}
155+
156+
{/* Error Message */}
157+
{errorMessage && <p className="text-danger mt-3">{errorMessage}</p>}
158+
</div>
159+
</div>
160+
</div>
161+
</div>
162+
) : (
163+
<p>Loading...</p>
164+
);
165+
}
166+
167+
export default EditProfile;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// Profile Page that can list user accomplishments and other information
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import React, {useEffect, useState} from "react";
2+
import { useNavigate } from "react-router-dom";
3+
import { Dropdown, ButtonGroup } from "react-bootstrap";
4+
import {jwtDecode} from "jwt-decode";
5+
import userService from "../../services/users";
6+
7+
8+
async function getUserFromToken() {
9+
const jwtToken = sessionStorage.getItem('jwt_token');
10+
if (jwtToken) {
11+
const decodedToken = jwtDecode(jwtToken);
12+
try {
13+
// decodedToken has an id field in auth-controller.js
14+
// user fetched by id has a username field in user-model.js
15+
const id = decodedToken.id;
16+
const user = await userService.getUser(
17+
decodedToken.id, {headers: {Authorization: `Bearer ${jwtToken}`}})
18+
// getUser return an Object with data, message, and type
19+
// The user data is nested in Object.data and hence we need a double .data to access it
20+
return {id: id, username: user.data.data.username};
21+
} catch (error) {
22+
console.error(error);
23+
return "No User";
24+
}
25+
}
26+
return "No User";
27+
}
28+
29+
function UserAvatarBox() {
30+
const navigate = useNavigate();
31+
const [user, setUser] = useState(null);
32+
const [username, setUsername] = useState("No User");
33+
34+
const handleLogout = () => {
35+
sessionStorage.removeItem('jwt_token');
36+
navigate('/login');
37+
};
38+
39+
useEffect(() => {
40+
async function fetchData() {
41+
const user = await getUserFromToken();
42+
console.log("User: ", user);
43+
if (user) {
44+
setUser(user)
45+
setUsername(user.username);
46+
console.log(user.username);
47+
}
48+
}
49+
fetchData();
50+
}, []);
51+
52+
return (
53+
<Dropdown as={ButtonGroup} className="ms-auto">
54+
{/* Nice to have image in the future*/}
55+
<Dropdown.Toggle id="dropdown-custom-1">
56+
{username}
57+
</Dropdown.Toggle>
58+
<Dropdown.Menu className="super-colors">
59+
<Dropdown.Item eventKey="1" onClick={handleLogout}>
60+
<span style={{color: 'red'}}>Logout</span>
61+
</Dropdown.Item>
62+
<Dropdown.Item eventKey="2" onClick={() => navigate(`/profile/${user.id}`)}>Edit Profile</Dropdown.Item>
63+
</Dropdown.Menu>
64+
</Dropdown>
65+
);
66+
}
67+
68+
export default UserAvatarBox;

Frontend/src/services/users.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ const createUser = async (newUser) => {
99
return response;
1010
};
1111

12-
const getUser = async (id) => {
13-
const response = await axios.get(`${baseUrl}/${user}/${id}`);
12+
const getUser = async (id, jwt_token) => {
13+
const response = await axios.get(`${baseUrl}/${user}/${id}`, jwt_token);
1414
return response;
1515
};
1616

@@ -19,9 +19,16 @@ const loginUser = async (userCredentials) => {
1919
return response;
2020
}
2121

22+
const updateUser = async (id, newUser, jwt_token) => {
23+
// note that user has the fields username, email, and password
24+
console.log(user)
25+
const response = await axios.patch(`${baseUrl}/${user}/${id}`, newUser, jwt_token);
26+
return response;
27+
}
28+
2229
const verifyToken = async (authHeader) => {
2330
const response = await axios.get(`${baseUrl}/${auth}/verify-token`, authHeader);
2431
return response;
2532
}
2633

27-
export default { createUser, getUser, loginUser, verifyToken };
34+
export default { createUser, getUser, loginUser, updateUser, verifyToken };

0 commit comments

Comments
 (0)