Skip to content

Commit 233363b

Browse files
committed
Add navbar and userContext to store username and email
1 parent aace294 commit 233363b

File tree

9 files changed

+255
-24
lines changed

9 files changed

+255
-24
lines changed

ai/usage-log.md

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -244,24 +244,41 @@ Request to implement simple cookie utilities for JWT token storage and removal,
244244
## Entry 8
245245

246246
# Date/Time:
247-
2025-09-15 22:30
247+
2025-09-16 00:05
248248

249249
# Tool:
250250
GitHub Copilot (model: Claude Sonnet 4)
251251

252252
# Prompt/Command:
253-
Request to implement controlled state management and password visibility toggle in login component, maintaining consistency with signup component patterns.
253+
Request to implement UserContext for managing user authentication state and create a global navigation bar that appears on all non-auth pages with conditional rendering.
254254

255255
# Output Summary:
256-
- Added controlled state for email and password fields with proper value/onChange handlers
257-
- Implemented password visibility toggle with Eye/EyeOff icons (consistent with SignUpComponent)
258-
- Updated input components to use state values and change handlers
256+
- Created React UserContext (/contexts/UserContext.tsx) for centralized user state management
257+
- Implemented automatic token verification using verifyToken API
258+
- Added user data persistence across page refreshes with token-based restoration
259+
- Created Navbar component (/app/components/layout/Navbar.tsx) with PeerPrep branding and navigation
260+
- Implemented NavbarWrapper (/app/components/layout/NavbarWrapper.tsx) for conditional rendering
261+
- Updated root layout.tsx to include UserProvider and NavbarWrapper
262+
- Enhanced LoginComponent to set user data in context after successful authentication
263+
- Updated WelcomeComponent to display actual username from UserContext instead of hardcoded name
264+
- Fixed cookie utilities with getToken function for token retrieval
259265

260266
# Action Taken:
261-
- [x] Accepted as-is
262-
- [ ] Modified
267+
- [ ] Accepted as-is
268+
- [x] Modified
263269
- [ ] Rejected
264270

