Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 11 additions & 14 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -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 (

<ThemeWrapper>
<div className="relative flex flex-col min-h-screen">
<ScrollProgressBar/>
<ScrollProgressBar />

{/* Navbar */}
<Navbar />

{/* Main content */}
<main className="flex-grow bg-gray-50 flex justify-center items-center">
<Router/>
<main className="flex-grow bg-gray-50 dark:bg-gray-900 flex justify-center items-center">
<Router />
</main>

{/* Footer */}
Expand All @@ -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",
},
},
}}
/>
</div>

</ThemeWrapper>
);
}

export default App;
export default App;
149 changes: 149 additions & 0 deletions src/Routes/Login/Login.tsx
Original file line number Diff line number Diff line change
@@ -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<LoginFormData>({ email: "", password: "" });
const [message, setMessage] = useState<string>("");
const [isLoading, setIsLoading] = useState<boolean>(false);

const navigate = useNavigate();
const themeContext = useContext(ThemeContext) as ThemeContextType;
const { mode } = themeContext;

const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
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 (
<div
className={`min-h-screen w-full flex items-center justify-center relative overflow-hidden ${
mode === "dark"
? "bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900"
: "bg-gradient-to-br from-slate-100 via-purple-100 to-slate-100"
}`}
>
{/* Background blobs */}
<div className="absolute inset-0 pointer-events-none">
<div className={`absolute -top-40 -right-40 w-96 h-96 ${mode === "dark" ? "bg-purple-500" : "bg-purple-300"} rounded-full blur-3xl opacity-30 animate-pulse`} />
<div className={`absolute -bottom-40 -left-40 w-96 h-96 ${mode === "dark" ? "bg-blue-500" : "bg-blue-300"} rounded-full blur-3xl opacity-30 animate-pulse`} />
<div className={`absolute top-40 left-40 w-96 h-96 ${mode === "dark" ? "bg-pink-500" : "bg-pink-300"} rounded-full blur-3xl opacity-30 animate-pulse`} />
</div>

{/* Login Card */}
<div className="relative w-full max-w-md px-6">
<div className="text-center mb-10">
<div className={`inline-flex items-center justify-center w-20 h-20 bg-white rounded-3xl mb-6 shadow-2xl overflow-hidden`}>
<img src="/crl-icon.png" alt="Logo" className="w-14 h-14 object-contain" />
</div>
<h1 className={`text-4xl font-bold bg-clip-text text-transparent mb-2 ${
mode === "dark" ? "bg-gradient-to-r from-purple-300 via-pink-300 to-indigo-300" : "bg-gradient-to-r from-purple-600 via-pink-600 to-indigo-600"
}`}>
GitHubTracker
</h1>
<p className={`${mode === "dark" ? "text-slate-300" : "text-gray-700"} text-lg font-medium`}>Track your GitHub journey</p>
</div>

{/* Form */}
<div className={`rounded-3xl p-10 shadow-2xl border ${
mode === "dark" ? "bg-white/10 backdrop-blur-xl border-white/20 text-white" : "bg-white border-gray-200 text-black"
}`}>
<h2 className={`text-2xl font-bold text-center mb-8 ${mode === "dark" ? "text-white" : "text-gray-800"}`}>Welcome Back</h2>

<form onSubmit={handleSubmit} className="space-y-6">
<input
type="email"
name="email"
placeholder="Enter your email"
value={formData.email}
onChange={handleChange}
autoComplete="username"
required
className={`w-full px-4 py-4 rounded-2xl focus:outline-none transition-all ${
mode === "dark"
? "bg-white/5 border border-white/10 text-white placeholder-slate-400 focus:ring-2 focus:ring-purple-500"
: "bg-gray-100 border border-gray-300 text-gray-900 placeholder-gray-500 focus:ring-2 focus:ring-purple-400"
}`}
/>

<input
type="password"
name="password"
autoComplete="current-password"
placeholder="Enter your password"
value={formData.password}
onChange={handleChange}
required
className={`w-full px-4 py-4 rounded-2xl focus:outline-none transition-all ${
mode === "dark"
? "bg-white/5 border border-white/10 text-white placeholder-slate-400 focus:ring-2 focus:ring-purple-500"
: "bg-gray-100 border border-gray-300 text-gray-900 placeholder-gray-500 focus:ring-2 focus:ring-purple-400"
}`}
/>

<button
type="submit"
disabled={isLoading}
className="w-full bg-gradient-to-r from-purple-600 via-pink-600 to-indigo-600 text-white py-4 px-6 rounded-2xl font-semibold focus:ring-4 focus:ring-purple-500/50 transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isLoading ? "Signing in..." : "Sign In"}
</button>
</form>

{message && (
<div className={`mt-6 p-4 rounded-2xl text-center text-sm font-medium ${
message === "Login successful"
? "bg-green-500/20 text-green-300 border border-green-500/30"
: "bg-red-500/20 text-red-300 border border-red-500/30"
}`}>
{message}
</div>
)}
</div>

{/* Footer Text */}
<div className="text-center mt-8 pb-8">
<p className={`${mode === "dark" ? "text-slate-500" : "text-gray-600"} text-sm`}>
Don't have an account?
<a href="#" className="ml-1 text-purple-400 hover:text-purple-300 transition-colors duration-300">
Sign up here
</a>
</p>
</div>
</div>

{/* Lower gradient */}
<div className={`${mode === "dark" ? "from-slate-900" : "from-slate-100"} absolute bottom-0 left-0 w-full h-20 bg-gradient-to-t to-transparent`} />
</div>
);
};

