Skip to content

Conversation

@ASR1015
Copy link
Contributor

@ASR1015 ASR1015 commented Sep 2, 2025

Summary

  • Added X-GitHub-Api-Version: 2022-11-28 to all GitHub requests (stops “deprecated/unversioned” warnings).
  • Token handling: read PAT from localStorage (gh_pat / github_pat / token) and support optional field in UI.
  • Router cleanup (React Router context + element structure).
  • Contributors page: fetches from the configured repo; verified in dev + preview.
  • ContributorProfile page: shows user profile + latest PRs with copy-link, loading/empty states.

Testing

  • npm run dev and npm run preview both OK.
  • Verified: /, /contributors, /contributor/:username, /track.
  • With PAT set via localStorage.setItem('gh_pat','<token>'), issues/PRs load.

Notes

  • GitHub shows a deprecation warning for search/issues endpoint. Functionality works, but API is scheduled for removal on Sep 4, 2025.
  • Follow-up: migrate PR/issue search to GraphQL or new REST endpoints before that date.

Summary by CodeRabbit

  • New Features

    • App now uses a data-router with a RootLayout (header/footer), lazy-loaded pages, nested routes and an error fallback.
  • Improvements

    • Contributor profile: authenticated GitHub loading, clearer not-found/loading states, stable PR list, improved copy-share UX.
    • Contributors page: lean, token-authenticated contributor list with loading/empty/error messaging.
    • API clients: standardized GitHub headers/version, new helper utilities and Octokit support (GraphQL, pagination).
  • Refactor

    • Routing consolidated to the new router pattern; main render path simplified.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 2, 2025

Warning

Rate limit exceeded

@ASR1015 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 19 minutes and 30 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 47f8948 and 5847da2.

📒 Files selected for processing (1)
  • src/pages/Contributors/Contributors.tsx (1 hunks)

Walkthrough

Replace BrowserRouter with React Router v6.4+ data-router and RootLayout; add ghFetch/ghJson and Octokit factory; refactor ContributorProfile, Contributors, and useGitHubData to use token-authenticated GitHub REST/GraphQL calls; update constants, deps, and export AppRouter.

Changes

Cohort / File(s) Summary
Routing & app entry
src/App.tsx, src/Routes/Router.tsx, src/main.tsx
Replace BrowserRouter/Routes with createBrowserRouter/RouterProvider; add RootLayout (ThemeWrapper, ScrollProgressBar, Navbar, Footer, Suspense+Outlet) and errorElement; switch default exports to router/AppRouter; remove BrowserRouter wrapper in main.tsx.
Pages — contributor features
src/pages/ContributorProfile/ContributorProfile.tsx, src/pages/Contributors/Contributors.tsx
Make pages token-aware using ghFetch/octokit; ContributorProfile now fetches profile + PRs via GraphQL, adds Profile type and PR.id, improves error/loading/toast UX and stable keys; Contributors rewritten to use ghFetch, repo resolution, abortable fetch, minimal list UI, and token lookup from localStorage.
GitHub helpers & clients
src/lib/githubFetch.ts, src/lib/octokit.ts, src/utils/constants.ts
Add ghFetch and ghJson, introduce GITHUB_API_VERSION/GITHUB_API_BASE and derived GITHUB_REPO_API_BASE; add makeOctokit factory composing Octokit with paginateRest and throttling and configured headers/retry handlers.
Hooks & data fetching
src/hooks/useGitHubData.ts
Replace REST search with GraphQL via octokit.graphql; add cursor caching (useRef), unify mapping of edges→nodes, expose issues, prs, totalIssues, totalPrs, loading, error, and fetchData.
Deps / manifest
package.json
Add @octokit/core, @octokit/plugin-paginate-rest, and @octokit/plugin-throttling to dependencies.
Dynamic imports & route children
src/pages/*, src/Routes/*
Add React.lazy dynamic imports for pages, wrap Outlet with Suspense, and configure root route children (index/Home, track, signup, login, about, contact, contributors, contributor/:username).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Router as RouterProvider
  participant Root as RootLayout
  participant Page as RouteComponent
  Note over Router,Page: data-router resolves route and renders nested element
  User->>Router: Navigate to path
  Router->>Root: Render RootLayout (ThemeWrapper, Navbar, Footer)
  Root->>Page: Render nested route via Outlet (Suspense)
  Page-->>User: Display content / loading / error
Loading
sequenceDiagram
  autonumber
  participant Page as Contributors / ContributorProfile
  participant GHHelper as ghFetch / octokit.graphql
  participant API as GitHub API
  Note right of GHHelper #DDEBF7: Requests include Accept and X-GitHub-Api-Version\nAuthorization when token present
  Page->>GHHelper: Request (REST path or GraphQL, token)
  GHHelper->>API: HTTP/GraphQL request
  API-->>GHHelper: Response (ok / error)
  GHHelper-->>Page: Return JSON or throw error
  alt ok
    Page->>Page: Map nodes→items, update state
  else error
    Page->>Page: Surface error (toast/state)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

I hop through routes with nimble feet,
Outlets bloom where pages meet.
I nibble tokens, call the GitHub light,
List contributors into the night.
A rabbit cheers: commits and carrots — delight! 🥕

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@netlify
Copy link

netlify bot commented Sep 2, 2025

Deploy Preview for github-spy ready!

Name Link
🔨 Latest commit 5847da2
🔍 Latest deploy log https://app.netlify.com/projects/github-spy/deploys/68b6fc5850f4690008408164
😎 Deploy Preview https://deploy-preview-205--github-spy.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@ASR1015
Copy link
Contributor Author

ASR1015 commented Sep 2, 2025

Hi, I have fixed the GitHub API deprecation warnings by adding the required application/vnd.github+json header and updating contributor/profile fetch logic with PAT support.

I have tested locally (contributors list + profile pages working fine). Please review and let me know if any changes are needed.

@ASR1015
Copy link
Contributor Author

ASR1015 commented Sep 2, 2025

The PR is ready for review. Please let me know if any changes are needed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (13)
src/lib/githubFetch.ts (2)

9-17: Normalize path handling (supports absolute URLs and avoids double slashes).

Prevents subtle bugs when callers pass paths without a leading slash or a full URL.

-  return fetch(`${base}${path}`, { ...init, headers });
+  const url = path.startsWith("http")
+    ? path
+    : `${base}${path.startsWith("/") ? "" : "/"}${path}`;
+  return fetch(url, { ...init, headers });

11-13: Pick one Authorization scheme and document it.

Use a single scheme to avoid confusion. “Bearer” works for fine‑grained and classic PATs.

Confirm your tokens are fine‑grained/classic PATs; if any integration expects the old “token ” scheme, we can switch.

-  if (token) {
-    headers.set("Authorization", `Bearer ${token}`); // or `token ${token}`
-  }
+  if (token) {
+    headers.set("Authorization", `Bearer ${token}`);
+  }
src/Routes/Router.tsx (2)

11-20: Add a 404 route.

Prevents blank screens for unknown paths.

   { path: "/contributor/:username", element: <ContributorProfile /> },
+  { path: "*", element: <div className="p-6">Not Found</div> },

1-1: Consider lazy-loading route components to cut initial bundle size.

Wrap provider in Suspense and convert imports to lazy.

-import { createBrowserRouter, RouterProvider } from "react-router-dom";
+import { createBrowserRouter, RouterProvider } from "react-router-dom";
+import { lazy, Suspense } from "react";
-import Tracker from "../pages/Tracker/Tracker.tsx";
-import About from "../pages/About/About";
-import Contact from "../pages/Contact/Contact";
-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 Home from "../pages/Home/Home.tsx";
+const Tracker = lazy(() => import("../pages/Tracker/Tracker.tsx"));
+const About = lazy(() => import("../pages/About/About"));
+const Contact = lazy(() => import("../pages/Contact/Contact"));
+const Contributors = lazy(() => import("../pages/Contributors/Contributors"));
+const Signup = lazy(() => import("../pages/Signup/Signup.tsx"));
+const Login = lazy(() => import("../pages/Login/Login.tsx"));
+const ContributorProfile = lazy(() => import("../pages/ContributorProfile/ContributorProfile.tsx"));
+const Home = lazy(() => import("../pages/Home/Home.tsx"));
-export default function AppRouter() {
-  return <RouterProvider router={router} />;
-}
+export default function AppRouter() {
+  return (
+    <Suspense fallback={<div className="p-4">Loading...</div>}>
+      <RouterProvider router={router} />
+    </Suspense>
+  );
+}
src/pages/ContributorProfile/ContributorProfile.tsx (1)

106-117: Guard repository_url before splitting.

Avoids runtime errors if a result lacks repository_url.

-          {prs.map((pr) => {
-            const repoName = pr.repository_url.split("/").slice(-2).join("/");
+          {prs.map((pr) => {
+            const repoName = pr.repository_url
+              ? pr.repository_url.split("/").slice(-2).join("/")
+              : "unknown/unknown";
src/App.tsx (2)

8-16: Unify import style (drop .tsx extensions).

Keeps imports consistent and avoids lint noise. Safe with typical TS/bundler configs.

-import Tracker from "./pages/Tracker/Tracker.tsx";
+import Tracker from "./pages/Tracker/Tracker";
@@
-import Signup from "./pages/Signup/Signup.tsx";
+import Signup from "./pages/Signup/Signup";
-import Login from "./pages/Login/Login.tsx";
+import Login from "./pages/Login/Login";
-import ContributorProfile from "./pages/ContributorProfile/ContributorProfile.tsx";
+import ContributorProfile from "./pages/ContributorProfile/ContributorProfile";
-import Home from "./pages/Home/Home.tsx";
+import Home from "./pages/Home/Home";

17-25: Avoid global vertical centering in

.

items-center (and justify-center) will vertically center every page, which is awkward for scrollable content (e.g., lists). Prefer top-start layout.

-        <main className="flex-grow bg-gray-50 dark:bg-gray-800 flex justify-center items-center">
+        <main className="flex-grow bg-gray-50 dark:bg-gray-800 flex justify-start items-stretch">
           <Outlet />
         </main>

Optionally add scroll restoration:

+        {/* Keeps scroll position consistent on navigation */}
+        <ScrollRestoration />

