All files / src/app/forgot-password-page ForgotPasswordPage.jsx

100% Statements 23/23
100% Branches 10/10
100% Functions 5/5
100% Lines 23/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142                      1x 38x 38x 38x 38x 38x   38x 11x 11x   11x 2x 2x     9x 9x       9x     5x   4x 4x   9x       38x                                                         1x                                                               8x                                           1x                            
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import axios from "axios";
import CircularProgress from "@mui/material/CircularProgress";
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import MVP_LOGO from "@/assets/logo/mvp_logo2.png";
import { API_ENDPOINTS } from "@/api/endpoints";
import "./ForgotPasswordPage.scss";
 
export const ForgotPasswordPage = () => {
    const navigate = useNavigate();
    const [email, setEmail] = useState("");
    const [isSubmitting, setIsSubmitting] = useState(false);
    const [error, setError] = useState(null);
    const [sent, setSent] = useState(false);
 
    const handleSubmit = async (e) => {
        e.preventDefault();
        setError(null);
 
        if (!email.trim()) {
            setError("Please enter your email address.");
            return;
        }
 
        setIsSubmitting(true);
        try {
            // Route through the backend (which calls Auth0 server-side) so the API
            // bearer token is never sent to the Auth0 domain, and the backend can
            // rate-limit and return uniform (enumeration-resistant) responses.
            await axios.post(API_ENDPOINTS.auth.forgotPassword, {
                email: email.trim().toLowerCase(),
            });
            setSent(true);
        } catch (err) {
            const detail = err?.response?.data?.detail;
            setError(detail || "Something went wrong. Please try again.");
        } finally {
            setIsSubmitting(false);
        }
    };
 
    return (
        <div id="forgot-password-page" className="forgot-password-page">
            <div className="fp-background">
                <div className="gradient-overlay" />
            </div>
 
            <div id="fp-container" className="fp-container">
                <div id="fp-logo-section" className="fp-logo-section">
                    <img id="fp-logo" src={MVP_LOGO} alt="EruditionTX" className="fp-logo" />
                    <span id="fp-logo-text" className="fp-logo-text">
                        EruditionTX
                    </span>
                </div>
 
                <div id="fp-box" className="fp-card">
                    {sent ? (
                        <div id="fp-success" className="fp-success">
                            <CheckCircleOutlineIcon className="fp-success-icon" />
                            <h1 id="fp-success-title" className="fp-title">
                                Check your inbox
                            </h1>
                            <p id="fp-success-body" className="fp-success-body">
                                If <strong>{email}</strong> is associated with an account, you will receive a password
                                reset link shortly.
                            </p>
                            <button
                                id="btn-back-to-login"
                                type="button"
                                className="fp-submit"
                                onClick={() => navigate("/login")}
                            >
                                Back to log in
                            </button>
                        </div>
                    ) : (
                        <>
                            <div id="fp-header" className="fp-header">
                                <h1 id="fp-title" className="fp-title">
                                    Forgot password?
                                </h1>
                                <p id="fp-subtitle" className="fp-subtitle">
                                    Enter your email and we&rsquo;ll send you a reset link.
                                </p>
                            </div>
 
                            {error && (
                                <div id="fp-error-message" className="fp-error">
                                    <ErrorOutlineIcon sx={{ fontSize: 18, flexShrink: 0, marginTop: "1px" }} />
                                    <span>{error}</span>
                                </div>
                            )}
 
                            <form id="fp-form" className="fp-form" onSubmit={handleSubmit} noValidate>
                                <div id="fp-email-field" className="fp-form-field">
                                    <label htmlFor="fp-email">Email address</label>
                                    <div id="fp-email-input-wrapper" className="fp-input-wrapper">
                                        <input
                                            id="fp-email"
                                            type="email"
                                            placeholder="you@example.com"
                                            value={email}
                                            onChange={(e) => setEmail(e.target.value)}
                                            autoComplete="email"
                                            autoFocus
                                            required
                                            disabled={isSubmitting}
                                        />
                                    </div>
                                </div>
 
                                <button id="btn-send-reset" type="submit" className="fp-submit" disabled={isSubmitting}>
                                    {isSubmitting ? (
                                        <CircularProgress size={20} sx={{ color: "#003754" }} />
                                    ) : (
                                        "Send reset link"
                                    )}
                                </button>
                            </form>
 
                            <button
                                id="btn-back-to-login-link"
                                type="button"
                                className="fp-back-link"
                                onClick={() => navigate("/login")}
                            >
                                <ArrowBackIcon sx={{ fontSize: 15 }} />
                                Back to log in
                            </button>
                        </>
                    )}
                </div>
            </div>
        </div>
    );
};
 
export default ForgotPasswordPage;