Skip to content

Commit 13f97d4

Browse files
committed
implemented admin login/signup
1 parent e9f5e02 commit 13f97d4

File tree

13 files changed

+403
-8
lines changed

13 files changed

+403
-8
lines changed

backend/controller/admin.controller.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,16 @@ async function loginAdmin(req, res) {
7373
const token = jwt.sign(payload, process.env.JWT_SECRET, {
7474
expiresIn: "1h",
7575
});
76+
res.cookie("authToken", token, {
77+
maxAge: 1000 * 60 * 60,
78+
httpOnly: true,
79+
secure: true,
80+
});
7681
res.json({
7782
message: "Login successful",
7883
token,
7984
role: "admin",
80-
admin: { id: admin._id, name: admin.name, email: admin.email },
85+
admin: { id: admin._id, name: admin.name, email: admin.email, role: "admin" },
8186
});
8287
} catch (error) {
8388
logger.error("Error logging in admin:", {

backend/controller/customer.controller.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ async function loginCustomer(req, res) {
143143
id: customer._id,
144144
name: customer.name,
145145
email: customer.email,
146+
role: "customer"
146147
},
147148
});
148149
} catch (error) {

frontend/src/components/Pages/Admin.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { message } from 'antd';
22
import React, { useEffect, useState } from 'react';
33
import { useNavigate } from 'react-router-dom';
4+
import { useUser } from '../../context/userContext';
45

56
const Admin = () => {
67
const [events, setEvents] = useState([]);
78
const [error, setError] = useState(null);
89
const navigate = useNavigate();
10+
const {user} = useUser();
911

1012
// Fetch all events
1113
const fetchData = async () => {
@@ -122,7 +124,7 @@ const Admin = () => {
122124
<div className="h-fit min-h-screen w-screen flex flex-col items-center justify-start p-12 pt-[10vh]">
123125
<div className="Header w-full flex flex-col items-center">
124126
<h1 className="title text-[#323232] font-black text-7xl mb-6">
125-
Hi {Admin.name}!
127+
Hi {user.name}!
126128
</h1>
127129
<h1 className="mt-[-2vh] text-[#666] font-semibold text-2xl">
128130
Welcome to Admin Panel
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import React, { useState, useEffect } from 'react';
2+
import photo from '../../../assets/login.png';
3+
import { Link, useNavigate } from 'react-router-dom';
4+
import { message } from 'antd';
5+
import Cookies from 'js-cookie';
6+
import { FaEye } from 'react-icons/fa';
7+
import { FaEyeSlash } from 'react-icons/fa6';
8+
import { useUser } from '../../../context/userContext';
9+
10+
const AdminLogin = () => {
11+
const API_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:3000';
12+
const [data, setData] = useState({
13+
email: '',
14+
password: '',
15+
});
16+
const [hidden, setHidden] = useState(true);
17+
const [isLoading, setIsLoading] = useState(false);
18+
const [error, setError] = useState(null);
19+
const {user, setUser} = useUser();
20+
21+
const navigate = useNavigate();
22+
23+
const handleChange = (e) => {
24+
setData({ ...data, [e.target.name]: e.target.value });
25+
};
26+
27+
const handleSubmit = async (e) => {
28+
e.preventDefault();
29+
setIsLoading(true);
30+
setError(null);
31+
try {
32+
const response = await fetch(`${API_URL}/api/admin/login`, {
33+
method: 'POST',
34+
headers: {
35+
'Content-Type': 'application/json',
36+
},
37+
body: JSON.stringify(data),
38+
});
39+
const result = await response.json();
40+
if (!response.ok) {
41+
throw new Error(result.message || 'Login failed');
42+
}
43+
const res = JSON.stringify(result.admin)
44+
Cookies.set('authToken', result.token, { expires: 1, secure: true });
45+
Cookies.set("authenticatedUser", res, {expires: 1, secure: true})
46+
setUser(result.admin)
47+
message.success('Login successful');
48+
navigate('/admin');
49+
} catch (err) {
50+
setError(err.message || 'An error occurred. Please try again.');
51+
} finally {
52+
setIsLoading(false);
53+
}
54+
};
55+
56+
useEffect(() => {
57+
window.scrollTo(0, 0);
58+
}, []);
59+
60+
return (
61+
<div className="w-screen h-screen dark:bg-black flex items-center justify-center lg:pt-10 px-4">
62+
{/* Background Image */}
63+
<img
64+
src={photo}
65+
alt="login"
66+
loading="lazy"
67+
className="absolute w-3/4 lg:w-auto lg:opacity-100 opacity-10 object-cover"
68+
/>
69+
{/* Login Form */}
70+
<form
71+
onSubmit={handleSubmit}
72+
className="z-10 p-8 lg:p-14 bg-[#f1e9dc] dark:bg-amber-800 dark:text-white flex flex-col gap-6 rounded-lg border-2 border-black shadow-[4px_4px_0px_0px_black] w-full max-w-md lg:max-w-xl"
73+
>
74+
<div className="text-[#323232] dark:text-white font-black text-4xl lg:text-6xl mb-2">
75+
Admin Login,
76+
<span className="block text-[#666] dark:text-gray-400 font-semibold text-lg lg:text-2xl mt-1">
77+
Log in to continue
78+
</span>
79+
</div>
80+
81+
<input
82+
className="w-full h-12 rounded-md border-2 border-black bg-beige shadow-[4px_4px_0px_0px_black] text-[15px] font-semibold text-[#323232] p-2.5 focus:outline-none focus:border-[#2d8cf0] placeholder-[#666]"
83+
name="email"
84+
placeholder="Email"
85+
type="email"
86+
onChange={handleChange}
87+
/>
88+
89+
<div className="relative w-full">
90+
<input
91+
className="w-full h-12 rounded-md border-2 border-black bg-beige shadow-[4px_4px_0px_0px_black] text-[15px] font-semibold text-[#323232] p-2.5 focus:outline-none focus:border-[#2d8cf0] placeholder-[#666]"
92+
name="password"
93+
placeholder="Password"
94+
type={hidden ? 'password' : 'text'}
95+
onChange={handleChange}
96+
/>
97+
<button
98+
className="absolute top-1/2 transform -translate-y-1/2 right-4"
99+
onClick={(e) => {
100+
e.preventDefault();
101+
setHidden(!hidden);
102+
}}
103+
>
104+
{hidden ? <FaEyeSlash /> : <FaEye />}
105+
</button>
106+
</div>
107+
108+
<Link
109+
to="/email-verify"
110+
className="text-sm lg:text-base text-gray-500 dark:text-gray-200 hover:text-red-500 transition"
111+
>
112+
Forgot Password?
113+
</Link>
114+
115+
<h3 className="flex justify-between items-center w-full text-sm lg:text-base">
116+
Don’t have an account?
117+
<Link
118+
to="/admin-signup"
119+
className="text-green-500 font-semibold hover:scale-110 transition"
120+
>
121+
Register Here
122+
</Link>
123+
</h3>
124+
125+
<a
126+
href={`${API_URL}/api/user/auth/google`}
127+
className="w-full"
128+
>
129+
<button
130+
type="button"
131+
className="w-full h-12 rounded-md border-2 dark:text-white border-black bg-beige shadow-[4px_4px_0px_0px_black] text-[17px] font-semibold text-[#323232] transition active:translate-x-[3px] active:translate-y-[3px]"
132+
>
133+
Sign in with Google
134+
</button>
135+
</a>
136+
137+
{error && <p className="text-red-500 mt-2">{error}</p>}
138+
139+
<button
140+
type="submit"
141+
className="w-full h-12 rounded-md dark:text-white border-2 border-black bg-beige shadow-[4px_4px_0px_0px_black] text-[17px] font-semibold text-[#323232] transition active:translate-x-[3px] active:translate-y-[3px]"
142+
>
143+
{isLoading ? 'Loading...' : 'Let’s Log you in →'}
144+
</button>
145+
</form>
146+
</div>
147+
);
148+
};
149+
150+
export default AdminLogin;
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import { useState, useEffect } from 'react';
2+
import photo from '../../../assets/login.png';
3+
import { useNavigate, Link } from 'react-router-dom';
4+
import { FaEye } from 'react-icons/fa';
5+
import { FaEyeSlash } from 'react-icons/fa6';
6+
import zxcvbn from 'zxcvbn'; // Password strength checker
7+
8+
const AdminSignup = () => {
9+
const API_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:3000';
10+
const navigate = useNavigate();
11+
const [isLoading, setIsLoading] = useState(false);
12+
const [error, setError] = useState(null);
13+
const [passwordStrength, setPasswordStrength] = useState(0);
14+
const [data, setData] = useState({ name: '', email: '', password: '' });
15+
const [hidden, setHidden] = useState(true);
16+
17+
const handleChange = (e) => {
18+
setData({ ...data, [e.target.name]: e.target.value });
19+
if (e.target.name === 'password') {
20+
const result = zxcvbn(e.target.value);
21+
setPasswordStrength(result.score);
22+
}
23+
};
24+
25+
const handleSubmit = async (e) => {
26+
e.preventDefault();
27+
setIsLoading(true);
28+
29+
// Input validation
30+
if (!data.email || !data.password || !data.name) {
31+
setError('Please fill in all fields');
32+
setIsLoading(false);
33+
return;
34+
}
35+
if (data.password.length < 8) {
36+
setError('Password must be at least 8 characters long');
37+
setIsLoading(false);
38+
return;
39+
}
40+
if (data.name.length < 3) {
41+
setError('Name must be at least 3 characters long');
42+
setIsLoading(false);
43+
return;
44+
}
45+
if (!data.email.includes('@')) {
46+
setError('Please enter a valid email address');
47+
setIsLoading(false);
48+
return;
49+
}
50+
51+
try {
52+
const response = await fetch(`${API_URL}/api/admin/register`, {
53+
method: 'POST',
54+
headers: { 'Content-Type': 'application/json' },
55+
body: JSON.stringify(data),
56+
});
57+
const result = await response.json();
58+
59+
if (!response.ok) {
60+
setIsLoading(false);
61+
setError(result.error);
62+
return;
63+
}
64+
65+
66+
// alert('OTP sent to your email. Verify to complete registration.');
67+
navigate('/admin-login');
68+
69+
} catch (error) {
70+
setError(error.message);
71+
console.error('Error:', error);
72+
} finally {
73+
setIsLoading(false); // Ensure loading state is reset after request
74+
}
75+
};
76+
77+
78+
useEffect(() => {
79+
window.scrollTo(0, 0);
80+
}, []);
81+
82+
const getPasswordStrengthColor = (score) => {
83+
const colors = ['#ff4d4d', '#ff944d', '#ffd24d', '#d2ff4d', '#4dff88'];
84+
return colors[score];
85+
};
86+
87+
const getPasswordStrengthText = (score) => {
88+
const strengthLevels = ['Very Weak', 'Weak', 'Okay', 'Good', 'Strong'];
89+
return strengthLevels[score];
90+
};
91+
92+
return (
93+
<div className="w-screen h-screen flex items-center dark:text-white dark:bg-black justify-center pt-10 relative overflow-hidden">
94+
<img
95+
src={photo}
96+
alt="login"
97+
loading="lazy"
98+
className="absolute w-3/4 lg:w-auto lg:opacity-100 opacity-10 object-cover"
99+
/>
100+
<form className="z-10 p-8 sm:p-16 bg-[#f1e9dc] dark:bg-amber-800 dark:text-white rounded-lg border-2 border-black shadow-[4px_4px_0px_0px_black] flex flex-col gap-5 w-11/12 sm:w-auto px-[3vw] pt-[5vh]">
101+
<div className="text-[#323232] dark:text-white font-black text-4xl sm:text-7xl mb-4 sm:mb-6 mt-4">
102+
Admin SignUp
103+
<br />
104+
<span className="block text-[#666] dark:text-gray-400 font-semibold text-xl sm:text-2xl">
105+
Register to continue
106+
</span>
107+
</div>
108+
<input
109+
className="input w-full h-10 rounded-md border-2 border-black bg-beige p-2.5 shadow-[4px_4px_0px_0px_black] focus:outline-none"
110+
name="name"
111+
placeholder="Name"
112+
type="text"
113+
onChange={handleChange}
114+
/>
115+
<input
116+
className="input w-full h-10 rounded-md border-2 border-black bg-beige p-2.5 shadow-[4px_4px_0px_0px_black] focus:outline-none"
117+
name="email"
118+
placeholder="Email"
119+
type="email"
120+
onChange={handleChange}
121+
/>
122+
<div className="relative w-full">
123+
<input
124+
className="input w-full h-10 rounded-md border-2 border-black bg-beige p-2.5 shadow-[4px_4px_0px_0px_black] focus:outline-none"
125+
name="password"
126+
placeholder="Password"
127+
type={hidden ? 'password' : 'text'}
128+
onChange={handleChange}
129+
/>
130+
<button
131+
className="absolute top-1/2 -translate-y-1/2 right-4"
132+
onClick={(e) => {
133+
e.preventDefault();
134+
setHidden(!hidden);
135+
}}
136+
>
137+
{hidden ? <FaEyeSlash /> : <FaEye />}
138+
</button>
139+
</div>
140+
<div className="w-full mt-2">
141+
<div
142+
className="h-2 rounded-full"
143+
style={{
144+
backgroundColor: getPasswordStrengthColor(passwordStrength),
145+
width: `${(passwordStrength + 1) * 20}%`,
146+
}}
147+
></div>
148+
<p className="text-sm text-[#666] dark:text-gray-200 mt-1">
149+
Strength: {getPasswordStrengthText(passwordStrength)}
150+
</p>
151+
</div>
152+
{error && (
153+
<div className="w-full p-2 bg-red-100 text-red-700 border border-red-400 rounded-md">
154+
{error}
155+
</div>
156+
)}
157+
<h3 className="flex justify-between w-full">
158+
Already have an account?
159+
<Link
160+
to="/admin-login"
161+
className="text-[#666] dark:text-white font-semibold hover:text-green-500"
162+
>
163+
Login
164+
</Link>
165+
</h3>
166+
<a href={`${API_URL}/api/user/auth/google`} className="w-full">
167+
<button className="button-confirm w-full h-10 rounded-md border-2 border-black bg-beige text-[17px] font-semibold shadow-[4px_4px_0px_0px_black] hover:text-green-300">
168+
Sign up with Google
169+
</button>
170+
</a>
171+
<button
172+
className="button-confirm w-full h-10 rounded-md border-2 border-black bg-beige text-[17px] font-semibold shadow-[4px_4px_0px_0px_black] mb-2 hover:text-green-300"
173+
onClick={handleSubmit}
174+
>
175+
{isLoading ? 'Loading...' : "Let's go →"}
176+
</button>
177+
</form>
178+
</div>
179+
);
180+
};
181+
182+
export default AdminSignup;

0 commit comments

Comments
 (0)