Skip to content

Commit 650d79e

Browse files
committed
chore: login Signup forgot page and its backend functions
1 parent a2663a8 commit 650d79e

12 files changed

Lines changed: 2141 additions & 200 deletions

File tree

frontend/package-lock.json

Lines changed: 1626 additions & 28 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,30 @@
1111
},
1212
"dependencies": {
1313
"@tailwindcss/vite": "^4.1.15",
14+
"axios": "^1.6.0",
1415
"framer-motion": "^12.23.24",
1516
"jspdf": "^3.0.3",
1617
"lucide-react": "^0.546.0",
17-
"axios": "^1.6.0",
1818
"react": "^19.1.1",
1919
"react-dom": "^19.1.1",
20+
"react-hot-toast": "^2.6.0",
21+
"react-icons": "^5.5.0",
2022
"react-router-dom": "^7.9.4",
21-
"recharts": "^3.3.0",
22-
"tailwindcss": "^4.1.15"
23+
"react-spinners": "^0.17.0",
24+
"recharts": "^3.3.0"
2325
},
2426
"devDependencies": {
2527
"@eslint/js": "^9.36.0",
2628
"@types/react": "^19.1.16",
2729
"@types/react-dom": "^19.1.9",
2830
"@vitejs/plugin-react": "^5.0.4",
31+
"autoprefixer": "^10.4.21",
2932
"eslint": "^9.36.0",
3033
"eslint-plugin-react-hooks": "^5.2.0",
3134
"eslint-plugin-react-refresh": "^0.4.22",
3235
"globals": "^16.4.0",
36+
"postcss": "^8.5.6",
37+
"tailwindcss": "^3.4.18",
3338
"vite": "^7.1.7"
3439
}
3540
}

frontend/postcss.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default {
2+
plugins: {
3+
tailwindcss: {},
4+
autoprefixer: {},
5+
},
6+
}

frontend/src/App.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Register from "./pages/Register";
1212
import Chatbot from "./pages/Chatbot";
1313
import NotFound from "./pages/NotFound";
1414
import { AuthProvider } from "./context/AuthContext";
15+
import ForgotPassword from "./pages/Forgotpassword";
1516