And import it:

-import { createBrowserRouter, RouterProvider, Outlet } from "react-router-dom";
+import { createBrowserRouter, RouterProvider, Outlet, ScrollRestoration } from "react-router-dom";
src/pages/Contributors/Contributors.tsx (6)

4-7: Strongly type contributors to avoid any[].

Add a minimal type:

type Contributor = {
  id: number;
  login: string;
  html_url: string;
  avatar_url: string;
  contributions: number;
};

Then:

-const [contributors, setContributors] = useState<any[]>([]);
+const [contributors, setContributors] = useState<Contributor[]>([]);

10-17: getToken is unused; either use it or remove it.

Use it to dedupe token retrieval.

-      const token =
-        localStorage.getItem("gh_pat") ||
-        localStorage.getItem("github_pat") ||
-        localStorage.getItem("token") ||
-        "";
+      const token = await getToken();

(Apply within loadContributors; see diff below.)


56-61: Duplicate token retrieval.

Replace the inline localStorage reads with the getToken helper.

-      const token =
-        localStorage.getItem("gh_pat") ||
-        localStorage.getItem("github_pat") ||
-        localStorage.getItem("token") ||
-        "";
+      const token = await getToken();

62-71: Fetching contributors: endpoint usage is fine; consider including anonymous contributors if desired.

Optional: append &anon=1 if you want to include anonymous commits in the list.

-          `/repos/${owner}/${repo}/contributors?per_page=50`,
+          `/repos/${owner}/${repo}/contributors?per_page=50&anon=1`,

(Only if this matches your UX.)


80-87: Error surfacing is good; add a hint when hitting 403 (rate limit).

On 403, append “(rate limited or token missing/insufficient scope)”.
No code change required; informational.


101-108: Link to the in-app profile view as well.

Make it easy to jump to /contributor/:username without leaving the app.

-            <a href={c.html_url} target="_blank" rel="noreferrer" className="text-blue-700 hover:underline">
-              {c.login}
-            </a>
+            <a href={`/contributor/${c.login}`} className="text-blue-700 hover:underline">
+              {c.login}
+            </a>
+            <a href={c.html_url} target="_blank" rel="noreferrer" className="text-sm text-blue-700 hover:underline opacity-80">
+              (GitHub)
+            </a>

If you prefer SPA navigation, switch the first anchor to Link and import it:

import { Link } from "react-router-dom";
-            <a href={`/contributor/${c.login}`} className="text-blue-700 hover:underline">
+            <Link to={`/contributor/${c.login}`} className="text-blue-700 hover:underline">
               {c.login}
-            </a>
+            </Link>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 22e52f9 and 1ffe964.

📒 Files selected for processing (5)
  • src/App.tsx (2 hunks)
  • src/Routes/Router.tsx (2 hunks)
  • src/lib/githubFetch.ts (1 hunks)
  • src/pages/ContributorProfile/ContributorProfile.tsx (4 hunks)
  • src/pages/Contributors/Contributors.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/App.tsx (5)
src/pages/Contributors/Contributors.tsx (1)
  • Contributors (4-116)
src/pages/ContributorProfile/ContributorProfile.tsx (1)
  • ContributorProfile (20-127)
src/Routes/Router.tsx (1)
  • AppRouter (22-24)
src/pages/Home/Home.tsx (1)
  • Home (5-14)
src/pages/Tracker/Tracker.tsx (3)
  • theme (48-349)
  • _ (238-241)
  • _ (94-96)
src/Routes/Router.tsx (1)
src/App.tsx (1)
  • AppRouter (60-67)
src/lib/githubFetch.ts (2)
src/hooks/useGitHubData.ts (3)
  • getOctokit (3-71)
  • octokit (12-26)
  • username (29-58)
src/hooks/useGitHubAuth.ts (3)
  • username (4-25)
  • username (9-12)
  • octokit (14-14)