export default Login;
23 changes: 11 additions & 12 deletions src/Routes/Router.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
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 (
<Routes>
{/* Redirect from root (/) to the home page */}
<Route path="/signup" element={<Signup />} />
<Route path="/login" element={<Login />} />
<Route path="/" element={<Navigate to="/home" replace />} />
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/home" element={<Home />} />
<Route path="/contributors" element={<Contributors />} />
<Route path="/user/:username" element={<UserProfile />} />
</Routes>
);
};

};
export default Router;
39 changes: 39 additions & 0 deletions src/ThemeContext.tsx
Original file line number Diff line number Diff line change
@@ -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<ThemeContextType | null>(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 (
<ThemeContext.Provider value={{ mode, toggleTheme }}>
<ThemeProvider theme={muiTheme}>
{children}
</ThemeProvider>
</ThemeContext.Provider>
);
};

export default ThemeWrapper;
export type { ThemeContextType };
53 changes: 41 additions & 12 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -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<boolean>(false);
const themeContext = useContext(ThemeContext);
if (!themeContext) return null;

const { toggleTheme, mode } = themeContext;

return (
<nav className="bg-gray-800 text-white shadow-lg">
<nav className="bg-white text-black dark:bg-gray-800 dark:text-white shadow-lg">
<div className="container mx-auto px-6 py-4 flex justify-between items-center">
{/* Logo Section */}
<Link
to="/"
className="text-2xl font-bold hover:text-gray-300 cursor-pointer flex items-center"
>
<img src="/crl-icon.png" alt="CRL Icon" className="h-8 mr-2" />
GitHub Tracker
</Link>
<Link
to="/"
className="text-2xl font-bold hover:text-gray-300 cursor-pointer flex items-center"
>
<img src="/crl-icon.png" alt="CRL Icon" className="h-8 mr-2" />
GitHub Tracker
</Link>

{/* Desktop Links */}
<div className="hidden md:flex space-x-6">
<Link
to="/home"
to="/"
className="text-lg font-medium hover:text-gray-300 transition-all px-2 py-1 border border-transparent hover:border-gray-400 rounded"
>
Home
Expand All @@ -45,7 +50,15 @@ const Navbar: React.FC = () => {
<Link
to="/login"
className="text-lg font-medium hover:text-gray-300 transition-all px-2 py-1 border border-transparent hover:border-gray-400 rounded"
>Login</Link>
>
Login
</Link>
<button
onClick={toggleTheme}
className="text-sm font-semibold px-3 py-1 rounded border border-gray-500 hover:text-gray-300 hover:border-gray-300 transition duration-200"
>
{mode === "dark" ? "🌞 Light" : "🌙 Dark"}
</button>
</div>

{/* Mobile Menu Button */}
Expand Down Expand Up @@ -105,6 +118,22 @@ const Navbar: React.FC = () => {
>
Contributors
</Link>
<Link
to="/login"
className="block text-lg font-medium hover:text-gray-300 transition-all px-2 py-1 border border-transparent hover:border-gray-400 rounded"
onClick={() => setIsOpen(false)}
>
Login
</Link>
<button
onClick={() => {
toggleTheme();
setIsOpen(false);
}}
className="text-sm font-semibold px-3 py-1 rounded border border-gray-500 hover:text-gray-300 hover:border-gray-300 transition duration-200 w-full text-left"
>
{mode === "dark" ? "🌞 Light" : "🌙 Dark"}
</button>
</div>
</div>
)}
Expand Down
Loading