Skip to content
Closed
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
2 changes: 2 additions & 0 deletions src/Routes/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Contributors from "../pages/Contributors/Contributors";
import Signup from "../pages/Signup/Signup.tsx";
import Login from "../pages/Login/Login.tsx";
import ContributorProfile from "../pages/ContributorProfile/ContributorProfile.tsx";
import Profile from "../pages/Profile/Profile";
import Home from "../pages/Home/Home.tsx";

const Router = () => {
Expand All @@ -18,6 +19,7 @@ const Router = () => {
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/contributors" element={<Contributors />} />
<Route path="/profile" element={<Profile />} />
<Route path="/contributor/:username" element={<ContributorProfile />} />
</Routes>
);
Expand Down
84 changes: 78 additions & 6 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { Link } from "react-router-dom";
import { useState, useContext } from "react";
import { ThemeContext } from "../context/ThemeContext";
import { AuthContext } from "../context/AuthContext";
import { useNavigate } from "react-router-dom";
import { Moon, Sun } from 'lucide-react';


const Navbar: React.FC = () => {

const [isOpen, setIsOpen] = useState<boolean>(false);
const themeContext = useContext(ThemeContext);
const auth = useContext(AuthContext);
const navigate = useNavigate();

if (!themeContext)
return null;
Expand Down Expand Up @@ -46,12 +50,57 @@ 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>
{auth && auth.username ? (
<div className="relative">
<button
onClick={() => setIsOpen((v) => !v)}
className="text-lg font-medium px-2 py-1 border border-transparent rounded flex items-center space-x-2"
>
<span>{auth.username}</span>
<svg
className="w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>

{/* Dropdown */}
{isOpen && (
<div className="absolute right-0 mt-2 w-44 bg-white dark:bg-gray-700 text-black dark:text-white rounded shadow-lg z-50">
<button
onClick={() => {
setIsOpen(false);
navigate('/profile');
}}
className="block w-full text-left px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600"
>
View / Edit Profile
</button>
<button
onClick={() => {
setIsOpen(false);
auth.logout();
navigate('/login');
}}
className="block w-full text-left px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600"
>
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 @@ -124,6 +173,29 @@ const Navbar: React.FC = () => {
>
Login
</Link>
{auth && auth.username && (
<>
<button
onClick={() => {
setIsOpen(false);
navigate('/profile');
}}
className="block text-left w-full text-lg font-medium hover:text-gray-300 transition-all px-2 py-1 border border-transparent hover:border-gray-400 rounded"
>
View / Edit Profile
</button>
<button
onClick={() => {
setIsOpen(false);
auth.logout();
navigate('/login');
}}
className="block text-left w-full text-lg font-medium hover:text-gray-300 transition-all px-2 py-1 border border-transparent hover:border-gray-400 rounded"
>
Logout
</button>
</>
)}
<button
onClick={() => {
toggleTheme();
Expand Down
74 changes: 74 additions & 0 deletions src/context/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, { createContext, useState, useEffect } from "react";
import { Octokit } from "octokit";

type AuthContextType = {
username: string;
token: string;
setUsername: (u: string) => void;
setToken: (t: string) => void;
logout: () => void;
getOctokit: () => any | null;
};

export const AuthContext = createContext<AuthContextType | null>(null);

const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [username, setUsernameState] = useState<string>("");
const [token, setTokenState] = useState<string>("");

useEffect(() => {
// hydrate from localStorage if present
try {
const s = localStorage.getItem("gh_username");
const t = localStorage.getItem("gh_token");
if (s) setUsernameState(s);
if (t) setTokenState(t);
} catch (e) {
// ignore
}
}, []);

const setUsername = (u: string) => {
setUsernameState(u);
try {
if (u) localStorage.setItem("gh_username", u);
else localStorage.removeItem("gh_username");
} catch (e) {}
};

const setToken = (t: string) => {
setTokenState(t);
try {
if (t) localStorage.setItem("gh_token", t);
else localStorage.removeItem("gh_token");
} catch (e) {}
};

const logout = () => {
setUsernameState("");
setTokenState("");
try {
localStorage.removeItem("gh_username");
localStorage.removeItem("gh_token");
} catch (e) {}
};

const getOctokit = () => {
if (!username || !token) return null;
try {
return new Octokit({ auth: token });
} catch (e) {
return null;
}
};

return (
<AuthContext.Provider
value={{ username, token, setUsername, setToken, logout, getOctokit }}
>
{children}
</AuthContext.Provider>
);
};

export default AuthProvider;
9 changes: 6 additions & 3 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ import App from "./App.tsx";
import "./index.css";
import { BrowserRouter } from "react-router-dom";
import ThemeWrapper from "./context/ThemeContext.tsx";
import AuthProvider from "./context/AuthContext";

createRoot(document.getElementById("root")!).render(
<StrictMode>
<ThemeWrapper>
<BrowserRouter>
<App />
</BrowserRouter>
<AuthProvider>
<BrowserRouter>
<App />
</BrowserRouter>
</AuthProvider>
</ThemeWrapper>
</StrictMode>
);
63 changes: 63 additions & 0 deletions src/pages/Profile/Profile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React, { useContext, useState } from "react";
import { AuthContext } from "../../context/AuthContext";
import { useNavigate } from "react-router-dom";

const Profile: React.FC = () => {
const auth = useContext(AuthContext);
const navigate = useNavigate();

if (!auth) return null;

const { username, token, setUsername, setToken } = auth;

const [localUsername, setLocalUsername] = useState(username || "");
const [localToken, setLocalToken] = useState(token || "");
Comment on lines +6 to +14
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: Fix hook ordering to comply with Rules of Hooks.

The useState hooks on lines 13-14 are called after the early return on line 9, violating React's Rules of Hooks. Hooks must be called unconditionally in the same order on every render.

Apply this diff to fix the hook ordering:

 const Profile: React.FC = () => {
   const auth = useContext(AuthContext);
   const navigate = useNavigate();
+  const [localUsername, setLocalUsername] = useState("");
+  const [localToken, setLocalToken] = useState("");
 
   if (!auth) return null;
 
   const { username, token, setUsername, setToken } = auth;
-
-  const [localUsername, setLocalUsername] = useState(username || "");
-  const [localToken, setLocalToken] = useState(token || "");
+
+  // Sync with auth context values on mount/change
+  React.useEffect(() => {
+    setLocalUsername(username || "");
+    setLocalToken(token || "");
+  }, [username, token]);
📝 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
const auth = useContext(AuthContext);
const navigate = useNavigate();
if (!auth) return null;
const { username, token, setUsername, setToken } = auth;
const [localUsername, setLocalUsername] = useState(username || "");
const [localToken, setLocalToken] = useState(token || "");
const auth = useContext(AuthContext);
const navigate = useNavigate();
const [localUsername, setLocalUsername] = useState("");
const [localToken, setLocalToken] = useState("");
if (!auth) return null;
const { username, token, setUsername, setToken } = auth;
// Sync with auth context values on mount/change
React.useEffect(() => {
setLocalUsername(username || "");
setLocalToken(token || "");
}, [username, token]);
🧰 Tools
🪛 Biome (2.1.2)

[error] 13-13: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 14-14: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🤖 Prompt for AI Agents
In src/pages/Profile/Profile.tsx around lines 6 to 14, the useState hooks are
called after an early return (if (!auth) return null), violating the Rules of
Hooks; move the hooks to execute unconditionally before any early returns: call
useContext(AuthContext), then declare the useState hooks (initializing with
auth?.username and auth?.token or empty strings) immediately, then perform the
guard/early-return based on auth (or render a loading/null) and finally
destructure setters from auth if needed; ensure hook order is preserved on every
render.


const handleSave = (e: React.FormEvent) => {
e.preventDefault();
setUsername(localUsername);
setToken(localToken);
navigate("/");
};

return (
<div className="max-w-3xl mx-auto mt-6 p-6 bg-white dark:bg-gray-800 dark:text-white shadow rounded">
<h2 className="text-2xl font-semibold mb-4">Profile</h2>
<form onSubmit={handleSave} className="space-y-4">
<div>
<label className="block text-sm font-medium">GitHub Username</label>
<input
className="mt-1 block w-full rounded border px-3 py-2 bg-white dark:bg-gray-700"
value={localUsername}
onChange={(e) => setLocalUsername(e.target.value)}
required
/>
</div>

<div>
<label className="block text-sm font-medium">Personal Access Token</label>
<input
className="mt-1 block w-full rounded border px-3 py-2 bg-white dark:bg-gray-700"
value={localToken}
onChange={(e) => setLocalToken(e.target.value)}
type="password"
placeholder="••••••••"
/>
</div>

<div className="flex space-x-2">
<button className="px-4 py-2 bg-blue-600 text-white rounded">Save</button>
<button
type="button"
onClick={() => navigate(-1)}
className="px-4 py-2 border rounded"
>
Cancel
</button>
</div>
</form>
</div>
);
};

export default Profile;
19 changes: 10 additions & 9 deletions src/pages/Tracker/Tracker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ import {
InputLabel,
} from "@mui/material";
import { useTheme } from "@mui/material/styles";
import { useGitHubAuth } from "../../hooks/useGitHubAuth";
import { useContext } from "react";
import { AuthContext } from "../../context/AuthContext";
import { useGitHubData } from "../../hooks/useGitHubData";

const ROWS_PER_PAGE = 10;
Expand All @@ -49,14 +50,14 @@ const Home: React.FC = () => {

const theme = useTheme();

const {
username,
setUsername,
token,
setToken,
error: authError,
getOctokit,
} = useGitHubAuth();
const auth = useContext(AuthContext);

const username = auth?.username || "";
const setUsername = auth?.setUsername || (() => {});
const token = auth?.token || "";
const setToken = auth?.setToken || (() => {});
const authError = "";
const getOctokit = auth?.getOctokit || (() => null);
Comment on lines +53 to +60
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

Major issue: Authentication error handling removed.

The authError is hardcoded to an empty string (line 59), removing any authentication error feedback that may have been present in the previous useGitHubAuth hook implementation. Users won't see errors related to invalid credentials, API rate limits, or token issues.

Consider adding error state to AuthContext or handling authentication errors within the Tracker component:

  const auth = useContext(AuthContext);
  
  const username = auth?.username || "";
  const setUsername = auth?.setUsername || (() => {});
  const token = auth?.token || "";
  const setToken = auth?.setToken || (() => {});
- const authError = "";
+ const [authError, setAuthError] = useState("");
  const getOctokit = auth?.getOctokit || (() => null);

Then wrap the getOctokit call or data fetching with try-catch to set authError when authentication fails.

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

🤖 Prompt for AI Agents
In src/pages/Tracker/Tracker.tsx around lines 53 to 60, authError is hardcoded
to an empty string which strips out authentication error handling; restore a
real error state by either reading authError from AuthContext (add it to the
context if missing) or creating a local state const [authError, setAuthError] =
useState<string>("") and remove the hardcoded value, then wrap getOctokit calls
and any GitHub API data fetching in try/catch blocks that call setAuthError with
the caught error message (and clear it on success); ensure any UI that showed
authentication errors uses this error state so users see credential/API/token
issues.


const {
issues,
Expand Down