-
Notifications
You must be signed in to change notification settings - Fork 62
Add app-wide AuthContext, Profile page, and navbar integration (Closes #47) #214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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; |
| 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 || ""); | ||
|
|
||
| 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; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Major issue: Authentication error handling removed. The 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.
🤖 Prompt for AI Agents |
||
|
|
||
| const { | ||
| issues, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Fix hook ordering to comply with Rules of Hooks.
The
useStatehooks 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
🧰 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