password reset done

This commit is contained in:
ATUL GUNJAL 2025-05-24 12:26:04 +05:30
parent 529b928e07
commit ab51b412c5
3 changed files with 272 additions and 38 deletions

View File

@ -7,12 +7,14 @@ import Link from "next/link";
export default function AuthPage() { export default function AuthPage() {
const router = useRouter(); const router = useRouter();
const { user, loading: authLoading, isAuthenticated, login, register } = useAuth(); const { user, loading: authLoading, isAuthenticated, login, register,sendPasswordResetEmail, resetPassword } = useAuth();
const [isLoginForm, setIsLoginForm] = useState(true); const [isLoginForm, setIsLoginForm] = useState(true);
const [darkMode, setDarkMode] = useState(false); const [darkMode, setDarkMode] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
const [showResetForm, setShowResetForm] = useState(false);
const [resetEmail, setResetEmail] = useState("");
const [resetSent, setResetSent] = useState(false);
// Form states // Form states
const [loginData, setLoginData] = useState({ const [loginData, setLoginData] = useState({
email: "", email: "",
@ -33,6 +35,26 @@ export default function AuthPage() {
general: "", general: "",
}); });
// -----------reset password------------------------
const handleForgotPassword = async (e) => {
e.preventDefault();
setLoading(true);
setErrors({ ...errors, general: "" });
try {
await sendPasswordResetEmail(resetEmail);
setResetSent(true);
} catch (error) {
console.error("Reset error:", error);
setErrors({
...errors,
general: "Failed to send reset email. Please try again."
});
} finally {
setLoading(false);
}
};
// ------------------------------------------
// Redirect if already authenticated // Redirect if already authenticated
useEffect(() => { useEffect(() => {
if (!authLoading && isAuthenticated) { if (!authLoading && isAuthenticated) {
@ -153,27 +175,74 @@ export default function AuthPage() {
} }
return ( return (
<div className={`min-h-auto ${darkMode ? "dark bg-gray-900" : "bg-white"}`}> <div className={`min-h-auto ${darkMode ? "dark bg-gray-900" : "bg-white"}`}>
<div className="flex flex-col items-center justify-center p-6"> <div className="flex flex-col items-center justify-center p-6">
<div className="w-full max-w-md"> <div className="w-full max-w-md">
<div className="mb-8"> <div className="mb-8">
<h1 className="text-2xl font-bold text-gray-800 dark:text-white/90"> <h1 className="text-2xl font-bold text-gray-800 dark:text-white/90">
{isLoginForm ? "Sign In" : "Sign Up"} {showResetForm ? "Reset Password" : isLoginForm ? "Sign In" : "Sign Up"}
</h1> </h1>
<p className="text-sm text-gray-500 dark:text-gray-400"> <p className="text-sm text-gray-500 dark:text-gray-400">
{isLoginForm {showResetForm
? resetSent
? "Check your email for reset instructions"
: "Enter your email to receive a reset link"
: isLoginForm
? "Enter your email and password to sign in" ? "Enter your email and password to sign in"
: "Create your account to get started"} : "Create your account to get started"}
</p> </p>
</div>
{errors.general && (
<div className="mb-4 p-3 text-sm text-red-600 bg-red-50 rounded-lg dark:bg-red-900/20 dark:text-red-300">
{errors.general}
</div> </div>
)}
{errors.general && ( {showResetForm ? (
<div className="mb-4 p-3 text-sm text-red-600 bg-red-50 rounded-lg dark:bg-red-900/20 dark:text-red-300"> // Password Reset Form
{errors.general} <div className="space-y-4">
</div> {resetSent ? (
)} <div className="p-4 bg-green-50 text-green-800 rounded-lg dark:bg-green-900/20 dark:text-green-300">
We've sent a password reset link to your email. Please check your inbox.
{isLoginForm ? ( </div>
) : (
<form onSubmit={handleForgotPassword} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Email Address*
</label>
<input
type="email"
value={resetEmail}
onChange={(e) => setResetEmail(e.target.value)}
className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-700 dark:text-white"
placeholder="your@email.com"
required
/>
</div>
<button
type="submit"
className="w-full py-2 px-4 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition"
disabled={loading}
>
{loading ? "Sending..." : "Send Reset Link"}
</button>
</form>
)}
<button
onClick={() => {
setShowResetForm(false);
setResetSent(false);
setResetEmail("");
setErrors({ general: "" });
}}
className="w-full text-center text-sm text-blue-600 hover:underline dark:text-blue-400"
>
Back to {isLoginForm ? "login" : "sign up"}
</button>
</div>
) :isLoginForm ? (
// Login Form // Login Form
<form onSubmit={handleLogin} className="space-y-4"> <form onSubmit={handleLogin} className="space-y-4">
<div> <div>
@ -354,6 +423,7 @@ export default function AuthPage() {
</form> </form>
)} )}
{!showResetForm && (
<div className="mt-6 text-center"> <div className="mt-6 text-center">
<p className="text-sm text-gray-600 dark:text-gray-400"> <p className="text-sm text-gray-600 dark:text-gray-400">
{isLoginForm ? "Don't have an account?" : "Already have an account?"} {isLoginForm ? "Don't have an account?" : "Already have an account?"}
@ -367,26 +437,39 @@ export default function AuthPage() {
{isLoginForm ? "Sign up" : "Sign in"} {isLoginForm ? "Sign up" : "Sign in"}
</button> </button>
</p> </p>
{/* {isLoginForm && (
<button
type="button"
onClick={() => {
setShowResetForm(true);
setIsLoginForm(false);
}}
className="mt-2 text-sm text-blue-600 hover:underline dark:text-blue-400"
>
Forgot password?
</button>
)} */}
</div> </div>
</div>
</div>
{/* Dark mode toggle */}
<button
onClick={() => setDarkMode(!darkMode)}
className="fixed bottom-6 right-6 p-3 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200"
aria-label="Toggle dark mode"
>
{darkMode ? (
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" clipRule="evenodd" />
</svg>
) : (
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
</svg>
)} )}
</button> </div>
</div> </div>
);
{/* Dark mode toggle */}
<button
onClick={() => setDarkMode(!darkMode)}
className="fixed bottom-6 right-6 p-3 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200"
aria-label="Toggle dark mode"
>
{darkMode ? (
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" clipRule="evenodd" />
</svg>
) : (
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
</svg>
)}
</button>
</div>
);
} }

View File

@ -55,7 +55,40 @@ export const AuthProvider = ({ children }) => {
throw error; throw error;
} }
}; };
// --------password reset------------------------
const sendPasswordResetEmail = async (email) => {
try {
await account.createRecovery(
email,
`${window.location.origin}/reset-password` // Your reset password URL
);
return true;
} catch (error) {
console.error("Password reset error:", error);
throw error;
}
};
const resetPassword = async (userId, secret, newPassword, confirmPassword) => {
if (newPassword !== confirmPassword) {
throw new Error("Passwords don't match");
}
try {
await account.updateRecovery(
userId,
secret,
newPassword,
confirmPassword
);
return true;
} catch (error) {
console.error("Password update error:", error);
throw error;
}
};
// -----------------------------------------------
return ( return (
<AuthContext.Provider <AuthContext.Provider
value={{ value={{
@ -65,6 +98,8 @@ export const AuthProvider = ({ children }) => {
login, login,
register, register,
logout, logout,
sendPasswordResetEmail,
resetPassword,
}} }}
> >
{children} {children}

View File

@ -0,0 +1,116 @@
'use client';
import { useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { useAuth } from '../../context/AuthContext';
export default function ResetPassword() {
const router = useRouter();
const searchParams = useSearchParams();
const { resetPassword } = useAuth();
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [success, setSuccess] = useState(false);
// Get userId and secret from URL
const userId = searchParams.get('userId');
const secret = searchParams.get('secret');
const [formData, setFormData] = useState({
password: '',
confirmPassword: ''
});
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setError('');
try {
await resetPassword(
userId,
secret,
formData.password,
formData.confirmPassword
);
setSuccess(true);
setTimeout(() => router.push('/'), 3000);
} catch (err) {
setError(err.message || 'Password reset failed');
} finally {
setLoading(false);
}
};
const handleChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value
});
};
if (!userId || !secret) {
return (
<div className="text-center py-10">
<h2 className="text-xl font-bold">Invalid reset link</h2>
<p>The password reset link is invalid or has expired.</p>
</div>
);
}
if (success) {
return (
<div className="text-center py-10">
<h2 className="text-xl font-bold">Password reset successful!</h2>
<p>You will be redirected to the login page shortly.</p>
</div>
);
}
return (
<div className="max-w-md mx-auto p-6">
<h1 className="text-2xl font-bold mb-6">Reset Password</h1>
{error && (
<div className="mb-4 p-3 text-sm text-red-600 bg-red-50 rounded-lg">
{error}
</div>
)}
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block mb-1">New Password</label>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
className="w-full p-2 border rounded"
required
minLength="8"
/>
</div>
<div>
<label className="block mb-1">Confirm Password</label>
<input
type="password"
name="confirmPassword"
value={formData.confirmPassword}
onChange={handleChange}
className="w-full p-2 border rounded"
required
minLength="8"
/>
</div>
<button
type="submit"
className="w-full py-2 px-4 bg-blue-600 text-white rounded hover:bg-blue-700"
disabled={loading}
>
{loading ? 'Resetting...' : 'Reset Password'}
</button>
</form>
</div>
);
}