Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"dotenv": "^16.4.5",
"express": "^4.21.1",
"express-session": "^1.18.1",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.8.2",
"passport": "^0.7.0",
"passport-local": "^1.0.0"
Expand Down
72 changes: 69 additions & 3 deletions backend/routes/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const express = require("express");
const passport = require("passport");
const User = require("../models/User");
const router = express.Router();
const jwt = require("jsonwebtoken");

// Signup route
router.post("/signup", async (req, res) => {
Expand All @@ -23,9 +24,16 @@ router.post("/signup", async (req, res) => {
});

// Login route
router.post("/login", passport.authenticate('local'), (req, res) => {
res.status(200).json( { message: 'Login successful', user: req.user } );
});
router.post("/login", passport.authenticate("local", { session: false }), (req, res) => {
try {
const user = req.user;
const token = jwt.sign({ id: user.id }, process.env.SESSION_SECRET, { expiresIn: "1d" });
res.status(200).json({ message: "Login successful", token, user });
} catch (error) {
res.status(500).json({ message: "Login failed", error: error.message });
}
}
);
Comment on lines +27 to +36
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify Passport authentication configuration.

The login route uses passport.authenticate("local", { session: false }) which requires proper Passport local strategy configuration. Ensure the strategy is correctly set up to validate credentials before JWT issuance.


🏁 Script executed:

#!/bin/bash
# Verify Passport local strategy configuration
cat backend/config/passportConfig.js

Length of output: 1289



---

🏁 Script executed:

```shell
#!/bin/bash
# Check if User model has comparePassword method
rg -n "comparePassword" backend/models/User.js -A 3

Length of output: 152


🏁 Script executed:

#!/bin/bash
# Verify passportConfig is imported in auth.js
head -20 backend/routes/auth.js | grep -E "import|require"

Length of output: 150



---

🏁 Script executed:

```shell
#!/bin/bash
# Check entire auth.js for passportConfig import
rg -n "passportConfig|passport.use" backend/routes/auth.js

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Show full auth.js file to understand structure
cat backend/routes/auth.js

Length of output: 3399


Import passportConfig.js in auth.js to register the LocalStrategy.

The Passport local strategy is properly configured in backend/config/passportConfig.js with bcrypt password validation, but it's never imported in backend/routes/auth.js. When the login route calls passport.authenticate("local"), the strategy won't be registered, causing authentication to fail.

Add this import at the top of backend/routes/auth.js:

require("../config/passportConfig");
🤖 Prompt for AI Agents
In backend/routes/auth.js around lines 27-36, the Passport LocalStrategy from
backend/config/passportConfig.js is not imported so
passport.authenticate("local") has no strategy registered; fix by adding a
top-of-file require/import that loads ../config/passportConfig (so the
LocalStrategy and bcrypt validation are registered before any
passport.authenticate calls), then keep the login route as-is.


// Logout route
router.get("/logout", (req, res) => {
Expand All @@ -39,4 +47,62 @@ router.get("/logout", (req, res) => {
});
});

// ---------------- AUTH MIDDLEWARE ----------------
function requireAuth(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return res.status(401).json({ message: "Missing or invalid token" });
}

const token = authHeader.split(" ")[1];
try {
const decoded = jwt.verify(token, process.env.SESSION_SECRET);
req.userId = decoded.id;
next();
} catch (err) {
return res.status(401).json({ message: "Invalid or expired token" });
}
}

// ---------------- GET PROFILE ----------------
router.get("/profile", requireAuth, async (req, res) => {
try {
const user = await User.findById(req.userId).select("-password");
if (!user) return res.status(404).json({ message: "User not found" });
res.status(200).json({ user });
} catch (err) {
res.status(500).json({ message: "Error fetching profile", error: err.message });
}
});

// ---------------- EDIT PROFILE ----------------
router.put("/profile", requireAuth, async (req, res) => {
try {
const updates = {};
const { username, email, bio, avatar, newPassword } = req.body;

if (username !== undefined) updates.username = username;
if (email !== undefined) updates.email = email;
if (bio !== undefined) updates.bio = bio;
if (avatar !== undefined) updates.avatar = avatar;

// Handle password update if newPassword is provided
if (newPassword !== undefined && newPassword.trim().length > 0) {
// Password will be hashed by the User model's pre-save hook
updates.password = newPassword;
}
Comment on lines +89 to +93
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Password update bypasses hashing via findByIdAndUpdate.

While password update logic was added per the previous review, it uses findByIdAndUpdate on line 95, which bypasses mongoose pre-save hooks. The password hashing (typically done in a pre-save hook) will not execute, causing passwords to be stored in plaintext.

Apply this diff to properly hash passwords:

 router.put("/profile", requireAuth, async (req, res) => {
     try {
-        const updates = {};
         const { username, email, bio, avatar, newPassword } = req.body;
 
-        if (username !== undefined) updates.username = username;
-        if (email !== undefined) updates.email = email;
-        if (bio !== undefined) updates.bio = bio;
-        if (avatar !== undefined) updates.avatar = avatar;
-
-        // Handle password update if newPassword is provided
-        if (newPassword !== undefined && newPassword.trim().length > 0) {
-            // Password will be hashed by the User model's pre-save hook
-            updates.password = newPassword;
-        }
-
-        const user = await User.findByIdAndUpdate(req.userId, updates, {
-            new: true,
-            runValidators: true,
-            select: "-password",
-        });
+        const user = await User.findById(req.userId);
+        if (!user) return res.status(404).json({ message: "User not found" });
+
+        // Apply updates
+        if (username !== undefined) user.username = username;
+        if (email !== undefined) user.email = email;
+        if (bio !== undefined) user.bio = bio;
+        if (avatar !== undefined) user.avatar = avatar;
+        
+        // Handle password update if newPassword is provided
+        if (newPassword !== undefined && newPassword.trim().length > 0) {
+            user.password = newPassword; // Will be hashed by pre-save hook
+        }
+
+        await user.save(); // Triggers pre-save hooks and validators
+        
+        // Remove password from response
+        const userResponse = user.toObject();
+        delete userResponse.password;
 
-        if (!user) return res.status(404).json({ message: "User not found" });
-
-        res.status(200).json({ message: "Profile updated successfully", user });
+        res.status(200).json({ message: "Profile updated successfully", user: userResponse });
     } catch (err) {
         res.status(500).json({ message: "Error updating profile", error: err.message });
     }
 });
🤖 Prompt for AI Agents
In backend/routes/auth.js around lines 89 to 93, the current password-update
sets updates.password and later uses findByIdAndUpdate which bypasses Mongoose
pre-save hooks so the password won’t be hashed; instead, either (a) load the
user with User.findById(id), set user.password = newPassword and call await
user.save() so the pre-save hook runs, or (b) if you prefer to keep a single
update call, manually hash newPassword with the same hashing utility (e.g.,
bcrypt.hash with the same salt rounds) before assigning updates.password; ensure
you await the hashing/save and propagate errors accordingly.


const user = await User.findByIdAndUpdate(req.userId, updates, {
new: true,
runValidators: true,
select: "-password",
});

if (!user) return res.status(404).json({ message: "User not found" });

res.status(200).json({ message: "Profile updated successfully", user });
} catch (err) {
res.status(500).json({ message: "Error updating profile", error: err.message });
}
});
module.exports = router;
25 changes: 25 additions & 0 deletions lib/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/// <reference types="vite/client" />
// src/services/api.ts
import axios from "axios";

// Backend base URL from .env
const backendUrl = import.meta.env.VITE_BACKEND_URL;

const api = axios.create({
baseURL: backendUrl,
headers: { "Content-Type": "application/json" },
});

// Interceptor to attach token from localStorage
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem("token");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);

