@@ -98,4 +100,4 @@ const MobileNavLink = ({ to, children, onClick }: { to: string, children: React.
`block px-6 py-4 rounded-2xl text-xl font-bold transition-all ${isActive ? "bg-blue-600 text-white shadow-lg translate-x-2" : "text-slate-600 dark:text-gray-400 hover:bg-white/40 dark:hover:bg-gray-800/40"}`}>{children}
);
-export default Navbar;
\ No newline at end of file
+export default Navbar;
diff --git a/src/pages/Issues/Issues.tsx b/src/pages/Issues/Issues.tsx
new file mode 100644
index 0000000..09ca3ff
--- /dev/null
+++ b/src/pages/Issues/Issues.tsx
@@ -0,0 +1,237 @@
+import React, { useState, useEffect, useCallback } from "react";
+import {
+ Container,
+ Box,
+ Paper,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ TablePagination,
+ Link,
+ CircularProgress,
+ Alert,
+ FormControl,
+ InputLabel,
+ Select,
+ MenuItem,
+ Typography,
+ SelectChangeEvent,
+} from "@mui/material";
+import { useTheme } from "@mui/material/styles";
+import { IssueOpenedIcon, IssueClosedIcon } from "@primer/octicons-react";
+
+const ROWS_PER_PAGE = 10;
+
+interface IssueItem {
+ id: number;
+ title: string;
+ state: string;
+ created_at: string;
+ repository_url: string;
+ html_url: string;
+}
+
+const Issues: React.FC = () => {
+ const theme = useTheme();
+
+ const [issues, setIssues] = useState
([]);
+ const [totalIssues, setTotalIssues] = useState(0);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState("");
+ const [page, setPage] = useState(0);
+
+ const [language, setLanguage] = useState("");
+ const [tag, setTag] = useState("");
+ const [sortOrder, setSortOrder] = useState("desc");
+
+ const fetchIssues = useCallback(async (currentPage: number, currentLanguage: string, currentTag: string, currentOrder: string) => {
+ setLoading(true);
+ setError("");
+
+ try {
+ let q = "is:issue is:open";
+ if (currentLanguage) {
+ q += ` language:${currentLanguage}`;
+ }
+ if (currentTag) {
+ q += ` label:"${currentTag}"`;
+ }
+
+ const response = await fetch(
+ `https://api.github.com/search/issues?q=${encodeURIComponent(q)}&sort=created&order=${currentOrder}&per_page=${ROWS_PER_PAGE}&page=${currentPage + 1}`
+ );
+
+ if (!response.ok) {
+ if (response.status === 403) {
+ throw new Error("GitHub API rate limit exceeded.");
+ }
+ throw new Error("Failed to fetch data");
+ }
+
+ const data = await response.json();
+ setIssues(data.items);
+ setTotalIssues(data.total_count > 1000 ? 1000 : data.total_count); // GitHub limits search results to 1000
+ } catch (err: unknown) {
+ if (err instanceof Error) {
+ setError(err.message || "Failed to fetch issues");
+ } else {
+ setError("Failed to fetch issues");
+ }
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+
+ useEffect(() => {
+ fetchIssues(page, language, tag, sortOrder);
+ }, [page, language, tag, sortOrder, fetchIssues]);
+
+ const handlePageChange = (_: unknown, newPage: number) => {
+ setPage(newPage);
+ };
+
+ const formatDate = (dateString: string): string =>
+ new Date(dateString).toLocaleDateString();
+
+ return (
+
+
+ Explore GitHub Issues
+
+
+
+
+
+ Language
+
+
+
+
+ Tags / Labels
+
+
+
+
+ Sort by Time
+
+
+
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+ {loading ? (
+
+
+
+ ) : (
+
+
+
+
+
+ Title
+ Repository
+ State
+ Created
+
+
+
+ {issues.map((item) => (
+
+
+ {item.state === "closed" ? (
+
+ ) : (
+
+ )}
+
+ {item.title}
+
+
+
+ {item.repository_url.split("/").slice(-2).join("/")}
+
+ {item.state}
+ {formatDate(item.created_at)}
+
+ ))}
+ {issues.length === 0 && !loading && !error && (
+
+
+ No issues found.
+
+
+ )}
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default Issues;