Skip to content

Commit 19fdfe5

Browse files
committed
Add history FE (not tested yet)
1 parent 1c9410b commit 19fdfe5

File tree

8 files changed

+235
-3
lines changed

8 files changed

+235
-3
lines changed

Frontend/src/App.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import Home from './components/Home';
55
import Login from './components/auth/Login';
66
import SignUp from './components/auth/SignUp';
77
import CollaborationSpace from './components/collab/CollaborationSpace';
8-
import EditProfile from "./components/user/EditProfile";
8+
import EditProfilePage from "./components/user/EditProfilePage";
9+
import HistoryPage from "./components/user/HistoryPage"
910
import ProtectedRoute from './components/routes/ProtectedRoute';
1011

1112
function App() {
@@ -26,7 +27,10 @@ function App() {
2627
<Route path='/home' element={<ProtectedRoute><Home /></ProtectedRoute>} />
2728

2829
{/* Edit Profile page route */}
29-
<Route path='/profile/:id' element={<ProtectedRoute><EditProfile /></ProtectedRoute>} />
30+
<Route path='/profile/:id' element={<ProtectedRoute><EditProfilePage /></ProtectedRoute>} />
31+
32+
{/* History page route */}
33+
<Route path='/history/:id' element={<ProtectedRoute><HistoryPage /></ProtectedRoute>} />
3034

3135
{/* Collaboration page route */}
3236
<Route path="/collaboration/:roomId" element={<ProtectedRoute><CollaborationSpace /></ProtectedRoute>} />
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// HistoryAttempt.js
2+
import React from 'react';
3+
4+
const HistoryAttempt = ({ attempt }) => {
5+
const { matchedUser, question, startTime, duration, codeFile } = attempt;
6+
7+
return (
8+
<div className="history-attempt">
9+
<h3>Attempt with {matchedUser}</h3>
10+
<p>Question: {question.title}</p>
11+
<p>Start Time: {new Date(startTime).toLocaleString()}</p>
12+
<p>Duration: {duration} minutes</p>
13+
<button onClick={() => downloadCode(codeFile)}>Download Code</button>
14+
</div>
15+
);
16+
};
17+
18+
const downloadCode = (file) => {
19+
// Logic for downloading the binary code file
20+
const blob = new Blob([file], { type: 'application/octet-stream' });
21+
const link = document.createElement('a');
22+
link.href = URL.createObjectURL(blob);
23+
link.download = 'code_attempt.bin';
24+
link.click();
25+
};
26+
27+
export default HistoryAttempt;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React, { useState, useEffect } from 'react';
2+
import NavigationBar from '../NavigationBar';
3+
import HistoryTable from './HistoryTable';
4+
import ProfileSidebar from './ProfileSidebar';
5+
import { getUserFromToken } from "./utils/authUtils";
6+
7+
function HistoryPage() {
8+
const [userID, setUserID] = useState(null);
9+
10+
useEffect(() => {
11+
async function fetchData() {
12+
try {
13+
const user = await getUserFromToken();
14+
if (user) {
15+
setUserID(user.userId); // asynchronous
16+
console.log(`I have gotten the user id in history page: ${user.userId}`)
17+
}
18+
} catch (error) {
19+
console.error('Error fetching user ID in history page component:', error);
20+
}
21+
}
22+
fetchData();
23+
}, []);
24+
25+
26+
27+
return(
28+
<div>
29+
<NavigationBar/>
30+
<div className="row">
31+
<div className="Navbar col-2">
32+
<ProfileSidebar userID={userID}/>
33+
</div>
34+
<div className="col-10">
35+
<HistoryTable userID={userID}/>
36+
</div>
37+
</div>
38+
</div>
39+
);
40+
}
41+
42+
export default HistoryPage;
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// AttemptHistory.js
2+
import React, { useState, useEffect } from 'react';
3+
import HistoryAttempt from './HistoryAttempt';
4+
import historyService from "../../services/history";
5+
import { getUserFromToken } from "./utils/authUtils";
6+
// import {jwtDecode} from "jwt-decode";
7+
// import userService from "../../services/users";
8+
9+
// export async function getUserFromToken() {
10+
// const jwtToken = sessionStorage.getItem('jwt_token');
11+
// if (jwtToken) {
12+
// const decodedToken = jwtDecode(jwtToken);
13+
// try {
14+
// // decodedToken has an id field in auth-controller.js
15+
// // user fetched by id has a username field in user-model.js
16+
// const id = decodedToken.id;
17+
// const user = await userService.getUser(
18+
// decodedToken.id, {headers: {Authorization: `Bearer ${jwtToken}`}})
19+
// // getUser return an Object with data, message, and type
20+
// // The user data is nested in Object.data and hence we need a double .data to access it
21+
// return {id: id, username: user.data.data.username};
22+
// } catch (error) {
23+
// console.error(error);
24+
// return "No User";
25+
// }
26+
// }
27+
// return "No User";
28+
// }
29+
30+
31+
const HistoryTable = ({userID}) => {
32+
// const [userID, setUserID] = useState(null);
33+
const [history, setHistory] = useState([]);
34+
35+
// useEffect(() => {
36+
// async function fetchData() {
37+
// try {
38+
// const user = await getUserFromToken();
39+
// if (user) {
40+
// setUserID(user.userId); // asynchronous
41+
// const result = await historyService.getHistoryByUserId(user.userId);
42+
// setHistory(result.data);
43+
// console.log(`I have gotten the user id: ${user.userId}`)
44+
// }
45+
// } catch (error) {
46+
// console.error('Error fetching attempt history:', error);
47+
// }
48+
// }
49+
// fetchData();
50+
// }, []);
51+
52+
useEffect(() => {
53+
async function fetchData() {
54+
try {
55+
if (userID) {
56+
const result = await historyService.getHistoryByUserId(userID);
57+
setHistory(result.data);
58+
console.log(`I have gotten the user id in table: ${userID}`)
59+
}
60+
} catch (error) {
61+
console.error('Error fetching attempt history:', error);
62+
}
63+
}
64+
fetchData();
65+
}, []);
66+
67+
return (
68+
<div className="history-table">
69+
<h2>Attempt History</h2>
70+
<table className="table table-striped">
71+
<thead>
72+
<tr>
73+
<th>Matched User</th>
74+
<th>Question</th>
75+
<th>Start Time</th>
76+
<th>Duration</th>
77+
<th>Code</th>
78+
</tr>
79+
</thead>
80+
<tbody>
81+
{history.map((attempt, index) => (
82+
<HistoryAttempt key={index} attempt={attempt} />
83+
))}
84+
</tbody>
85+
</table>
86+
</div>
87+
);
88+
};
89+
90+
export default HistoryTable;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// ProfileSidebar.js
2+
import React from 'react';
3+
import { Link } from 'react-router-dom';
4+
5+
const ProfileSidebar = ({ userID }) => {
6+
return (
7+
<div
8+
className="profile-sidebar"
9+
style={{
10+
display: 'flex',
11+
flexDirection: 'column',
12+
alignItems: 'center',
13+
position: 'relative',
14+
top: '30%', // Start 30% down from the top
15+
}}
16+
>
17+
<Link to={`/profile/${userID}`}>
18+
<button className="btn btn-primary mb-3">Edit Profile</button>
19+
</Link>
20+
<Link to={`/history/${userID}`}>
21+
<button className="btn btn-secondary">Attempt History</button>
22+
</Link>
23+
</div>
24+
);
25+
};
26+
27+
export default ProfileSidebar;

Frontend/src/components/user/userAvatarBox.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ function UserAvatarBox() {
5959
<Dropdown.Item eventKey="1" onClick={handleLogout}>
6060
<span style={{color: 'red'}}>Logout</span>
6161
</Dropdown.Item>
62-
<Dropdown.Item eventKey="2" onClick={() => navigate(`/profile/${user.id}`)}>Edit Profile</Dropdown.Item>
62+
<Dropdown.Item eventKey="2" onClick={() => navigate(`/profile/${user.id}`)}>Edit Profile</Dropdown.Item>
63+
<Dropdown.Item eventKey="3" onClick={() => navigate(`/history/${user.id}`)}>View Attempt History</Dropdown.Item>
6364
</Dropdown.Menu>
6465
</Dropdown>
6566
);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// utils/authUtils.js
2+
import {jwtDecode} from "jwt-decode";
3+
import userService from "../../../services/users";
4+
5+
export async function getUserFromToken() {
6+
const jwtToken = sessionStorage.getItem('jwt_token');
7+
if (jwtToken) {
8+
try {
9+
const decodedToken = jwtDecode(jwtToken);
10+
const userId = decodedToken.id;
11+
12+
const userResponse = await userService.getUser(userId, {
13+
headers: { Authorization: `Bearer ${jwtToken}` }
14+
});
15+
16+
// Assuming the user data is nested as described
17+
const user = userResponse.data.data.username
18+
return { userId: userId, username: user };
19+
} catch (error) {
20+
console.error("Error fetching user:", error);
21+
return null; // Return null to indicate no user data could be retrieved
22+
}
23+
}
24+
return null;
25+
}

Frontend/src/services/history.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import axios from 'axios';
2+
3+
const baseUrl = 'http://localhost:3005/api/histories';
4+
5+
// Fetch all attempt history for a user by their object ID
6+
const getHistoryByUserId = (userId) => {
7+
return axios.get(`${baseUrl}/user/${userId}`);
8+
};
9+
10+
// Sample function to add a new history attempt (optional)
11+
const createHistoryAttempt = async (newAttempt) => {
12+
const response = await axios.post(baseUrl, newAttempt);
13+
return response.data;
14+
};
15+
16+
export default { getHistoryByUserId, createHistoryAttempt };

0 commit comments

Comments
 (0)