src/pages/ContributorProfile/ContributorProfile.tsx (2)
src/pages/Tracker/Tracker.tsx (1)
  • GitHubItem (38-46)
src/hooks/useGitHubData.ts (1)
  • username (29-58)
🔇 Additional comments (4)
src/App.tsx (3)

1-1: Good switch to data router.

Using createBrowserRouter/RouterProvider is the right move for v6.4+.


35-36: Toaster config looks good.

Reasonable durations and dark-mode styling.


60-67: Replace as any with as const for the future prop

-      future={{ v7_startTransition: true } as any}
+      future={{ v7_startTransition: true } as const}

Version ^6.28.0 supports the v7 flags; main.tsx imports and renders the default export (AppRouter) as <App/>.

src/pages/Contributors/Contributors.tsx (1)

1-1: ghFetch import LGTM.

Assuming ghFetch sets X-GitHub-Api-Version: 2022-11-28 and Authorization when token is present.

Could you confirm githubFetch adds the version header and Accept: application/vnd.github+json?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/App.tsx (1)

58-60: Heads-up: ContributorProfile route depends on deprecated /search/issues.

Endpoint removal date is September 4, 2025—migrate ASAP (GraphQL or new REST).

Want a follow-up PR checklist for this migration?

🧹 Nitpick comments (6)
src/App.tsx (6)

1-2: Add ScrollRestoration for better UX on navigation.

Restores scroll position between route changes.

Apply:

-import { createBrowserRouter, RouterProvider, Outlet } from "react-router-dom";
+import { createBrowserRouter, RouterProvider, Outlet, ScrollRestoration } from "react-router-dom";

9-16: Normalize lazy import paths.

Mixing extensions can hurt cache predictability; keep consistent.

-const Home = lazy(() => import("./pages/Home/Home.tsx"));
-const Tracker = lazy(() => import("./pages/Tracker/Tracker.tsx"));
+const Home = lazy(() => import("./pages/Home/Home"));
+const Tracker = lazy(() => import("./pages/Tracker/Tracker"));
 const About = lazy(() => import("./pages/About/About"));
 const Contact = lazy(() => import("./pages/Contact/Contact"));
 const Contributors = lazy(() => import("./pages/Contributors/Contributors"));
-const Signup = lazy(() => import("./pages/Signup/Signup.tsx"));
-const Login = lazy(() => import("./pages/Login/Login.tsx"));
-const ContributorProfile = lazy(() => import("./pages/ContributorProfile/ContributorProfile.tsx"));
+const Signup = lazy(() => import("./pages/Signup/Signup"));
+const Login = lazy(() => import("./pages/Login/Login"));
+const ContributorProfile = lazy(() => import("./pages/ContributorProfile/ContributorProfile"));

18-44: Add ScrollRestoration and adjust layout alignment.

Centering all pages can be awkward for long lists; start at top-left.

       <Navbar />
-      <main className="flex-grow bg-gray-50 dark:bg-gray-800 flex justify-center items-center">
+      <ScrollRestoration />
+      <main className="flex-grow bg-gray-50 dark:bg-gray-800 flex justify-center items-start">

25-27: Use an accessible Suspense fallback.

Improves screen reader experience.

-  <Suspense fallback={<div className="p-6">Loading…</div>}>
+  <Suspense fallback={<div role="status" aria-live="polite" className="p-6">Loading…</div>}>

64-70: Consolidate router definitions into one AppRouter. You have two export default function AppRouter() definitions (in src/App.tsx and src/Routes/Router.tsx); remove the redundant one or re-export from a single module to maintain a single source of truth.


46-63: Add 404 fallback route and configure router basename

Neither your Vite config nor package.json defines a custom base path (no base in vite.config.ts, no homepage/BASE_URL in package.json), so if you deploy to GitHub Pages you’ll need to pass basename to createBrowserRouter. You should also add a catch-all route to prevent blank screens on unknown URLs:

const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    errorElement: <div className="p-6">Something went wrong loading this page.</div>,
    children: [
      { index: true, element: <Home /> },
      { path: "track", element: <Tracker /> },
      { path: "signup", element: <Signup /> },
      { path: "login", element: <Login /> },
      { path: "about", element: <About /> },
      { path: "contact", element: <Contact /> },
      { path: "contributors", element: <Contributors /> },
      { path: "contributor/:username", element: <ContributorProfile /> },
+     { path: "*", element: <div className="p-6">Page not found.</div> },
    ],
  },
-]);
+], { basename: import.meta.env.BASE_URL || "/" });

(optional) verify or set a base in vite.config.ts or a homepage field in package.json if you’re deploying under a subpath.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1ffe964 and 1985c85.

📒 Files selected for processing (1)
  • src/App.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/App.tsx (1)
src/Routes/Router.tsx (2)
  • AppRouter (22-24)
  • Routes (11-24)
🔇 Additional comments (1)
src/App.tsx (1)

30-39: Toaster config looks good.

Sane defaults and dark-mode aware.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
src/lib/githubFetch.ts (4)

10-15: Don’t override caller Authorization; accept preformatted schemes and preserve custom Accept.

Avoid clobbering an Authorization already provided via init.headers, and allow callers to pass a preformatted "Bearer ..." or "token ..." string. Also set Accept only if absent so callers can opt into previews.

Apply this diff:

-  const headers = new Headers(init.headers || {});
-  if (token) {
-    headers.set("Authorization", `Bearer ${token}`); // or `token ${token}`
-  }
-  headers.set("Accept", "application/vnd.github+json");
+  const headers = new Headers(init.headers || {});
+  // Preserve caller-provided Authorization; allow preformatted schemes.
+  if (!headers.has("Authorization") && token) {
+    const preformatted = token.startsWith("Bearer ") || token.startsWith("token ");
+    headers.set("Authorization", preformatted ? token : `Bearer ${token}`);
+  }
+  if (!headers.has("Accept")) {
+    headers.set("Accept", "application/vnd.github+json");
+  }
   headers.set("X-GitHub-Api-Version", GITHUB_API_VERSION);

9-10: Build the request URL with URL(base, path) for robust joining and absolute-URL support.

Handles missing/extra slashes and lets callers pass absolute URLs without double-prefixing.

Apply this diff:

-  return fetch(`${base}${path}`, { ...init, headers });
+  const url = new URL(path, base).toString();
+  return fetch(url, { ...init, headers });

Also applies to: 17-17


4-8: Annotate ghFetch return type.

Minor clarity/readability win in TS.

Apply this diff:

-export async function ghFetch(
+export async function ghFetch(
   path: string,
   token: string,
   init: RequestInit = {}
-) {
+): Promise<Response> {

20-33: Throw a structured error with parsed GitHub payload (message, errors, docs URL) and headers.

Improves debuggability (rate-limit headers, validation errors) and avoids logging entire raw bodies.

Apply this diff:

 export async function ghJson<T>(
   path: string,
   token: string,
   init: RequestInit = {}
 ): Promise<T> {
   const res = await ghFetch(path, token, init);
   if (!res.ok) {
-    const text = await res.text().catch(() => "");
-    throw new Error(
-      `GitHub API ${res.status} ${res.statusText}${text ? `: ${text}` : ""}`
-    );
+    const ct = res.headers.get("content-type") || "";
+    let body: unknown;
+    if (ct.includes("application/json")) {
+      body = await res.json().catch(() => undefined);
+    } else {
+      body = (await res.text().catch(() => ""))?.slice(0, 2000);
+    }
+    throw new GitHubApiError(res.status, res.statusText, body, res.headers);
   }
   return res.json() as Promise<T>;
 }

Add this helper class (outside the ranges shown, e.g., right below the version constant):

export class GitHubApiError extends Error {
  constructor(
    public status: number,
    public statusText: string,
    public body: unknown,
    public headers: Headers
  ) {
    const summary =
      typeof body === "object" && body && "message" in (body as any)
        ? String((body as any).message)
        : typeof body === "string"
        ? body
        : "";
    super(`GitHub API ${status} ${statusText}${summary ? `: ${summary}` : ""}`);
    this.name = "GitHubApiError";
  }
}

Notes:

  • UI can inspect x-ratelimit-remaining/reset to render friendlier messages.
  • If you ever call ghJson on endpoints returning 204, consider a variant that returns void for those routes.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1985c85 and df1f6b0.

📒 Files selected for processing (1)
  • src/lib/githubFetch.ts (1 hunks)
🔇 Additional comments (1)
src/lib/githubFetch.ts (1)

2-2: Versioned/Accept headers are correct for GitHub REST.

Good call setting X-GitHub-Api-Version and the vendor Accept; this removes unversioned warnings and aligns with current REST guidance.

Also applies to: 14-15

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
package.json (1)

33-45: Remove v5 type package for React Router; v6 ships types

@types/react-router-dom@^5.3.3 conflicts with react-router-dom@^6.28.0 (v6 bundles its own types). Keeping it can cause wrong typings.

Apply:

@@
-    "@types/react-router-dom": "^5.3.3",
🧹 Nitpick comments (3)
package.json (1)

19-28: Remove unused octokit dependency from package.json
Automated scans across the codebase found no ES-module imports or CommonJS requires of the legacy octokit package, so you can safely delete the "octokit": "^4.0.2" entry (and clean up your lockfile).

src/utils/constants.ts (2)

1-4: Allow env overrides for GH Enterprise and future version bumps

Enable configuring base URL and API version via env (Vite) while keeping current defaults.

Apply:

-export const GITHUB_API_VERSION = "2022-11-28";
+export const GITHUB_API_VERSION =
+  import.meta?.env?.VITE_GITHUB_API_VERSION ?? "2022-11-28";
 
-export const GITHUB_API_BASE = "https://api.github.com";
+export const GITHUB_API_BASE =
+  import.meta?.env?.VITE_GITHUB_API_BASE ?? "https://api.github.com";

3-7: Minor: centralize Accept header constant (optional)

If multiple callers set Accept, export a constant to avoid string drift.

Example:

export const GITHUB_ACCEPT = "application/vnd.github+json";
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between df1f6b0 and f594f4e.

📒 Files selected for processing (5)
  • package.json (1 hunks)
  • src/lib/octokit.ts (1 hunks)
  • src/main.tsx (0 hunks)
  • src/pages/ContributorProfile/ContributorProfile.tsx (4 hunks)
  • src/utils/constants.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/main.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/ContributorProfile/ContributorProfile.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
package.json (1)
src/hooks/useGitHubData.ts (1)
  • getOctokit (3-71)
src/lib/octokit.ts (2)
src/hooks/useGitHubAuth.ts (3)
  • octokit (14-14)
  • username (9-12)
  • username (4-25)
src/hooks/useGitHubData.ts (3)
  • getOctokit (3-71)
  • octokit (12-26)
  • username (29-58)
src/utils/constants.ts (1)
src/pages/Tracker/Tracker.tsx (1)
  • GitHubItem (38-46)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hooks/useGitHubData.ts (1)

61-81: Rate-limit detection is unreliable for GraphQL; handle 200 + errors and Octokit GraphqlResponseError.

GraphQL often returns 200 with an errors array; err.status may be undefined.

-      } catch (err: any) {
-        if (err.status === 403) {
-          setError('GitHub API rate limit exceeded. Please wait or use a token.');
-          setRateLimited(true); // Prevent further fetches
-        } else {
-          setError(err.message || 'Failed to fetch data');
-        }
-      } finally {
+      } catch (err: any) {
+        const msg = err?.message || 'Failed to fetch data';
+        const isRateLimit =
+          err?.status === 403 ||
+          /rate limit/i.test(msg) ||
+          Array.isArray(err?.errors) && err.errors.some((e: any) => /rate limit/i.test(e?.message));
+        if (isRateLimit) {
+          setError('GitHub API rate limit exceeded. Please wait or use a token.');
+          setRateLimited(true);
+        } else {
+          setError(msg);
+        }
+      } finally {
♻️ Duplicate comments (1)
src/pages/ContributorProfile/ContributorProfile.tsx (1)

106-113: Await clipboard write to properly handle failures.

-  const handleCopyLink = () => {
-    try {
-      navigator.clipboard.writeText(window.location.href);
+  const handleCopyLink = async () => {
+    try {
+      await navigator.clipboard.writeText(window.location.href);
       toast.success("🔗 Shareable link copied to clipboard!");
     } catch {
       toast.error("Could not copy link");
     }
   };
🧹 Nitpick comments (1)
src/hooks/useGitHubData.ts (1)

3-3: Type the octokit factory and narrow “type”.

Optional hardening: use proper types to catch regressions at compile time.

-export const useGitHubData = (getOctokit: () => any) => {
+import type { Octokit } from '@octokit/core';
+export const useGitHubData = (getOctokit: () => Octokit) => {-  const fetchPaginated = async (octokit: any, username: string, type: string, page = 1, per_page = 10) => {
+  const fetchPaginated = async (
+    octokit: Octokit,
+    username: string,
+    type: 'issue' | 'pr',
+    page = 1,
+    per_page = 10
+  ) => {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f594f4e and d367be4.

📒 Files selected for processing (2)
  • src/hooks/useGitHubData.ts (1 hunks)
  • src/pages/ContributorProfile/ContributorProfile.tsx (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/hooks/useGitHubData.ts (1)
src/pages/Tracker/Tracker.tsx (2)
  • GitHubItem (38-46)
  • data (101-138)
src/pages/ContributorProfile/ContributorProfile.tsx (2)
src/pages/Contributors/Contributors.tsx (3)
  • contributors (27-142)
  • Contributor (19-25)
  • contributor (74-137)
src/pages/Tracker/Tracker.tsx (1)
  • GitHubItem (38-46)
🔇 Additional comments (1)
src/pages/ContributorProfile/ContributorProfile.tsx (1)

79-83: Ensure Content-Type header is set for GraphQL POST.

If githubFetch doesn’t add it, GitHub will reject the body.

-        const prsRes = await gh("/graphql", token, {
+        const prsRes = await gh("/graphql", token, {
           method: "POST",
-          body: JSON.stringify(gqlQuery),
+          headers: { "Content-Type": "application/json" },
+          body: JSON.stringify(gqlQuery),
         });

If githubFetch already injects this header, skip this change.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
src/hooks/useGitHubData.ts (2)

47-52: Stop fabricating cursors; clamp page size and use real endCursor.

btoa('cursor:...') is invalid and will 400/500 or page incorrectly. Use search.pageInfo.endCursor and cap first≤100. Also add a stable sort to prevent page drift.

Apply within these ranges:

-    const queryString = `author:${username} is:${type}`;
+    const queryString = `author:${username} is:${type} sort:updated-desc`;
     const response = await octokit.graphql(query, {
       queryString,
-      first: per_page,
-      after: page > 1 ? btoa(`cursor:${(page - 1) * per_page}`) : null,
+      first: Math.min(100, per_page),
+      after: page > 1 ? (cursorsRef.current?.[type]?.[page - 1] ?? null) : null,
     });

And outside this hunk:

- import { useState, useCallback } from 'react';
+ import { useState, useCallback, useRef } from 'react';
-  const fetchPaginated = async (octokit: any, username: string, type: string, page = 1, per_page = 10) => {
+  const fetchPaginated = async (
+    octokit: any,
+    username: string,
+    type: 'issue' | 'pr',
+    page = 1,
+    per_page = 10
+  ) => {
// add near other state in the hook
const cursorsRef = useRef<{ issue: Record<number, string | null>; pr: Record<number, string | null> }>({
  issue: {},
  pr: {},
});

54-57: Return Tracker-shaped items and cache cursors.

Tracker expects GitHubItem (id number, html_url, created_at, state, repository_url, pull_request.merged_at). Returning raw nodes breaks UI.

-    return {
-      items: response.search.edges.map((edge: any) => edge.node),
-      total: response.search.issueCount,
-    };
+    const endCursor = response.search.pageInfo?.endCursor ?? null;
+    // cache cursor for next page
+    cursorsRef.current[type] ||= {};
+    cursorsRef.current[type][page] = endCursor;
+
+    const items = response.search.edges.map((edge: any) => {
+      const n = edge.node;
+      const base = {
+        id: n.databaseId as number,
+        title: n.title as string,
+        html_url: n.url as string,
+        created_at: n.createdAt as string,
+        state: n.state as string,
+        repository_url: n.repository?.url as string,
+      };
+      return 'mergedAt' in n && n.mergedAt
+        ? { ...base, pull_request: { merged_at: n.mergedAt as string } }
+        : base;
+    });
+    return { items, total: response.search.issueCount as number };
🧹 Nitpick comments (6)
src/hooks/useGitHubData.ts (1)

81-86: Handle GraphQL rate limiting, not just HTTP 403.

octokit.graphql often returns 200 with errors.type === 'RATE_LIMITED'. Detect that to set a meaningful error and allow retry after cooldown/token change.

-      } catch (err: any) {
-        if (err.status === 403) {
+      } catch (err: any) {
+        const isRateLimited =
+          err?.status === 403 ||
+          err?.errors?.some?.((e: any) => e?.type === 'RATE_LIMITED');
+        if (isRateLimited) {
           setError('GitHub API rate limit exceeded. Please wait or use a token.');
           setRateLimited(true); // Prevent further fetches
         } else {
           setError(err.message || 'Failed to fetch data');
         }
src/Routes/Router.tsx (1)

11-20: Add a catch-all 404 route.

Prevents blank screens for unknown paths and improves UX.

   { path: "/contributor/:username", element: <ContributorProfile /> },
+  { path: "*", element: <Home /> },
src/pages/Contributors/Contributors.tsx (4)

10-17: Remove duplication: use getToken() below or delete it.

You reimplement token retrieval later. Prefer a single helper.

-    async function getToken() {
+    async function getToken() {
       return (
         localStorage.getItem("gh_pat") ||
         localStorage.getItem("github_pat") ||
         localStorage.getItem("token") ||
         ""
       );
     }

19-50: Cache resolved repo to avoid repeated search calls.

Persist to localStorage and reuse to reduce latency and rate-limit pressure.

-    async function resolveRepoFullName(token: string): Promise<string | null> {
+    async function resolveRepoFullName(token: string): Promise<string | null> {
+      const cached = localStorage.getItem("gh_repo_full_name");
+      if (cached) return cached;
       // Try to find the repo by searching both org and user scopes and dash/underscore variants
       const owners = ["GitMetricsLab", "gitmetricsslab", "ASR1015"]; // include common forks/owners
       const names = ["github-tracker", "github_tracker"]; // dash vs underscore
@@
-            if (match?.full_name) return match.full_name as string;
+            if (match?.full_name) {
+              localStorage.setItem("gh_repo_full_name", match.full_name as string);
+              return match.full_name as string;
+            }
@@
-            if (match2?.full_name) return match2.full_name as string;
+            if (match2?.full_name) {
+              localStorage.setItem("gh_repo_full_name", match2.full_name as string);
+              return match2.full_name as string;
+            }

56-61: Use getToken() here.

Prevents drift if token sourcing changes.

-      const token =
-        localStorage.getItem("gh_pat") ||
-        localStorage.getItem("github_pat") ||
-        localStorage.getItem("token") ||
-        "";
+      const token = await getToken();

67-70: Consider including anonymous contributors (optional).

If you want complete counts, add anon=1.

-          `/repos/${fullName}/contributors?per_page=50`,
+          `/repos/${fullName}/contributors?per_page=50&anon=1`,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d367be4 and 74eee8f.

📒 Files selected for processing (4)
  • src/Routes/Router.tsx (2 hunks)
  • src/hooks/useGitHubData.ts (1 hunks)
  • src/pages/ContributorProfile/ContributorProfile.tsx (4 hunks)
  • src/pages/Contributors/Contributors.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/ContributorProfile/ContributorProfile.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
src/hooks/useGitHubData.ts (1)
src/pages/Tracker/Tracker.tsx (3)
  • GitHubItem (38-46)
  • data (101-138)
  • theme (48-349)
src/Routes/Router.tsx (1)
src/App.tsx (1)
  • App (8-42)
🔇 Additional comments (2)
src/hooks/useGitHubData.ts (1)

13-45: GraphQL selection now includes required fields and pageInfo — good.

databaseId/state/repository.url/mergedAt and pageInfo are present; this unblocks Tracker mapping and real pagination.

src/pages/Contributors/Contributors.tsx (1)

62-66: LGTM: repo resolution with fallback.

Dynamic repo lookup with a sane fallback matches the PR goal.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/pages/ContributorProfile/ContributorProfile.tsx (1)

96-99: Don’t cast raw GraphQL nodes; map to PR[] and handle GraphQL errors.

Current cast leaves repository_url undefined and breaks the list (split on undefined). Map fields and surface GraphQL errors.

-        const prsData = await prsRes.json();
-        const prs = prsData.data?.search?.nodes || [];
-        setPRs(prs as PR[]);
+        const prsData = await prsRes.json();
+        if (Array.isArray(prsData.errors) && prsData.errors.length) {
+          throw new Error(prsData.errors.map((e: any) => e.message).join('; '));
+        }
+        const nodes = prsData.data?.search?.nodes ?? [];
+        const mapped: PR[] = nodes
+          .filter((n: any) => n && typeof n.databaseId === 'number' && n.url && n.repository?.url)
+          .map((n: any) => ({
+            id: n.databaseId,
+            title: n.title,
+            html_url: n.url,
+            repository_url: n.repository.url,
+          }));
+        setPRs(mapped);
🧹 Nitpick comments (6)
src/hooks/useGitHubData.ts (4)

79-89: Reset pagination cursors when user changes or on first page.

Prevents carrying over stale cursors between users or after manual resets.

   const fetchData = useCallback(
     async (username: string, page = 1, perPage = 10) => {
       
       const octokit = getOctokit();
 
       if (!octokit || !username || rateLimited) return;
 
       setLoading(true);
       setError('');
+      // reset cursors when switching user or when starting from page 1
+      if (page === 1 || lastUserRef.current !== username) {
+        cursorsRef.current = { issue: {}, pr: {} };
+        lastUserRef.current = username;
+      }

Add alongside other refs (outside this hunk):

const lastUserRef = useRef<string>('');

4-5: Type the data shape end-to-end (avoid any[]).

Match Tracker’s GitHubItem and catch shape drift at compile time.

-  const [issues, setIssues] = useState([]);
-  const [prs, setPrs] = useState([]);
+  const [issues, setIssues] = useState<GitHubItem[]>([]);
+  const [prs, setPrs] = useState<GitHubItem[]>([]);

Add (outside this hunk):

type GitHubItem = {
  id: number;
  title: string;
  state: string;
  created_at: string;
  pull_request?: { merged_at: string | null };
  repository_url: string;
  html_url: string;
};

And cast the mapped array:

-    const items = response.search.edges.map((edge: any) => {
+    const items: GitHubItem[] = response.search.edges.map((edge: any) => {

Also applies to: 59-70


27-28: Remove unused edge.cursor to trim payload.

-          edges {
-            cursor
+          edges {
             node {

99-105: Surface GraphQL errors and widen rate-limit detection.

Octokit GraphQL may return 200 with errors or throw 400 with an errors array.

-      } catch (err: any) {
-        if (err.status === 403) {
+      } catch (err: any) {
+        const gqlErrors = err?.errors || err?.response?.data?.errors;
+        if (Array.isArray(gqlErrors) && gqlErrors.length) {
+          setError(gqlErrors.map((e: any) => e.message).join('; '));
+        } else if (err.status === 403 || /rate limit/i.test(err?.message ?? '')) {
           setError('GitHub API rate limit exceeded. Please wait or use a token.');
           setRateLimited(true); // Prevent further fetches
         } else {
           setError(err.message || 'Failed to fetch data');
         }
src/pages/ContributorProfile/ContributorProfile.tsx (2)

155-159: Defensive repo name parsing.

Avoids surprises if URL format changes.

-          {prs.map((pr) => {
-            const repoName = pr.repository_url.split("/").slice(-2).join("/");
+          {prs.map((pr) => {
+            const repoPath = new URL(pr.repository_url).pathname.replace(/^\/|\/$/g, "");
+            const parts = repoPath.split("/");
+            const repoName = parts.slice(-2).join("/");

56-75: Optional: add pageInfo and support “Load more” later.

You already sort by updated; fetching pageInfo here enables incremental pagination for PR lists.

-              search(query: $query, type: ISSUE, first: 100) {
+              search(query: $query, type: ISSUE, first: 100) {
+                pageInfo { hasNextPage endCursor }
                 nodes {
                   ... on PullRequest {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 74eee8f and b8a87eb.

📒 Files selected for processing (3)
  • src/hooks/useGitHubData.ts (2 hunks)
  • src/pages/ContributorProfile/ContributorProfile.tsx (4 hunks)
  • src/pages/Contributors/Contributors.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/Contributors/Contributors.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
src/pages/ContributorProfile/ContributorProfile.tsx (2)
src/pages/Tracker/Tracker.tsx (1)
  • GitHubItem (38-46)
src/hooks/useGitHubAuth.ts (1)
  • username (4-25)
src/hooks/useGitHubData.ts (1)
src/pages/Tracker/Tracker.tsx (2)
  • GitHubItem (38-46)
  • data (101-138)
🔇 Additional comments (1)
src/pages/ContributorProfile/ContributorProfile.tsx (1)

82-85: Set Content-Type for GraphQL POST (unless gh() already does).

Avoids reliance on wrapper defaults and ensures proper parsing.

-        const prsRes = await gh("/graphql", token, {
+        const prsRes = await gh("/graphql", token, {
           method: "POST",
+          headers: { "Content-Type": "application/json" },
           body: JSON.stringify(gqlQuery),
         });

If gh() injects this header, feel free to skip.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hooks/useGitHubData.ts (1)

100-106: Handle GraphQL rate limits (v4 returns 200 + errors, not 403).

Extend detection to cover GraphQL “RATE_LIMITED” errors and common messages so UX stays coherent.

-        if (err.status === 403) {
+        if (
+          err?.status === 403 ||
+          err?.errors?.some((e: any) => e?.type === 'RATE_LIMITED') ||
+          /rate limit/i.test(err?.message ?? '')
+        ) {
           setError('GitHub API rate limit exceeded. Please wait or use a token.');
           setRateLimited(true); // Prevent further fetches
         } else {
           setError(err.message || 'Failed to fetch data');
         }
♻️ Duplicate comments (1)
src/hooks/useGitHubData.ts (1)

54-55: Ensure X-GitHub-Api-Version header applies to graphql too.

Confirm getOctokit() comes from a factory that sets X-GitHub-Api-Version: 2022-11-28 so GraphQL calls don’t warn.

Run:

#!/bin/bash
rg -n --glob '!**/node_modules/**' 'makeOctokit|new Octokit|X-GitHub-Api-Version|graphql\(' -C2
🧹 Nitpick comments (3)
src/hooks/useGitHubData.ts (3)

85-86: Avoid “stuck” rateLimited state; auto-reset or expose a reset.

Once set, rateLimited blocks all future fetches for the session. Add a short TTL reset (e.g., 60s) or expose a clear function to the caller.

-          setRateLimited(true); // Prevent further fetches
+          setRateLimited(true); // Prevent further fetches
+          setTimeout(() => setRateLimited(false), 60_000);

27-29: Trim unused cursor from selection to reduce payload.

You only use pageInfo.endCursor. Drop edges.cursor.

-          edges {
-            cursor
-            node {
+          edges {
+            node {

5-7: Add strict types for items and state to catch regressions at compile time.

Type states and the paginated result to match Tracker’s GitHubItem.

 import { useState, useRef, useCallback } from 'react';
+type GitHubItem = {
+  id: number;
+  title: string;
+  state: string;
+  created_at: string;
+  repository_url: string;
+  html_url: string;
+  pull_request?: { merged_at: string | null };
+};

 export const useGitHubData = (getOctokit: () => any) => {
   type SearchType = 'issue' | 'pr';
-  const [issues, setIssues] = useState([]);
-  const [prs, setPrs] = useState([]);
+  const [issues, setIssues] = useState<GitHubItem[]>([]);
+  const [prs, setPrs] = useState<GitHubItem[]>([]);

-  const fetchPaginated = async (octokit: any, username: string, type: SearchType, page = 1, per_page = 10) => {
+  const fetchPaginated = async (
+    octokit: any,
+    username: string,
+    type: SearchType,
+    page = 1,
+    per_page = 10
+  ): Promise<{ items: GitHubItem[]; total: number }> => {

Also applies to: 18-18, 60-71

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b8a87eb and 1d3341a.

📒 Files selected for processing (1)
  • src/hooks/useGitHubData.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-02T13:07:19.983Z
Learnt from: ASR1015
PR: GitMetricsLab/github_tracker#205
File: src/hooks/useGitHubData.ts:0-0
Timestamp: 2025-09-02T13:07:19.983Z
Learning: In src/hooks/useGitHubData.ts, cursor handling for GraphQL pagination uses a type-safe approach: SearchType = 'issue' | 'pr', cursorsRef as Record<SearchType, Record<number, string | null>>, and fetchPaginated caches endCursor per page per type for proper pagination state management.

Applied to files:

  • src/hooks/useGitHubData.ts
🧬 Code graph analysis (1)
src/hooks/useGitHubData.ts (1)
src/pages/Tracker/Tracker.tsx (2)
  • GitHubItem (38-46)
  • data (101-138)
🔇 Additional comments (1)
src/hooks/useGitHubData.ts (1)

18-51: LGTM: Solid GraphQL migration and correct field mapping.

Using search(type: ISSUE) with is:issue/is:pr filters, clamped page size, and mapping to Tracker’s shape looks correct.

Also applies to: 60-71

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hooks/useGitHubData.ts (1)

82-114: Rate-limit detection won’t trigger for GraphQL errors; broaden checks.
GraphQL often throws with status 200 and error type RATE_LIMITED. Your 403 check will miss it.

Apply:

-      } catch (err: any) {
-        if (err.status === 403) {
-          setError('GitHub API rate limit exceeded. Please wait or use a token.');
-          setRateLimited(true); // Prevent further fetches
-        } else {
-          setError(err.message || 'Failed to fetch data');
-        }
+      } catch (err: any) {
+        const rateLimitedErr =
+          err?.status === 403 ||
+          err?.response?.headers?.['x-ratelimit-remaining'] === '0' ||
+          (Array.isArray(err?.errors) &&
+            err.errors.some(
+              (e: any) =>
+                e?.type === 'RATE_LIMITED' || /rate limit/i.test(e?.message ?? '')
+            ));
+        if (rateLimitedErr) {
+          setError('GitHub API rate limit exceeded. Please wait or use a token.');
+          setRateLimited(true);
+        } else {
+          setError(err?.message ?? 'Failed to fetch data');
+        }
🧹 Nitpick comments (5)
src/hooks/useGitHubData.ts (5)

13-16: Cursor cache scope: future-proof by keying with full query string.
Today you key by username + page size, which is fine. If qualifiers change later (repo/date/etc.), collisions can reappear. Safer: use queryString.

Apply:

-    const cursorKey = `${username}:${Math.min(100, per_page)}`;
+    const cursorKey = `${queryString}:${Math.min(100, per_page)}`;

18-51: Optional: include __typename to aid mapping/debug.
Adding __typename makes it trivial to tell Issue vs PullRequest without relying on field presence.

Apply:

           node {
+            __typename
             ... on Issue {

61-72: Optional: add explicit typing for mapped items to catch shape drift.

Apply:

-    const items = response.search.edges.map((edge: any) => {
+    type GitHubItem = {
+      id: number;
+      title: string;
+      state: string;
+      created_at: string;
+      repository_url: string;
+      html_url: string;
+      pull_request?: { merged_at: string | null };
+    };
+    const items: GitHubItem[] = response.search.edges.map((edge: any) => {

5-7: Type the state arrays for stronger guarantees.

Apply:

-  const [issues, setIssues] = useState([]);
+  const [issues, setIssues] = useState<GitHubItem[]>([]);
...
-  const [prs, setPrs] = useState([]);
+  const [prs, setPrs] = useState<GitHubItem[]>([]);

Note: use the GitHubItem type from the previous comment (or centralize it in a shared types module).


82-114: Ensure getOctokit() provides a GraphQL-capable client with version header.
If getOctokit() returns a bare @octokit/core without graphql or without X-GitHub-Api-Version, this hook will fail or still warn. Please verify makeOctokit(...) (with default headers) backs getOctokit().

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1d3341a and 3e82610.

📒 Files selected for processing (1)
  • src/hooks/useGitHubData.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-02T13:07:19.983Z
Learnt from: ASR1015
PR: GitMetricsLab/github_tracker#205
File: src/hooks/useGitHubData.ts:0-0
Timestamp: 2025-09-02T13:07:19.983Z
Learning: In src/hooks/useGitHubData.ts, cursor handling for GraphQL pagination uses a type-safe approach: SearchType = 'issue' | 'pr', cursorsRef as Record<SearchType, Record<number, string | null>>, and fetchPaginated caches endCursor per page per type for proper pagination state management.

Applied to files:

  • src/hooks/useGitHubData.ts
🧬 Code graph analysis (1)
src/hooks/useGitHubData.ts (2)
src/pages/Tracker/Tracker.tsx (1)
  • GitHubItem (38-46)
src/hooks/useGitHubAuth.ts (1)
  • username (4-25)
🔇 Additional comments (6)
src/hooks/useGitHubData.ts (6)

1-1: Good: correct React imports for hooks in use.


4-4: Type-safety win with SearchType.


53-58: Paging stability caveat.
sort:updated-desc can cause duplicates/skips across pages when items update between requests. Acceptable, but ensure UI tolerates minor jitter.


74-77: LGTM: cursor caching per type and scope avoids cross-user/page-size collisions.


79-79: Returning total from issueCount is correct for the filtered query.


116-125: Return shape looks good and stable for consumers.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
src/lib/octokit.ts (5)

11-11: Trim token before applying auth

Avoid treating whitespace-only tokens as valid.

Apply:

-    auth: token || undefined,
+    auth: token?.trim() || undefined,

6-6: Export an Octokit instance type for downstream typing

Helps consumers type variables/params without importing implementation details.

Apply:

 const MyOctokit = Octokit.plugin(paginateRest, throttling);
+
+export type OctokitClient = InstanceType<typeof MyOctokit>;

1-4: Add types for throttling callback params

Improves IntelliSense and guards against breaking changes.

Apply:

 import { Octokit } from "@octokit/core";
 import { paginateRest } from "@octokit/plugin-paginate-rest";
 import { throttling } from "@octokit/plugin-throttling";
 import { GITHUB_API_VERSION, GITHUB_API_BASE } from "../utils/constants";
+import type { RequestOptions } from "@octokit/types";

23-31: Type the rate-limit handler and use a default param instead of optional

Cleaner than optional + nullish coalescing.

Apply:

-      onRateLimit: (retryAfter, options, octokit, retryCount?: number) => {
-        const count = retryCount ?? 0;
-        if (count === 0) {
+      onRateLimit: (
+        retryAfter: number,
+        options: RequestOptions,
+        _octokit: unknown,
+        retryCount = 0
+      ) => {
+        if (retryCount === 0) {
           console.warn(
             `Rate Limit: ${options.method} ${options.url}. Retrying in ${retryAfter}s.`
           );
           return true; // single retry
         }
         return false;
       },

34-39: Also type the secondary rate-limit handler

Minor DX improvement; consistent with the primary handler.

Apply:

-      onSecondaryRateLimit: (_retryAfter, options) => {
+      onSecondaryRateLimit: (_retryAfter: number, options: RequestOptions) => {
         console.warn(
           `Secondary rate limit: ${options.method} ${options.url}.`
         );
         return true; // single retry
       },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3e82610 and 58bbb99.

📒 Files selected for processing (3)
  • src/hooks/useGitHubData.ts (2 hunks)
  • src/lib/octokit.ts (1 hunks)
  • src/pages/ContributorProfile/ContributorProfile.tsx (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/hooks/useGitHubData.ts
  • src/pages/ContributorProfile/ContributorProfile.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/lib/octokit.ts (2)
src/utils/constants.ts (2)
  • GITHUB_API_BASE (3-3)
  • GITHUB_API_VERSION (1-1)
src/hooks/useGitHubAuth.ts (3)
  • octokit (14-14)
  • username (9-12)
  • username (4-25)
🔇 Additional comments (1)
src/lib/octokit.ts (1)

14-20: API versioning and headers are correctly centralized

Using shared constants plus Accept header is correct and should silence deprecation warnings consistently.

@mehul-m-prajapati
Copy link
Collaborator

@ASR1015 : Don't make big change in one commit, It is harder to review.

Please split commits

@ASR1015
Copy link
Contributor Author

ASR1015 commented Sep 2, 2025

@ASR1015 : Don't make big change in one commit, It is harder to review.

Please split commits

ok sir

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/pages/Contributors/Contributors.tsx (1)

66-70: Dynamic repo resolution + fallback: looks good.

Nice improvement over hard-coding; matches PR intent.

🧹 Nitpick comments (5)
src/pages/Contributors/Contributors.tsx (5)

71-74: Paginate contributors; at least bump per_page to 100.

Large repos will be truncated. Quick win: request 100 per page; ideal: follow Link: rel="next".

-          `/repos/${fullName}/contributors?per_page=50`,
+          `/repos/${fullName}/contributors?per_page=100`,

Example pagination helper (place near loadContributors):

async function fetchAll<T>(url: string, token: string): Promise<T[]> {
  const out: T[] = [];
  let next: string | null = url;
  while (next) {
    const res = await gh(next, token);
    if (res.status === 204) break;
    if (!res.ok) throw new Error(`GitHub ${res.status}: ${await res.text()}`);
    out.push(...(await res.json()));
    const link = res.headers.get("Link") || "";
    const m = link.match(/<([^>]+)>;\s*rel="next"/);
    next = m ? m[1] : null;
  }
  return out;
}

93-99: Don’t log AbortError on unmount.

Noise in console when the component unmounts mid-request.

-      } catch (err: any) {
-        if (isMounted)
-          setError(
-            err?.message ||
-              "Failed to fetch contributors. Check owner/repo and token (use 'repo' scope if private)."
-          );
-        console.error("[contributors] error", err);
+      } catch (err: unknown) {
+        if ((err as any)?.name === "AbortError") return;
+        if (isMounted) {
+          const msg =
+            err instanceof Error
+              ? err.message
+              : "Failed to fetch contributors. Check owner/repo and token (use 'repo' scope if private).";
+          setError(msg);
+        }
+        console.error("[contributors] error", err);

123-124: Add noopener to external links.

Prevents window.opener attacks.

-            <a href={c.html_url} target="_blank" rel="noreferrer" className="text-blue-700 hover:underline">
+            <a href={c.html_url} target="_blank" rel="noopener noreferrer" className="text-blue-700 hover:underline">

27-58: Cache resolved repo name to reduce Search API calls.

Lightweight localStorage cache with TTL will cut rate-limit pressure and speed up mount.

const CACHE_KEY = "resolved_repo_full_name";
const CACHE_TTL = 24 * 60 * 60 * 1000; // 24h

async function resolveRepoFullName(token: string): Promise<string | null> {
  const cached = localStorage.getItem(CACHE_KEY);
  if (cached) {
    try {
      const { v, ts } = JSON.parse(cached);
      if (Date.now() - ts < CACHE_TTL && typeof v === "string") return v;
    } catch {}
  }

  // ...existing search logic...

  if (match?.full_name) {
    localStorage.setItem(CACHE_KEY, JSON.stringify({ v: match.full_name, ts: Date.now() }));
    return match.full_name as string;
  }
  return null;
}

18-19: Pass AbortController.signal into all ghFetch calls
ghFetch’s signature accepts a RequestInit. Wire ac.signal into each gh(…) invocation (lines 36–39, 48–49, 71–74, 107–109) to enable request cancellation. Alternatively, drop the AbortController if you don’t intend to cancel requests.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 58bbb99 and 47f8948.

📒 Files selected for processing (1)
  • src/pages/Contributors/Contributors.tsx (1 hunks)

@ASR1015
Copy link
Contributor Author

ASR1015 commented Sep 2, 2025

Fixed GitHub API deprecations and contributor profile issues. All checks passed, ready to merge

@ASR1015
Copy link
Contributor Author

ASR1015 commented Sep 2, 2025

•	Replaced BrowserRouter/Routes with React Router v6.4+ createBrowserRouter and RouterProvider for modern routing.
•	Added ghFetch/ghJson helper and Octokit factory with proper headers, throttling, and token-aware requests.
•	Refactored ContributorProfile and Contributors to fetch contributors + PRs via GraphQL (with cursor handling, error states, and 204 support).
•	Updated types: added PR_id, proper mapping for PR shape, and type-safe cursor refs.
•	Handled edge cases:
•	204 No Content contributors API.
•	Pagination cursor collisions (keyed by username & per_page).
•	Unmerged PRs misclassified as issues (ensures pull_request always attached).
•	Consolidated constants (GITHUB_API_VERSION, GITHUB_API_BASE) for consistency.
•	Improved UI states: safe unmount, better loading/error handling, and fallback repo resolution.
•	Added rate-limit retry & safeguards against empty tokens from localStorage.

✅ All checks passed
✅ No conflicts with base branch

This PR fully modernizes GitHub API usage, removes deprecated patterns, and ensures stability for contributors and PR fetching.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants