password reset done
This commit is contained in:
parent
529b928e07
commit
ab51b412c5
@ -7,12 +7,14 @@ import Link from "next/link";
|
||||
|
||||
export default function AuthPage() {
|
||||
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 [darkMode, setDarkMode] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
const [showResetForm, setShowResetForm] = useState(false);
|
||||
const [resetEmail, setResetEmail] = useState("");
|
||||
const [resetSent, setResetSent] = useState(false);
|
||||
// Form states
|
||||
const [loginData, setLoginData] = useState({
|
||||
email: "",
|
||||
@ -33,6 +35,26 @@ export default function AuthPage() {
|
||||
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
|
||||
useEffect(() => {
|
||||
if (!authLoading && isAuthenticated) {
|
||||
@ -153,27 +175,74 @@ export default function AuthPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<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="w-full max-w-md">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-2xl font-bold text-gray-800 dark:text-white/90">
|
||||
{isLoginForm ? "Sign In" : "Sign Up"}
|
||||
</h1>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{isLoginForm
|
||||
<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="w-full max-w-md">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-2xl font-bold text-gray-800 dark:text-white/90">
|
||||
{showResetForm ? "Reset Password" : isLoginForm ? "Sign In" : "Sign Up"}
|
||||
</h1>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{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"
|
||||
: "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>
|
||||
)}
|
||||
|
||||
{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>
|
||||
)}
|
||||
|
||||
{isLoginForm ? (
|
||||
{showResetForm ? (
|
||||
// Password Reset Form
|
||||
<div className="space-y-4">
|
||||
{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.
|
||||
</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
|
||||
<form onSubmit={handleLogin} className="space-y-4">
|
||||
<div>
|
||||
@ -354,6 +423,7 @@ export default function AuthPage() {
|
||||
</form>
|
||||
)}
|
||||
|
||||
{!showResetForm && (
|
||||
<div className="mt-6 text-center">
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
{isLoginForm ? "Don't have an account?" : "Already have an account?"}
|
||||
@ -367,26 +437,39 @@ export default function AuthPage() {
|
||||
{isLoginForm ? "Sign up" : "Sign in"}
|
||||
</button>
|
||||
</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>
|
||||
|
||||
{/* 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>
|
||||
);
|
||||
|
||||
{/* 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>
|
||||
);
|
||||
}
|
@ -55,7 +55,40 @@ export const AuthProvider = ({ children }) => {
|
||||
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 (
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
@ -65,6 +98,8 @@ export const AuthProvider = ({ children }) => {
|
||||
login,
|
||||
register,
|
||||
logout,
|
||||
sendPasswordResetEmail,
|
||||
resetPassword,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
116
src/app/pages/reset-password/page.js
Normal file
116
src/app/pages/reset-password/page.js
Normal 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>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user