Skip to content

Commit 2dfe8c1

Browse files
Merge pull request #133 from GitMetricsLab/pr-115
added theme switch
2 parents f00eb98 + c2f2ea8 commit 2dfe8c1

File tree

12 files changed

+714
-393
lines changed

12 files changed

+714
-393
lines changed

src/App.tsx

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
import Navbar from "./components/Navbar";
22
import Footer from "./components/Footer";
3-
import ScrollProgressBar from './components/ScrollProgressBar';
3+
import ScrollProgressBar from "./components/ScrollProgressBar";
44
import { Toaster } from "react-hot-toast";
5-
65
import Router from "./Routes/Router";
6+
import ThemeWrapper from "./ThemeContext"; // ✅ import your wrapper
77

88
function App() {
99
return (
10-
10+
<ThemeWrapper>
1111
<div className="relative flex flex-col min-h-screen">
12-
<ScrollProgressBar/>
12+
<ScrollProgressBar />
1313

1414
{/* Navbar */}
1515
<Navbar />
1616

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

2222
{/* Footer */}
@@ -27,24 +27,21 @@ function App() {
2727
reverseOrder={false}
2828
gutter={8}
2929
containerClassName="mt-12"
30-
containerStyle={{}}
3130
toastOptions={{
32-
className: 'bg-white',
31+
className: "bg-white dark:bg-gray-800 text-black dark:text-white",
3332
duration: 5000,
34-
//removeDelay: 1000,
35-
3633
success: {
3734
duration: 3000,
3835
iconTheme: {
39-
primary: 'green',
40-
secondary: 'white',
36+
primary: "green",
37+
secondary: "white",
4138
},
4239
},
4340
}}
4441
/>
4542
</div>
46-
43+
</ThemeWrapper>
4744
);
4845
}
4946

50-
export default App;
47+
export default App;

src/Routes/Login/Login.tsx

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import React, { useState, ChangeEvent, FormEvent, useContext } from "react";
2+
import axios from "axios";
3+
import { useNavigate } from "react-router-dom";
4+
import { ThemeContext } from "../../ThemeContext";
5+
import type { ThemeContextType } from "../../ThemeContext";
6+
7+
const backendUrl = import.meta.env.VITE_BACKEND_URL;
8+
9+
interface LoginFormData {
10+
email: string;
11+
password: string;
12+
}
13+
14+
const Login: React.FC = () => {
15+
const [formData, setFormData] = useState<LoginFormData>({ email: "", password: "" });
16+
const [message, setMessage] = useState<string>("");
17+
const [isLoading, setIsLoading] = useState<boolean>(false);
18+
19+
const navigate = useNavigate();
20+
const themeContext = useContext(ThemeContext) as ThemeContextType;
21+
const { mode } = themeContext;
22+
23+
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
24+
const { name, value } = e.target;
25+
setFormData({ ...formData, [name]: value });
26+
};
27+
28+
const handleSubmit = async (e: FormEvent) => {
29+
e.preventDefault();
30+
setIsLoading(true);
31+
32+
try {
33+
const response = await axios.post(`${backendUrl}/api/auth/login`, formData);
34+
setMessage(response.data.message);
35+
36+
if (response.data.message === "Login successful") {
37+
navigate("/home");
38+
}
39+
} catch (error: any) {
40+
setMessage(error.response?.data?.message || "Something went wrong");
41+
} finally {
42+
setIsLoading(false);
43+
}
44+
};
45+
46+
return (
47+
<div
48+
className={`min-h-screen w-full flex items-center justify-center relative overflow-hidden ${
49+
mode === "dark"
50+
? "bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900"
51+
: "bg-gradient-to-br from-slate-100 via-purple-100 to-slate-100"
52+
}`}
53+
>
54+
{/* Background blobs */}
55+
<div className="absolute inset-0 pointer-events-none">
56+
<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`} />
57+
<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`} />
58+
<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`} />
59+
</div>
60+
61+
{/* Login Card */}
62+
<div className="relative w-full max-w-md px-6">
63+
<div className="text-center mb-10">
64+
<div className={`inline-flex items-center justify-center w-20 h-20 bg-white rounded-3xl mb-6 shadow-2xl overflow-hidden`}>
65+
<img src="/crl-icon.png" alt="Logo" className="w-14 h-14 object-contain" />
66+
</div>
67+
<h1 className={`text-4xl font-bold bg-clip-text text-transparent mb-2 ${
68+
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"
69+
}`}>
70+
GitHubTracker
71+
</h1>
72+
<p className={`${mode === "dark" ? "text-slate-300" : "text-gray-700"} text-lg font-medium`}>Track your GitHub journey</p>
73+
</div>
74+
75+
{/* Form */}
76+
<div className={`rounded-3xl p-10 shadow-2xl border ${
77+
mode === "dark" ? "bg-white/10 backdrop-blur-xl border-white/20 text-white" : "bg-white border-gray-200 text-black"
78+
}`}>
79+
<h2 className={`text-2xl font-bold text-center mb-8 ${mode === "dark" ? "text-white" : "text-gray-800"}`}>Welcome Back</h2>
80+
81+
<form onSubmit={handleSubmit} className="space-y-6">
82+
<input
83+
type="email"
84+
name="email"
85+
placeholder="Enter your email"
86+
value={formData.email}
87+
onChange={handleChange}
88+
autoComplete="username"
89+
required
90+
className={`w-full px-4 py-4 rounded-2xl focus:outline-none transition-all ${
91+
mode === "dark"
92+
? "bg-white/5 border border-white/10 text-white placeholder-slate-400 focus:ring-2 focus:ring-purple-500"
93+
: "bg-gray-100 border border-gray-300 text-gray-900 placeholder-gray-500 focus:ring-2 focus:ring-purple-400"
94+
}`}
95+
/>
96+
97+
<input
98+
type="password"
99+
name="password"
100+
autoComplete="current-password"
101+
placeholder="Enter your password"
102+
value={formData.password}
103+
onChange={handleChange}
104+
required
105+
className={`w-full px-4 py-4 rounded-2xl focus:outline-none transition-all ${
106+
mode === "dark"
107+
? "bg-white/5 border border-white/10 text-white placeholder-slate-400 focus:ring-2 focus:ring-purple-500"
108+
: "bg-gray-100 border border-gray-300 text-gray-900 placeholder-gray-500 focus:ring-2 focus:ring-purple-400"
109+
}`}
110+
/>
111+
112+
<button
113+
type="submit"
114+
disabled={isLoading}
115+
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"
116+
>
117+
{isLoading ? "Signing in..." : "Sign In"}
118+
</button>
119+
</form>
120+
121+
{message && (
122+
<div className={`mt-6 p-4 rounded-2xl text-center text-sm font-medium ${
123+
message === "Login successful"
124+
? "bg-green-500/20 text-green-300 border border-green-500/30"
125+
: "bg-red-500/20 text-red-300 border border-red-500/30"
126+
}`}>
127+
{message}
128+
</div>
129+
)}
130+
</div>
131+
132+
{/* Footer Text */}
133+
<div className="text-center mt-8 pb-8">
134+
<p className={`${mode === "dark" ? "text-slate-500" : "text-gray-600"} text-sm`}>
135+
Don't have an account?
136+
<a href="#" className="ml-1 text-purple-400 hover:text-purple-300 transition-colors duration-300">
137+
Sign up here
138+
</a>
139+
</p>
140+
</div>
141+
</div>
142+
143+
{/* Lower gradient */}
144+
<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`} />
145+
</div>
146+
);
147+
};
148+
149+
export default Login;

