Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.15.6",
"@mui/material": "^5.15.6",
"@octokit/rest": "^22.0.0",
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove redundant Octokit packages to avoid version drift

With @octokit/rest added, the existing "octokit" meta-package (Line 25) is now redundant and may pull in a different, transitive @octokit/rest version. Keeping both can bloat install size and introduce subtle mismatches.

Either drop "octokit" or justify why both are required; keeping only one source of the REST client is the safer default.

🤖 Prompt for AI Agents
In package.json at line 19, the dependency "@octokit/rest" is added while the
"octokit" meta-package is also present at line 25, causing redundancy and
potential version conflicts. Remove the "octokit" package from the dependencies
to avoid pulling in multiple versions of the REST client and reduce install
size, unless there is a specific reason to keep both.

"@primer/octicons-react": "^19.15.5",
"@vitejs/plugin-react": "^4.3.3",
"axios": "^1.7.7",
Expand Down
11 changes: 7 additions & 4 deletions src/hooks/useGitHubAuth.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { useState, useMemo } from 'react';
import { Octokit } from '@octokit/core';
import { Octokit } from '@octokit/rest';

export const useGitHubAuth = () => {
const [username, setUsername] = useState('');
const [token, setToken] = useState('');
const [error, setError] = useState('');

// FIX: The Octokit instance only depends on the token for authentication.
const octokit = useMemo(() => {
if (!username || !token) return null;
// Only create an instance if a token exists.
if (!token) return null;

return new Octokit({ auth: token });
}, [username, token]);
}, [token]); // Dependency array should only contain `token`.

const getOctokit = () => octokit;

Expand All @@ -22,4 +25,4 @@ export const useGitHubAuth = () => {
setError,
getOctokit,
};
};
};
89 changes: 47 additions & 42 deletions src/hooks/useGitHubData.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,69 @@
import { useState, useCallback } from 'react';
import { Octokit } from '@octokit/rest';
import { Endpoints } from "@octokit/types";

export const useGitHubData = (getOctokit: () => any) => {
const [issues, setIssues] = useState([]);
const [prs, setPrs] = useState([]);
// FIX: Derive the item type directly from the library instead of defining it manually.
type IssueSearchResponse = Endpoints["GET /search/issues"]["response"];
export type GitHubItem = IssueSearchResponse["data"]["items"][0];

export const useGitHubData = (getOctokit: () => Octokit | null) => {
const [issues, setIssues] = useState<GitHubItem[]>([]);
const [prs, setPrs] = useState<GitHubItem[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [totalIssues, setTotalIssues] = useState(0);
const [totalPrs, setTotalPrs] = useState(0);
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,
});

return {
items: response.data.items,
total: response.data.total_count,
};
};

const fetchData = useCallback(
async (username: string, page = 1, perPage = 10) => {

async (
username: string,
page: number,
perPage: number,
type: 'issue' | 'pr',
state: string
) => {
const octokit = getOctokit();

if (!octokit || !username || rateLimited) return;

setLoading(true);
setError('');
setRateLimited(false);

try {
const [issueRes, prRes] = await Promise.all([
fetchPaginated(octokit, username, 'issue', page, perPage),
fetchPaginated(octokit, username, 'pr', page, perPage),
]);
let query = `author:${username} is:${type}`;
if (state !== 'all') {
query += state === 'merged' ? ` is:merged` : ` state:${state}`;
}

const response = await octokit.request('GET /search/issues', {
q: query,
sort: 'created',
order: 'desc',
per_page: perPage,
page,
});

setIssues(issueRes.items);
setPrs(prRes.items);
setTotalIssues(issueRes.total);
setTotalPrs(prRes.total);
if (type === 'issue') {
setIssues(response.data.items);
setTotalIssues(response.data.total_count);
} else {
setPrs(response.data.items);
setTotalPrs(response.data.total_count);
}
} catch (err: any) {
if (err.status === 403) {
setError('GitHub API rate limit exceeded. Please wait or use a token.');
setRateLimited(true); // Prevent further fetches
setError('GitHub API rate limit exceeded. Please wait or use a valid token.');
setRateLimited(true);
} else {
setError(err.message || 'Failed to fetch data');
if (type === 'issue') {
setIssues([]);
setTotalIssues(0);
} else {
setPrs([]);
setTotalPrs(0);
}
}
} finally {
setLoading(false);
Expand All @@ -59,13 +72,5 @@ export const useGitHubData = (getOctokit: () => any) => {
[getOctokit, rateLimited]
);

return {
issues,
prs,
totalIssues,
totalPrs,
loading,
error,
fetchData,
};
};
return { issues, prs, totalIssues, totalPrs, loading, error, fetchData };
};
Loading