Skip to content

Commit 1e6a956

Browse files
authored
Merge branch 'GitMetricsLab:main' into main
2 parents 53ede56 + b59fd71 commit 1e6a956

7 files changed

Lines changed: 115 additions & 53 deletions

File tree

.github/ISSUE_TEMPLATE/bug_report.yml

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,6 @@ body:
1616
description: "A clear and concise description of what the bug is."
1717
placeholder: "It bugs out when ..."
1818

19-
- type: dropdown
20-
id: operating-system
21-
attributes:
22-
label: "💻 Operating system"
23-
description: "What OS is your app running on?"
24-
options:
25-
- Linux
26-
- MacOS
27-
- Windows
28-
- Something else
29-
validations:
30-
required: true
31-
3219
- type: dropdown
3320
id: browsers
3421
attributes:

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,25 @@
88
"build": "vite build",
99
"lint": "eslint .",
1010
"preview": "vite preview",
11-
"docker:dev":"docker compose --profile dev up --build",
12-
"docker:prod":"docker compose --profile prod up -d --build"
11+
"docker:dev": "docker compose --profile dev up --build",
12+
"docker:prod": "docker compose --profile prod up -d --build"
1313
},
1414
"dependencies": {
1515
"@emotion/react": "^11.11.3",
1616
"@emotion/styled": "^11.11.0",
1717
"@mui/icons-material": "^5.15.6",
1818
"@mui/material": "^5.15.6",
19+
"@primer/octicons-react": "^19.15.5",
1920
"@vitejs/plugin-react": "^4.3.3",
2021
"axios": "^1.7.7",
2122
"lucide-react": "^0.525.0",
2223
"octokit": "^4.0.2",
24+
"postcss": "^8.4.47",
2325
"react": "^18.3.1",
2426
"react-dom": "^18.3.1",
2527
"react-hot-toast": "^2.4.1",
2628
"react-icons": "^5.3.0",
2729
"react-router-dom": "^6.28.0",
28-
"postcss": "^8.4.47",
2930
"tailwindcss": "^3.4.14"
3031
},
3132
"devDependencies": {
@@ -51,5 +52,4 @@
5152
"supertest": "^7.1.4",
5253
"vite": "^5.4.10"
5354
}
54-
5555
}

src/context/ThemeContext.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// src/ThemeContext.tsx
2-
import React, { createContext, useMemo, useState, useEffect, ReactNode } from 'react';
2+
import { createContext, useMemo, useState, useEffect, ReactNode } from 'react';
33
import { createTheme, ThemeProvider, Theme } from '@mui/material/styles';
44

55
interface ThemeContextType {
@@ -9,22 +9,32 @@ interface ThemeContextType {
99

1010
export const ThemeContext = createContext<ThemeContextType | null>(null);
1111

12+
const THEME_STORAGE_KEY = 'theme';
13+
1214
const ThemeWrapper = ({ children }: { children: ReactNode }) => {
13-
const [mode, setMode] = useState<'light' | 'dark'>('light');
15+
const [mode, setMode] = useState<'light' | 'dark'>(() => {
16+
const savedMode = localStorage.getItem(THEME_STORAGE_KEY);
17+
return savedMode === 'dark' ? 'dark' : 'light';
18+
});
1419

20+
// Sync mode with <html> class and localStorage
1521
useEffect(() => {
1622
if (mode === 'dark') {
1723
document.documentElement.classList.add('dark');
1824
} else {
1925
document.documentElement.classList.remove('dark');
2026
}
27+
localStorage.setItem(THEME_STORAGE_KEY, mode);
2128
}, [mode]);
2229

2330
const toggleTheme = () => {
24-
setMode(prev => (prev === 'light' ? 'dark' : 'light'));
31+
setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'));
2532
};
2633

27-
const muiTheme: Theme = useMemo(() => createTheme({ palette: { mode } }), [mode]);
34+
const muiTheme: Theme = useMemo(
35+
() => createTheme({ palette: { mode } }),
36+
[mode]
37+
);
2838

2939
return (
3040
<ThemeContext.Provider value={{ mode, toggleTheme }}>
@@ -36,4 +46,4 @@ const ThemeWrapper = ({ children }: { children: ReactNode }) => {
3646
};
3747

3848
export default ThemeWrapper;
39-
export type { ThemeContextType };
49+
export type { ThemeContextType };

src/hooks/useGitHubAuth.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
import { useState } from 'react';
1+
import { useState, useMemo } from 'react';
22
import { Octokit } from '@octokit/core';
33

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

9-
const getOctokit = () => {
9+
const octokit = useMemo(() => {
1010
if (!username || !token) return null;
1111
return new Octokit({ auth: token });
12-
};
12+
}, [username, token]);
13+
14+
const getOctokit = () => octokit;
1315

1416
return {
1517
username,

src/hooks/useGitHubData.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
import { useState, useCallback } from 'react';
22

3-
export const useGitHubData = (octokit: any) => {
4-
3+
export const useGitHubData = (getOctokit: () => any) => {
54
const [issues, setIssues] = useState([]);
65
const [prs, setPrs] = useState([]);
76
const [loading, setLoading] = useState(false);
87
const [error, setError] = useState('');
98
const [totalIssues, setTotalIssues] = useState(0);
109
const [totalPrs, setTotalPrs] = useState(0);
10+
const [rateLimited, setRateLimited] = useState(false);
1111

12-
const fetchPaginated = async (username: string, type: string, page = 1, per_page = 10) => {
13-
12+
const fetchPaginated = async (octokit: any, username: string, type: string, page = 1, per_page = 10) => {
1413
const q = `author:${username} is:${type}`;
1514
const response = await octokit.request('GET /search/issues', {
1615
q,
@@ -28,29 +27,36 @@ export const useGitHubData = (octokit: any) => {
2827

2928
const fetchData = useCallback(
3029
async (username: string, page = 1, perPage = 10) => {
30+
31+
const octokit = getOctokit();
3132

32-
if (!octokit || !username) return;
33+
if (!octokit || !username || rateLimited) return;
3334

3435
setLoading(true);
3536
setError('');
3637

3738
try {
3839
const [issueRes, prRes] = await Promise.all([
39-
fetchPaginated(username, 'issue', page, perPage),
40-
fetchPaginated(username, 'pr', page, perPage),
40+
fetchPaginated(octokit, username, 'issue', page, perPage),
41+
fetchPaginated(octokit, username, 'pr', page, perPage),
4142
]);
4243

4344
setIssues(issueRes.items);
4445
setPrs(prRes.items);
4546
setTotalIssues(issueRes.total);
4647
setTotalPrs(prRes.total);
4748
} catch (err: any) {
48-
setError(err.message);
49+
if (err.status === 403) {
50+
setError('GitHub API rate limit exceeded. Please wait or use a token.');
51+
setRateLimited(true); // Prevent further fetches
52+
} else {
53+
setError(err.message || 'Failed to fetch data');
54+
}
4955
} finally {
5056
setLoading(false);
5157
}
5258
},
53-
[octokit]
59+
[getOctokit, rateLimited]
5460
);
5561

5662
return {

src/index.css

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
11
@tailwind base;
22
@tailwind components;
3-
@tailwind utilities;
3+
@tailwind utilities;
4+
5+
6+
.icon-merged {
7+
color: #2ea44f; /* Or use your theme color */
8+
}
9+
.icon-pr-open {
10+
color: #0969da;
11+
}
12+
.icon-pr-closed {
13+
color: #cf222e;
14+
}
15+
.icon-issue-open {
16+
color: #2ea44f;
17+
}
18+
.icon-issue-closed {
19+
color: #cf222e;
20+
}

src/pages/Home/Home.tsx

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import React, { useState, useEffect } from "react";
1+
import React, { useState, useEffect } from "react"
2+
import {
3+
IssueOpenedIcon,
4+
IssueClosedIcon,
5+
GitPullRequestIcon,
6+
GitPullRequestClosedIcon,
7+
GitMergeIcon,
8+
} from '@primer/octicons-react';
29
import {
310
Container,
411
Box,
@@ -39,7 +46,9 @@ interface GitHubItem {
3946
}
4047

4148
const Home: React.FC = () => {
49+
4250
const theme = useTheme();
51+
4352
const {
4453
username,
4554
setUsername,
@@ -48,7 +57,7 @@ const Home: React.FC = () => {
4857
error: authError,
4958
getOctokit,
5059
} = useGitHubAuth();
51-
const octokit = getOctokit();
60+
5261
const {
5362
issues,
5463
prs,
@@ -57,7 +66,7 @@ const Home: React.FC = () => {
5766
loading,
5867
error: dataError,
5968
fetchData,
60-
} = useGitHubData(octokit);
69+
} = useGitHubData(getOctokit);
6170

6271
const [tab, setTab] = useState(0);
6372
const [page, setPage] = useState(0);
@@ -74,7 +83,7 @@ const Home: React.FC = () => {
7483
if (username) {
7584
fetchData(username, page + 1, ROWS_PER_PAGE);
7685
}
77-
}, [username, tab, page, fetchData]);
86+
}, [tab, page]);
7887

7988
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
8089
e.preventDefault();
@@ -121,12 +130,29 @@ const Home: React.FC = () => {
121130
return filtered;
122131
};
123132

133+
const getStatusIcon = (item: GitHubItem) => {
134+
135+
if (item.pull_request) {
136+
137+
if (item.pull_request.merged_at)
138+
return <GitMergeIcon size={16} className="icon-merged" />;
139+
140+
if (item.state === 'closed')
141+
return <GitPullRequestClosedIcon size={16} className="icon-pr-closed" />;
142+
143+
return <GitPullRequestIcon size={16} className="icon-pr-open" />;
144+
}
145+
146+
if (item.state === 'closed')
147+
return <IssueClosedIcon size={16} className="icon-issue-closed" />;
148+
149+
return <IssueOpenedIcon size={16} className="icon-issue-open" />;
150+
};
151+
152+
124153
// Current data and filtered data according to tab and filters
125154
const currentRawData = tab === 0 ? issues : prs;
126-
const currentFilteredData = filterData(
127-
currentRawData,
128-
tab === 0 ? issueFilter : prFilter
129-
);
155+
const currentFilteredData = filterData(currentRawData, tab === 0 ? issueFilter : prFilter);
130156
const totalCount = tab === 0 ? totalIssues : totalPrs;
131157

132158
return (
@@ -251,8 +277,11 @@ const Home: React.FC = () => {
251277
</Box>
252278
) : (
253279
<Box sx={{ maxHeight: "400px", overflowY: "auto" }}>
280+
254281
<TableContainer component={Paper}>
282+
255283
<Table size="small">
284+
256285
<TableHead>
257286
<TableRow>
258287
<TableCell>Title</TableCell>
@@ -261,31 +290,41 @@ const Home: React.FC = () => {
261290
<TableCell>Created</TableCell>
262291
</TableRow>
263292
</TableHead>
293+
264294
<TableBody>
265295
{currentFilteredData.map((item) => (
266296
<TableRow key={item.id}>
267-
<TableCell>
268-
<Link
269-
href={item.html_url}
270-
target="_blank"
271-
rel="noopener noreferrer"
272-
underline="hover"
273-
sx={{ color: theme.palette.primary.main }}
274-
>
275-
{item.title}
276-
</Link>
297+
298+
<TableCell sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
299+
{getStatusIcon(item)}
300+
<Link
301+
href={item.html_url}
302+
target="_blank"
303+
rel="noopener noreferrer"
304+
underline="hover"
305+
sx={{ color: theme.palette.primary.main }}
306+
>
307+
{item.title}
308+
</Link>
277309
</TableCell>
310+
311+
278312
<TableCell align="center">
279313
{item.repository_url.split("/").slice(-1)[0]}
280314
</TableCell>
315+
281316
<TableCell align="center">
282317
{item.pull_request?.merged_at ? "merged" : item.state}
283318
</TableCell>
319+
284320
<TableCell>{formatDate(item.created_at)}</TableCell>
321+
285322
</TableRow>
286323
))}
287324
</TableBody>
325+
288326
</Table>
327+
289328
<TablePagination
290329
component="div"
291330
count={totalCount}
@@ -294,6 +333,7 @@ const Home: React.FC = () => {
294333
rowsPerPage={ROWS_PER_PAGE}
295334
rowsPerPageOptions={[ROWS_PER_PAGE]}
296335
/>
336+
297337
</TableContainer>
298338
</Box>
299339
)}

0 commit comments

Comments
 (0)