diff --git a/src/App.tsx b/src/App.tsx index e9adacf..e4c1995 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,22 +1,22 @@ import Navbar from "./components/Navbar"; import Footer from "./components/Footer"; -import ScrollProgressBar from './components/ScrollProgressBar'; +import ScrollProgressBar from "./components/ScrollProgressBar"; import { Toaster } from "react-hot-toast"; - import Router from "./Routes/Router"; +import ThemeWrapper from "./ThemeContext"; // ✅ import your wrapper function App() { return ( - +
- + {/* Navbar */} {/* Main content */} -
- +
+
{/* Footer */} @@ -27,24 +27,21 @@ function App() { reverseOrder={false} gutter={8} containerClassName="mt-12" - containerStyle={{}} toastOptions={{ - className: 'bg-white', + className: "bg-white dark:bg-gray-800 text-black dark:text-white", duration: 5000, - //removeDelay: 1000, - success: { duration: 3000, iconTheme: { - primary: 'green', - secondary: 'white', + primary: "green", + secondary: "white", }, }, }} />
- +
); } -export default App; +export default App; \ No newline at end of file diff --git a/src/Routes/Login/Login.tsx b/src/Routes/Login/Login.tsx new file mode 100644 index 0000000..ba66cf5 --- /dev/null +++ b/src/Routes/Login/Login.tsx @@ -0,0 +1,149 @@ +import React, { useState, ChangeEvent, FormEvent, useContext } from "react"; +import axios from "axios"; +import { useNavigate } from "react-router-dom"; +import { ThemeContext } from "../../ThemeContext"; +import type { ThemeContextType } from "../../ThemeContext"; + +const backendUrl = import.meta.env.VITE_BACKEND_URL; + +interface LoginFormData { + email: string; + password: string; +} + +const Login: React.FC = () => { + const [formData, setFormData] = useState({ email: "", password: "" }); + const [message, setMessage] = useState(""); + const [isLoading, setIsLoading] = useState(false); + + const navigate = useNavigate(); + const themeContext = useContext(ThemeContext) as ThemeContextType; + const { mode } = themeContext; + + const handleChange = (e: ChangeEvent) => { + const { name, value } = e.target; + setFormData({ ...formData, [name]: value }); + }; + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + setIsLoading(true); + + try { + const response = await axios.post(`${backendUrl}/api/auth/login`, formData); + setMessage(response.data.message); + + if (response.data.message === "Login successful") { + navigate("/home"); + } + } catch (error: any) { + setMessage(error.response?.data?.message || "Something went wrong"); + } finally { + setIsLoading(false); + } + }; + + return ( +
+ {/* Background blobs */} +
+
+
+
+
+ + {/* Login Card */} +
+
+
+ Logo +
+

+ GitHubTracker +

+

Track your GitHub journey

+
+ + {/* Form */} +
+

Welcome Back

+ +
+ + + + + +
+ + {message && ( +
+ {message} +
+ )} +
+ + {/* Footer Text */} +
+

+ Don't have an account? + + Sign up here + +

+
+
+ + {/* Lower gradient */} +
+
+ ); +}; + +export default Login; \ No newline at end of file diff --git a/src/Routes/Router.tsx b/src/Routes/Router.tsx index 007d11d..900a915 100644 --- a/src/Routes/Router.tsx +++ b/src/Routes/Router.tsx @@ -1,12 +1,13 @@ -import { Navigate, Route, Routes } from "react-router-dom"; +import { Navigate, Route, Routes } from "react-router-dom" +import Home from "../pages/Home/Home" +import About from "../pages/About/About" +import Contact from "../pages/Contact/Contact" +import Contributors from "../pages/Contributors/Contributors" +import Signup from "../pages/Signup/Signup.tsx" +import Login from "../pages/Login/Login.tsx" +import UserProfile from "../pages/UserProfile/UserProfile.tsx" + -import Home from "../pages/Home/Home"; // Import the Home component -import About from "../pages/About/About"; // Import the About component -import Contact from "../pages/Contact/Contact"; // Import the Contact component -import Contributors from "../pages/Contributors/Contributors"; -import Signup from "../pages/Signup/Signup.tsx"; -import Login from "../pages/Login/Login.tsx"; -import UserProfile from "../pages/UserProfile/UserProfile.tsx"; const Router = () => { return ( @@ -14,14 +15,12 @@ const Router = () => { {/* Redirect from root (/) to the home page */} } /> } /> - } /> + } /> } /> } /> - } /> } /> } /> ); -}; - + }; export default Router; diff --git a/src/ThemeContext.tsx b/src/ThemeContext.tsx new file mode 100644 index 0000000..27fa822 --- /dev/null +++ b/src/ThemeContext.tsx @@ -0,0 +1,39 @@ +// src/ThemeContext.tsx +import React, { createContext, useMemo, useState, useEffect, ReactNode } from 'react'; +import { createTheme, ThemeProvider, Theme } from '@mui/material/styles'; + +interface ThemeContextType { + mode: 'light' | 'dark'; + toggleTheme: () => void; +} + +export const ThemeContext = createContext(null); + +const ThemeWrapper = ({ children }: { children: ReactNode }) => { + const [mode, setMode] = useState<'light' | 'dark'>('light'); + + useEffect(() => { + if (mode === 'dark') { + document.documentElement.classList.add('dark'); + } else { + document.documentElement.classList.remove('dark'); + } + }, [mode]); + + const toggleTheme = () => { + setMode(prev => (prev === 'light' ? 'dark' : 'light')); + }; + + const muiTheme: Theme = useMemo(() => createTheme({ palette: { mode } }), [mode]); + + return ( + + + {children} + + + ); +}; + +export default ThemeWrapper; +export type { ThemeContextType }; \ No newline at end of file diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 9e9c2b2..bf67ba3 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,25 +1,30 @@ -import { Link } from 'react-router-dom'; -import { useState } from 'react'; +import { Link } from "react-router-dom"; +import { useState, useContext } from "react"; +import { ThemeContext } from "../ThemeContext"; const Navbar: React.FC = () => { const [isOpen, setIsOpen] = useState(false); + const themeContext = useContext(ThemeContext); + if (!themeContext) return null; + + const { toggleTheme, mode } = themeContext; return ( -
)} diff --git a/src/hooks/useTheme.ts b/src/hooks/useTheme.ts new file mode 100644 index 0000000..af536f4 --- /dev/null +++ b/src/hooks/useTheme.ts @@ -0,0 +1,31 @@ +import { useEffect, useState } from "react"; + +type ThemeMode = "light" | "dark"; + +const getInitialTheme = (): ThemeMode => { + const stored = localStorage.getItem("theme"); + if (stored === "light" || stored === "dark") { + return stored; + } + return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; +}; + +export const useTheme = () => { + const [theme, setTheme] = useState(getInitialTheme); + + useEffect(() => { + const root = document.documentElement; + + if (theme === "dark") { + root.classList.add("dark"); + root.classList.remove("light"); + } else { + root.classList.add("light"); + root.classList.remove("dark"); + } + + localStorage.setItem("theme", theme); + }, [theme]); + + return { theme, setTheme } +} diff --git a/src/main.tsx b/src/main.tsx index a9f043f..699b94e 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,11 +3,14 @@ import { createRoot } from "react-dom/client"; import App from "./App.tsx"; import "./index.css"; import { BrowserRouter } from "react-router-dom"; +import ThemeWrapper from "./ThemeContext.tsx"; createRoot(document.getElementById("root")!).render( + + -); +); \ No newline at end of file diff --git a/src/pages/About/About.tsx b/src/pages/About/About.tsx index 4272234..e6713a8 100644 --- a/src/pages/About/About.tsx +++ b/src/pages/About/About.tsx @@ -2,7 +2,7 @@ const About = () => { return (
{/* Hero Section */} -
+

About Us

Welcome to GitHub Tracker! We simplify issue tracking for developers. @@ -10,39 +10,38 @@ const About = () => {

{/* Mission Section */} -
+

Our Mission

- We aim to provide an efficient and user-friendly way to track GitHub issues and pull requests. - Our goal is to make it easy for developers to stay organized and focused on their projects without getting bogged down by the details. + We aim to provide an efficient and user-friendly way to track GitHub + issues and pull requests. Our goal is to make it easy for developers to + stay organized and focused on their projects without getting bogged down + by the details.

{/* Features Section */} -
+

What We Do

- -
-
+
+
🔍
-

Simple Issue Tracking

-

+

Simple Issue Tracking

+

Track your GitHub issues seamlessly with intuitive filters and search options.

- -
+
👥
-

Team Collaboration

-

+

Team Collaboration

+

Collaborate with your team in real-time, manage issues and pull requests effectively.

- -
+
⚙️
-

Customizable Settings

-

+

Customizable Settings

+

Customize your issue tracking workflow to match your team's needs.

diff --git a/src/pages/Contact/Contact.tsx b/src/pages/Contact/Contact.tsx index a75368e..41dbd42 100644 --- a/src/pages/Contact/Contact.tsx +++ b/src/pages/Contact/Contact.tsx @@ -1,16 +1,20 @@ -import { useState } from 'react'; -import { Github, Mail, Phone, Send, X, CheckCircle } from 'lucide-react'; +import { useState, useContext } from "react"; +import { Github, Mail, Phone, Send, X, CheckCircle } from "lucide-react"; +import { ThemeContext } from "../../ThemeContext"; +import type { ThemeContextType } from "../../ThemeContext"; function Contact() { const [showPopup, setShowPopup] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); + const themeContext = useContext(ThemeContext) as ThemeContextType; + const { mode } = themeContext; const handleSubmit = async () => { setIsSubmitting(true); - + // Simulate API call - await new Promise(resolve => setTimeout(resolve, 1500)); - + await new Promise((resolve) => setTimeout(resolve, 1500)); + setIsSubmitting(false); setShowPopup(true); @@ -25,7 +29,13 @@ function Contact() { }; return ( -
+
{/* Animated background elements */}
@@ -37,15 +47,28 @@ function Contact() { {/* Header Section */}
-
- Logo -
+
+ Logo +

GitHub Tracker

-

- Get in touch with us to discuss your project tracking needs or report any issues +

+ Get in touch with us to discuss your project tracking needs or report + any issues

@@ -53,159 +76,249 @@ function Contact() { {/* Contact Info Cards */}
-

Let's Connect

-

- We're here to help you track and manage your GitHub repositories more effectively +

+ Let's Connect +

+

+ We're here to help you track and manage your GitHub repositories + more effectively

-
-
-
- -
-
-

Phone Support

-

(123) 456-7890

-

Mon-Fri, 9AM-6PM EST

-
-
-
- -
-
-
- -
-
-

Email Us

-

support@githubtracker.com

-

We'll respond within 24 hours

+ {[...Array(3)].map((_, index) => { + const contactTypes = [ + { + title: "Phone Support", + iconBg: "from-blue-500 to-cyan-500", + detail: "(123) 456-7890", + sub: "Mon-Fri, 9AM-6PM EST", + Icon: Phone, + }, + { + title: "Email Us", + iconBg: "from-purple-500 to-pink-500", + detail: "support@githubtracker.com", + sub: "We'll respond within 24 hours", + Icon: Mail, + }, + { + title: "GitHub Issues", + iconBg: "from-green-500 to-teal-500", + detail: "github.com/yourorg/githubtracker", + sub: "Report bugs & feature requests", + Icon: Github, + }, + ]; + const { title, iconBg, detail, sub, Icon } = contactTypes[index]; + return ( +
+
+
+ +
+
+

+ {title} +

+

+ {detail} +

+

+ {sub} +

+
+
-
-
- -
-
-
- -
-
-

GitHub Issues

-

github.com/yourorg/githubtracker

-

Report bugs & feature requests

-
-
-
+ ); + })}
{/* Contact Form */} -
-

Send us a Message

- +
+

+ Send us a Message +

+
+ {/* Full Name */}
-
+ {/* Email */}
-
+ {/* Subject */}
-
-
-
- -
- {/* Success Popup Modal */} + {/* Success Popup */} {showPopup && ( -
-
-
-
- -
- -

Message Sent Successfully!

-

- Thank you for reaching out to GitHub Tracker. We've received your message and will get back to you within 24 hours. -

- - -
+
+ +
+ Thank you for contacting us! We will get back to you shortly.
+
)}
); } -export default Contact; \ No newline at end of file +export default Contact; diff --git a/src/pages/Contributors/Contributors.tsx b/src/pages/Contributors/Contributors.tsx index ab9de23..4ccec97 100644 --- a/src/pages/Contributors/Contributors.tsx +++ b/src/pages/Contributors/Contributors.tsx @@ -11,9 +11,10 @@ import { CircularProgress, Alert, } from "@mui/material"; -import { FaGithub } from "react-icons/fa"; +import { FaGithub } from "react-icons/fa"; // GitHub Icon import axios from "axios"; -import { Link } from "react-router-dom"; // ✅ Added +import { Link } from "react-router-dom"; +import { useTheme } from "../../hooks/useTheme"; interface Contributor { id: number; @@ -27,17 +28,25 @@ const ContributorsPage = () => { const [contributors, setContributors] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const { theme } = useTheme(); + + // Theme-based colors + const bgColor = theme === "dark" ? "#1f1f1f" : "#FFFFFF"; + const textColor = theme === "dark" ? "#FFFFFF" : "#333333"; + const cardBg = theme === "dark" ? "#2a2a2a" : "#FFFFFF"; + const borderColor = theme === "dark" ? "#444444" : "#E0E0E0"; + const hoverBorder = theme === "dark" ? "#666666" : "#C0C0C0"; useEffect(() => { const fetchContributors = async () => { try { const response = await axios.get( - "https://api.github.com/repos/GitMetricsLab/github_tracker/contributors", + "https://api.github.com/repos/mehul-m-prajapati/github_tracker/contributors", { withCredentials: false } ); setContributors(response.data); } catch (err) { - setError("Failed to fetch contributors. Please try again later. " + err); + setError("Failed to fetch contributors. Please try again later."); } finally { setLoading(false); } @@ -46,6 +55,11 @@ const ContributorsPage = () => { fetchContributors(); }, []); + // To trigger re-render on theme change (optional) + useEffect(() => { + setContributors((prev) => [...prev]); + }, [theme]); + if (loading) { return ( @@ -66,6 +80,8 @@ const ContributorsPage = () => { { {contributors.map((contributor) => ( {contributor.login} - + {contributor.contributions} Contributions - + Thank you for your valuable contributions! @@ -145,6 +146,7 @@ const ContributorsPage = () => { startIcon={} href={contributor.html_url} target="_blank" + onClick={(e) => e.stopPropagation()} sx={{ backgroundColor: "#24292f", color: "#fff", @@ -155,7 +157,6 @@ const ContributorsPage = () => { backgroundColor: "#444", }, }} - onClick={(e) => e.stopPropagation()} // prevent nested Link trigger > GitHub Profile diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx index d01792c..613a32e 100644 --- a/src/pages/Home/Home.tsx +++ b/src/pages/Home/Home.tsx @@ -22,13 +22,13 @@ import { FormControl, InputLabel, } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; import { useGitHubAuth } from "../../hooks/useGitHubAuth"; import { useGitHubData } from "../../hooks/useGitHubData"; import { usePagination } from "../../hooks/usePagination"; const ROWS_PER_PAGE = 10; -// Define the shape of the data received from GitHub interface GitHubItem { id: number; title: string; @@ -40,7 +40,7 @@ interface GitHubItem { } const Home: React.FC = () => { - // Hooks for managing user authentication + const theme = useTheme(); const { username, setUsername, @@ -49,7 +49,6 @@ const Home: React.FC = () => { error: authError, getOctokit, } = useGitHubAuth(); - const octokit = getOctokit(); const { issues, @@ -58,11 +57,9 @@ const Home: React.FC = () => { error: dataError, fetchData, } = useGitHubData(octokit); - const { page, itemsPerPage, handleChangePage, paginateData } = usePagination(ROWS_PER_PAGE); - // State for various filters and tabs const [tab, setTab] = useState(0); const [issueFilter, setIssueFilter] = useState("all"); const [prFilter, setPrFilter] = useState("all"); @@ -71,66 +68,50 @@ const Home: React.FC = () => { const [startDate, setStartDate] = useState(""); const [endDate, setEndDate] = useState(""); - // Handle data submission to fetch GitHub data const handleSubmit = (e: React.FormEvent): void => { e.preventDefault(); fetchData(username); }; - // Format date strings into a readable format - const formatDate = (dateString: string): string => { - return new Date(dateString).toLocaleDateString(); - }; - - // Filter data based on selected criteria - const filterData = ( - data: GitHubItem[], - filterType: string - ): GitHubItem[] => { - let filteredData = [...data]; + const formatDate = (dateString: string): string => + new Date(dateString).toLocaleDateString(); - if (filterType === "open" || filterType === "closed" || filterType === "merged") { - filteredData = filteredData.filter((item) => + const filterData = (data: GitHubItem[], filterType: string): GitHubItem[] => { + let filtered = [...data]; + if (["open", "closed", "merged"].includes(filterType)) { + filtered = filtered.filter((item) => filterType === "merged" - ? item.pull_request?.merged_at + ? !!item.pull_request?.merged_at : item.state === filterType ); } - if (searchTitle) { - filteredData = filteredData.filter((item) => + filtered = filtered.filter((item) => item.title.toLowerCase().includes(searchTitle.toLowerCase()) ); } - if (selectedRepo) { - filteredData = filteredData.filter((item) => + filtered = filtered.filter((item) => item.repository_url.includes(selectedRepo) ); } - if (startDate) { - filteredData = filteredData.filter( + filtered = filtered.filter( (item) => new Date(item.created_at) >= new Date(startDate) ); } if (endDate) { - filteredData = filteredData.filter( + filtered = filtered.filter( (item) => new Date(item.created_at) <= new Date(endDate) ); } - - return filteredData; + return filtered; }; - // Determine the current tab's data const currentData = tab === 0 ? filterData(issues, issueFilter) : filterData(prs, prFilter); - - // Paginate the filtered data const displayData = paginateData(currentData); - // Main UI rendering return ( { flexDirection: "column", minHeight: "78vh", mt: 4, + color: theme.palette.text.primary, }} > - {/* Authentication Form */} - +
{ required sx={{ flex: 1 }} /> -
- {/* Filters Section */} - - {/* Search Title */} - setSearchTitle(e.target.value)} - sx={{ - flexBasis: { xs: "100%", sm: "100%", md: "48%", lg: "23%" }, - flexGrow: 1, - }} - /> - - {/* Repository */} - setSelectedRepo(e.target.value)} - sx={{ - flexBasis: { xs: "100%", sm: "100%", md: "48%", lg: "23%" }, - flexGrow: 1, - }} - /> - - {/* Start Date */} - setStartDate(e.target.value)} - InputLabelProps={{ shrink: true }} - sx={{ - flexBasis: { xs: "100%", sm: "100%", md: "48%", lg: "23%" }, - flexGrow: 1, - }} - /> + + setSearchTitle(e.target.value)} + sx={{ minWidth: 200 }} + /> + setSelectedRepo(e.target.value)} + sx={{ minWidth: 200 }} + /> + setStartDate(e.target.value)} + InputLabelProps={{ shrink: true }} + sx={{ minWidth: 150 }} + /> + setEndDate(e.target.value)} + InputLabelProps={{ shrink: true }} + sx={{ minWidth: 150 }} + /> + - {/* End Date */} - setEndDate(e.target.value)} - InputLabelProps={{ shrink: true }} - sx={{ - flexBasis: { xs: "100%", sm: "100%", md: "48%", lg: "23%" }, - flexGrow: 1, - }} - /> - - - -{/* Tabs and State Dropdown */} - - setTab(newValue)} - variant="scrollable" - scrollButtons="auto" - sx={{ flexGrow: 1, minWidth: "200px" }} - > - - - - - - State - - - - - -{/* Error Alert */} -{(authError || dataError) && ( - - {authError || dataError} - -)} + + setTab(newValue)} sx={{ flex: 1 }}> + + + + + State + + + -{/* Table Section */} -{loading ? ( - - - -) : ( - - - - - - - Title - Repository - State - Created - - - - {displayData.map((item: GitHubItem) => ( - - - - {item.title} - - - - {item.repository_url.split("/").slice(-1)[0]} - - - {item.pull_request?.merged_at ? "merged" : item.state} - - {formatDate(item.created_at)} - - ))} - -
- -
-
+ {(authError || dataError) && ( + + {authError || dataError} + + )} + {loading ? ( + + + + ) : ( + + + + + + Title + Repository + State + Created + + + + {displayData.map((item: GitHubItem) => ( + + + + {item.title} + + + + {item.repository_url.split("/").slice(-1)[0]} + + + {item.pull_request?.merged_at ? "merged" : item.state} + + + {formatDate(item.created_at)} + + + ))} + +
+ +
)}
diff --git a/tailwind.config.js b/tailwind.config.js index 9b2ed50..4cdd0f6 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,5 +1,6 @@ /** @type {import('tailwindcss').Config} */ module.exports = { + darkMode: 'class', content: [ "./index.html", // For any HTML files in the root "./src/**/*.{js,jsx,ts,tsx}", // For all JS/JSX/TS/TSX files inside src folder @@ -9,3 +10,4 @@ module.exports = { }, plugins: [], } + \ No newline at end of file