1617
export default function App() {
1718
return (
@@ -27,6 +28,7 @@ export default function App() {
2728
<Route path="/chatbot" element={<Chatbot />} />
2829
<Route path="/login" element={<Login />} />
2930
<Route path="/register" element={<Register />} />
31+
<Route path='/forgot-password' element={<ForgotPassword/>}/>
3032
<Route path="*" element={<NotFound />} />
3133
</Routes>
3234

frontend/src/index.css

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
@import "tailwindcss";
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;
24
body {
35
font-family: "Poppins", sans-serif;
46
margin: 0;
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import React, { useState } from "react";
2+
import { IoIosArrowRoundBack } from "react-icons/io";
3+
import { useNavigate } from "react-router-dom";
4+
import { ClipLoader } from "react-spinners";
5+
import { sendOtp, verifyOtp, resetPassword } from "../services/authService";
6+
7+
const ForgotPassword = () => {
8+
const [step, setStep] = useState(1);
9+
const [email, setEmail] = useState("");
10+
const [otp, setOtp] = useState("");
11+
const [newPassword, setNewPassword] = useState("");
12+
const [confirmPassword, setConfirmPassword] = useState("");
13+
const [err, setErr] = useState("");
14+
const [loading, setLoading] = useState(false);
15+
16+
const navigate = useNavigate();
17+
18+
const handleSendOtp = async () => {
19+
setLoading(true);
20+
try {
21+
await sendOtp(email);
22+
setErr("");
23+
setStep(2);
24+
} catch (error) {
25+
setErr(error?.response?.data?.message || "Failed to send OTP");
26+
} finally {
27+
setLoading(false);
28+
}
29+
};
30+
31+
const handleVerifyOtp = async () => {
32+
setLoading(true);
33+
try {
34+
await verifyOtp(email, otp);
35+
setErr("");
36+
setStep(3);
37+
} catch (error) {
38+
setErr(error?.response?.data?.message || "OTP verification failed");
39+
} finally {
40+
setLoading(false);
41+
}
42+
};
43+
44+
const handleResetPassword = async () => {
45+
if (newPassword !== confirmPassword) {
46+
setErr("Passwords do not match");
47+
return;
48+
}
49+
50+
setLoading(true);
51+
try {
52+
await resetPassword(email, newPassword);
53+
setErr("");
54+
navigate("/signin");
55+
} catch (error) {
56+
setErr(error?.response?.data?.message || "Password reset failed");
57+
} finally {
58+
setLoading(false);
59+
}
60+
};
61+
62+
return (
63+
<div className="flex w-full items-center justify-center min-h-screen p-4 bg-gray-900">
64+
<div className="bg-gray-800 rounded-xl shadow-lg w-full max-w-md p-8">
65+
<div className="flex items-center gap-4 mb-4">
66+
<IoIosArrowRoundBack
67+
size={30}
68+
className="text-cyan-400 cursor-pointer"
69+
onClick={() => navigate("/login")}
70+
/>
71+
<h1 className="text-2xl font-bold text-center text-cyan-400">Forgot Password</h1>
72+
</div>
73+
74+
{step === 1 && (
75+
<div>
76+
<label className="block text-gray-300 font-medium mb-1">Email</label>
77+
<input
78+
type="email"
79+
className="w-full border border-gray-700 rounded-lg px-3 py-2 mb-4 bg-gray-900 text-gray-100 focus:outline-none"
80+
placeholder="Enter your Email"
81+
value={email}
82+
onChange={(e) => setEmail(e.target.value)}
83+
/>
84+
<button
85+
className="w-full bg-cyan-500 text-white py-2 rounded-lg hover:bg-cyan-600 transition"
86+
onClick={handleSendOtp}
87+
disabled={loading}
88+
>
89+
{loading ? <ClipLoader size={20} color="white" /> : "Send OTP"}
90+
</button>
91+
{err && <p className="text-red-500 text-center mt-2">{err}</p>}
92+
</div>
93+
)}
94+
95+
{step === 2 && (
96+
<div>
97+
<label className="block text-gray-300 font-medium mb-1">OTP</label>
98+
<input
99+
type="text"
100+
className="w-full border border-gray-700 rounded-lg px-3 py-2 mb-4 bg-gray-900 text-gray-100 focus:outline-none"
101+
placeholder="Enter OTP"
102+
value={otp}
103+
onChange={(e) => setOtp(e.target.value)}
104+
/>
105+
<button
106+
className="w-full bg-cyan-500 text-white py-2 rounded-lg hover:bg-cyan-600 transition"
107+
onClick={handleVerifyOtp}
108+
disabled={loading}
109+
>
110+
{loading ? <ClipLoader size={20} color="white" /> : "Verify OTP"}
111+
</button>
112+
{err && <p className="text-red-500 text-center mt-2">{err}</p>}
113+
</div>
114+
)}
115+
116+
{step === 3 && (
117+
<div>
118+
<label className="block text-gray-300 font-medium mb-1">New Password</label>
119+
<input
120+
type="password"
121+
className="w-full border border-gray-700 rounded-lg px-3 py-2 mb-4 bg-gray-900 text-gray-100 focus:outline-none"
122+
placeholder="New Password"
123+
value={newPassword}
124+
onChange={(e) => setNewPassword(e.target.value)}
125+
/>
126+
<label className="block text-gray-300 font-medium mb-1">Confirm Password</label>
127+
<input
128+
type="password"
129+
className="w-full border border-gray-700 rounded-lg px-3 py-2 mb-4 bg-gray-900 text-gray-100 focus:outline-none"
130+
placeholder="Confirm Password"
131+
value={confirmPassword}
132+
onChange={(e) => setConfirmPassword(e.target.value)}
133+
/>
134+
<button
135+
className="w-full bg-cyan-500 text-white py-2 rounded-lg hover:bg-cyan-600 transition"
136+
onClick={handleResetPassword}
137+
disabled={loading}
138+
>
139+
{loading ? <ClipLoader size={20} color="white" /> : "Reset Password"}
140+
</button>
141+
{err && <p className="text-red-500 text-center mt-2">{err}</p>}
142+
</div>
143+
)}
144+
</div>
145+
</div>
146+
);
147+
};
148+
149+
export default ForgotPassword;

frontend/src/pages/Login.jsx

Lines changed: 91 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,107 @@
11
import React, { useState } from "react";
2-
import { useNavigate } from "react-router-dom";
3-
import { toast } from "react-hot-toast";
4-
import axios from "axios";
5-
import { useAuth } from "../context/AuthContext";
2+
import { FaRegEye, FaRegEyeSlash } from "react-icons/fa";
3+
import { useNavigate } from "react-router-dom";
4+
import { ClipLoader } from "react-spinners";
5+
import { loginUser } from "../services/authService";
66

7-
export default function Login() {
7+
const Login = () => {
88
const [email, setEmail] = useState("");
99
const [password, setPassword] = useState("");
10-
const [loading, setLoading] = useState(false);
11-
12-
const { login } = useAuth();
13-
const navigate = useNavigate();
10+
const [showPassword, setShowPassword] = useState(false);
11+
const [err, setErr] = useState("");
12+
const [loading, setLoading] = useState(false);
13+
const navigate = useNavigate();
1414

1515
const handleLogin = async (e) => {
1616
e.preventDefault();
17+
setErr("");
1718
setLoading(true);
18-
const loadingToast = toast.loading("Logging in...");
19-
2019
try {
21-
22-
const { data } = await axios.post(
23-
"/api/v1/user/login",
24-
{ email, password },
25-
{
26-
headers: { "Content-Type": "application/json" },
27-
withCredentials: true,
28-
}
29-
);
30-
31-
32-
toast.dismiss(loadingToast);
33-
34-
if (data.success) {
35-
toast.success(data.message || "Logged in successfully!");
36-
login(data.user);
37-
navigate("/dashboard");
38-
} else {
39-
40-
toast.error(data.message || "Login failed. Please try again.");
41-
}
42-
20+
const result = await loginUser({ email, password });
21+
console.log(result.data);
22+
setLoading(false);
23+
navigate("/"); // Redirect on success
4324
} catch (error) {
44-
45-
console.error("Login error:", error);
46-
toast.dismiss(loadingToast); // Dismiss loading toast
47-
48-
49-
const errorMessage =
50-
error.response?.data?.message || "Login failed. Please check your credentials.";
51-
toast.error(errorMessage);
52-
53-
} finally {
54-
setLoading(false);
25+
setErr(error?.response?.data?.message || error.message || "Login failed.");
26+
setLoading(false);
5527
}
5628
};
5729

5830
return (
59-
<div className="page login">
60-
<h1>Login</h1>
61-
<form onSubmit={handleLogin}>
62-
<input
63-
type="email"
64-
placeholder="Email"
65-
value={email}
66-
onChange={(e) => setEmail(e.target.value)}
67-
required
68-
/>
69-
<input
70-
type="password"
71-
placeholder="Password"
72-
value={password}
73-
onChange={(e) => setPassword(e.target.value)}
74-
required
75-
/>
76-
77-
<button type="submit" disabled={loading}>
78-
{loading ? "Logging in..." : "Login"}
79-
</button>
80-
</form>
31+
<div className="min-h-screen flex items-center justify-center bg-gray-900 p-6">
32+
<div className="bg-gray-800 rounded-2xl shadow-2xl w-full max-w-md p-10 border border-gray-700">
33+
<h1 className="text-4xl font-extrabold mb-4 text-cyan-400 text-center">MailMERN</h1>
34+
<p className="text-gray-400 mb-8 text-center">Sign in to your account</p>
35+
36+
<form onSubmit={handleLogin} className="space-y-6">
37+
<div>
38+
<label htmlFor="email" className="block mb-2 font-medium text-gray-300">Email</label>
39+
<input
40+
type="email"
41+
id="email"
42+
placeholder="Enter your Email"
43+
value={email}
44+
onChange={(e) => setEmail(e.target.value)}
45+
required
46+
className="w-full px-4 py-3 rounded-xl border border-gray-700 bg-gray-900 text-gray-100 focus:outline-none focus:ring-2 focus:ring-cyan-400 transition"
47+
/>
48+
</div>
49+
50+
<div>
51+
<label htmlFor="password" className="block mb-2 font-medium text-gray-300">Password</label>
52+
<div className="relative">
53+
<input
54+
type={showPassword ? "text" : "password"}
55+
id="password"
56+
placeholder="Enter your password"
57+
value={password}
58+
onChange={(e) => setPassword(e.target.value)}
59+
required
60+
className="w-full px-4 py-3 rounded-xl border border-gray-700 bg-gray-900 text-gray-100 focus:outline-none focus:ring-2 focus:ring-cyan-400 pr-12 transition"
61+
/>
62+
<button
63+
type="button"
64+
onClick={() => setShowPassword(prev => !prev)}
65+
className="absolute right-3 top-3 text-gray-400 hover:text-cyan-400 transition"
66+
>
67+
{showPassword ? <FaRegEyeSlash /> : <FaRegEye />}
68+
</button>
69+
</div>
70+
</div>
71+
72+
<div className="text-right">
73+
<button
74+
type="button"
75+
onClick={() => navigate("/forgot-password")}
76+
className="text-cyan-400 font-medium hover:underline transition"
77+
>
78+
Forgot Password?
79+
</button>
80+
</div>
81+
82+
{err && <p className="text-red-500 text-center">{err}</p>}
83+
84+
<button
85+
type="submit"
86+
disabled={loading}
87+
className="w-full flex items-center justify-center gap-2 bg-gradient-to-r from-cyan-500 to-blue-500 text-white font-bold py-3 rounded-full shadow-lg hover:scale-105 transition-transform duration-300"
88+
>
89+
{loading ? <ClipLoader size={20} color="white" /> : "Sign In"}
90+
</button>
91+
</form>
92+
93+
<p className="mt-6 text-center text-gray-300">
94+
Don't have an account?{" "}
95+
<span
96+
onClick={() => navigate("/register")}
97+
className="text-cyan-400 font-semibold cursor-pointer hover:underline"
98+
>
99+
Sign Up
100+
</span>
101+
</p>
102+
</div>
81103
</div>
82104
);
83-
}
105+
};
106+
107+
export default Login;

0 commit comments

Comments
 (0)