Skip to content
Closed
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
26 changes: 11 additions & 15 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +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 @@ -28,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;
158 changes: 114 additions & 44 deletions src/Routes/Login/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import React, { useState, ChangeEvent, FormEvent } from "react";
import React, { useState, ChangeEvent, FormEvent, useContext } from "react";
import axios from "axios";
import { useNavigate } from "react-router-dom"; // Import the hook for navigation
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;
Expand All @@ -11,8 +14,11 @@ interface LoginFormData {
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(); // Initialize the navigate hook
const navigate = useNavigate();
const themeContext = useContext(ThemeContext) as ThemeContextType;
const { mode } = themeContext;

const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
Expand All @@ -21,59 +27,123 @@ const Login: React.FC = () => {

const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
setIsLoading(true);

try {
const response = await axios.post(`${backendUrl}/api/auth/login`,
formData,

);
setMessage(response.data.message); // Show success message from backend

// Navigate to /home if login is successful
if (response.data.message === 'Login successful') {
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="max-w-md mt-12 mx-auto bg-white p-8 rounded-lg shadow-md">
<h2 className="text-2xl font-semibold text-center mb-6">Login</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<input
type="email"
name="email"
placeholder="Email"
value={formData.email}
onChange={handleChange}
required
className="w-full p-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<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>
<div>
<input
type="password"
name="password"
placeholder="Password"
value={formData.password}
onChange={handleChange}
required
className="w-full p-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>

{/* 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>
<button
type="submit"
className="w-full bg-blue-500 text-white py-3 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
>
Login
</button>
</form>
{message && <p className="text-center text-red-500 mt-4">{message}</p>}
</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;

export default Login;
5 changes: 2 additions & 3 deletions src/Routes/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,5 @@ const Router = () => {
<Route path="/contributors" element={<Contributors />} />
</Routes>
);
};

export default Router;
};
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 };
22 changes: 17 additions & 5 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { Link } from 'react-router-dom';
import { useState } from 'react';
import { Link } from "react-router-dom";
import { useState } from "react";
import { ThemeContext } from "../ThemeContext";
import { useContext } from "react";

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">
Expand All @@ -12,7 +18,6 @@ const Navbar: React.FC = () => {
<img src="/crl-icon.png" alt="CRL Icon" className="h-8 mr-2" />
GitHub Tracker
</div>

{/* Desktop Links */}
<div className="hidden md:flex space-x-6">
<Link
Expand Down Expand Up @@ -42,7 +47,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 All @@ -69,7 +82,6 @@ const Navbar: React.FC = () => {
</button>
</div>
</div>

{/* Mobile Links */}
{isOpen && (
<div className="md:hidden bg-gray-800">
Expand Down
5 changes: 4 additions & 1 deletion src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<StrictMode>
<ThemeWrapper>
<BrowserRouter>
<App />
</BrowserRouter>
</ThemeWrapper>
</StrictMode>
);
);
Loading