Skip to content

Commit 15c036e

Browse files
feat: merge GSSoC PR #2034 by @Aryanbansal-05 (conflict-free)
2 parents b2a10f5 + d08b8c2 commit 15c036e

4 files changed

Lines changed: 82 additions & 70 deletions

File tree

Frontend/src/pages/ForgotPassword.jsx

Lines changed: 66 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@ import React, { useState, useEffect } from "react";
22
import { Link } from "react-router-dom";
33
import { supabase } from "../lib/supabaseClient";
44
import { BrainCircuit, Mail, ArrowLeft, Loader2, CheckCircle2, Lock, KeyRound, AlertCircle } from "lucide-react";
5-
5+
66
import { motion, AnimatePresence } from "framer-motion";
7+
import validatePassword from "@/utils/validatePassword";
78

89
function ForgotPassword() {
9-
const [step, setStep] = useState(1);
10-
const [email, setEmail] = useState('');
11-
const [otp, setOtp] = useState('');
12-
const [newPassword, setNewPassword] = useState('');
13-
const [loading, setLoading] = useState(false);
14-
const [message, setMessage] = useState('');
15-
const [error, setError] = useState('');
16-
const [timeLeft, setTimeLeft] = useState(900);
17-
const [timerExpired, setTimerExpired] = useState(false);
10+
const [step, setStep] = useState(1);
11+
const [email, setEmail] = useState('');
12+
const [otp, setOtp] = useState('');
13+
const [newPassword, setNewPassword] = useState('');
14+
const [loading, setLoading] = useState(false);
15+
const [message, setMessage] = useState('');
16+
const [error, setError] = useState('');
17+
const [timeLeft, setTimeLeft] = useState(900);
18+
const [timerExpired, setTimerExpired] = useState(false);
1819

1920
useEffect(() => {
2021
if (step !== 2) return;
@@ -46,79 +47,80 @@ function ForgotPassword() {
4647
return;
4748
}
4849
return prev - 1;
49-
});
50-
}, 1000);
51-
return () => clearInterval(timer);
50+
});
51+
}, 1000);
52+
return () => clearInterval(timer);
5253
}, [step]);
5354

54-
const formatTime = (seconds) => {
55+
const formatTime = (seconds) => {
5556
const mins = Math.floor(seconds / 60);
5657
const secs = seconds % 60;
5758
return `${mins}:${secs.toString().padStart(2, '0')}`;
58-
};
59+
};
5960

60-
try {
61-
const { error } = await supabase.auth.resetPasswordForEmail(email);
62-
if (error) throw error;
63-
setMessage("Check your email for the 6-digit recovery code!");
64-
setStep(2);
65-
} catch (err) {
66-
console.error("Password reset error:", err);
67-
setError(err.message || "An error occurred. Please try again.");
68-
} finally {
69-
setLoading(false);
70-
}
61+
try {
62+
const { error } = await supabase.auth.resetPasswordForEmail(email);
63+
if (error) throw error;
64+
setMessage("Check your email for the 6-digit recovery code!");
65+
setStep(2);
66+
} catch (err) {
67+
console.error("Password reset error:", err);
68+
setError(err.message || "An error occurred. Please try again.");
69+
} finally {
70+
setLoading(false);
71+
}
7172
};
7273

73-
if (error) throw error;
74+
if (error) throw error;
7475

75-
setMessage('Check your email for the 6-digit recovery code!');
76-
setStep(2);
76+
setMessage('Check your email for the 6-digit recovery code!');
77+
setStep(2);
7778
} catch (err) {
78-
console.error('Password reset error:', err);
79-
setError(err.message || 'An error occurred. Please try again.');
80-
} finally {
81-
setLoading(false);
82-
}
79+
console.error('Password reset error:', err);
80+
setError(err.message || 'An error occurred. Please try again.');
81+
} finally {
82+
setLoading(false);
83+
}
8384
};
8485

85-
const handleVerifyOtp = async (e) => {
86+
const handleVerifyOtp = async (e) => {
8687
e.preventDefault();
8788
if (!otp || otp.length !== 6) {
88-
setError('Please enter the 6-digit code');
89-
return;
89+
setError('Please enter the 6-digit code');
90+
return;
9091
}
9192
if (timerExpired) {
92-
setError('Your code has expired. Please request a new one.');
93-
return;
93+
setError('Your code has expired. Please request a new one.');
94+
return;
9495
}
9596

9697
setLoading(true);
9798
setError('');
9899

99100
try {
100-
const { error } = await supabase.auth.verifyOtp({
101-
email,
102-
token: otp,
103-
type: 'recovery',
104-
});
105-
106-
if (error) throw error;
107-
setStep(3);
108-
setMessage('Code verified. Please enter your new password.');
101+
const { error } = await supabase.auth.verifyOtp({
102+
email,
103+
token: otp,
104+
type: 'recovery',
105+
});
106+
107+
if (error) throw error;
108+
setStep(3);
109+
setMessage('Code verified. Please enter your new password.');
109110
} catch (err) {
110-
console.error('OTP verification error:', err);
111-
setError('Invalid or expired code. Please check your email and try again.');
111+
console.error('OTP verification error:', err);
112+
setError('Invalid or expired code. Please check your email and try again.');
112113
} finally {
113-
setLoading(false);
114+
setLoading(false);
114115
}
115-
};
116+
};
116117