src/Routes/Router.tsx

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,26 @@
1-
import { Navigate, Route, Routes } from "react-router-dom";
1+
import { Navigate, Route, Routes } from "react-router-dom"
2+
import Home from "../pages/Home/Home"
3+
import About from "../pages/About/About"
4+
import Contact from "../pages/Contact/Contact"
5+
import Contributors from "../pages/Contributors/Contributors"
6+
import Signup from "../pages/Signup/Signup.tsx"
7+
import Login from "../pages/Login/Login.tsx"
8+
import UserProfile from "../pages/UserProfile/UserProfile.tsx"
9+
210

3-
import Home from "../pages/Home/Home"; // Import the Home component
4-
import About from "../pages/About/About"; // Import the About component
5-
import Contact from "../pages/Contact/Contact"; // Import the Contact component
6-
import Contributors from "../pages/Contributors/Contributors";
7-
import Signup from "../pages/Signup/Signup.tsx";
8-
import Login from "../pages/Login/Login.tsx";
9-
import UserProfile from "../pages/UserProfile/UserProfile.tsx";
1011

1112
const Router = () => {
1213
return (
1314
<Routes>
1415
{/* Redirect from root (/) to the home page */}
1516
<Route path="/signup" element={<Signup />} />
1617
<Route path="/login" element={<Login />} />
17-
<Route path="/" element={<Navigate to="/home" replace />} />
18+
<Route path="/" element={<Home />} />
1819
<Route path="/about" element={<About />} />
1920
<Route path="/contact" element={<Contact />} />
20-
<Route path="/home" element={<Home />} />
2121
<Route path="/contributors" element={<Contributors />} />
2222
<Route path="/user/:username" element={<UserProfile />} />
2323
</Routes>
2424
);
25-
};
26-
25+
};
2726
export default Router;