265271
# Author Notes:
266-
- Maintained consistency between login and signup component patterns
267-
- Simple state management improvement for better form control
272+
- Validated UserContext properly handles token verification through API calls rather than JWT parsing
273+
- Confirmed conditional navbar rendering excludes auth pages (/auth/*) as required
274+
- Reviewed user state persistence across page refreshes and browser sessions
275+
- Tested integration between LoginComponent, UserContext, and display components
276+
- Security considerations: Token verification through backend API ensures data integrity
277+
- UX improvements: Consistent navigation experience with user-aware display elements
278+
- Maintainability: Centralized user state management with clear separation of concerns
279+
- Debug logging enables easier troubleshooting of authentication state issues
280+
- Navbar modified by me after AI implementation to meet specific design requirements
281+
282+
---
283+
284+

frontend/src/app/components/auth/LoginComponent.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,16 @@ import { useRouter } from "next/navigation";
1818
import { login } from "@/services/userServiceApi";
1919
import { handleApiError, handleApiSuccess } from "@/services/errorHandler";
2020
import { toast } from "sonner";
21-
import { addToken } from "@/services/userServiceCookies";
21+
import { addToken } from "@/services/userServiceCookies";
22+
import { useUser } from "@/contexts/UserContext";
2223

2324
export default function LoginForm() {
2425
const [email, setEmail] = useState("");
2526
const [password, setPassword] = useState("");
2627
const [showPassword, setShowPassword] = useState(false);
2728

2829
const router = useRouter();
30+
const { setUser } = useUser();
2931

3032
const handleLogin = async (e: React.MouseEvent<HTMLButtonElement>) => {
3133
e.preventDefault();
@@ -46,8 +48,23 @@ export default function LoginForm() {
4648
return;
4749
}
4850

49-
// Store token and show success
51+
// Store token
5052
addToken(token);
53+
54+
// Parse token to get user data and set in context
55+
try {
56+
const payload = JSON.parse(atob(token.split('.')[1]));
57+
if (payload.username && payload.email) {
58+
setUser({
59+
username: payload.username,
60+
email: payload.email,
61+
});
62+
}
63+
} catch (error) {
64+
console.error("Error parsing token:", error);
65+
}
66+
67+
// Show success message
5168
handleApiSuccess(
5269
"Login successful!",
5370
`Welcome back! Redirecting to homepage...`,

frontend/src/app/components/home/WelcomeComponent.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
import { Card, CardTitle, CardHeader, CardContent } from "@/components/ui/card";
44
import { Button } from "@/components/ui/button";
5+
import { useUser } from "@/contexts/UserContext";
56

67
export default function WelcomePage() {
8+
const { user } = useUser();
9+
710
function directToHome() {
811
window.location.href = "/match";
912
}
@@ -13,7 +16,7 @@ export default function WelcomePage() {
1316
<CardHeader>
1417
<CardTitle className="text-4xl flex">
1518
Hello
16-
<p className="ml-2 font-bold">Derrick Wong</p>
19+
<p className="ml-2 font-bold">{user?.username || "Guest"}</p>
1720
</CardTitle>
1821
<CardTitle className="text-4xl flex">Ready to start coding?</CardTitle>
1922
</CardHeader>
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* AI Assistance Disclosure:
3+
* Tool: GitHub Copilot (model: Claude 3.5 Sonnet), date: 2025-09-15
4+
* Purpose: To create a reusable navigation bar component for the PeerPrep application.
5+
* Author Review: I validated correctness, security, and performance of the code.
6+
* I modified the styling to match the application's theme.
7+
*/
8+
9+
"use client";
10+
11+
import Link from "next/link";
12+
import { useState } from "react";
13+
import { Button } from "@/components/ui/button";
14+
import { User, LogOut } from "lucide-react";
15+
import { removeToken } from "@/services/userServiceCookies";
16+
import { useUser } from "@/contexts/UserContext";
17+
18+
export default function Navbar() {
19+
const [activeTab, setActiveTab] = useState("Dashboard");
20+
const { user, setUser } = useUser();
21+
22+
const navItems = [
23+
{ name: "Dashboard", href: "/home" },
24+
{ name: "Find Partner", href: "/match" },
25+
];
26+
27+
const handleLogout = () => {
28+
removeToken();
29+
setUser(null);
30+
window.location.href = "/auth/login";
31+
};
32+
33+
return (
34+
<nav className="bg-white border-b border-gray-200 px-6 py-3">
35+
<div className="flex justify-between items-center">
36+
{/* Logo */}
37+
<div className="flex items-center">
38+
<div className="text-blue-600 font-bold text-xl">
39+
<span className="text-2xl">👥</span> PeerPrep
40+
</div>
41+
</div>
42+
43+
{/* Navigation Items */}
44+
<div className="flex space-x-8">
45+
{navItems.map((item) => (
46+
<Link
47+
key={item.name}
48+
href={item.href}
49+
onClick={() => setActiveTab(item.name)}
50+
className={`px-3 py-2 text-sm font-medium transition-colors ${
51+
activeTab === item.name
52+
? "text-blue-600 border-b-2 border-blue-600"
53+
: "text-gray-600 hover:text-blue-600"
54+
}`}
55+
>
56+
{item.name}
57+
</Link>
58+
))}
59+
</div>
60+
61+
{/* User Profile & Logout */}
62+
<div className="flex items-center space-x-3">
63+
<div className="flex items-center space-x-2">
64+
<div className="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
65+
<User className="w-4 h-4 text-blue-600" />
66+
</div>
67+
{user && (
68+
<span className="text-sm font-medium text-gray-700">
69+
{user.username}
70+
</span>
71+
)}
72+
</div>
73+
<Button variant="outline" size="sm" onClick={handleLogout}>
74+
<LogOut className="w-4 h-4 mr-2" />
75+
Logout
76+
</Button>
77+
</div>
78+
</div>
79+
</nav>
80+
);
81+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* AI Assistance Disclosure:
3+
* Tool: GitHub Copilot (model: Claude Sonnet 4), date: 2025-09-16
4+
* Purpose: To implement conditional navbar rendering that hides navigation on authentication pages using Next.js pathname detection.
5+
* Author Review: I validated correctness, security, and performance of the code.
6+
*/
7+
8+
"use client";
9+
10+
import { usePathname } from "next/navigation";
11+
import Navbar from "./Navbar";
12+
13+
export default function NavbarWrapper() {
14+
const pathname = usePathname();
15+
16+
// Don't show navbar on auth pages
17+
const isAuthPage = pathname.startsWith("/auth");
18+
19+
if (isAuthPage) {
20+
return null;
21+
}
22+
23+
return <Navbar />;
24+
}

frontend/src/app/home/page.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,9 @@ import WelcomePage from "../components/home/WelcomeComponent";
55

66
export default function HomePage() {
77
return (
8-
<div className="h-screen flex flex-col">
9-
<div className="flex justify-between items-center bg-white w-full h-[5%]">
10-
<div className="ml-4 text-black">hamburger</div>
11-
<div className="text-black font-bold">PeerPrep</div>
12-
<div className="ml-4 text-black">Avatar</div>
13-
</div>
14-
8+
<div className="min-h-screen flex flex-col">
159
<WelcomePage />
16-
1710
<StatisticPage />
18-
1911
<div className="flex flex-1 m-10">
2012
<HistoryPage />
2113
<QuickActionsPage />

frontend/src/app/layout.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import type { Metadata } from "next";
99
import { Geist, Geist_Mono } from "next/font/google";
1010
import { Toaster } from "sonner";
11+
import { UserProvider } from "@/contexts/UserContext";
12+
import NavbarWrapper from "./components/layout/NavbarWrapper";
1113
import "./globals.css";
1214

1315
const geistSans = Geist({
@@ -21,8 +23,8 @@ const geistMono = Geist_Mono({
2123
});
2224

2325
export const metadata: Metadata = {
24-
title: "Create Next App",
25-
description: "Generated by create next app",
26+
title: "PeerPrep",
27+
description: "A collaborative coding platform for interview preparation",
2628
};
2729

2830
export default function RootLayout({
@@ -35,7 +37,10 @@ export default function RootLayout({
3537
<body
3638
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
3739
>
38-
{children}
40+
<UserProvider>
41+
<NavbarWrapper />
42+
{children}
43+
</UserProvider>
3944
<Toaster
4045
position="top-center"
4146
richColors
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
* AI Assistance Disclosure:
3+
* Tool: GitHub Copilot (model: Claude Sonnet 4), date: 2025-09-16
4+
* Purpose: To implement React UserContext for managing user authentication state with automatic token verification and user data fetching from API.
5+
* Author Review: I validated correctness, security, and performance of the code.
6+
*/
7+
8+
"use client";
9+
10+
import React, { createContext, useContext, useState, useEffect, ReactNode } from "react";
11+
import { getToken } from "@/services/userServiceCookies";
12+
import { verifyToken } from "@/services/userServiceApi";
13+
14+
interface User {
15+
username: string;
16+
email: string;
17+
}
18+
19+
interface UserContextType {
20+
user: User | null;
21+
setUser: (user: User | null) => void;
22+
}
23+
24+
const UserContext = createContext<UserContextType | undefined>(undefined);
25+
26+
export const useUser = () => {
27+
const context = useContext(UserContext);
28+
if (context === undefined) {
29+
throw new Error("useUser must be used within a UserProvider");
30+
}
31+
return context;
32+
};
33+
34+
interface UserProviderProps {
35+
children: ReactNode;
36+
}
37+
38+
export const UserProvider: React.FC<UserProviderProps> = ({ children }) => {
39+
const [user, setUser] = useState<User | null>(null);
40+
41+
useEffect(() => {
42+
console.log("UserContext useEffect triggered, current user:", user);
43+
44+
// If user context is empty but we have a token, fetch user data
45+
if (!user) {
46+
const token = getToken();
47+
console.log("Token from cookies:", token);
48+
49+
if (token) {
50+
console.log("Token found, calling verifyToken API...");
51+
verifyToken(token)
52+
.then((response) => {
53+
console.log("VerifyToken response:", response.data);
54+
const userData = response.data?.data;
55+
if (userData?.username && userData?.email) {
56+
console.log("Setting user data:", userData);
57+
setUser({
58+
username: userData.username,
59+
email: userData.email,
60+
});
61+
} else {
62+
console.log("User data incomplete:", userData);
63+
}
64+
})
65+
.catch((error) => {
66+
console.error("Error verifying token:", error);
67+
});
68+
} else {
69+
console.log("No token found in cookies");
70+
}
71+
}
72+
}, [user]);
73+
74+
return (
75+
<UserContext.Provider value={{ user, setUser }}>
76+
{children}
77+
</UserContext.Provider>
78+
);
79+
};

frontend/src/services/userServiceCookies.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,17 @@ export function addToken(token: string): void {
1111

1212
export function removeToken(): void {
1313
document.cookie = 'token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC';
14+
}
15+
16+
export function getToken(): string | null {
17+
if (typeof document === 'undefined') return null;
18+
19+
const cookies = document.cookie.split(';');
20+
for (const cookie of cookies) {
21+
const [name, value] = cookie.trim().split('=');
22+
if (name === 'token') {
23+
return value;
24+
}
25+
}
26+
return null;
1427
}

0 commit comments

Comments
 (0)