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
105 changes: 105 additions & 0 deletions src/components/DeveloperSummary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React, { useEffect, useState } from "react";
import {
Paper,
Typography,
Box,
Avatar,
Chip,
} from "@mui/material";

interface DeveloperProfile {
name: string;
bio: string;
followers: number;
following: number;
public_repos: number;
avatar_url: string;
}

interface Props {
username: string;
}

const DeveloperSummary: React.FC<Props> = ({ username }) => {
const [profile, setProfile] =
useState<DeveloperProfile | null>(null);

useEffect(() => {
if (!username) return;

fetch(`https://api.github.com/users/${username}`)
.then((res) => res.json())
.then((data) => setProfile(data))
.catch(console.error);
}, [username]);
Comment on lines +27 to +34
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Error responses are stored and rendered as profile data.

res.json() is called unconditionally, but the GitHub API returns a JSON body even for failures (e.g. {"message":"Not Found"} on 404, or rate-limit/403). That error object is then passed to setProfile, so the card renders with profile.public_repos etc. as undefined — e.g. "undefined public repositories and undefined followers". Check res.ok before storing.

🛡️ Proposed fix to guard non-OK responses
     fetch(`https://api.github.com/users/${username}`)
-      .then((res) => res.json())
+      .then((res) => {
+        if (!res.ok) throw new Error(`GitHub API error: ${res.status}`);
+        return res.json();
+      })
       .then((data) => setProfile(data))
       .catch(console.error);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/DeveloperSummary.tsx` around lines 27 - 34, The fetch in the
useEffect (triggered by username) calls res.json() unconditionally and passes
that result to setProfile, causing error payloads (e.g. {"message":"Not Found"})
to be treated as a valid profile; update the fetch chain to check res.ok before
setting state: after fetch, inspect res.ok and if false extract the error
message (e.g. await res.json() or res.text()) and handle it (throw or set an
error state) so setProfile only receives a successful user object; modify the
useEffect/fetch block that references username and setProfile to guard non-OK
responses and avoid rendering error objects as profile data.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fetch fires on every keystroke and is subject to a race condition.

In Tracker.tsx the username prop is bound directly to the input's onChange (live value), so this effect runs on every keystroke. Each run issues an unauthenticated GitHub request, which quickly exhausts the 60 requests/hour anonymous limit. Additionally, with no cleanup/abort, responses can resolve out of order and overwrite newer state, and the previous profile is never cleared when username changes (stale card shown for the new name).

Consider debouncing and aborting in-flight requests. If you also pass the existing token from the Tracker, requests would be authenticated and far less rate-limited.

♻️ Proposed debounce + abort
   useEffect(() => {
     if (!username) return;
 
-    fetch(`https://api.github.com/users/${username}`)
-      .then((res) => res.json())
-      .then((data) => setProfile(data))
-      .catch(console.error);
-  }, [username]);
+    const controller = new AbortController();
+    const timer = setTimeout(() => {
+      fetch(`https://api.github.com/users/${username}`, {
+        signal: controller.signal,
+      })
+        .then((res) => {
+          if (!res.ok) throw new Error(`GitHub API error: ${res.status}`);
+          return res.json();
+        })
+        .then((data) => setProfile(data))
+        .catch((err) => {
+          if (err.name !== "AbortError") console.error(err);
+        });
+    }, 500);
+
+    return () => {
+      clearTimeout(timer);
+      controller.abort();
+    };
+  }, [username]);
📝 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
useEffect(() => {
if (!username) return;
fetch(`https://api.github.com/users/${username}`)
.then((res) => res.json())
.then((data) => setProfile(data))
.catch(console.error);
}, [username]);
useEffect(() => {
if (!username) return;
const controller = new AbortController();
const timer = setTimeout(() => {
fetch(`https://api.github.com/users/${username}`, {
signal: controller.signal,
})
.then((res) => {
if (!res.ok) throw new Error(`GitHub API error: ${res.status}`);
return res.json();
})
.then((data) => setProfile(data))
.catch((err) => {
if (err.name !== "AbortError") console.error(err);
});
}, 500);
return () => {
clearTimeout(timer);
controller.abort();
};
}, [username]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/DeveloperSummary.tsx` around lines 27 - 34, The effect in
DeveloperSummary (the useEffect that reads username and calls fetch and
setProfile) runs on every keystroke, causes unauthenticated rate-limit issues,
can suffer raced responses, and never clears stale profile; update it to
debounce/defer requests and abort in-flight fetches: create an AbortController
inside the effect and call controller.abort in the cleanup, start the fetch only
after a short debounce timeout (clear the timeout in cleanup), clear profile
immediately when username becomes empty or when starting a new request, and
accept an optional token prop from Tracker to include an Authorization header
for authenticated requests; reference the useEffect, username, setProfile, and
fetch call when making these changes.


if (!profile) return null;

return (
<Paper
elevation={2}
sx={{
p: 3,
mb: 3,
borderRadius: 3,
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: 2,
mb: 2,
}}
>
<Avatar
src={profile.avatar_url}
sx={{ width: 64, height: 64 }}
/>

<Box>
<Typography variant="h6">
{profile.name || username}
</Typography>

{profile.bio && (
<Typography
variant="body2"
color="text.secondary"
>
{profile.bio}
</Typography>
)}
</Box>
</Box>

<Box
sx={{
display: "flex",
gap: 1,
flexWrap: "wrap",
mb: 2,
}}
>
<Chip
label={`${profile.public_repos} Repositories`}
/>
<Chip
label={`${profile.followers} Followers`}
/>
<Chip
label={`${profile.following} Following`}
/>
</Box>

<Typography>
{profile.name || username} has{" "}
{profile.public_repos} public repositories and{" "}
{profile.followers} followers, showing active
participation in the GitHub community.
</Typography>
</Paper>
);
};

export default DeveloperSummary;
4 changes: 3 additions & 1 deletion src/pages/Tracker/Tracker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import { useTheme } from "@mui/material/styles";
import { useGitHubAuth } from "../../hooks/useGitHubAuth";
import { useGitHubData } from "../../hooks/useGitHubData";
import { KeyIcon } from "lucide-react";
import DeveloperSummary from "../../components/DeveloperSummary";


const ROWS_PER_PAGE = 10;

Expand Down Expand Up @@ -241,7 +243,7 @@ const Home: React.FC = () => {
</Box>
</form>
</Paper>

<DeveloperSummary username={username} />
{/* Filters */}
<Box sx={{ mb: 2, display: "flex", flexWrap: "wrap", gap: 2 }}>
<TextField
Expand Down
Loading