src/ThemeContext.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// src/ThemeContext.tsx
2+
import React, { createContext, useMemo, useState, useEffect, ReactNode } from 'react';
3+
import { createTheme, ThemeProvider, Theme } from '@mui/material/styles';
4+
5+
interface ThemeContextType {
6+
mode: 'light' | 'dark';
7+
toggleTheme: () => void;
8+
}
9+
10+
export const ThemeContext = createContext<ThemeContextType | null>(null);
11+
12+
const ThemeWrapper = ({ children }: { children: ReactNode }) => {
13+
const [mode, setMode] = useState<'light' | 'dark'>('light');
14+
15+
useEffect(() => {
16+
if (mode === 'dark') {
17+
document.documentElement.classList.add('dark');
18+
} else {
19+
document.documentElement.classList.remove('dark');
20+
}
21+
}, [mode]);
22+
23+
const toggleTheme = () => {
24+
setMode(prev => (prev === 'light' ? 'dark' : 'light'));
25+
};
26+
27+
const muiTheme: Theme = useMemo(() => createTheme({ palette: { mode } }), [mode]);
28+
29+
return (
30+
<ThemeContext.Provider value={{ mode, toggleTheme }}>
31+
<ThemeProvider theme={muiTheme}>
32+
{children}
33+
</ThemeProvider>
34+
</ThemeContext.Provider>
35+
);
36+
};
37+
38+
export default ThemeWrapper;
39+
export type { ThemeContextType };

src/components/Navbar.tsx

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
1-
import { Link } from 'react-router-dom';
2-
import { useState } from 'react';
1+
import { Link } from "react-router-dom";
2+
import { useState, useContext } from "react";
3+
import { ThemeContext } from "../ThemeContext";
34

45
const Navbar: React.FC = () => {
56
const [isOpen, setIsOpen] = useState<boolean>(false);
7+
const themeContext = useContext(ThemeContext);
8+
if (!themeContext) return null;
9+
10+
const { toggleTheme, mode } = themeContext;
611

712
return (
8-
<nav className="bg-gray-800 text-white shadow-lg">
13+
<nav className="bg-white text-black dark:bg-gray-800 dark:text-white shadow-lg">
914
<div className="container mx-auto px-6 py-4 flex justify-between items-center">
1015
{/* Logo Section */}
11-
<Link
12-
to="/"
13-
className="text-2xl font-bold hover:text-gray-300 cursor-pointer flex items-center"
14-
>
15-
<img src="/crl-icon.png" alt="CRL Icon" className="h-8 mr-2" />
16-
GitHub Tracker
17-
</Link>
16+
<Link
17+
to="/"
18+
className="text-2xl font-bold hover:text-gray-300 cursor-pointer flex items-center"
19+
>
20+
<img src="/crl-icon.png" alt="CRL Icon" className="h-8 mr-2" />
21+
GitHub Tracker
22+
</Link>
1823

1924
{/* Desktop Links */}
2025
<div className="hidden md:flex space-x-6">
2126
<Link
22-
to="/home"
27+
to="/"
2328
className="text-lg font-medium hover:text-gray-300 transition-all px-2 py-1 border border-transparent hover:border-gray-400 rounded"
2429
>
2530
Home
@@ -45,7 +50,15 @@ const Navbar: React.FC = () => {
4550
<Link
4651
to="/login"
4752
className="text-lg font-medium hover:text-gray-300 transition-all px-2 py-1 border border-transparent hover:border-gray-400 rounded"
48-
>Login</Link>
53+
>
54+
Login
55+
</Link>
56+
<button
57+
onClick={toggleTheme}
58+
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"
59+
>
60+
{mode === "dark" ? "🌞 Light" : "🌙 Dark"}
61+
</button>
4962
</div>
5063

5164
{/* Mobile Menu Button */}
@@ -105,6 +118,22 @@ const Navbar: React.FC = () => {
105118
>
106119
Contributors
107120
</Link>
121+
<Link
122+
to="/login"
123+
className="block text-lg font-medium hover:text-gray-300 transition-all px-2 py-1 border border-transparent hover:border-gray-400 rounded"
124+
onClick={() => setIsOpen(false)}
125+
>
126+
Login
127+
</Link>
128+
<button
129+
onClick={() => {
130+
toggleTheme();
131+
setIsOpen(false);
132+
}}
133+
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"
134+
>
135+
{mode === "dark" ? "🌞 Light" : "🌙 Dark"}
136+
</button>
108137
</div>
109138
</div>
110139
)}

0 commit comments

Comments
 (0)