export default api;
1 change: 1 addition & 0 deletions public/profile.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 34 additions & 1 deletion src/Routes/Router.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Routes, Route } from "react-router-dom";
import { Routes, Route, useNavigate } from "react-router-dom";
import { useEffect, useState } from "react";
import Tracker from "../pages/Tracker/Tracker.tsx";
import About from "../pages/About/About";
import Contact from "../pages/Contact/Contact";
Expand All @@ -7,8 +8,23 @@ import Signup from "../pages/Signup/Signup.tsx";
import Login from "../pages/Login/Login.tsx";
import ContributorProfile from "../pages/ContributorProfile/ContributorProfile.tsx";
import Home from "../pages/Home/Home.tsx";
import Profile from "../pages/Profile/Profile.tsx";
import ProtectedRoute from "../components/ProtectedRoute.tsx";
import EditProfile from "../pages/Editprofile/EditProfile.tsx";

const Router = () => {
const [isAuthenticated, setIsAuthenticated] = useState(
!!localStorage.getItem("token")
);
useEffect(() => {
const syncAuth = () => setIsAuthenticated(!!localStorage.getItem("token"));
window.addEventListener("authChange", syncAuth);
window.addEventListener("storage", syncAuth);
return () => {
window.removeEventListener("authChange", syncAuth);
window.removeEventListener("storage", syncAuth);
};
}, []);
return (
<Routes>
<Route path="/" element={<Home />} />
Expand All @@ -19,6 +35,23 @@ const Router = () => {
<Route path="/contact" element={<Contact />} />
<Route path="/contributors" element={<Contributors />} />
<Route path="/contributor/:username" element={<ContributorProfile />} />
{/* Protected route */}
<Route
path="/profile"
element={
<ProtectedRoute isAuthenticated={isAuthenticated}>
<Profile />
</ProtectedRoute>
}
/>
<Route
path="/profile/edit"
element={
<ProtectedRoute isAuthenticated={isAuthenticated}>
<EditProfile />
</ProtectedRoute>
}
/>
</Routes>
);
};
Expand Down
185 changes: 168 additions & 17 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,57 @@
import { Link } from "react-router-dom";
import { useState, useContext } from "react";
import { Link, useNavigate, useLocation } from "react-router-dom";
import { useState, useContext, useEffect } from "react";
import { ThemeContext } from "../context/ThemeContext";
import { Moon, Sun } from 'lucide-react';

import { Moon, Sun } from "lucide-react";
import { useRef } from "react";

const Navbar: React.FC = () => {
const navigate = useNavigate();
const location = useLocation();

const [menuOpen, setMenuOpen] = useState(false);
const menuRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const onClick = (e: MouseEvent) => {
if (menuRef.current && !menuRef.current.contains(e.target as Node))
setMenuOpen(false);
};

const onStorage = (e: StorageEvent) => {
if (e.key === "token") setIsAuthed(!!e.newValue);
};

const onAuthChange = () => {
setIsAuthed(!!localStorage.getItem("token"));
};
Comment on lines +24 to +26
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

authChange listener should also refresh user data from localStorage

The listener updates isAuthed but doesn't re-read the user object. When a profile update dispatches authChange, the Navbar will still display stale username/email/avatar until a full page refresh.

After adding a user state variable (see other comment), update this listener:

 const onAuthChange = () => {
   setIsAuthed(!!localStorage.getItem("token"));
+  setUser(readUserFromStorage());
 };

Similarly, update the storage listener if you want cross-tab user updates:

 const onStorage = (e: StorageEvent) => {
-  if (e.key === "token") setIsAuthed(!!e.newValue);
+  if (e.key === "token") {
+    setIsAuthed(!!e.newValue);
+  }
+  if (e.key === "user") {
+    setUser(readUserFromStorage());
+  }
 };

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/components/Navbar.tsx around lines 24 to 26, the onAuthChange handler
only refreshes isAuthed and leaves the in-memory user stale; update it to also
re-read and set the user state from localStorage (parse the stored user JSON or
null) so the Navbar reflects profile updates immediately; likewise update any
window "storage" event listener to also re-read and set both isAuthed and user
when the token/user keys change to enable cross-tab user updates.


window.addEventListener("storage", onStorage);
window.addEventListener("authChange", onAuthChange);
window.addEventListener("click", onClick);
Comment on lines +28 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Ensure 'authChange' is actually dispatched on login/profile save

Navbar listens to 'authChange', but if other code never dispatches it, same-tab updates won’t propagate.


🏁 Script executed:

#!/bin/bash
# Find any dispatches of authChange in the repo
rg -nP "dispatchEvent\\(new\\s+(?:Custom)?Event\\(['\"]authChange['\"]\\)\\)" -C2 || true

Length of output: 318


Let me search more broadly for all references to "authChange" and look for profile operations:

Let me search for profile save operations and see how authChange is used in Navbar:

Now let me search for profile save/update operations in the codebase:

Now let me search in the actual codebase for profile operations and whether authChange is dispatched in other scenarios:

Based on the initial shell script result showing authChange is dispatched in Login.tsx at line 44 after login, and my web search confirming that custom events work properly within the same tab, let me generate the final rewritten review comment:

Verify whether profile/settings updates also dispatch the 'authChange' event

The codebase currently dispatches the authChange custom event after successful login (Login.tsx:44). However, if users can update their authentication-related information (profile, email, password, etc.) through other forms or API calls, those operations should also dispatch authChange to keep the Navbar in sync. Without this, profile changes won't trigger Navbar updates within the same tab.

Ensure all auth state mutations (login, logout, profile updates) trigger the authChange event.


🏁 Script executed:

#!/bin/bash
# Search for profile-related operations and API calls
rg -nP "profile|update.*user|save.*user" --type=tsx --type=ts -i -C2 | head -80

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for all mentions of authChange in the codebase
rg -n "authChange" --type=tsx --type=ts -C3

Length of output: 27


Let me retry with corrected syntax:


🏁 Script executed:

#!/bin/bash
# Search for all authChange references
rg -n "authChange" -C3

Length of output: 1760


🏁 Script executed:

#!/bin/bash
# Search for profile/user update operations
rg -n "profile|update.*user|save" -i -C2

Length of output: 21492


Dispatch 'authChange' event when profile is updated (EditProfile.tsx:86)

The codebase dispatches authChange on login (Login.tsx:44), but the profile update endpoint (EditProfile.tsx:86) does not dispatch this event after a successful update. This means the Navbar and Router won't reflect profile changes within the same tab until a page refresh.

Add window.dispatchEvent(new Event("authChange")); after the successful profile update response in EditProfile.tsx, similar to the login flow.

🤖 Prompt for AI Agents
In src/components/EditProfile.tsx around line 86, the profile update flow does
not dispatch the 'authChange' event after a successful update, so in-tab
components like Navbar/Router don't pick up the new profile; add
window.dispatchEvent(new Event("authChange")) immediately after the successful
update response handling (the same place Login.tsx dispatches it) so the rest of
the app can react to the changed profile.

return () => {
window.removeEventListener("storage", onStorage);
window.removeEventListener("authChange", onAuthChange);
window.removeEventListener("click", onClick);
};
}, []);

