Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
13 changes: 13 additions & 0 deletions src/components/Cards/statCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
interface Props {
title: string;
value: string | number;
}

export default function StatCard({ title, value }: Props) {
return (
<div className="bg-[#1F2937] p-4 rounded-xl h-32">
<p className="text-gray-400 text-sm">{title}</p>
<h2 className="text-2xl font-bold text-green-400">{value}</h2>
</div>
);
}
121 changes: 121 additions & 0 deletions src/components/Charts/ActivityChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { useState, useMemo } from "react";
import {
LineChart, Line,
BarChart, Bar,
PieChart, Pie, Cell, LabelList,
XAxis, YAxis, Tooltip, Legend
} from "recharts";

const tooltipFormatter = (
value?: number | string,
name?: string
): [string, string] => {
return [
value !== undefined ? value.toString() : "-",
name !== undefined ? name : "-"
];
};

export default function ActivityChart({ repos }: any) {
const [chartType, setChartType] = useState("line");
const [filter, setFilter] = useState("top");
Comment on lines +19 to +21
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid any type; use explicit types for props and state.

Per coding guidelines, avoid any. The repos prop and state variables should be explicitly typed.

🔧 Proposed fix
+import type { Repo } from "../../types/github";
+
+type ChartType = "line" | "bar" | "pie";
+type FilterType = "top" | "inactive";
+
-export default function ActivityChart({ repos }: any) {
-  const [chartType, setChartType] = useState("line");
-  const [filter, setFilter] = useState("top"); 
+export default function ActivityChart({ repos }: { repos: Repo[] }) {
+  const [chartType, setChartType] = useState<ChartType>("line");
+  const [filter, setFilter] = useState<FilterType>("top"); 

As per coding guidelines: "Avoid 'any', use explicit types".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Charts/ActivityChart.tsx` around lines 19 - 21, Create
explicit types and annotate the component and state: define a Repo interface
matching the shape of items in repos (e.g., id: string, name: string, stats?:
Record<string, number>) and a Props interface like Props { repos: Repo[] }, then
update the component signature to ActivityChart({ repos }: Props). Also add
narrow union types for the state (e.g., type ChartType = "line" | "bar"; type
FilterType = "top" | "all") and annotate the hooks as
useState<ChartType>("line") and useState<FilterType>("top") so chartType,
setChartType, filter, and setFilter are strongly typed.


const COLORS = ["#22c55e", "#3b82f6", "#f59e0b", "#ef4444", "#8b5cf6", "#ec4899"];
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Move COLORS array outside the component to avoid recreation on each render.

♻️ Proposed fix
+const COLORS = ["#22c55e", "#3b82f6", "#f59e0b", "#ef4444", "#8b5cf6", "#ec4899"];
+
 export default function ActivityChart({ repos }: any) {
   const [chartType, setChartType] = useState("line");
   const [filter, setFilter] = useState("top"); 
-
-  const COLORS = ["#22c55e", "#3b82f6", "#f59e0b", "#ef4444", "#8b5cf6", "#ec4899"];
📝 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 COLORS = ["#22c55e", "#3b82f6", "#f59e0b", "#ef4444", "#8b5cf6", "#ec4899"];
const COLORS = ["#22c55e", "#3b82f6", "#f59e0b", "#ef4444", "#8b5cf6", "#ec4899"];
export default function ActivityChart({ repos }: any) {
const [chartType, setChartType] = useState("line");
const [filter, setFilter] = useState("top");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Charts/ActivityChart.tsx` at line 23, The COLORS array is
declared inside the ActivityChart component and gets recreated on every render;
move the const COLORS = [...] declaration out of the ActivityChart function
(make it a module-level constant) so it is instantiated once, keeping the symbol
name COLORS and leaving the ActivityChart component code unchanged otherwise.


const data = useMemo(() => {
let processed = [...repos];

if (filter === "top") {
processed.sort((a, b) => b.stargazers_count - a.stargazers_count);
}
if (filter === "inactive") {
const now = new Date().getTime();
processed = processed.filter(r =>
now - new Date(r.updated_at).getTime() > 90 * 24 * 60 * 60 * 1000
);
}

return processed.slice(0, 8).map((r: any) => ({
name: r.name,
stars: r.stargazers_count,
forks: r.forks_count
}));
Comment on lines +38 to +42
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Replace any with Repo type in the map callback.

🔧 Proposed fix
-    return processed.slice(0, 8).map((r: any) => ({
+    return processed.slice(0, 8).map((r) => ({
       name: r.name,
       stars: r.stargazers_count,
       forks: r.forks_count
     }));

With proper typing on repos, the type will be inferred automatically.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Charts/ActivityChart.tsx` around lines 38 - 42, The map
callback currently types its parameter as any; change it to use the Repo type
(e.g., map((r: Repo) => ...)) or, better, properly type the array stored in
processed so TypeScript can infer the item type (ensure the variable/prop that
sets processed is typed as Repo[]). Update references in ActivityChart (the map
callback and the processed declaration) to use Repo so fields like
stargazers_count and forks_count are correctly typed.

}, [repos, filter]);

return (
<div className="bg-[#1F2937] p-4 rounded mt-6 text-white" >

<div className="flex gap-3 mb-4">
{["line", "bar", "pie"].map(type => (
<button
key={type}
onClick={() => setChartType(type)}
className={`px-3 py-1 rounded ${chartType === type ? "bg-green-500" : "bg-gray-700"}`}
>
{type.charAt(0).toUpperCase() + type.slice(1)}
</button>
))}
</div>

<div className="flex gap-3 mb-4">
<button
onClick={() => setFilter("top")}
className={`px-3 py-1 rounded ${filter === "top" ? "bg-blue-500" : "bg-gray-700"}`}
>
Top Repos
</button>
<button
onClick={() => setFilter("inactive")}
className={`px-3 py-1 rounded ${filter === "inactive" ? "bg-blue-500" : "bg-gray-700"}`}
>
Inactive
</button>
</div>

{chartType === "line" && (
<LineChart width={600} height={300} data={data}>
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="stars" stroke="#22c55e" />
<Line type="monotone" dataKey="forks" stroke="#3b82f6" />
</LineChart>
)}

{chartType === "bar" && (
<BarChart width={600} height={300} data={data}>
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="stars" fill="#22c55e">
<LabelList dataKey="stars" position="top" />
</Bar>
<Bar dataKey="forks" fill="#3b82f6">
<LabelList dataKey="forks" position="top" />
</Bar>
</BarChart>
)}

{chartType === "pie" && (
<PieChart width={400} height={400}>
<Pie
data={data}
dataKey="stars"
nameKey="name"
outerRadius={100}
label={({ name, value }) => `${name}: ${value}`}
>
{data.map((_, index) => (
<Cell key={index} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
<Tooltip formatter={tooltipFormatter} />
<Legend />
</PieChart>
)}

</div>
);
}
212 changes: 212 additions & 0 deletions src/components/Graph/NetworkGraph.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import ForceGraph2D from "react-force-graph-2d";
import { useEffect, useState, useRef } from "react";
import { fetchRepoContributors } from "../../services/githubService";
Copy link
Copy Markdown

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

🏁 Script executed:

# First, examine the NetworkGraph component
cat -n src/components/Graph/NetworkGraph.tsx | head -50

Repository: AOSSIE-Org/OrgExplorer

Length of output: 1751


🏁 Script executed:

# Then examine the githubService to see the Authorization header implementation
cat -n src/services/githubService.ts

Repository: AOSSIE-Org/OrgExplorer

Length of output: 832


🏁 Script executed:

# Also check if there's any token in environment variables or config
find . -type f \( -name ".env*" -o -name "*.config.*" -o -name "config.*" \) | grep -v node_modules | head -20

Repository: AOSSIE-Org/OrgExplorer

Length of output: 160


🏁 Script executed:

# Check if NetworkGraph has "use client" directive
grep -n "use client" src/components/Graph/NetworkGraph.tsx

Repository: AOSSIE-Org/OrgExplorer

Length of output: 48


🏁 Script executed:

# Get full NetworkGraph file
wc -l src/components/Graph/NetworkGraph.tsx

Repository: AOSSIE-Org/OrgExplorer

Length of output: 107


🏁 Script executed:

# Search for API routes or backend proxy for GitHub
find . -path ./node_modules -prune -o -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" \) -print | xargs grep -l "fetchRepoContributors\|github" | grep -E "api|routes|proxy" | head -20

Repository: AOSSIE-Org/OrgExplorer

Length of output: 48


🏁 Script executed:

# Check vite.config.ts for env variable handling
cat -n vite.config.ts

Repository: AOSSIE-Org/OrgExplorer

Length of output: 353


🏁 Script executed:

# Read full NetworkGraph file to understand complete usage pattern
cat -n src/components/Graph/NetworkGraph.tsx

Repository: AOSSIE-Org/OrgExplorer

Length of output: 5605


🏁 Script executed:

# Check project structure - look for API routes or backend endpoints
ls -la src/ | head -30

Repository: AOSSIE-Org/OrgExplorer

Length of output: 735


🏁 Script executed:

# Check if there are any API routes in a common structure
find . -path ./node_modules -prune -o -type d -name "api" -o -type d -name "routes" -print | head -20

Repository: AOSSIE-Org/OrgExplorer

Length of output: 48


Move GitHub API calls to a backend API route to prevent token exposure in client-side code.

NetworkGraph calls fetchRepoContributors directly from the browser at line 30, and src/services/githubService.ts includes a GitHub token in the Authorization header (line 19). Environment variables prefixed with VITE_ are automatically embedded in the client bundle and exposed in network requests. Create a backend endpoint to handle GitHub API calls and move the token authentication there.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Graph/NetworkGraph.tsx` at line 3, NetworkGraph currently
calls fetchRepoContributors (from src/services/githubService.ts) from
client-side code which embeds the GitHub token; instead create a backend API
route (e.g., /api/github/contributors) that performs the GitHub fetch using a
server-only env var (not VITE_*) and returns sanitized contributor data. Move
the Authorization header logic out of fetchRepoContributors into that new server
handler, update NetworkGraph to call the new backend endpoint (replace direct
fetchRepoContributors usage), and remove any client-side usage of the token or
VITE_ prefixed env vars; ensure the new server handler includes error handling,
rate-limit/backoff considerations, and minimal response fields to avoid leaking
sensitive data.

import * as d3 from "d3-force";

export default function NetworkGraph({ repos }: any) {
const [graphData, setGraphData] = useState<any>({
nodes: [],
links: []
});

const fgRef = useRef<any>(null);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Pervasive use of any types throughout the component.

Per coding guidelines, avoid any. This component uses any extensively for props, state, refs, and callback parameters.

🔧 Suggested type definitions
import type { Repo } from "../../types/github";

interface GraphNode {
  id: string;
  type: "repo" | "user";
  stars?: number;
  contributions?: number;
  avatar?: string;
  x?: number;
  y?: number;
  fx?: number;
  fy?: number;
}

interface GraphLink {
  source: string;
  target: string;
  weight: number;
}

interface GraphData {
  nodes: GraphNode[];
  links: GraphLink[];
}

export default function NetworkGraph({ repos }: { repos: Repo[] }) {
  const [graphData, setGraphData] = useState<GraphData>({
    nodes: [],
    links: []
  });
  // ...
}

As per coding guidelines: "Avoid 'any', use explicit types".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Graph/NetworkGraph.tsx` around lines 6 - 12, Replace pervasive
any types in NetworkGraph: type the component prop ({ repos }: { repos: Repo[]
}), define and use explicit interfaces for GraphNode, GraphLink, and GraphData
for useState<GraphData> (graphData and setGraphData), and type the fgRef with
React.RefObject<ForceGraphMethods | null> (or appropriate ref type) instead of
any; also replace any callback parameters within the component (e.g., node
click/hover handlers) with the concrete GraphNode type so all usages of repos,
graphData, setGraphData, and fgRef have explicit types rather than any.


// IMAGE CACHE
const imageCache = useRef<{ [key: string]: HTMLImageElement }>({});

// BUILD GRAPH
useEffect(() => {
const buildGraph = async () => {
const nodes: any[] = [];
const links: any[] = [];
const addedUsers = new Set();

for (let repo of repos.slice(0, 6)) {
nodes.push({
id: repo.name,
type: "repo",
stars: repo.stargazers_count
});

try {
const contributors = await fetchRepoContributors(repo.contributors_url);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the Repo type definition and check if contributors_url is used elsewhere
echo "=== Repo type definition ==="
cat src/types/github.ts

echo -e "\n=== Usage of contributors_url ==="
rg -n "contributors_url" --type ts

Repository: AOSSIE-Org/OrgExplorer

Length of output: 547


🏁 Script executed:

#!/bin/bash
# Check where repos are created and how they're hydrated
echo "=== Search for where repos are fetched/created ==="
rg -n "fetchRepo|new Repo|repos =" --type ts -A 2 | head -50

echo -e "\n=== Check tsconfig for strict mode ==="
cat tsconfig.json | grep -A 5 -B 5 "strict"

echo -e "\n=== Check fetchRepoContributors implementation ==="
rg -n "fetchRepoContributors" --type ts -B 2 -A 5

Repository: AOSSIE-Org/OrgExplorer

Length of output: 2900


🏁 Script executed:

#!/bin/bash
# Check how repos are loaded from GitHub API or localStorage
echo "=== Search for mergeRepos and repo initialization ==="
rg -n "mergeRepos|from.*api|response.*repo" --type ts -B 2 -A 3 | head -80

echo -e "\n=== Check if there are any type assertions bypassing the error ==="
rg -n "as Repo|as any|unknown" --type ts | grep -i repo

Repository: AOSSIE-Org/OrgExplorer

Length of output: 1327


🏁 Script executed:

#!/bin/bash
# Check fetchOrgRepos to understand what data is returned from GitHub API
echo "=== Check fetchOrgRepos implementation ==="
rg -n "fetchOrgRepos|export const fetch" src/services/githubService.ts -A 15

echo -e "\n=== Check if repos property in state/storage includes the URL ==="
rg -n "repos\[|repos\.map|repos\.forEach" --type ts -B 1 -A 1 | head -40

echo -e "\n=== Look at the entire NetworkGraph file to understand repo structure ==="
head -50 src/components/Graph/NetworkGraph.tsx

Repository: AOSSIE-Org/OrgExplorer

Length of output: 2573


🏁 Script executed:

#!/bin/bash
# Check tsconfig.json completely
echo "=== Full tsconfig.json ==="
cat tsconfig.json

echo -e "\n=== Verify what GitHub API actually returns for /orgs/{org}/repos ==="
# Check GitHub API documentation or examples in the code
rg -n "github.com/api" --type ts -B 2 -A 2

echo -e "\n=== Check if there's any documentation or comments about repo structure ==="
rg -n "repo\.|Repo|Repository" src/types/github.ts -B 2 -A 2

Repository: AOSSIE-Org/OrgExplorer

Length of output: 613


🏁 Script executed:

#!/bin/bash
# Check tsconfig.app.json for strict settings
echo "=== Check tsconfig.app.json ==="
cat tsconfig.app.json | head -30

echo -e "\n=== Search GitHub API documentation or comments about contributors_url ==="
rg -n "contributors" --type ts -B 2 -A 2

echo -e "\n=== Verify how repos are cast/stored ==="
rg -n "Repo\[\]|as Repo" --type ts -B 1 -A 1

Repository: AOSSIE-Org/OrgExplorer

Length of output: 2842


🏁 Script executed:

#!/bin/bash
# Check mergeRepos implementation to see if it preserves contributors_url
echo "=== Full mergeRepos implementation ==="
cat src/utils/mergeOrgs.ts

echo -e "\n=== Check what's being stored to localStorage ==="
rg -n "localStorage.setItem.*repos" --type ts -B 2 -A 2

Repository: AOSSIE-Org/OrgExplorer

Length of output: 710


🏁 Script executed:

#!/bin/bash
# Verify the actual GitHub API response contains contributors_url
echo "=== Check if GitHub API docs or examples show contributors_url ==="
web_search "GitHub API /orgs/{org}/repos response schema contributors_url"

echo -e "\n=== Verify if there are any TypeScript errors being suppressed ==="
rg -n "@ts-ignore|@ts-expect-error" --type ts

echo -e "\n=== Check if this code actually compiles ==="
# Look for build errors or how the code is handled
ls -la | grep -E "build|dist|.next"
</parameter>

Repository: AOSSIE-Org/OrgExplorer

Length of output: 420


🌐 Web query:

GitHub API REST v3 GET /repos/{owner}/{repo} response schema

💡 Result:

GET /repos/{owner}/{repo} returns a repository JSON object (“full repository”) (HTTP 200). (docs.github.com)

Shape (high-level)

Top-level fields commonly include (non-exhaustive):

  • Identity/URLs: id, node_id, name, full_name, url, html_url, plus many related URLs like issues_url, pulls_url, contents_url, etc. (octokit.github.io)
  • Ownership: owner (user/org object: login, id, avatar_url, html_url, …). (octokit.github.io)
  • Visibility & flags: private, fork, archived, disabled, visibility, is_template. (octokit.github.io)
  • Metadata: description, homepage, language, default_branch. (octokit.github.io)
  • Counts: forks_count, stargazers_count, watchers_count, open_issues_count, size, subscribers_count, network_count. (octokit.github.io)
  • Timestamps: created_at, updated_at, pushed_at. (octokit.github.io)
  • Permissions (when authenticated): permissions: { admin, push, pull }. (octokit.github.io)
  • Fork info (when applicable): parent and source objects. (octokit.github.io)

Formal schema (machine-readable)

For an actual OpenAPI schema (rather than an example), use GitHub’s official OpenAPI bundle in github/rest-api-description. (github.com)

Citations:


🌐 Web query:

GitHub API repository object contributors_url field

💡 Result:

In the GitHub REST API repository JSON, the contributors_url field is a hypermedia link: it’s the API URL you can GET to list that repository’s contributors (i.e., the “List repository contributors” endpoint, typically GET /repos/{owner}/{repo}/contributors). The contributors list is sorted by number of commits, and GitHub notes the data may be cached and a few hours old. [1]

Example value (from a repository object payload):

  • "contributors_url": "https://api.github.com/repos/<owner>/<repo>/contributors" [2]

Use it by sending a GET request to that URL (optionally with query params like anon, per_page, page). [1]

References:
[1] https://docs.github.com/en/enterprise-server%403.19/rest/repos/repos?apiVersion=2022-11-28
[2] https://gist.github.com/ngs/4266399


🏁 Script executed:

#!/bin/bash
# Verify if TypeScript catches this as an error
echo "=== Check if repos loaded from localStorage are properly typed ==="
cat src/pages/Repositories.tsx

echo -e "\n=== Check if repos from localStorage ever lack contributors_url ==="
rg -n "JSON.parse.*repos|localStorage.getItem.*repos" --type ts -B 1 -A 3

echo -e "\n=== Verify the actual type flow: API response → mergeRepos → Repo[] ==="
echo "Check Overview.tsx lines 53-60:"
sed -n '50,65p' src/pages/Overview.tsx

Repository: AOSSIE-Org/OrgExplorer

Length of output: 2358


🏁 Script executed:

#!/bin/bash
# Check if this is actually causing a TypeScript compilation error
echo "=== Check for any build output or error logs ==="
find . -name "*.tsbuildinfo" -o -name ".tsc-errors" 2>/dev/null | head -5

echo -e "\n=== Verify if the type error is caught by looking at how repos flow through mergeRepos ==="
# The issue: fetchOrgRepos returns raw API response (which HAS contributors_url)
# but it's typed as Repo[] (which doesn't have contributors_url)
rg -n "allRepos\[|repo\.|Repo" src/utils/mergeOrgs.ts

echo -e "\n=== Check if there's a real issue: does repo.contributors_url exist at runtime? ==="
# If mergeRepos receives raw API responses and just stores them as-is,
# then contributors_url WILL exist at runtime despite the type mismatch
echo "Checking what mergeRepos actually does with the raw data:"
cat src/utils/mergeOrgs.ts

Repository: AOSSIE-Org/OrgExplorer

Length of output: 892


Add contributors_url to the Repo type definition.

The Repo interface is incomplete. While repo.contributors_url will exist at runtime (the GitHub API includes it in repository responses, and mergeRepos stores the full objects), the TypeScript type definition does not include this field. This causes a type safety violation with strict mode enabled.

Update src/types/github.ts:

export interface Repo {
  id: number;
  name: string;
  stargazers_count: number;
  forks_count: number;
  updated_at: string;
  contributors_url: string;  // Add this field
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Graph/NetworkGraph.tsx` at line 32, The Repo interface is
missing the contributors_url property which breaks type safety where
NetworkGraph calls fetchRepoContributors(repo.contributors_url); update the Repo
type definition (the Repo interface) to include a contributors_url: string field
so TypeScript recognizes the property and the strict build passes; ensure the
exported Repo interface used across the codebase (e.g., in mergeRepos and
NetworkGraph) is updated accordingly.


contributors.slice(0, 5).forEach((c: any) => {
if (!addedUsers.has(c.login)) {
addedUsers.add(c.login);

nodes.push({
id: c.login,
type: "user",
contributions: c.contributions,
avatar: c.avatar_url
});
}

links.push({
source: c.login,
target: repo.name,
weight: c.contributions
});
});

} catch (e) {
console.error(e);
}
}

setGraphData({ nodes, links });
};

if (repos.length) buildGraph();
}, [repos]);

// FORCE SETTINGS
useEffect(() => {
if (!fgRef.current) return;

fgRef.current.d3Force("center", d3.forceCenter(0, 0));
fgRef.current.d3Force("charge", d3.forceManyBody().strength(-100));
fgRef.current.d3Force("link", d3.forceLink().distance(70));

fgRef.current.d3Force(
"radial",
d3.forceRadial((node: any) => {
return node.type === "repo" ? 80 : 180;
}).strength(0.8)
);

}, [graphData]);

useEffect(() => {
if (!fgRef.current) return;

// trigger render after mount
setTimeout(() => {
fgRef.current.zoomToFit(400);
}, 300);
}, [graphData]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Merge duplicate useEffect hooks with identical [graphData] dependency.

Two separate useEffect hooks run on graphData changes. The second one (lines 81-88) only calls zoomToFit after a timeout—this can be combined with the first effect for clarity.

♻️ Proposed fix
   // FORCE SETTINGS
   useEffect(() => {
     if (!fgRef.current) return;

     fgRef.current.d3Force("center", d3.forceCenter(0, 0));
     fgRef.current.d3Force("charge", d3.forceManyBody().strength(-100));
     fgRef.current.d3Force("link", d3.forceLink().distance(70));

     fgRef.current.d3Force(
       "radial",
       d3.forceRadial((node: any) => {
         return node.type === "repo" ? 80 : 180;
       }).strength(0.8)
     );

+    // Trigger render after mount
+    setTimeout(() => {
+      fgRef.current?.zoomToFit(400);
+    }, 300);
   }, [graphData]);

-  useEffect(() => {
-  if (!fgRef.current) return;
-
-  //  trigger render after mount
-  setTimeout(() => {
-    fgRef.current.zoomToFit(400);
-  }, 300);
-}, [graphData]);
📝 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 (!fgRef.current) return;
fgRef.current.d3Force("center", d3.forceCenter(0, 0));
fgRef.current.d3Force("charge", d3.forceManyBody().strength(-100));
fgRef.current.d3Force("link", d3.forceLink().distance(70));
fgRef.current.d3Force(
"radial",
d3.forceRadial((node: any) => {
return node.type === "repo" ? 80 : 180;
}).strength(0.8)
);
}, [graphData]);
useEffect(() => {
if (!fgRef.current) return;
// trigger render after mount
setTimeout(() => {
fgRef.current.zoomToFit(400);
}, 300);
}, [graphData]);
useEffect(() => {
if (!fgRef.current) return;
fgRef.current.d3Force("center", d3.forceCenter(0, 0));
fgRef.current.d3Force("charge", d3.forceManyBody().strength(-100));
fgRef.current.d3Force("link", d3.forceLink().distance(70));
fgRef.current.d3Force(
"radial",
d3.forceRadial((node: any) => {
return node.type === "repo" ? 80 : 180;
}).strength(0.8)
);
// Trigger render after mount
setTimeout(() => {
fgRef.current?.zoomToFit(400);
}, 300);
}, [graphData]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Graph/NetworkGraph.tsx` around lines 65 - 88, Merge the two
useEffect hooks that share the [graphData] dependency into a single useEffect
that first checks fgRef.current, sets the d3 forces (d3Force("center"),
d3Force("charge"), d3Force("link"), d3Force("radial")) and then triggers
fgRef.current.zoomToFit(400) via setTimeout; reference the existing fgRef,
useEffect, d3Force and zoomToFit symbols and ensure you store the timeout id and
clear it in the effect cleanup to avoid leaks.


return (
<div className="h-[650px] relative overflow-hidden bg-[#020617] rounded-xl mt-6 border border-gray-800">

<ForceGraph2D
ref={fgRef}
graphData={graphData}
backgroundColor="#020617"

enableZoomInteraction={true}
enablePanInteraction={true}
enableNodeDrag={false}
autoPauseRedraw={false}

minZoom={0.5}
maxZoom={2}

onZoomEnd={({k, x, y}) => {
const limit = 200;

if(Math.abs(x) > limit || Math.abs(y) > limit) {
fgRef.current.centerAt(0, 0, 400);
}
}}
// AUTO CENTER + FREEZE
onEngineStop={() => {
fgRef.current.centerAt(0, 0, 400);
fgRef.current?.zoomToFit(400);

graphData.nodes.forEach((node: any) => {
node.fx = node.x;
node.fy = node.y;
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

fgRef.current?.pauseAnimation();
}}

d3VelocityDecay={0.3}
d3AlphaDecay={0.02}
cooldownTicks={100}
warmupTicks={100}

// TOOLTIP
nodeLabel={(node: any) =>
node.type === "repo"
? `📦 ${node.id} (⭐ ${node.stars})`
: `👤 ${node.id} (${node.contributions})`
}

// NODE RENDER (FIXED AVATAR)
nodeCanvasObject={(node: any, ctx, globalScale) => {
const size =
node.type === "repo"
? 10 + Math.log(node.stars + 1)
: 5;

const fontSize = 9 / globalScale;

if (node.type === "user" && node.avatar) {
let img = imageCache.current[node.avatar];

if (!img) {
img = new Image();
img.src = node.avatar;
imageCache.current[node.avatar] = img;
}

ctx.save();
ctx.beginPath();
ctx.arc(node.x, node.y, size, 0, 2 * Math.PI);
ctx.closePath();
ctx.clip();

if (img.complete) {
ctx.drawImage(
img,
node.x - size,
node.y - size,
size * 2,
size * 2
);
}

ctx.restore();
Comment on lines +136 to +146
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid allocating Image objects inside nodeCanvasObject.

This callback runs frequently; creating images per frame is a hot-path performance issue. Cache images (e.g., Map<string, HTMLImageElement>) and reuse them.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Graph/NetworkGraph.tsx` around lines 136 - 146, The
nodeCanvasObject callback currently allocates a new Image() for every render
when node.type === "user" and node.img is present; move to a persistent cache
(e.g., a Map<string, HTMLImageElement>) stored in the component/module scope and
reuse images instead of creating them per call. In nodeCanvasObject, lookup
node.img in the cache and only create and insert a new HTMLImageElement into the
Map if it doesn't exist (attach onload/onerror handlers to mark readiness), and
only call ctx.drawImage when the cached image is loaded; keep the existing
clipping/drawing logic
(ctx.save/ctx.beginPath/ctx.arc/ctx.clip/ctx.drawImage/ctx.restore) but operate
on the cached element to avoid per-frame allocations.


// white border
ctx.beginPath();
ctx.arc(node.x, node.y, size, 0, 2 * Math.PI);
ctx.strokeStyle = "#ffffff";
ctx.lineWidth = 1;
ctx.stroke();

} else {
// repo node
ctx.beginPath();
ctx.arc(node.x, node.y, size, 0, 2 * Math.PI);
ctx.fillStyle = "#22c55e";
ctx.fill();
}

// label
ctx.font = `${fontSize}px Inter`;
ctx.fillStyle = "#e2e8f0";
ctx.fillText(node.id, node.x + size + 2, node.y + size / 2);
}}

// EDGE DESIGN (VISIBLE)
linkWidth={(link: any) =>
Math.max(1, Math.log(link.weight + 1))
}

linkColor={() => "rgba(200,200,200,0.6)"}

linkDirectionalParticles={1}
linkDirectionalParticleSpeed={0.002}

// HOVER
onNodeHover={(node) => {
document.body.style.cursor = node ? "pointer" : "default";
}}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Cursor style side effect is not cleaned up.

When the component unmounts while hovering over a node, document.body.style.cursor will remain as "pointer". Add cleanup logic.

🛡️ Proposed fix: add useEffect cleanup
+  useEffect(() => {
+    return () => {
+      document.body.style.cursor = "default";
+    };
+  }, []);

Add this cleanup effect to reset the cursor on unmount.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Graph/NetworkGraph.tsx` around lines 206 - 208, The
onNodeHover handler in NetworkGraph sets document.body.style.cursor but doesn't
reset it if the component unmounts while hovered; add a useEffect inside the
NetworkGraph component that returns a cleanup function which resets
document.body.style.cursor = "" (or "default") on unmount, and ensure useEffect
is imported from React; keep the existing onNodeHover behavior (setting
"pointer" or "default") but rely on the cleanup to guarantee no lingering
pointer cursor after unmount.

/>
</div>
);
}
28 changes: 28 additions & 0 deletions src/components/HealthScore.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export default function HealthScore({ score, label }: any) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "HealthScore.tsx" -type f

Repository: AOSSIE-Org/OrgExplorer

Length of output: 98


🏁 Script executed:

cat -n ./src/components/HealthScore.tsx

Repository: AOSSIE-Org/OrgExplorer

Length of output: 1045


🏁 Script executed:

head -20 ./package.json | grep -E "(next|react)" || cat ./next.config.js 2>/dev/null || echo "Checking project type..."

Repository: AOSSIE-Org/OrgExplorer

Length of output: 182


🏁 Script executed:

cat ./package.json | grep -E '"(next|nextjs)"' || ls -la | grep -E "(next\.config|tsconfig)"

Repository: AOSSIE-Org/OrgExplorer

Length of output: 269


🏁 Script executed:

cat ./tsconfig.json

Repository: AOSSIE-Org/OrgExplorer

Length of output: 184


🏁 Script executed:

cat ./tsconfig.app.json

Repository: AOSSIE-Org/OrgExplorer

Length of output: 797


🏁 Script executed:

find . -name "*i18n*" -o -name "*lang*" -o -name "*translation*" -o -name "*.en.json" | head -20

Repository: AOSSIE-Org/OrgExplorer

Length of output: 48


Define explicit types for props and externalize hardcoded user-visible strings.

This component violates two coding guidelines:

  1. Props typed as any instead of explicit types (line 1)
  2. "Organization Health Score" is hardcoded instead of being externalized to i18n resources (lines 5-7)
Proposed fix
+interface HealthScoreProps {
+  score: number;
+  label: string;
+}
+
-export default function HealthScore({ score, label }: any) {
+export default function HealthScore({ score, label }: HealthScoreProps) {
   return (
     <div className="bg-[`#111827`] p-5 rounded-xl border border-gray-800 w-full mt-10">
 
       <h3 className="text-gray-400 text-sm mb-2">
-        Organization Health Score
+        {t('components.healthScore.title')}
       </h3>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/HealthScore.tsx` at line 1, The HealthScore component
currently uses an unsafe any for its props and hardcodes the user-facing string
"Organization Health Score"; update the component by defining an explicit props
interface (e.g., interface HealthScoreProps { score: number; label?: string } )
and use it in the HealthScore function signature (replace { score, label }: any
with the typed props), and replace the hardcoded "Organization Health Score"
string with an i18n lookup (e.g., via the existing translation helper or
useTranslation hook) so the label comes from your localization resources instead
of being inline.

return (
<div className="bg-[#111827] p-5 rounded-xl border border-gray-800 w-full mt-10">

<h3 className="text-gray-400 text-sm mb-2">
Organization Health Score
</h3>
Comment on lines +5 to +7
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Externalize section title for i18n.

"Organization Health Score" is user-visible and should be moved to localization resources.

As per coding guidelines, "Internationalization: User-visible strings should be externalized to resource files (i18n)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/HealthScore.tsx` around lines 5 - 7, The hard-coded
user-visible title "Organization Health Score" in the HealthScore component
should be externalized for i18n: replace the literal string in the <h3> (within
the HealthScore component) with a localized value (e.g.,
t('organizationHealthScore') or similar project i18n helper) and add the
corresponding key "organizationHealthScore" to the locale/resource files for all
supported languages; ensure you import/use the project's translation
hook/utility (e.g., useTranslation or t) in HealthScore and update
tests/snapshots if they depend on the text.


<div className="flex items-center justify-between mb-3">
<span className="text-3xl font-bold text-green-400">
{score}/100
</span>
<span className="text-sm text-gray-300">
{label}
</span>
</div>

{/* Progress bar */}
<div className="w-full bg-gray-700 h-3 rounded-full">
<div
className="h-3 rounded-full bg-green-500 transition-all duration-500"
style={{ width: `${score}%` }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard progress width with a bounded score value.

Use a clamped value to avoid invalid/overflowing width styles.

Proposed fix
+  const safeScore = Math.max(0, Math.min(100, score));
   return (
@@
-          style={{ width: `${score}%` }}
+          style={{ width: `${safeScore}%` }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/HealthScore.tsx` at line 22, The progress width uses the raw
score value (style={{ width: `${score}%` }}) which can produce
invalid/overflowing styles; clamp the value before rendering (e.g., compute a
boundedScore = Math.max(0, Math.min(100, score)) or equivalent) and use that
boundedScore in the inline style for the width in HealthScore (so replace
`${score}%` with `${boundedScore}%`) to ensure width is always between 0% and
100%.

></div>
</div>

</div>
);
}
Loading
Loading