diff --git a/backend/controller/admin.controller.js b/backend/controller/admin.controller.js index dbd7408..14997a7 100644 --- a/backend/controller/admin.controller.js +++ b/backend/controller/admin.controller.js @@ -73,11 +73,16 @@ async function loginAdmin(req, res) { const token = jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: "1h", }); + res.cookie("authToken", token, { + maxAge: 1000 * 60 * 60, + httpOnly: true, + secure: true, + }); res.json({ message: "Login successful", token, role: "admin", - admin: { id: admin._id, name: admin.name, email: admin.email }, + admin: { id: admin._id, name: admin.name, email: admin.email, role: admin.role || "admin" }, }); } catch (error) { logger.error("Error logging in admin:", { diff --git a/backend/controller/customer.controller.js b/backend/controller/customer.controller.js index 3f494f2..b5b9b50 100644 --- a/backend/controller/customer.controller.js +++ b/backend/controller/customer.controller.js @@ -137,6 +137,7 @@ async function loginCustomer(req, res) { id: customer._id, name: customer.name, email: customer.email, + role: "customer" }, }); } catch (error) { diff --git a/frontend/src/components/Pages/Admin.jsx b/frontend/src/components/Pages/Admin.jsx index bc58882..99bf816 100644 --- a/frontend/src/components/Pages/Admin.jsx +++ b/frontend/src/components/Pages/Admin.jsx @@ -1,11 +1,13 @@ import { message } from 'antd'; import React, { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; +import { useUser } from '../../context/userContext'; const Admin = () => { const [events, setEvents] = useState([]); const [error, setError] = useState(null); const navigate = useNavigate(); + const {user} = useUser(); // Fetch all events const fetchData = async () => { @@ -122,7 +124,7 @@ const Admin = () => {

- Hi {Admin.name}! + Hi {user.name}!

Welcome to Admin Panel @@ -265,7 +267,7 @@ const Admin = () => {
{error &&

{error}

} - {events.map((event) => ( + {events.length > 0 && events.map((event) => (
{ + const API_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:3000'; + const [data, setData] = useState({ + email: '', + password: '', + }); + const [hidden, setHidden] = useState(true); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const {user, setUser} = useUser(); + + const navigate = useNavigate(); + + const handleChange = (e) => { + setData({ ...data, [e.target.name]: e.target.value }); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(data.email)) { + setError('Please enter a valid email address'); + return; + } + if (data.password.length < 8) { + setError('Password must be at least 8 characters long'); + return; + } + setIsLoading(true); + setError(null); + try { + const response = await fetch(`${API_URL}/api/admin/login`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }); + const result = await response.json(); + if (!response.ok) { + throw new Error(result.message || 'Login failed'); + } + const res = JSON.stringify(result.admin) + Cookies.set('authToken', result.token, { expires: 1, secure: true }); + Cookies.set("authenticatedUser", res, {expires: 1, secure: true}) + setUser(result.admin) + message.success('Login successful'); + navigate('/admin'); + } catch (err) { + setError(err.message || 'An error occurred. Please try again.'); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + window.scrollTo(0, 0); + }, []); + + return ( +
+ {/* Background Image */} + login + {/* Login Form */} +
+
+ Admin Login, + + Log in to continue + +
+ + + +
+ + +
+ + + Forgot Password? + + +

+ Don’t have an account? + + Register Here + +

+ + + + + + {error &&

{error}

} + + +
+
+ ); +}; + +export default AdminLogin; diff --git a/frontend/src/components/Pages/Admin/AdminSignup.jsx b/frontend/src/components/Pages/Admin/AdminSignup.jsx new file mode 100644 index 0000000..cc5baf9 --- /dev/null +++ b/frontend/src/components/Pages/Admin/AdminSignup.jsx @@ -0,0 +1,183 @@ +import { useState, useEffect } from 'react'; +import photo from '../../../assets/login.png'; +import { useNavigate, Link } from 'react-router-dom'; +import { FaEye } from 'react-icons/fa'; +import { FaEyeSlash } from 'react-icons/fa6'; +import zxcvbn from 'zxcvbn'; // Password strength checker + +const AdminSignup = () => { + const API_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:3000'; + const navigate = useNavigate(); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [passwordStrength, setPasswordStrength] = useState(0); + const [data, setData] = useState({ name: '', email: '', password: '' }); + const [hidden, setHidden] = useState(true); + + const handleChange = (e) => { + setData({ ...data, [e.target.name]: e.target.value }); + if (e.target.name === 'password') { + const result = zxcvbn(e.target.value); + setPasswordStrength(result.score); + } + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + setIsLoading(true); + + // Input validation + if (!data.email || !data.password || !data.name) { + setError('Please fill in all fields'); + setIsLoading(false); + return; + } + if (data.password.length < 8) { + setError('Password must be at least 8 characters long'); + setIsLoading(false); + return; + } + if (data.name.length < 3) { + setError('Name must be at least 3 characters long'); + setIsLoading(false); + return; + } + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(data.email)) { + setError('Please enter a valid email address'); + setIsLoading(false); + return; + } + + try { + const response = await fetch(`${API_URL}/api/admin/register`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data), + }); + const result = await response.json(); + + if (!response.ok) { + setIsLoading(false); + setError(result.error); + return; + } + + + // alert('OTP sent to your email. Verify to complete registration.'); + navigate('/admin-login'); + + } catch (error) { + setError(error.message); + console.error('Error:', error); + } finally { + setIsLoading(false); // Ensure loading state is reset after request + } + }; + + + useEffect(() => { + window.scrollTo(0, 0); + }, []); + + const getPasswordStrengthColor = (score) => { + const colors = ['#ff4d4d', '#ff944d', '#ffd24d', '#d2ff4d', '#4dff88']; + return colors[score]; + }; + + const getPasswordStrengthText = (score) => { + const strengthLevels = ['Very Weak', 'Weak', 'Okay', 'Good', 'Strong']; + return strengthLevels[score]; + }; + + return ( +
+ login +
+
+ Admin SignUp +
+ + Register to continue + +
+ + +
+ + +
+
+
+

+ Strength: {getPasswordStrengthText(passwordStrength)} +

+
+ {error && ( +
+ {error} +
+ )} +

+ Already have an account? + + Login + +

+ + + + +
+
+ ); +}; + +export default AdminSignup; diff --git a/frontend/src/components/Pages/Login.jsx b/frontend/src/components/Pages/Login.jsx index e3e5c1a..62c027c 100644 --- a/frontend/src/components/Pages/Login.jsx +++ b/frontend/src/components/Pages/Login.jsx @@ -32,9 +32,15 @@ const Login = () => { body: JSON.stringify({ ...data, rememberMe }), // Include rememberMe in the body }); const result = await response.json(); - if (!response.ok) throw new Error(result.message || 'Login failed'); - - // Set cookie expiration based on Remember Me option + // console.log(result); + + if (!response) { + throw new Error(result.message || 'Login failed'); + } + const res = JSON.stringify(result.user) + + Cookies.set("authenticatedUser", res, {expires: 1, secure: true, sameSite: 'strict'}) + Cookies.set('authToken', result.token, { expires: rememberMe ? 7 : 1 / 24, // 7 days if Remember Me is checked, 1 hour otherwise secure: process.env.NODE_ENV === "production", @@ -44,6 +50,8 @@ const Login = () => { message.success('Login successful'); navigate('/'); } catch (err) { + console.log(err); + setError(err.message || 'An error occurred. Please try again.'); } finally { setIsLoading(false); diff --git a/frontend/src/components/Pages/Signup.jsx b/frontend/src/components/Pages/Signup.jsx index 37498c3..ce678fe 100644 --- a/frontend/src/components/Pages/Signup.jsx +++ b/frontend/src/components/Pages/Signup.jsx @@ -95,7 +95,7 @@ const Signup = () => { src={photo} alt="login" loading="lazy" - className="w-3/4 absolute inset-0" + className="absolute w-3/4 lg:w-auto lg:opacity-100 opacity-10 object-cover" />
diff --git a/frontend/src/components/Pages/VerifyOtp.tsx b/frontend/src/components/Pages/VerifyOtp.jsx similarity index 100% rename from frontend/src/components/Pages/VerifyOtp.tsx rename to frontend/src/components/Pages/VerifyOtp.jsx diff --git a/frontend/src/components/Shared/Navbar.jsx b/frontend/src/components/Shared/Navbar.jsx index ff33ddc..13d20eb 100644 --- a/frontend/src/components/Shared/Navbar.jsx +++ b/frontend/src/components/Shared/Navbar.jsx @@ -56,6 +56,7 @@ const Navbar = () => { }) Cookies.remove('authToken'); + Cookies.remove("authenticatedUser"); setToken(null); setIsModalOpen(false); // Close the modal setIsMenuOpen(false); // after getting logged out close the menu if it is open diff --git a/frontend/src/context/userContext.jsx b/frontend/src/context/userContext.jsx new file mode 100644 index 0000000..bf63fcd --- /dev/null +++ b/frontend/src/context/userContext.jsx @@ -0,0 +1,35 @@ +import React, { createContext, useState, useContext } from 'react'; +import Cookies from 'js-cookie'; + +export const UserContext = createContext({ + user: null, + setUser: () => {}, +}); + +export const UserProvider = ({ children }) => { + const data = Cookies.get("authenticatedUser"); + + let userData = null; + try { + userData = data ? JSON.parse(data) : null; + } catch (error) { + console.error('Invalid user data in cookie:', error); + Cookies.remove("authenticatedUser"); + } + + const [user, setUser] = useState(userData); + + return ( + + {children} + + ); +}; + +export const useUser = () => { + const context = useContext(UserContext); + if (context === undefined) { + throw new Error('useUser must be used within a UserProvider'); + } + return context; +}; diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 0853bb3..49c8e91 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -3,9 +3,12 @@ import ReactDOM from 'react-dom/client'; import './index.css'; import router from './router'; import { RouterProvider } from 'react-router-dom'; +import { UserProvider } from './context/userContext.jsx'; ReactDOM.createRoot(document.getElementById('root')).render( - + + + ); diff --git a/frontend/src/router/ProtectedRoute.jsx b/frontend/src/router/ProtectedRoute.jsx new file mode 100644 index 0000000..a548288 --- /dev/null +++ b/frontend/src/router/ProtectedRoute.jsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { Route, Navigate } from 'react-router-dom'; +import { useUser } from '../context/userContext'; + +const ProtectedRoute = ({ children }) => { + const {user} = useUser(); + + if (!user) { + return ; + } + + return user.role === "admin" ? children : ; +}; + +export default ProtectedRoute; \ No newline at end of file diff --git a/frontend/src/router/index.jsx b/frontend/src/router/index.jsx index 2d8d058..f9d16c9 100644 --- a/frontend/src/router/index.jsx +++ b/frontend/src/router/index.jsx @@ -22,6 +22,9 @@ import VerifyOtp from '../components/Pages/VerifyOtp'; import EmailVerify from '../components/Pages/EmailVerify'; import Membership from '../components/Membership'; import OtpRegisterVerify from '../components/Pages/verifyRegisterOtp'; +import AdminLogin from '../components/Pages/Admin/AdminLogin'; +import AdminSignup from '../components/Pages/Admin/AdminSignup'; +import ProtectedRoute from './ProtectedRoute'; import Profile from '../components/Pages/Dashboard'; const router = createBrowserRouter( createRoutesFromElements( @@ -37,7 +40,16 @@ const router = createBrowserRouter( } /> } /> } /> - } /> + + + + } + /> + } /> + } /> } /> } /> } />