From e6032fe3800df3ce3fa77061a121bc27c141fa95 Mon Sep 17 00:00:00 2001 From: Siddharth Sahai Date: Tue, 12 Aug 2025 15:11:07 +0530 Subject: [PATCH] added pages for forgot password, reset password and updated login page --- src/App.jsx | 4 + src/pages/ForgotPassword.jsx | 185 ++++++++++++++++++++++++++++++ src/pages/Login.jsx | 9 +- src/pages/ResetPassword.jsx | 210 +++++++++++++++++++++++++++++++++++ src/utils/apiConfig.js | 7 +- 5 files changed, 410 insertions(+), 5 deletions(-) create mode 100644 src/pages/ForgotPassword.jsx create mode 100644 src/pages/ResetPassword.jsx diff --git a/src/App.jsx b/src/App.jsx index 799af4a..670ea31 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -16,6 +16,8 @@ import NewFeature from './pages/NewFeature.jsx'; import BlogList from './components/ui/BlogSection.jsx'; import BlogDetail from './components/ui/BlogDetail.jsx'; import AddBlog from './components/ui/AddBlog.jsx'; +import ForgotPassword from './pages/ForgotPassword.jsx'; +import ResetPassword from './pages/ResetPassword.jsx'; const App = () => { return ( <> @@ -30,6 +32,8 @@ const App = () => { } /> } /> } /> + } /> + } /> } /> } /> } /> diff --git a/src/pages/ForgotPassword.jsx b/src/pages/ForgotPassword.jsx new file mode 100644 index 0000000..29abaa0 --- /dev/null +++ b/src/pages/ForgotPassword.jsx @@ -0,0 +1,185 @@ +import React, { useState } from 'react'; +import { Mail, ArrowLeft, KeyRound } from 'lucide-react'; +import { Link, useNavigate } from 'react-router-dom'; +import axios from 'axios'; +import { createApiUrl, API_ENDPOINTS } from '../utils/apiConfig'; +import { + AuthLayout, + AuthLogo, + AuthHeader, + InputField, + SubmitButton +} from '../components'; + +const ForgotPassword = () => { + const [emailId, setEmail] = useState(''); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(''); + const [error, setError] = useState(''); + const [otp, setOtp] = useState(''); + const [otpSent, setOtpSent] = useState(false); + const [verifying, setVerifying] = useState(false); + const navigate = useNavigate(); + + const handleSubmit = async (e) => { + e.preventDefault(); + + if (!emailId.trim()) { + setError('Please enter your email address'); + return; + } + + setLoading(true); + setError(''); + setMessage(''); + + try { + const response = await axios.post( + createApiUrl(API_ENDPOINTS.FORGOT_PASSWORD), + { emailId: emailId.trim() } + ); + setMessage(response.data.message); + setOtpSent(true); + } catch (err) { + console.error('Forgot password error:', err); + setError(err.response?.data?.message || 'Something went wrong. Please try again.'); + } finally { + setLoading(false); + } + }; + + const handleVerifyOtp = async (e) => { + e.preventDefault(); + if (!otp.trim()) { + setError('Please enter the OTP sent to your email'); + return; + } + setError(''); + setVerifying(true); + try { + const response = await axios.post( + createApiUrl(API_ENDPOINTS.VERIFY_OTP), + { emailId: emailId.trim(), otp: otp.trim() } + ); + const token = response.data?.resetToken; + if (token) { + navigate(`/reset-password/${token}`); + } else { + setError('Verification failed. Please try again.'); + } + } catch (err) { + console.error('Verify OTP error:', err); + setError(err.response?.data?.message || 'Invalid OTP. Please try again.'); + } finally { + setVerifying(false); + } + }; + + return ( + + + +
+ + + Back to Login + +
+ + + + {!otpSent ? ( +
+ {error && ( +
+ {error} +
+ )} + + {message && ( +
+ {message} +
+ )} + + setEmail(e.target.value)} + type="email" + placeholder="Enter your email address" + icon={Mail} + required + /> + + + + ) : ( +
+ {error && ( +
+ {error} +
+ )} + + {message && ( +
+ {message} +
+ )} + + setOtp(e.target.value)} + type="text" + placeholder="Enter 6-digit code" + icon={KeyRound} + required + /> + + + + )} + +
+ Remember your password?{' '} + + Log in + +
+ +
+ {otpSent ? ( + <> +

Enter the code we sent to your email. It expires in 10 minutes.

+

Didn't receive it? Check spam or request a new code.

+ + ) : ( + <> +

We will send a 6-digit verification code to your email.

+

The code expires in 10 minutes for security.

+ + )} +
+
+ ); +}; + +export default ForgotPassword; \ No newline at end of file diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx index 8078651..f92c734 100644 --- a/src/pages/Login.jsx +++ b/src/pages/Login.jsx @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import { Mail, Lock } from 'lucide-react'; import { FaEye, FaEyeSlash } from 'react-icons/fa'; +import { Link } from 'react-router-dom'; import { AuthLayout, AuthLogo, @@ -93,9 +94,9 @@ const Login = () => { Remember me - + Forgot password? - + @@ -103,9 +104,9 @@ const Login = () => {
Don't have an account?{' '} - + Sign up - +
); diff --git a/src/pages/ResetPassword.jsx b/src/pages/ResetPassword.jsx new file mode 100644 index 0000000..2952bab --- /dev/null +++ b/src/pages/ResetPassword.jsx @@ -0,0 +1,210 @@ +import React, { useState, useEffect } from 'react'; +import { Lock, Eye, EyeOff, CheckCircle } from 'lucide-react'; +import { useParams, useNavigate, Link } from 'react-router-dom'; +import axios from 'axios'; +import { createApiUrl, API_ENDPOINTS } from '../utils/apiConfig'; +import { + AuthLayout, + AuthLogo, + AuthHeader, + InputField, + SubmitButton +} from '../components'; + +const ResetPassword = () => { + const { token } = useParams(); + const navigate = useNavigate(); + + const [newPassword, setNewPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [showPassword, setShowPassword] = useState(false); + const [showConfirm, setShowConfirm] = useState(false); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const [success, setSuccess] = useState(false); + + // Password strength indicators + const [passwordChecks, setPasswordChecks] = useState({ + length: false, + uppercase: false, + lowercase: false, + number: false, + special: false + }); + + useEffect(() => { + if (!token) { + setError('Invalid reset link'); + return; + } + + // Check password strength + const checks = { + length: newPassword.length >= 8, + uppercase: /[A-Z]/.test(newPassword), + lowercase: /[a-z]/.test(newPassword), + number: /\d/.test(newPassword), + special: /[!@#$%^&*(),.?":{}|<>]/.test(newPassword) + }; + setPasswordChecks(checks); + }, [newPassword, token]); + + const handleSubmit = async (e) => { + e.preventDefault(); + + if (newPassword !== confirmPassword) { + setError('Passwords do not match'); + return; + } + + if (!Object.values(passwordChecks).every(Boolean)) { + setError('Please ensure your password meets all requirements'); + return; + } + + setLoading(true); + setError(''); + + try { + const response = await axios.post( + createApiUrl(API_ENDPOINTS.RESET_PASSWORD(token)), + { newPassword } + ); + + setSuccess(true); + setNewPassword(''); + setConfirmPassword(''); + + // Redirect to login after 3 seconds + setTimeout(() => { + navigate('/login'); + }, 3000); + + } catch (err) { + console.error('Reset password error:', err); + setError(err.response?.data?.message || 'Something went wrong. Please try again.'); + } finally { + setLoading(false); + } + }; + + if (success) { + return ( + + +
+ +

Password Reset Successful!

+

+ Your password has been updated. You will be redirected to the login page shortly. +

+ + Go to Login Now + +
+
+ ); + } + + return ( + + + + + +
+ {error && ( +
+ {error} +
+ )} + +
+
+ setNewPassword(e.target.value)} + type={showPassword ? 'text' : 'password'} + placeholder="New Password" + icon={Lock} + required + /> + +
+ +
+ setConfirmPassword(e.target.value)} + type={showConfirm ? 'text' : 'password'} + placeholder="Confirm New Password" + icon={Lock} + required + /> + +
+
+ + {/* Password Strength Indicator */} +
+

Password Requirements:

+
+
+ + At least 8 characters +
+
+ + One uppercase letter +
+
+ + One lowercase letter +
+
+ + One number +
+
+ + One special character +
+
+
+ + + + +
+ Remember your password?{' '} + + Log in + +
+
+ ); +}; + +export default ResetPassword; \ No newline at end of file diff --git a/src/utils/apiConfig.js b/src/utils/apiConfig.js index 71f1646..fcabd13 100644 --- a/src/utils/apiConfig.js +++ b/src/utils/apiConfig.js @@ -1,5 +1,5 @@ // API configuration and base URL -export const BASE_URL = import.meta.env.VITE_BASE_URL || 'http://localhost:7777'; +export const BASE_URL = import.meta.env.VITE_BASE_URL || 'http://localhost:3000'; // API endpoints export const API_ENDPOINTS = { @@ -22,6 +22,11 @@ export const API_ENDPOINTS = { // User endpoints USER_CONNECTIONS: '/user/connections', USER_REQUESTS_RECEIVED: '/user/requests/received', + + // Password reset endpoints + FORGOT_PASSWORD: '/auth/password/forgot', + VERIFY_OTP: '/auth/password/verify-otp', + RESET_PASSWORD: (token) => `/auth/password/reset/${token}`, }; // Helper function to create full API URL