117-
const handleUpdatePassword = async (e) => {
118+
const handleUpdatePassword = async (e) => {
118119
e.preventDefault();
119-
if (!newPassword || newPassword.length < 6) {
120-
setError('Password must be at least 6 characters long.');
121-
return;
120+
const passwordError = validatePassword(newPassword);
121+
if (passwordError) {
122+
setError(passwordError);
123+
return;
122124
}
123125

124126
setLoading(true);
@@ -129,9 +131,9 @@ function ForgotPassword() {
129131
{/* Background Patterns */}
130132
<div className="absolute top-0 left-0 w-full h-full opacity-[0.03] pointer-events-none" style={{ backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E")` }}></div>
131133
<div className="absolute top-0 left-0 w-[500px] h-[500px] bg-emerald-500/10 rounded-full blur-[120px] -translate-x-1/2 -translate-y-1/2 pointer-events-none"></div>
132-
134+
133135
<div className="w-full max-w-md relative z-10 flex flex-col items-center">
134-
136+
135137
{/* Back Button - Expose beautifully at the top */}
136138
<Link
137139
to="/login"
@@ -151,7 +153,7 @@ function ForgotPassword() {
151153
</Link>
152154
</div>
153155

154-
<motion.div
156+
<motion.div
155157
initial={{ opacity: 0, y: 20 }}
156158
animate={{ opacity: 1, y: 0 }}
157159
className="bg-white dark:bg-[#1a2e24] border border-white/50 dark:border-[#2a4034] shadow-[0_20px_50px_rgba(0,0,0,0.08)] dark:shadow-[0_20px_50px_rgba(0,0,0,0.4)] rounded-[2.5rem] p-6 sm:p-10 relative overflow-hidden w-full"
@@ -169,7 +171,7 @@ function ForgotPassword() {
169171

170172
<AnimatePresence mode="wait">
171173
{step === 4 ? (
172-
<motion.div
174+
<motion.div
173175
key="success"
174176
initial={{ opacity: 0, scale: 0.95 }}
175177
animate={{ opacity: 1, scale: 1 }}
@@ -192,7 +194,7 @@ function ForgotPassword() {
192194
) : (
193195
<div className="space-y-6">
194196
{error && (
195-
<motion.div
197+
<motion.div
196198
initial={{ opacity: 0, height: 0 }}
197199
animate={{ opacity: 1, height: 'auto' }}
198200
className="bg-red-50 dark:bg-red-950/20 border border-red-100 dark:border-red-900/30 text-red-600 dark:text-red-400 px-5 py-4 rounded-2xl text-sm font-semibold flex items-start gap-3"
@@ -258,9 +260,8 @@ function ForgotPassword() {
258260
autoFocus
259261
/>
260262
</div>
261-
<p className={`text-center text-xs font-bold mt-1 ${
262-
timerExpired ? 'text-red-500' : timeLeft < 60 ? 'text-orange-500' : 'text-slate-400'
263-
}`}>
263+
<p className={`text-center text-xs font-bold mt-1 ${timerExpired ? 'text-red-500' : timeLeft < 60 ? 'text-orange-500' : 'text-slate-400'
264+
}`}>
264265
{timerExpired ? '⚠ Code expired — request a new one below' : `Expires in ${formatTime(timeLeft)}`}
265266
</p>
266267
</div>
@@ -310,7 +311,7 @@ function ForgotPassword() {
310311

311312
<button
312313
type="submit"
313-
disabled={loading || newPassword.length < 6}
314+
disabled={loading || newPassword.length < 8}
314315
className="w-full rounded-2xl py-4 font-bold transition-all flex items-center justify-center gap-2 text-white bg-gradient-to-r from-emerald-600 to-emerald-500 hover:from-emerald-700 hover:to-emerald-600 shadow-lg"
315316
onMouseEnter={(e) => e.currentTarget.style.transform = 'translateY(-2px)'}
316317
onMouseLeave={(e) => e.currentTarget.style.transform = 'translateY(0)'}

Frontend/src/pages/ResetPassword.jsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useNavigate, Link } from "react-router-dom";
33
import { motion, AnimatePresence } from "framer-motion";
44
import { supabase } from "../lib/supabaseClient";
55
import { BrainCircuit, Lock, Eye, EyeOff, Loader2, CheckCircle2, ArrowLeft } from "lucide-react";
6+
import validatePassword from "@/utils/validatePassword";
67

78
function ResetPassword() {
89
const [password, setPassword] = useState('');
@@ -25,10 +26,11 @@ function ResetPassword() {
2526

2627
const handleSubmit = async (e) => {
2728
e.preventDefault();
28-
if (password.length < 8) {
29-
setError("Password must be at least 8 characters long");
30-
return;
31-
}
29+
const passwordError = validatePassword(password);
30+
if (passwordError) {
31+
setError(passwordError);
32+
return;
33+
}
3234
if (password !== confirmPassword) {
3335
setError("Passwords do not match");
3436
return;

Frontend/src/pages/Signup.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ function Signup() {
2727
const dropdownRef = useRef(null);
2828
const navigate = useNavigate();
2929
const { signup, loginWithGoogle, loading, user, profile } = useAuthStore();
30-
const passwordRules = { minLength: 6 };
30+
const passwordRules = { minLength: 8, requireUppercase: true, requireNumber: true };
3131
const passwordChecks = getPasswordValidation(password, passwordRules);
3232
const passwordWarning = getPasswordValidationMessage(passwordChecks, passwordRules);
3333
const confirmPasswordWarning = confirmPassword && password !== confirmPassword ? "Passwords do not match." : "";
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const validatePassword = (pw) => {
2+
if (!pw || pw.length < 8) return "Password must be at least 8 characters long.";
3+
if (!/[a-z]/.test(pw)) return "Password must contain at least one lowercase letter.";
4+
if (!/[A-Z]/.test(pw)) return "Password must contain at least one uppercase letter.";
5+
if (!/[0-9]/.test(pw)) return "Password must contain at least one number.";
6+
return null;
7+
};
8+
9+
export default validatePassword;

0 commit comments

Comments
 (0)