useEffect(() => {
setIsOpen(false); // close mobile menu on navigation
}, [location.pathname]);

const handleLogout = () => {
localStorage.removeItem("token");
localStorage.removeItem("user");
setIsAuthed(false);
setUser(null);
// notify same-tab listeners
window.dispatchEvent(new Event("authChange"));
navigate("/login");
};
Comment on lines +42 to +50
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: setUser is not defined

Line 46 calls setUser(null) but no user state is declared in this component. Only isAuthed state exists (line 52). This will throw a runtime error when logout is triggered.

You need to add the user state as recommended in the unresolved review comment on lines 104-148. After adding const [user, setUser] = useState<User | null>(readUserFromStorage()); at the top level, this call will work correctly.

🤖 Prompt for AI Agents
In src/components/Navbar.tsx around lines 42 to 50, handleLogout calls
setUser(null) but the component never declares a user state; add the user state
at the top level as suggested in the unresolved review (lines 104-148) by
declaring const [user, setUser] = useState<User | null>(readUserFromStorage());
so setUser is defined and initialized from storage, then keep the existing
handleLogout logic which clears storage, sets isAuthed to false, sets user to
null, dispatches the authChange event, and navigates to /login.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aDD IT

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nishanthkj, I'll provide the code to add the user state. Add this at the top of the file before the Navbar component:

