Home >Backend Development >Golang >Password Reset Feature: Frontend

Password Reset Feature: Frontend

DDD
DDDOriginal
2024-10-02 12:07:011031browse

Password Reset Feature: Frontend

Frontend

The frontend part is very easy compared to the backend part. All I need to do is create a modal, and use it to send data twice.

  • Firstly send email to send OTP to
  • Then send the OTP and the new password to change it

To create the modal, I copied some code, the classNames for the encapsulation of a modal, from the MessageModal component in my earlier project Chat-Nat.

Planning

I'll add a "Forgot Password?" button on the login page, and set the onClick handler to open the modal

I need to use a boolean state to denote whether the OTP has been sent to the user's email before asking for it. I'm naming the state isOTPSent

  • If !isOTPSent -> just ask for the email address, send api req, then if successful setOTPSent(true)
  • If isOTPSent -> now also ask for the OTP and the new password, then if successful, close the modal

Here are a few components and hooks that I'm reusing from the existing frontend of this project:

  • Box -> It neatly wrapped my login and register pages into a card, centered on the page, reusing here with title "Password Reset"
  • AuthForm -> Just a form but I coded it to disable the submit button and set button text to "Loading..." when we are waiting on a response from the server
  • FormInput -> Input field with a label of its own, with value setter and onChange handler, optionally with an isRequired boolean
  • useAxios -> Custom hook to handle the responses from the server which needs a token refresh. apiReq function for the normal request sending, some custom error handling to display an alert() and refresh token, refreshReq function to refresh the auth token and try the initial request again.

Here's the entire code for the modal:

// src/components/PasswordResetModal.tsx
import React, { useState } from "react"
import AuthForm from "./AuthForm";
import FormInput from "./FormInput";
import Box from "./Box";
import { useAxios } from "../hooks/useAxios";

interface FormData {
    email: string,
    new_password: string,
    otp: string,
}

interface Props {
    isVisible: boolean,
    onClose: () => void,
}

const PasswordResetModal: React.FC<Props> = ({ isVisible, onClose }) => {
    const [formData, setFormData] = useState<FormData>({
        email: "",
        new_password: "",
        otp: ""
    });
    const [isLoading, setLoading] = useState<boolean>(false);
    const [isOTPSent, setOTPSent] = useState<boolean>(false);
    const { apiReq } = useAxios();

    const handleClose = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
        if ((e.target as HTMLElement).id === "wrapper") {
            onClose();

            // could have setOTPSent(false), but avoiding it in case user misclicks outside
        }
    };

    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const { name, value } = e.target;
        setFormData({
            ...formData,
            [name]: value,
        });
    };

    const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        setLoading(true);

        if (!isOTPSent) { // first request for sending otp,
            const response = await apiReq<unknown, FormData>("post", "/api/reset-password", formData)

            if (response) {
                alert("OTP has been sent to your email");
                setOTPSent(true);
            }
        } else { // then using otp to change password
            const response = await apiReq<unknown, FormData>("put", "/api/reset-password", formData)

            if (response) {
                alert("Password has been successfully reset\nPlease log in again");

                // clear the form
                setFormData({
                    email: "",
                    otp: "",
                    new_password: "",
                })

                // close modal
                onClose();
            }
        }

        setLoading(false);
    };

    if (!isVisible) return null;

    return (
        <div
            id="wrapper"
            className="fixed inset-0 bg-black bg-opacity-25 backdrop-blur-sm flex justify-center items-center"
            onClick={handleClose}>
            <Box title="Password Reset">
                <AuthForm
                    submitHandler={handleSubmit}
                    isLoading={isLoading}
                    buttonText={isOTPSent ? "Change Password" : "Send OTP"}>
                    <FormInput
                        id="email"
                        label="Your email"
                        type="email"
                        value={formData.email}
                        changeHandler={handleChange}
                        isRequired />

                    {isOTPSent && (<>
                        <FormInput
                            id="otp"
                            label="OTP"
                            type="text"
                            value={formData.otp}
                            changeHandler={handleChange}
                            isRequired />
                        <FormInput
                            id="new_password"
                            label="New Password"
                            type="password"
                            value={formData.new_password}
                            changeHandler={handleChange}
                            isRequired />
                    </>)}
                </AuthForm>
            </Box>
        </div>
    )
}

export default PasswordResetModal

And here's how the conditional rendering of the modal is handled in the Login form

// src/pages/auth/Login.tsx
import PasswordResetModal from "../../components/PasswordResetModal";

const Login: React.FC = () => {
    const [showModal, setShowModal] = useState<boolean>(false);

    return (
        <Section>
            <Box title="Login">
                <div className="grid grid-flow-col">
                    {/* link to the register page here */}
                    <button 
                    type="button"
                    onClick={() => setShowModal(true)}
                    className="text-blue-700 hover:text-white border border-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-3 py-2 text-center me-2 mb-2 dark:border-blue-500 dark:text-blue-500 dark:hover:text-white dark:hover:bg-blue-500 dark:focus:ring-blue-800">
                        Forgot Password?
                    </button>

                    <PasswordResetModal isVisible={showModal} onClose={() => setShowModal(false)} />
                </div>
            </Box>
        </Section>
    )

We're done! Or so I thought.

While running the app in my development environment, I discovered a bug where the emails wouldn't go through if the backend has been running since a long time.

We'll fix this bug in the next post

The above is the detailed content of Password Reset Feature: Frontend. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn