Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1ffe964
fix: add GitHub API version header, PAT usage, router cleanup; contri…
ASR1015 Sep 2, 2025
1985c85
perf(router): lazy-load routes + suspense fallback
ASR1015 Sep 2, 2025
df1f6b0
feat: add ghJson helper for API error handling
ASR1015 Sep 2, 2025
f594f4e
fix: handle GitHub API deprecations + robust profile/PR fetching
ASR1015 Sep 2, 2025
d367be4
fix: migrate /search/issues to GraphQL + improve error handling
ASR1015 Sep 2, 2025
5b67ede
fix: await clipboard write for reliable copy + error handling
ASR1015 Sep 2, 2025
9bc8d72
fix: leverage resolveRepoFullName with fallback in Contributors page
ASR1015 Sep 2, 2025
60a8c64
fix(router): remove duplicate RouterProvider and export router config…
ASR1015 Sep 2, 2025
74eee8f
feat: migrate Tracker to GraphQL search, add missing fields, better e…
ASR1015 Sep 2, 2025
14bfce2
fix(github-data): improve GraphQL query with correct fields, paginati…
ASR1015 Sep 2, 2025
232cfe9
fix: map GitHub response to Tracker fields + cache endCursor
ASR1015 Sep 2, 2025
7ba06b2
fix: align PR.id with GraphQL databaseId
ASR1015 Sep 2, 2025
949406a
fix: align PR GraphQL fields with Tracker requirements (databaseId, r…
ASR1015 Sep 2, 2025
b8a87eb
fix(contributors): handle 204 No Content to avoid JSON parse error
ASR1015 Sep 2, 2025
1d3341a
types: make GitHub cursor paging type-safe (SearchType + typed cursor…
ASR1015 Sep 2, 2025
3e82610
fix(search): key GraphQL pagination cursors by username+page size to …
ASR1015 Sep 2, 2025
b90005a
fix(profile): map GraphQL PR nodes to local PR shape + handle GraphQL…
ASR1015 Sep 2, 2025
b5b4b5e
octokit: use shared API version/base, skip empty auth, add Accept hea…
ASR1015 Sep 2, 2025
58bbb99
fix: always tag PRs with pull_request; avoid misclassifying open PRs …
ASR1015 Sep 2, 2025
47f8948
feat(contributors): type Contributor, DRY token, unmount-safe state u…
ASR1015 Sep 2, 2025
5847da2
feat(contributors): type Contributor, DRY token, unmount-safe state u…
ASR1015 Sep 2, 2025
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
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.15.6",
"@mui/material": "^5.15.6",
"@octokit/core": "^6.1.6",
"@octokit/plugin-paginate-rest": "^11.6.0",
"@octokit/plugin-throttling": "^9.6.1",
"@primer/octicons-react": "^19.15.5",
"@vitejs/plugin-react": "^4.3.3",
"axios": "^1.7.7",
Expand Down
56 changes: 41 additions & 15 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
import { createBrowserRouter, RouterProvider, Outlet } from "react-router-dom";
import { lazy, Suspense } from "react";
import Navbar from "./components/Navbar";
import Footer from "./components/Footer";
import ScrollProgressBar from "./components/ScrollProgressBar";
import { Toaster } from "react-hot-toast";
import Router from "./Routes/Router";
import ThemeWrapper from "./context/ThemeContext";

function App() {
const Home = lazy(() => import("./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"));

function RootLayout() {
return (
<ThemeWrapper>
<div className="relative flex flex-col min-h-screen">
<ScrollProgressBar />

<Navbar />

<main className="flex-grow bg-gray-50 dark:bg-gray-800 flex justify-center items-center">
<Router />
<Suspense fallback={<div className="p-6">Loading…</div>}>
<Outlet />
</Suspense>
</main>

<Footer />

<Toaster
position="top-center"
reverseOrder={false}
Expand All @@ -27,18 +35,36 @@ function App() {
toastOptions={{
className: "bg-white dark:bg-gray-800 text-black dark:text-white",
duration: 5000,
success: {
duration: 3000,
iconTheme: {
primary: "green",
secondary: "white",
},
},
success: { duration: 3000, iconTheme: { primary: "green", secondary: "white" } },
}}
/>
</div>
</ThemeWrapper>
);
}

export default App;
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 /> },
],
},
]);
Comment thread
ASR1015 marked this conversation as resolved.

