diff --git a/src/Routes/Router.tsx b/src/Routes/Router.tsx
index f8ccf5d7..b811e093 100644
--- a/src/Routes/Router.tsx
+++ b/src/Routes/Router.tsx
@@ -11,6 +11,7 @@ import ProfilePage from "../pages/Profile/ProfilePage.tsx";
import EditProfilePage from "../pages/Profile/EditProfilePage.tsx";
import Activity from "../pages/Activity.tsx";
import PrivacyPolicy from "../pages/Privacy/PrivacyPolicy.tsx"; // โ
Updated import path to match your new folder structure
+import RepoCompare from "../pages/RepoCompare/RepoCompare.tsx";
const Router = () => {
return (
@@ -26,6 +27,7 @@ const Router = () => {
}>
}>
} />
+ } />
{/* Privacy Policy page route */}
} />
diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx
index f4a19bd4..e5f7eb4f 100644
--- a/src/components/Navbar.tsx
+++ b/src/components/Navbar.tsx
@@ -1,16 +1,12 @@
import { NavLink, Link } from "react-router-dom";
import { useState, useContext } from "react";
import { ThemeContext } from "../context/ThemeContext";
-import ProfileDropDown from "./Profile/ProfileDropDown";
-import { logoutUser } from "../services/auth";
-
-import { Moon, Sun, Menu, X, Github } from "lucide-react";
+import { Moon, Sun, Menu, X } from "lucide-react";
const Navbar: React.FC = () => {
const [isOpen, setIsOpen] = useState(false);
const themeContext = useContext(ThemeContext);
-
if (!themeContext) return null;
const { toggleTheme, mode } = themeContext;
@@ -47,7 +43,6 @@ const Navbar: React.FC = () => {
alt="CRL Icon"
className="h-8 w-8 object-contain"
/>
-
GitHub Tracker
@@ -61,6 +56,11 @@ const Navbar: React.FC = () => {
Tracker
+ {/* โ
NEW FEATURE */}
+
+ Compare
+
+
Contributors
@@ -122,27 +122,20 @@ const Navbar: React.FC = () => {
-
+
Home
-
+
Tracker
-
+ {/* โ
NEW FEATURE */}
+
+ Compare
+
+
+
Contributors
{!user && (
@@ -182,15 +175,10 @@ const Navbar: React.FC = () => {
>
)}
-
+
+ Login
+
+
)}
@@ -198,4 +186,4 @@ const Navbar: React.FC = () => {
);
};
-export default Navbar;
+export default Navbar;
\ No newline at end of file
diff --git a/src/pages/RepoCompare/RepoCompare.tsx b/src/pages/RepoCompare/RepoCompare.tsx
new file mode 100644
index 00000000..d4a5a453
--- /dev/null
+++ b/src/pages/RepoCompare/RepoCompare.tsx
@@ -0,0 +1,176 @@
+import { useState } from "react";
+
+type RepoData = {
+ full_name: string;
+ stargazers_count: number;
+ forks_count: number;
+ open_issues_count: number;
+ watchers_count: number;
+ language: string;
+ updated_at: string;
+ description: string;
+};
+
+const RepoCompare = () => {
+ const [repo1, setRepo1] = useState("");
+ const [repo2, setRepo2] = useState("");
+
+ const [data1, setData1] = useState(null);
+ const [data2, setData2] = useState(null);
+
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState("");
+
+ const fetchRepo = async (repo: string): Promise => {
+ const res = await fetch(`https://api.github.com/repos/${repo}`);
+
+ if (!res.ok) {
+ throw new Error(`Repository not found: ${repo}`);
+ }
+
+ return res.json();
+ };
+
+ const handleCompare = async () => {
+ if (!repo1 || !repo2) {
+ setError("Please enter both repositories");
+ return;
+ }
+
+ try {
+ setLoading(true);
+ setError("");
+
+ const [r1, r2] = await Promise.all([
+ fetchRepo(repo1),
+ fetchRepo(repo2),
+ ]);
+
+ setData1(r1);
+ setData2(r2);
+ } catch (err: any) {
+ setError(err.message || "Something went wrong");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const formatDate = (date: string) => {
+ return new Date(date).toLocaleDateString();
+ };
+
+ return (
+
+
๐ GitHub Repo Comparison
+
+ {/* INPUT SECTION */}
+
+ setRepo1(e.target.value)}
+ style={{ flex: 1, padding: "10px" }}
+ />
+
+ setRepo2(e.target.value)}
+ style={{ flex: 1, padding: "10px" }}
+ />
+
+
+
+
+ {/* ERROR */}
+ {error && (
+
{error}
+ )}
+
+ {/* LOADING */}
+ {loading &&
Loading comparison...
}
+
+ {/* RESULT TABLE */}
+ {data1 && data2 && (
+
+
+
+
+ | Metric |
+ {data1.full_name} |
+ {data2.full_name} |
+
+
+
+
+
+ | โญ Stars |
+ {data1.stargazers_count} |
+ {data2.stargazers_count} |
+
+
+
+ | ๐ด Forks |
+ {data1.forks_count} |
+ {data2.forks_count} |
+
+
+
+ | ๐ Issues |
+ {data1.open_issues_count} |
+ {data2.open_issues_count} |
+
+
+
+ | ๐ Watchers |
+ {data1.watchers_count} |
+ {data2.watchers_count} |
+
+
+
+ | ๐ป Language |
+ {data1.language} |
+ {data2.language} |
+
+
+
+ | ๐
Last Updated |
+ {formatDate(data1.updated_at)} |
+ {formatDate(data2.updated_at)} |
+
+
+
+ | ๐งพ Description |
+ {data1.description} |
+ {data2.description} |
+
+
+
+
+ )}
+
+ );
+};
+
+const thStyle: React.CSSProperties = {
+ border: "1px solid #ddd",
+ padding: "10px",
+ background: "#f4f4f4",
+};
+
+const tdStyle: React.CSSProperties = {
+ border: "1px solid #ddd",
+ padding: "10px",
+};
+
+export default RepoCompare;
\ No newline at end of file
diff --git a/src/utils/github.ts b/src/utils/github.ts
new file mode 100644
index 00000000..e3621fdd
--- /dev/null
+++ b/src/utils/github.ts
@@ -0,0 +1,18 @@
+export async function fetchRepo(repo: string) {
+ const normalizedRepo = repo.trim();
+ const res = await fetch(`https://api.github.com/repos/${normalizedRepo}`);
+ if (!res.ok) {
+ throw new Error(`Repository not found: ${normalizedRepo}`);
+ }
+ return res.json();
+}
+}
+
+export async function compareRepos(repo1: string, repo2: string) {
+ const [a, b] = await Promise.all([
+ fetchRepo(repo1),
+ fetchRepo(repo2),
+ ]);
+
+ return { a, b };
+}
\ No newline at end of file