Skip to content
Merged
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 @@ -11,6 +11,7 @@ import ProfilePage from "../pages/Profile/ProfilePage.tsx";
import EditProfilePage from "../pages/Profile/EditProfilePage.tsx";
import Activity from "../pages/Activity.tsx";
import PrivacyPolicy from "../pages/Privacy/PrivacyPolicy.tsx"; // βœ… Updated import path to match your new folder structure
import RepoCompare from "../pages/RepoCompare/RepoCompare.tsx";

const Router = () => {
return (
Expand All @@ -26,6 +27,7 @@ const Router = () => {
<Route path="/me" element={<ProfilePage />}></Route>
<Route path="/profile/edit" element={<EditProfilePage />}></Route>
<Route path="/activity" element={<Activity />} />
<Route path="/compare" element={<RepoCompare />} />

{/* Privacy Policy page route */}
<Route path="/privacy" element={<PrivacyPolicy />} />
Expand Down
50 changes: 19 additions & 31 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import { NavLink, Link } from "react-router-dom";
import { useState, useContext } from "react";
import { ThemeContext } from "../context/ThemeContext";
import ProfileDropDown from "./Profile/ProfileDropDown";
import { logoutUser } from "../services/auth";

import { Moon, Sun, Menu, X, Github } from "lucide-react";
import { Moon, Sun, Menu, X } from "lucide-react";

const Navbar: React.FC = () => {
const [isOpen, setIsOpen] = useState(false);

const themeContext = useContext(ThemeContext);

if (!themeContext) return null;

const { toggleTheme, mode } = themeContext;
Expand Down Expand Up @@ -47,7 +43,6 @@ const Navbar: React.FC = () => {
alt="CRL Icon"
className="h-8 w-8 object-contain"
/>

<span>GitHub Tracker</span>
</Link>

Expand All @@ -61,6 +56,11 @@ const Navbar: React.FC = () => {
Tracker
</NavLink>

{/* βœ… NEW FEATURE */}
<NavLink to="/compare" className={navLinkStyles}>
Compare
</NavLink>

<NavLink to="/contributors" className={navLinkStyles}>
Contributors
</NavLink>
Expand Down Expand Up @@ -122,27 +122,20 @@ const Navbar: React.FC = () => {
<div className="md:hidden border-t border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900">
<div className="px-6 py-5 flex flex-col gap-3">

<NavLink
to="/"
className={navLinkStyles}
onClick={closeMenu}
>
<NavLink to="/" className={navLinkStyles} onClick={closeMenu}>
Home
</NavLink>

<NavLink
to="/track"
className={navLinkStyles}
onClick={closeMenu}
>
<NavLink to="/track" className={navLinkStyles} onClick={closeMenu}>
Tracker
</NavLink>

<NavLink
to="/contributors"
className={navLinkStyles}
onClick={closeMenu}
>
{/* βœ… NEW FEATURE */}
<NavLink to="/compare" className={navLinkStyles} onClick={closeMenu}>
Compare
</NavLink>

<NavLink to="/contributors" className={navLinkStyles} onClick={closeMenu}>
Contributors
</NavLink>
{!user && (
Expand Down Expand Up @@ -182,20 +175,15 @@ const Navbar: React.FC = () => {
</>
)}

<button
onClick={() => {
toggleTheme();
setIsOpen(false);
}}
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 w-full text-left"
>
{mode === "dark" ? "🌞 Light" : "πŸŒ™ Dark"}
</button>
<NavLink to="/login" className={navLinkStyles} onClick={closeMenu}>
Login
</NavLink>

</div>
</div>
)}
</nav>
);
};

export default Navbar;
export default Navbar;
176 changes: 176 additions & 0 deletions src/pages/RepoCompare/RepoCompare.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { useState } from "react";

type RepoData = {
full_name: string;
stargazers_count: number;
forks_count: number;
open_issues_count: number;
watchers_count: number;
language: string;
Comment on lines +8 to +9
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

🧩 Analysis chain

🌐 Web query:

In the GitHub REST API repository response, what is the semantic difference between watchers_count, stargazers_count, and subscribers_count, and which one represents actual watchers?

πŸ’‘ Result:

In the GitHub REST API response, the semantic difference between these fields is as follows: subscribers_count represents the number of actual watchersβ€”users who are subscribed to receive notifications for activity in a repository [1][2]. watchers, watchers_count, and stargazers_count all represent the number of users who have starred a repository [1][2]. The confusion stems from a historical change made in 2012, where GitHub repurposed the "watching" functionality and introduced "starring" [1]. While the API terminology was maintained for backward compatibility, watchers_count and its related fields currently function as a count of stars, not repository subscribers [1][2]. If you need to identify users who are actively watching (subscribing) to a repository, you should use subscribers_count [3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant sections around the referenced line ranges
sed -n '1,120p' src/pages/RepoCompare/RepoCompare.tsx | nl -ba | sed -n '1,120p'
echo "----"
sed -n '120,220p' src/pages/RepoCompare/RepoCompare.tsx | nl -ba | sed -n '120,220p'
echo "----"
# Specifically extract the likely mapping/transform areas by searching for watchers_count
rg -n "watchers_count|subscribers_count|stargazers_count|watchers" src/pages/RepoCompare/RepoCompare.tsx

Repository: GitMetricsLab/github_tracker

Length of output: 115


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== top of file =="
sed -n '1,60p' src/pages/RepoCompare/RepoCompare.tsx | cat -n

echo "== around lines 120-180 =="
sed -n '120,190p' src/pages/RepoCompare/RepoCompare.tsx | cat -n

echo "== occurrences of relevant fields =="
rg -n "watchers_count|subscribers_count|stargazers_count" src/pages/RepoCompare/RepoCompare.tsx || true

echo "== occurrences of string 'Watch' in UI =="
rg -n "Watch|watcher|Star|stargazer|subscriber" src/pages/RepoCompare/RepoCompare.tsx || true

Repository: GitMetricsLab/github_tracker

Length of output: 4843


Fix β€œWatchers” mapping (GitHub watchers_count β‰  watchers/subscribers)

  watchers_count: number;
  language: string;

The UI label β€œπŸ‘€ Watchers” renders watchers_count, but GitHub REST’s watchers_count (like stargazers_count) is effectively the repo’s star count. Actual watchers are subscribers_count. This will duplicate stars and misreport watcher analyticsβ€”render subscribers_count for β€œWatchers” (or relabel the row as Stars-equivalent).

Also applies to: 135-137

πŸ€– 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/pages/RepoCompare/RepoCompare.tsx` around lines 8 - 9, The repo model and
UI currently use GitHub's watchers_count (which is actually the star count) for
the "πŸ‘€ Watchers" row; update the type/property and rendering to use
subscribers_count instead. Replace occurrences of watchers_count in the
RepoCompare component (the interface/type declaration near the top and the
render row that shows "πŸ‘€ Watchers" β€” also at the referenced lines ~135-137)
with subscribers_count, or alternatively relabel the UI to "Stars" if you intend
to keep watchers_count; ensure the displayed value comes from the
subscribers_count field everywhere in RepoCompare.tsx.

updated_at: string;
description: string;
};

const RepoCompare = () => {
const [repo1, setRepo1] = useState("");
const [repo2, setRepo2] = useState("");

const [data1, setData1] = useState<RepoData | null>(null);
const [data2, setData2] = useState<RepoData | null>(null);

const [loading, setLoading] = useState(false);
const [error, setError] = useState("");

const fetchRepo = async (repo: string): Promise<RepoData> => {
const res = await fetch(`https://api.github.com/repos/${repo}`);

if (!res.ok) {
throw new Error(`Repository not found: ${repo}`);
}

return res.json();
};

const handleCompare = async () => {
if (!repo1 || !repo2) {
setError("Please enter both repositories");
return;
}

try {
setLoading(true);
setError("");

const [r1, r2] = await Promise.all([
fetchRepo(repo1),
fetchRepo(repo2),
]);

setData1(r1);
setData2(r2);
} catch (err: any) {
setError(err.message || "Something went wrong");
Comment on lines +51 to +52
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 | 🟑 Minor | ⚑ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify explicit-any usage and lint rule presence.
rg -n --type=ts --type=tsx 'catch\s*\(\s*[^)]*:\s*any\s*\)'
rg -n '`@typescript-eslint/no-explicit-any`' .

Repository: GitMetricsLab/github_tracker

Length of output: 100


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect the target file around the referenced lines
echo "=== RepoCompare.tsx lines 40-80 ==="
nl -ba src/pages/RepoCompare/RepoCompare.tsx | sed -n '40,80p'

# 2) Find explicit-any in catch clauses (avoid invalid --type=tsx)
echo "=== catch (..: any) occurrences ==="
rg -n "catch\s*\(\s*[^)]*\s:\s*any\s*\)" -S . || true

# 3) Check whether `@typescript-eslint/no-explicit-any` exists in ESLint config
echo "=== `@typescript-eslint/no-explicit-any` occurrences ==="
rg -n "`@typescript-eslint/no-explicit-any`" -S . || true

# 4) List likely ESLint config files to understand whether the rule is enabled
echo "=== ESLint config files ==="
fd -a -t f -e js -e cjs -e mjs -e json -e yaml -e yml -e ts \
  .eslintrc .eslintrc.* eslint.config .eslintignore package.json 2>/dev/null || true

Repository: GitMetricsLab/github_tracker

Length of output: 169


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== command availability ==="
command -v rg >/dev/null && echo "rg: ok" || echo "rg: missing"
command -v fd >/dev/null && echo "fd: ok" || echo "fd: missing"
command -v find >/dev/null && echo "find: ok" || echo "find: missing"
command -v cat >/dev/null && echo "cat: ok" || echo "cat: missing"
command -v sed >/dev/null && echo "sed: ok" || echo "sed: missing"
command -v head >/dev/null && echo "head: ok" || echo "head: missing"

# 1) Inspect the target file around the referenced lines
echo "=== RepoCompare.tsx lines 40-80 ==="
if [ -f "src/pages/RepoCompare/RepoCompare.tsx" ]; then
  cat -n src/pages/RepoCompare/RepoCompare.tsx | sed -n '40,90p'
else
  echo "Missing file: src/pages/RepoCompare/RepoCompare.tsx"
fi

# 2) Find explicit-any in catch clauses (avoid invalid --type=tsx)
echo "=== catch (...: any) occurrences ==="
rg -n "catch\s*\(\s*[^)]*\s:\s*any\s*\)" -S . || true

# 3) Check whether `@typescript-eslint/no-explicit-any` exists in ESLint config
echo "=== `@typescript-eslint/no-explicit-any` occurrences ==="
rg -n "`@typescript-eslint/no-explicit-any`" -S . || true

# 4) List likely ESLint config files to understand whether the rule is enabled
echo "=== ESLint config files ==="
# Prefer fd if available, otherwise fallback to find
if command -v fd >/dev/null; then
  fd -a -t f -e js -e cjs -e mjs -e json -e yaml -e yml -e ts \
    .eslintrc .eslintrc.* eslint.config .eslintignore package.json 2>/dev/null || true
else
  find . -maxdepth 4 -type f \( \
    -name ".eslintrc" -o -name ".eslintrc.*" -o -name "eslint.config.*" -o -name ".eslintignore" \
    -o -name "package.json" \
  \) 2>/dev/null || true
fi

# 5) If we found ESLint config file candidates, print the smallest relevant sections around the rule (if any)
echo "=== Snippets around rule in ESLint configs (if present) ==="
rg -n "`@typescript-eslint/no-explicit-any`" -S . -g'*.js' -g'*.cjs' -g'*.mjs' -g'*.json' -g'*.ts' -g'*.yml' -g'*.yaml' 2>/dev/null || true

Repository: GitMetricsLab/github_tracker

Length of output: 2005


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Files matching eslint config names ==="
fd -a -t f -e js -e cjs -e mjs -e json -e yaml -e yml -e ts \
  '.eslintrc' '.eslintrc.*' 'eslint.config.*' '.eslintignore' -H . 2>/dev/null || true

echo "=== Find eslint / typescript-eslint in package.json ==="
if [ -f package.json ]; then
  cat package.json | head -n 120
  echo "=== grep devDependencies/scripts for eslint ==="
  rg -n '"(eslint|typescript-eslint|`@typescript-eslint`)/' package.json || true
  rg -n 'eslint' package.json || true
else
  echo "Missing package.json"
fi

echo "=== Search for eslint configs and plugin usage ==="
rg -n "eslint\.config|no-explicit-any|`@typescript-eslint`" -S . || true

Repository: GitMetricsLab/github_tracker

Length of output: 2714


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== eslint.config.js ==="
if [ -f eslint.config.js ]; then
  cat -n eslint.config.js | sed -n '1,200p'
else
  echo "Missing eslint.config.js"
fi

echo "=== no-explicit-any references ==="
rg -n "no-explicit-any" -S . || true

echo "=== occurrences of ': any' in ts/tsx ==="
rg -n ":\s*any\b" --glob '*.{ts,tsx}' -S . || true

Repository: GitMetricsLab/github_tracker

Length of output: 1231


🌐 Web query:

typescript-eslint configs.recommended includes no-explicit-any

πŸ’‘ Result:

No. typescript-eslint’s configs.recommended does include the @typescript-eslint/no-explicit-any rule. The rule is documented as being enabled when you extend plugin:@typescript-eslint/recommended`` [1]. And the actual recommended configuration in the TypeScript ESLint repo sets @typescript-eslint/no-explicit-any: `error` [2]. So if you are seeing β€œconfigs.recommended includes no-explicit-any” reported as false/absent, that’s likely due to using a different config (e.g., `recommendedTypeChecked` vs a custom setup) or overriding the rule in your local ESLint config.

Citations:


Replace catch (err: any) with a typed unknown guard.

src/pages/RepoCompare/RepoCompare.tsx uses catch (err: any), which trips @typescript-eslint/no-explicit-any from the repo’s typescript-eslint recommended ESLint config.

Suggested fix
-    } catch (err: any) {
-      setError(err.message || "Something went wrong");
+    } catch (err: unknown) {
+      setError(err instanceof Error ? err.message : "Something went wrong");
     } finally {
🧰 Tools
πŸͺ› ESLint

[error] 51-51: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

πŸ€– 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/pages/RepoCompare/RepoCompare.tsx` around lines 51 - 52, The catch block
currently uses catch (err: any) which violates no-explicit-any; change it to
catch (err: unknown) and add a type guard to safely extract a message before
calling setError: inside the catch for the RepoCompare component, check if err
is an instance of Error (or has a string message) and call setError(err.message)
otherwise call setError(String(err) || "Something went wrong"); update the catch
around the async logic where setError is invoked so no explicit any is used.

} finally {
Comment on lines +40 to +53
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 | 🟑 Minor | ⚑ Quick win

Clear previous comparison data when a new request fails.

If a compare attempt fails, old data1/data2 remain visible under the new error message. Clear previous results before request (or in catch) to avoid stale analytics being shown as current.

Suggested fix
     try {
       setLoading(true);
       setError("");
+      setData1(null);
+      setData2(null);

@@
-    } catch (err: any) {
+    } catch (err: any) {
       setError(err.message || "Something went wrong");
     } finally {
πŸ“ 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
try {
setLoading(true);
setError("");
const [r1, r2] = await Promise.all([
fetchRepo(repo1),
fetchRepo(repo2),
]);
setData1(r1);
setData2(r2);
} catch (err: any) {
setError(err.message || "Something went wrong");
} finally {
try {
setLoading(true);
setError("");
setData1(null);
setData2(null);
const [r1, r2] = await Promise.all([
fetchRepo(repo1),
fetchRepo(repo2),
]);
setData1(r1);
setData2(r2);
} catch (err: any) {
setError(err.message || "Something went wrong");
} finally {
🧰 Tools
πŸͺ› ESLint

[error] 51-51: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

πŸ€– 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/pages/RepoCompare/RepoCompare.tsx` around lines 40 - 53, The current
compare flow leaves previous results visible when a new fetch fails; before
starting the new request (inside the try before await Promise.all or immediately
prior to setLoading(true)) clear stale results by calling setData1(null) and
setData2(null) (or equivalent empty-state values), or alternatively clear them
in the catch block before calling setError; update the logic around fetchRepo,
setData1 and setData2 so stale data is not shown when an error occurs.

setLoading(false);
}
};

const formatDate = (date: string) => {
return new Date(date).toLocaleDateString();
};

return (
<div style={{ padding: "20px", maxWidth: "1000px", margin: "auto" }}>
<h1 style={{ textAlign: "center" }}>πŸ” GitHub Repo Comparison</h1>

{/* INPUT SECTION */}
<div style={{ display: "flex", gap: "10px", marginBottom: "20px" }}>
<input
type="text"
placeholder="e.g. facebook/react"
value={repo1}
onChange={(e) => setRepo1(e.target.value)}
style={{ flex: 1, padding: "10px" }}
/>

<input
type="text"
placeholder="e.g. vuejs/core"
value={repo2}
onChange={(e) => setRepo2(e.target.value)}
style={{ flex: 1, padding: "10px" }}
/>

<button onClick={handleCompare} style={{ padding: "10px 20px" }}>
Compare
</button>
</div>

{/* ERROR */}
{error && (
<div style={{ color: "red", marginBottom: "10px" }}>{error}</div>
)}

{/* LOADING */}
{loading && <p>Loading comparison...</p>}

{/* RESULT TABLE */}
{data1 && data2 && (
<div style={{ overflowX: "auto" }}>
<table
style={{
width: "100%",
borderCollapse: "collapse",
textAlign: "center",
}}
>
<thead>
<tr>
<th style={thStyle}>Metric</th>
<th style={thStyle}>{data1.full_name}</th>
<th style={thStyle}>{data2.full_name}</th>
</tr>
</thead>

<tbody>
<tr>
<td style={tdStyle}>⭐ Stars</td>
<td style={tdStyle}>{data1.stargazers_count}</td>
<td style={tdStyle}>{data2.stargazers_count}</td>
</tr>

<tr>
<td style={tdStyle}>🍴 Forks</td>
<td style={tdStyle}>{data1.forks_count}</td>
<td style={tdStyle}>{data2.forks_count}</td>
</tr>

<tr>
<td style={tdStyle}>πŸ› Issues</td>
<td style={tdStyle}>{data1.open_issues_count}</td>
<td style={tdStyle}>{data2.open_issues_count}</td>
</tr>

<tr>
<td style={tdStyle}>πŸ‘€ Watchers</td>
<td style={tdStyle}>{data1.watchers_count}</td>
<td style={tdStyle}>{data2.watchers_count}</td>
</tr>

<tr>
<td style={tdStyle}>πŸ’» Language</td>
<td style={tdStyle}>{data1.language}</td>
<td style={tdStyle}>{data2.language}</td>
</tr>

<tr>
<td style={tdStyle}>πŸ“… Last Updated</td>
<td style={tdStyle}>{formatDate(data1.updated_at)}</td>
<td style={tdStyle}>{formatDate(data2.updated_at)}</td>
</tr>

<tr>
<td style={tdStyle}>🧾 Description</td>
<td style={tdStyle}>{data1.description}</td>
<td style={tdStyle}>{data2.description}</td>
</tr>
</tbody>
</table>
</div>
)}
</div>
);
};

const thStyle: React.CSSProperties = {
border: "1px solid #ddd",
padding: "10px",
background: "#f4f4f4",
};

const tdStyle: React.CSSProperties = {
border: "1px solid #ddd",
padding: "10px",
};

export default RepoCompare;
18 changes: 18 additions & 0 deletions src/utils/github.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export async function fetchRepo(repo: string) {
const normalizedRepo = repo.trim();
const res = await fetch(`https://api.github.com/repos/${normalizedRepo}`);
if (!res.ok) {
throw new Error(`Repository not found: ${normalizedRepo}`);
}
return res.json();
Comment thread
PremDhakad07 marked this conversation as resolved.
}
}
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 | πŸ”΄ Critical | ⚑ Quick win

Remove extra closing brace causing syntax error.

Line 9 contains an extraneous closing brace that will prevent the code from compiling.

πŸ› Fix for syntax error
   return res.json();
 }
-}
 
 export async function compareRepos(repo1: string, repo2: string) {
πŸ“ 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
}
return res.json();
}
export async function compareRepos(repo1: string, repo2: string) {
🧰 Tools
πŸͺ› Biome (2.4.15)

[error] 9-9: Expected a statement but instead found '}'.

(parse)

πŸ€– 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/utils/github.ts` at line 9, Remove the extraneous standalone closing
brace (a stray "}") in the github utility file that causes a syntax error;
delete that extra brace so the module's braces properly balance, run the
TypeScript/JS build to confirm compilation, and verify the module's exported
functions remain correctly scoped after removal.


export async function compareRepos(repo1: string, repo2: string) {
const [a, b] = await Promise.all([
fetchRepo(repo1),
fetchRepo(repo2),
]);

return { a, b };
}
Loading