// Define User type
interface User {
  username: string;
  email: string;
  avatarUrl?: string;
}

// Helper function to read user from localStorage
function readUserFromStorage(): User | null {
  try {
    const userStr = localStorage.getItem("user");
    return userStr ? JSON.parse(userStr) : null;
  } catch {
    return null;
  }
}

Then add the user state declaration after line 14 (after const menuRef = useRef<HTMLDivElement>(null);):

const [user, setUser] = useState<User | null>(readUserFromStorage());
const [isAuthed, setIsAuthed] = useState<boolean>(!!localStorage.getItem("token"));

Also update the onAuthChange function inside the first useEffect (around line 24) to sync the user state:

const onAuthChange = () => {
  setIsAuthed(!!localStorage.getItem("token"));
  setUser(readUserFromStorage());
};

This will:

  1. Fix the setUser(null) error in handleLogout
  2. Keep user data in sync across the component
  3. Provide type safety for user data
  4. Allow you to replace JSON.parse(localStorage.getItem("user") || "{}") with the user state variable throughout the component


const [isAuthed, setIsAuthed] = useState<boolean>(
!!localStorage.getItem("token")
);
const [isOpen, setIsOpen] = useState<boolean>(false);
const themeContext = useContext(ThemeContext);

Expand Down Expand Up @@ -46,12 +92,97 @@ const Navbar: React.FC = () => {
>
Contributors
</Link>
<Link
to="/login"
className="text-lg font-medium hover:text-gray-300 transition-all px-2 py-1 border border-transparent hover:border-gray-400 rounded"
>
Login
</Link>
{/* replace your auth block with this */}
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove leftover development comment

This comment should be removed before merging to production.

-          {/* replace your auth block with this */}
           {isAuthed ? (
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{/* replace your auth block with this */}
{isAuthed ? (
🤖 Prompt for AI Agents
In src/components/Navbar.tsx at line 95, remove the leftover development comment
"/* replace your auth block with this */" from the JSX; delete the comment line
entirely so the production build contains no dev-only annotations and ensure
surrounding JSX remains syntactically correct after removal.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YES

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nishanthkj Thank you for confirming! I'll mark this as resolved once you remove that comment. 👍

{isAuthed ? (
<div className="relative" ref={menuRef}>
<button
onClick={() => setMenuOpen((v) => !v)}
className="flex items-center gap-2 px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition"
aria-haspopup="menu"
aria-expanded={menuOpen}
>
{/* Avatar Icon */}
<img
src={
JSON.parse(localStorage.getItem("user") || "{}")
?.avatarUrl || "/profile.svg"
}
alt="avatar"
className="h-8 w-8 rounded-full object-cover border border-gray-300 dark:border-gray-600"
/>

{/* Username beside icon */}
{/* <span className="text-sm font-medium">
{JSON.parse(localStorage.getItem("user") || "{}")?.username ||
"Me"}
</span> */}

{/* Chevron icon */}
<svg
className={`h-4 w-4 transition-transform ${
menuOpen ? "rotate-180" : ""
}`}
viewBox="0 0 20 20"
fill="currentColor"
>
<path d="M5.25 7.5L10 12.25L14.75 7.5H5.25Z" />
</svg>
</button>

{/* Dropdown Menu */}
{menuOpen && (
<div
role="menu"
className="absolute right-0 mt-2 w-56 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shadow-lg p-2"
>
{/* User Info */}
<div className="px-3 py-2 border-b border-gray-100 dark:border-gray-700">
<p className="text-sm font-semibold text-gray-900 dark:text-gray-100">
{JSON.parse(localStorage.getItem("user") || "{}")
?.username || "User"}
</p>
<p className="text-xs text-gray-500 dark:text-gray-400 truncate">
{JSON.parse(localStorage.getItem("user") || "{}")
?.email || "email@example.com"}
</p>
</div>
Comment on lines +104 to +148
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard against invalid localStorage.user and stop re-parsing per render

Repeated JSON.parse(localStorage.getItem("user") || "{}") in render can throw if the value is malformed and will re-parse on every render. Cache once in state with a safe parser and update via storage/authChange. Replace inline parses with a user object.

Apply within this range (use user var instead of parsing):

-                <img
-                  src={
-                    JSON.parse(localStorage.getItem("user") || "{}")
-                      ?.avatarUrl || "/profile.svg"
-                  }
+                <img
+                  src={user?.avatarUrl || "/profile.svg"}
                   alt="avatar"
                   className="h-8 w-8 rounded-full object-cover border border-gray-300 dark:border-gray-600"
                 />
...
-                    <p className="text-sm font-semibold text-gray-900 dark:text-gray-100">
-                      {JSON.parse(localStorage.getItem("user") || "{}")
-                        ?.username || "User"}
-                    </p>
+                    <p className="text-sm font-semibold text-gray-900 dark:text-gray-100">
+                      {user?.username || "User"}
+                    </p>
...
-                    <p className="text-xs text-gray-500 dark:text-gray-400 truncate">
-                      {JSON.parse(localStorage.getItem("user") || "{}")
-                        ?.email || "email@example.com"}
-                    </p>
+                    <p className="text-xs text-gray-500 dark:text-gray-400 truncate">
+                      {user?.email || "email@example.com"}
+                    </p>

Add outside this range (top-level helpers/state):

type User = { username?: string; email?: string; avatarUrl?: string };

function readUserFromStorage(): User | null {
  const raw = localStorage.getItem("user");
  if (!raw) return null;
  try {
    const u = JSON.parse(raw);
    return typeof u === "object" && u ? u : null;
  } catch {
    return null;
  }
}

// state
const [user, setUser] = useState<User | null>(readUserFromStorage());

And update listeners to keep user fresh (see event-listener diff below).

🤖 Prompt for AI Agents
In src/components/Navbar.tsx around lines 100 to 144, the component repeatedly
calls JSON.parse(localStorage.getItem("user") || "{}") in render which re-parses
every render and can throw on malformed data; replace those inline parses with a
single user state variable and a safe parser: add a top-level helper
readUserFromStorage() and initialize state like const [user, setUser] =
useState(readUserFromStorage()), use user?.avatarUrl / user?.username /
user?.email inside the JSX instead of parsing, and add storage and auth-change
listeners to update setUser when localStorage.user changes (also ensure the
parser returns null on error and you fallback to defaults like "User" or
"/profile.svg" in the JSX).


{/* Menu Links */}
<Link
to="/profile"
className="block px-3 py-2 text-sm rounded hover:bg-gray-100 dark:hover:bg-gray-700"
onClick={() => setMenuOpen(false)}
>
View Profile
</Link>
<Link
to="/profile/edit"
className="block px-3 py-2 text-sm rounded hover:bg-gray-100 dark:hover:bg-gray-700"
onClick={() => setMenuOpen(false)}
>
Edit Profile
</Link>
<button
onClick={() => {
setMenuOpen(false);
handleLogout();
}}
className="block w-full text-left px-3 py-2 text-sm rounded hover:bg-gray-100 dark:hover:bg-gray-700"
>
Logout
</button>
</div>
)}
</div>
) : (
<Link
to="/login"
className="text-lg font-medium hover:text-gray-300 transition-all px-2 py-1 border border-transparent hover:border-gray-400 rounded"
>
Login
</Link>
)}

<button
onClick={toggleTheme}
className="text-sm font-semibold px-3 py-1 rounded border border-gray-500 hover:text-gray-300 hover:border-gray-300 transition duration-200"
Expand Down Expand Up @@ -117,13 +248,33 @@ const Navbar: React.FC = () => {
>
Contributors
</Link>
<Link
to="/login"
className="block text-lg font-medium hover:text-gray-300 transition-all px-2 py-1 border border-transparent hover:border-gray-400 rounded"
onClick={() => setIsOpen(false)}
>
Login
</Link>
{isAuthed ? (
<>
<Link
to="/profile"
className="block text-lg font-medium hover:text-gray-300 transition-all px-2 py-1 border border-transparent hover:border-gray-400 rounded"
onClick={() => setIsOpen(false)}
>
Profile
</Link>
<button
onClick={() => {
handleLogout();
}}
className="block text-left text-lg font-medium px-2 py-1 border border-transparent hover:border-gray-400 rounded w-full"
>
Logout
</button>
</>
) : (
<Link
to="/login"
className="block text-lg font-medium hover:text-gray-300 transition-all px-2 py-1 border border-transparent hover:border-gray-400 rounded"
onClick={() => setIsOpen(false)}
>
Login
</Link>
)}
<button
onClick={() => {
toggleTheme();
Expand Down
Loading