password reset

This commit is contained in:
jackiettran
2025-10-10 22:54:45 -04:00
parent 462dbf6b7a
commit b9e6cfc54d
15 changed files with 1976 additions and 178 deletions

View File

@@ -0,0 +1,175 @@
import React, { useState } from "react";
import { authAPI } from "../services/api";
interface ForgotPasswordModalProps {
show: boolean;
onHide: () => void;
onBackToLogin?: () => void;
}
const ForgotPasswordModal: React.FC<ForgotPasswordModalProps> = ({
show,
onHide,
onBackToLogin,
}) => {
const [email, setEmail] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [success, setSuccess] = useState(false);
const resetModal = () => {
setEmail("");
setError("");
setSuccess(false);
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError("");
try {
await authAPI.forgotPassword(email);
setSuccess(true);
} catch (err: any) {
console.error('Forgot password error:', err);
const errorData = err.response?.data;
// Check for rate limiting
if (err.response?.status === 429) {
const retryAfter = errorData?.retryAfter;
if (retryAfter) {
const minutes = Math.ceil(retryAfter / 60);
setError(`Too many password reset requests. Please try again in ${minutes} minute${minutes > 1 ? 's' : ''}.`);
} else {
setError('Too many password reset requests. Please try again later.');
}
} else if (errorData?.details) {
// Validation errors
const validationErrors = errorData.details.map((d: any) => d.message).join(', ');
setError(validationErrors);
} else {
setError(errorData?.error || "Failed to send reset email. Please try again.");
}
} finally {
setLoading(false);
}
};
if (!show) return null;
return (
<>
<div
className="modal show d-block"
tabIndex={-1}
style={{ backgroundColor: "rgba(0,0,0,0.5)" }}
>
<div className="modal-dialog modal-dialog-centered">
<div className="modal-content">
<div className="modal-header border-0 pb-0">
<button
type="button"
className="btn-close"
onClick={() => {
resetModal();
onHide();
}}
></button>
</div>
<div className="modal-body px-4 pb-4">
{success ? (
<>
<div className="text-center">
<i
className="bi bi-envelope-check text-success"
style={{ fontSize: "3rem" }}
></i>
<h4 className="mt-3">Check Your Email</h4>
<p className="text-muted">
If an account exists with that email address, you will
receive password reset instructions shortly.
</p>
<p className="text-muted small">
Please check your spam folder if you don't see the email
within a few minutes.
</p>
<button
type="button"
className="btn btn-primary mt-3"
onClick={() => {
resetModal();
onHide();
}}
>
Close
</button>
</div>
</>
) : (
<>
<h4 className="text-center mb-2">Forgot Password?</h4>
<p className="text-center text-muted mb-4">
Enter your email address and we'll send you instructions to
reset your password.
</p>
{error && (
<div className="alert alert-danger" role="alert">
{error}
</div>
)}
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label className="form-label">Email Address</label>
<input
type="email"
className="form-control"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="your@email.com"
required
/>
</div>
<button
type="submit"
className="btn btn-primary w-100 py-3"
disabled={loading || !email}
>
{loading ? "Sending..." : "Send Reset Instructions"}
</button>
</form>
<div className="text-center mt-3">
<small className="text-muted">
Remember your password?{" "}
<a
href="#"
className="text-primary text-decoration-none"
onClick={(e) => {
e.preventDefault();
resetModal();
if (onBackToLogin) {
onBackToLogin();
} else {
onHide();
}
}}
>
Back to Login
</a>
</small>
</div>
</>
)}
</div>
</div>
</div>
</div>
</>
);
};
export default ForgotPasswordModal;