176 lines
5.6 KiB
TypeScript
176 lines
5.6 KiB
TypeScript
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;
|