diff --git a/package.json b/package.json
index 43ad31cc..5d166404 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
{
- "name": "GitHub Tracker",
+ "name": "github-tracker",
"private": true,
"version": "0.0.0",
"type": "module",
diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx
index fd5eac86..596a3244 100644
--- a/src/components/Navbar.tsx
+++ b/src/components/Navbar.tsx
@@ -1,7 +1,7 @@
import { NavLink, Link } from "react-router-dom";
import { useState, useContext } from "react";
import { ThemeContext } from "../context/ThemeContext";
-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);
diff --git a/src/components/__test__/Navbar.test.tsx b/src/components/__test__/Navbar.test.tsx
index 780b9bb3..60bc0712 100644
--- a/src/components/__test__/Navbar.test.tsx
+++ b/src/components/__test__/Navbar.test.tsx
@@ -1,6 +1,6 @@
// src/components/__tests__/Navbar.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
-import { describe, it, expect, vi, beforeEach } from 'vitest'
+import { describe, it, expect, vi } from 'vitest'
import { MemoryRouter } from 'react-router-dom'
import { ThemeContext } from "../../context/ThemeContext";
import Navbar from '../Navbar.tsx'
@@ -51,31 +51,30 @@ describe('Navbar', () => {
// --- Mobile menu ---
it('mobile menu is hidden by default', () => {
renderNavbar()
- expect(screen.queryByText('About')).not.toBeInTheDocument()
+ expect(screen.getAllByRole('link', { name: /^tracker$/i })).toHaveLength(1)
})
it('opens mobile menu when hamburger is clicked', () => {
renderNavbar()
- const hamburger = screen.getAllByRole('button')[1] // second button = hamburger
+ const hamburger = screen.getAllByRole('button')[2] // third button = hamburger
fireEvent.click(hamburger)
- expect(screen.getByText('About')).toBeInTheDocument()
- expect(screen.getByText('Contact')).toBeInTheDocument()
+ expect(screen.getAllByRole('link', { name: /^tracker$/i })).toHaveLength(2)
})
it('closes mobile menu when a nav link is clicked', () => {
renderNavbar()
- const hamburger = screen.getAllByRole('button')[1]
+ const hamburger = screen.getAllByRole('button')[2]
fireEvent.click(hamburger) // open
+ expect(screen.getAllByRole('link', { name: /^tracker$/i })).toHaveLength(2)
const homeLinks = screen.getAllByRole('link', { name: /home/i })
fireEvent.click(homeLinks[homeLinks.length - 1]) // click the mobile one
- expect(screen.queryByText('About')).not.toBeInTheDocument() // closed
+ expect(screen.getAllByRole('link', { name: /^tracker$/i })).toHaveLength(1) // closed
})
- it('calls toggleTheme from the mobile menu button', () => {
+ it('calls toggleTheme from the mobile theme button', () => {
const { toggleTheme } = renderNavbar('dark')
- const hamburger = screen.getAllByRole('button')[1]
- fireEvent.click(hamburger)
- fireEvent.click(screen.getByText(/light/i))
+ const mobileThemeBtn = screen.getAllByRole('button')[1] // second button = mobile theme toggle
+ fireEvent.click(mobileThemeBtn)
expect(toggleTheme).toHaveBeenCalledTimes(1)
})
diff --git a/src/hooks/useGitHubAuth.ts b/src/hooks/useGitHubAuth.ts
index a0c24b2a..3044fa65 100644
--- a/src/hooks/useGitHubAuth.ts
+++ b/src/hooks/useGitHubAuth.ts
@@ -1,25 +1,54 @@
-import { useState, useMemo } from 'react';
+import { useState, useEffect } from 'react';
import { Octokit } from '@octokit/core';
export const useGitHubAuth = () => {
- const [username, setUsername] = useState('');
- const [token, setToken] = useState('');
+ const [username, setUsername] = useState(() => sessionStorage.getItem('tracker_username') || '');
+ const [token, setToken] = useState(() => sessionStorage.getItem('tracker_token') || '');
+ const [error, setError] = useState('');
- const octokit = useMemo(() => {
- if (!username) return null;
- if(token){
- return new Octokit({ auth: token });
+ useEffect(() => {
+ if (username) {
+ sessionStorage.setItem('tracker_username', username);
+ } else {
+ sessionStorage.removeItem('tracker_username');
+ }
+ if (token) {
+ sessionStorage.setItem('tracker_token', token);
+ } else {
+ sessionStorage.removeItem('tracker_token');
}
- return new Octokit();
}, [username, token]);
- const getOctokit = () => octokit;
+ const getOctokit = () => {
+ try {
+ setError('');
+ if (!username) return null;
+ if (token) {
+ return new Octokit({ auth: token });
+ }
+ return new Octokit();
+ } catch (err: any) {
+ setError(err instanceof Error ? err.message : String(err));
+ return null;
+ }
+ };
+
+ const logout = () => {
+ setUsername('');
+ setToken('');
+ setError('');
+ sessionStorage.removeItem('tracker_username');
+ sessionStorage.removeItem('tracker_token');
+ };
return {
username,
setUsername,
token,
setToken,
+ error,
+ setError,
getOctokit,
+ logout,
};
};
diff --git a/src/hooks/useGitHubData.ts b/src/hooks/useGitHubData.ts
index f4c78cf6..2953e4e7 100644
--- a/src/hooks/useGitHubData.ts
+++ b/src/hooks/useGitHubData.ts
@@ -239,6 +239,17 @@ export const useGitHubData = (
[getOctokit, rateLimited]
);
+ const clearData = useCallback(() => {
+ lastRequestId.current++;
+ setLoading(false);
+ setIssues([]);
+ setPrs([]);
+ setTotalIssues(0);
+ setTotalPrs(0);
+ setError('');
+ setRateLimited(false);
+ }, []);
+
return {
issues,
prs,
@@ -248,5 +259,6 @@ export const useGitHubData = (
error,
rateLimited,
fetchData,
+ clearData,
};
};
diff --git a/src/pages/Tracker/Tracker.tsx b/src/pages/Tracker/Tracker.tsx
index 576f39bf..5ee74dce 100644
--- a/src/pages/Tracker/Tracker.tsx
+++ b/src/pages/Tracker/Tracker.tsx
@@ -57,6 +57,7 @@ const Home: React.FC = () => {
setToken,
error: authError,
getOctokit,
+ logout,
} = useGitHubAuth();
const {
@@ -67,6 +68,7 @@ const Home: React.FC = () => {
loading,
error: dataError,
fetchData,
+ clearData,
} = useGitHubData(getOctokit);
const [tab, setTab] = useState(0);
@@ -79,6 +81,32 @@ const Home: React.FC = () => {
const [startDate, setStartDate] = useState("");
const [endDate, setEndDate] = useState("");
+ const handleResetFilters = () => {
+ setTab(0);
+ setPage(0);
+ setIssueFilter("all");
+ setPrFilter("all");
+ setSearchTitle("");
+ setSelectedRepo("");
+ setStartDate("");
+ setEndDate("");
+
+ localStorage.removeItem('tracker_tab');
+ localStorage.removeItem('tracker_page');
+ localStorage.removeItem('tracker_issueFilter');
+ localStorage.removeItem('tracker_prFilter');
+ localStorage.removeItem('tracker_searchTitle');
+ localStorage.removeItem('tracker_selectedRepo');
+ localStorage.removeItem('tracker_startDate');
+ localStorage.removeItem('tracker_endDate');
+ };
+
+ const handleLogout = () => {
+ logout();
+ clearData();
+ handleResetFilters();
+ };
+
// Fetch data when username, tab, or page changes
useEffect(() => {
if (username) {
@@ -238,6 +266,21 @@ const Home: React.FC = () => {
>
Fetch Data
+ {(username || token) && (
+
+ )}
@@ -272,6 +315,18 @@ const Home: React.FC = () => {
InputLabelProps={{ shrink: true }}
sx={{ minWidth: 150 }}
/>
+ {(tab !== 0 || page !== 0 || issueFilter !== "all" || prFilter !== "all" || searchTitle !== "" || selectedRepo !== "" || startDate !== "" || endDate !== "") && (
+
+ )}
{/* Tabs + State Filter */}