export default function AppRouter() {
return (
<RouterProvider
router={router}
/>
);
}
30 changes: 14 additions & 16 deletions src/Routes/Router.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Routes, Route } from "react-router-dom";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import Tracker from "../pages/Tracker/Tracker.tsx";
import About from "../pages/About/About";
import Contact from "../pages/Contact/Contact";
Expand All @@ -8,19 +8,17 @@ import Login from "../pages/Login/Login.tsx";
import ContributorProfile from "../pages/ContributorProfile/ContributorProfile.tsx";
import Home from "../pages/Home/Home.tsx";

const Router = () => {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/track" element={<Tracker />} />
<Route path="/signup" element={<Signup />} />
<Route path="/login" element={<Login />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/contributors" element={<Contributors />} />
<Route path="/contributor/:username" element={<ContributorProfile />} />
</Routes>
);
};
const router = createBrowserRouter([
{ path: "/", 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 /> },
]);

export default Router;
export default function AppRouter() {
return <RouterProvider router={router} />;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
41 changes: 32 additions & 9 deletions src/hooks/useGitHubData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,41 @@ export const useGitHubData = (getOctokit: () => any) => {
const [rateLimited, setRateLimited] = useState(false);

const fetchPaginated = async (octokit: any, username: string, type: string, page = 1, per_page = 10) => {
const q = `author:${username} is:${type}`;
const response = await octokit.request('GET /search/issues', {
q,
sort: 'created',
order: 'desc',
per_page,
page,
const query = `
query ($queryString: String!, $first: Int!, $after: String) {
search(query: $queryString, type: ISSUE, first: $first, after: $after) {
issueCount
edges {
cursor
node {
... on Issue {
id
title
url
createdAt
}
... on PullRequest {
id
title
url
createdAt
}
}
}
}
}
`;
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const queryString = `author:${username} is:${type}`;
const response = await octokit.graphql(query, {
queryString,
first: per_page,
after: page > 1 ? btoa(`cursor:${(page - 1) * per_page}`) : null,
});
Comment thread
ASR1015 marked this conversation as resolved.
Outdated

return {
items: response.data.items,
total: response.data.total_count,
items: response.search.edges.map((edge: any) => edge.node),
total: response.search.issueCount,
};
Comment thread
ASR1015 marked this conversation as resolved.
Outdated
};

Expand Down
33 changes: 33 additions & 0 deletions src/lib/githubFetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// src/lib/githubFetch.ts
export const GITHUB_API_VERSION = "2022-11-28";

export async function ghFetch(
path: string,
token: string,
init: RequestInit = {}
) {
const base = "https://api.github.com";
const headers = new Headers(init.headers || {});
if (token) {
headers.set("Authorization", `Bearer ${token}`); // or `token ${token}`
}
headers.set("Accept", "application/vnd.github+json");
headers.set("X-GitHub-Api-Version", GITHUB_API_VERSION);

return fetch(`${base}${path}`, { ...init, headers });
}

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}` : ""}`
);
}
return res.json() as Promise<T>;
}
Comment thread
ASR1015 marked this conversation as resolved.
22 changes: 22 additions & 0 deletions src/lib/octokit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Octokit } from "@octokit/core";
import { paginateRest } from "@octokit/plugin-paginate-rest";
import { throttling } from "@octokit/plugin-throttling";

const MyOctokit = Octokit.plugin(paginateRest, throttling);

export const makeOctokit = (token: string) =>
new MyOctokit({
auth: token,
userAgent: "github-tracker/1.0",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
request: {
// IMPORTANT: stops “deprecated / unversioned” warnings
headers: { "X-GitHub-Api-Version": "2022-11-28" },
},
throttle: {
onRateLimit: (retryAfter, options, octokit) => {
console.warn(`Rate limit hit for ${options.method} ${options.url}. Retrying in ${retryAfter}s.`);
return true; // auto-retry once
},
onSecondaryRateLimit: () => true,
},
});
Comment thread
ASR1015 marked this conversation as resolved.
Outdated
Comment thread
ASR1015 marked this conversation as resolved.
3 changes: 0 additions & 3 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@ import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { BrowserRouter } from "react-router-dom";
import ThemeWrapper from "./context/ThemeContext.tsx";

createRoot(document.getElementById("root")!).render(
<StrictMode>
<ThemeWrapper>
<BrowserRouter>
<App />
</BrowserRouter>
</ThemeWrapper>
</StrictMode>
);
Loading