Source

pages/Authentication/Login/LoginForm.jsx

import axios from "axios";
import { useState, useRef } from "react";
import { NavLink, useNavigate } from "react-router-dom";
import "../Login/login.css";
import baseUrl from "../../../array/base/config";
import { useEffect } from "react";
import { toast } from "react-toastify";
import {
  useChangePassword,
  useGetPhoneOtp,
  useRequestLogin,
  useUpdateEmail,
  useVerifyOtpFirstUser,
} from "../../../apis/Login";
import { Form, Input, InputNumber, Modal, Spin, Button } from "antd";
/**
 * A login form page for user authentication.
 *
 * This page provides a user interface for users to log in, request password changes, and verify their identity using OTP.
 *
 * @module Login
 * @returns {JSX.Element} This  returns the login form as a JSX element
 */
const LoginForm = () => {
  /**
   * State for username
   * @type {string}
   */
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  const [passwordShown, setPasswordShown] = useState(false);
  const [passwordModal, setPasswordModal] = useState(false);
  const [verifyModal, setVerifyModal] = useState(false);
  const [isEmailVerify, setIsEmailVerify] = useState(false);
  const [otpModal, setOtpModal] = useState(false);
  const [accessToken, setAccessToken] = useState(null);
  const navigate = useNavigate();
  const [form] = Form.useForm();
  const formRef = useRef(null);
  const inputRefs = [
    useRef(null),
    useRef(null),
    useRef(null),
    useRef(null),
    // Add more refs for additional input fields
  ];
  const { mutate: changeMutate, isLoading: changeLoading } =
    useChangePassword();
  const { mutate: phoneMutate, isLoading: phoneLoading } = useGetPhoneOtp();
  const { mutate: emailMutate, isLoading: emailLoading } = useUpdateEmail();
  const { mutate: verifyMutate, isLoading: verifyLoading } =
    useVerifyOtpFirstUser();
  const { mutate, isLoading } = useRequestLogin();
  useEffect(() => {
    checkLogin();
  });
  /**
   * Check if the user is already logged in and redirect to the dashboard if logged in.
   */
  const checkLogin = () => {
    const login = localStorage.getItem("token");
    if (login) {
      navigate("/dashboard");
    }
  };
  /**
   * Validate the password format.
   *
   * @function validatePassword
   * @param {Object} rule - The validation rule.
   * @param {string} value - The password value to validate.
   * @param {Function} callback - The callback function for validation.
   */
  const validatePassword = (rule, value, callback) => {
    // Regular expression to check for at least one special character
    const specialCharRegex = /[!@#$%^&*()_+{}\[\]:;<>,.?~]/;
    // for Upper case characters
    const uppercaseLetters = /[A-Z]/;
    // for lower case characters
    const lowercaseLetters = /[a-z]/;
    const digitRegex = /[0-9]/;
    if (
      value &&
      value.length >= 8 &&
      specialCharRegex.test(value) &&
      uppercaseLetters.test(value) &&
      lowercaseLetters.test(value) &&
      digitRegex.test(value)
    ) {
      callback(); // Password is valid, call the callback with no arguments
    } else {
      callback(
        "Password must be at least 8 characters long and contain at least one special character, uppercase letter, lowercase letter ond one  numeric digit ."
      );
    }
  };
  /**
   * Handle the process of getting and sending an OTP for verification.
   *
   * This function determines whether to send the OTP to a phone number or an email address based on the `isEmailVerify` state.
   *
   * @function handleGetOtp
   * @param {Object} values - The input values containing either a new phone number or a new email address.
   * @param {string} values.new_phone - The new phone number to verify (if not using email).
   * @param {string} values.new_email - The new email address to verify (if not using phone).
   *
   * @throws {Error} If there's an issue with sending the OTP or setting the OTP modal.
   */
  const handleGetOtp = (values) => {
    if (!isEmailVerify) {
      phoneMutate(
        { phone_number: values.new_phone, token: accessToken },
        {
          onSuccess: (res) => {
            setOtpModal(true);
          },
        }
      );
    } else {
      emailMutate(
        { email: values.new_email, token: accessToken },
        {
          onSuccess: (res) => {
            setOtpModal(true);
          },
        }
      );
    }
  };
  const onSubmit = (values) => {
    changeMutate(
      {
        old_password: values.old_password,
        password: values.new_password,
        password2: values.confirm_password,
        token: accessToken,
      },
      {
        onSuccess: (res) => {
          setPasswordModal(false);
        },
      }
    );
  };
  const handleKeyDown = (event, currentIndex) => {
    const { key } = event;
    if (key === "ArrowRight") {
      event.preventDefault();
      const nextIndex = currentIndex + 1;
      if (nextIndex < inputRefs.length) {
        inputRefs[nextIndex].current.focus();
      }
    } else if (key === "ArrowLeft") {
      event.preventDefault();
      const prevIndex = currentIndex - 1;
      if (prevIndex >= 0) {
        inputRefs[prevIndex].current.focus();
      }
    }
  };
  const handleInput1 = (e) => {
    const value = e.target.value;
    if (value.length === 1) {
      inputRefs[1].current.focus();
    }
  };

  const handleInput2 = (e) => {
    const value = e.target.value;
    if (value.length === 1) {
      inputRefs[2].current.focus();
    }
  };

  const handleInput3 = (e) => {
    const value = e.target.value;
    if (value.length === 1) {
      inputRefs[3].current.focus();
    }
  };
  /**
   * Custom validation rule for confirming a new password.
   *
   * This function checks if the value of the confirm password field matches the value of the new password field.
   *
   * @function validateConfirmPassword
   * @param {Object} options - Validation options.
   * @param {Function} options.getFieldValue - Function to get the value of other form fields.
   * @returns {Object} A validation object with resolved promise if the passwords match, or a rejection with an error message if they don't match.
   */
  // Custom validation rule for confirm password field
  const validateConfirmPassword = ({ getFieldValue }) => ({
    validator(_, value) {
      if (!value || getFieldValue("new_password") === value) {
        return Promise.resolve();
      }
      return Promise.reject(new Error("The two passwords does not match."));
    },
  });
  /**
   * Handle user sign-in.
   *
   * This function is called when the user submits the login form. It sends a request to authenticate the user and manages the subsequent actions based on the response.
   *
   * @function handleSignIn
   * @param {Object} event - The form submit event.
   */
  // Handling <login></login>
  const handleSignIn = (event) => {
    event.preventDefault();
    mutate(
      { username, password },
      {
        onSuccess: async (res) => {
          const token = res.data.data.key;
          await axios({
            method: "GET",
            url: `${baseUrl}/api/auth/userroles/`,
            headers: {
              Authorization: `Token ${token}`,
            },
          })
            .then((result) => {
              if (result?.data?.data?.results[0]?.is_password_change_required) {
                setAccessToken(token);
                setPasswordModal(true);
              } else {
                if (!result?.data?.data?.results[0]?.is_number_verified) {
                  setAccessToken(token);
                  setVerifyModal(true);
                } else {
                  localStorage.setItem("token", token);
                  navigate("/dashboard");
                  toast.success("Login successful");
                }
              }
            })
            .catch((error) => {
              toast.error(error.message);
            });
        },
      }
    );
  };
  /**
   * Handle OTP submission and verification.
   *
   * @function handleOtpSubmit
   * @param {Object} event - The form submit event.
   */
  const handleOtpSubmit = (event) => {
    event.preventDefault();
    const formData = new FormData(formRef.current);
    const formValues = Object.fromEntries(formData.entries());
    const otp = String(
      `${formValues.input1}${formValues.input2}${formValues.input3}${formValues.input4}`
    );
    verifyMutate(
      { otp, token: accessToken },
      {
        onSuccess: (res) => {
          toast.success("Verification successful");
          setOtpModal(false);
          setVerifyModal(false);
        },
      }
    );
  };
  return (
    <div className="login_form_container">
      <div className="">
        {/* Modal for password change  */}
        <Modal
          title="Change Password"
          centered
          open={passwordModal}
          onOk={() => form.submit()}
          okText="Change Password"
          okButtonProps={{
            loading: changeLoading,
            className: "send_btn",
          }}
          onCancel={() => setPasswordModal(false)}
          width={500}
        >
          <Form layout="vertical" onFinish={onSubmit} form={form}>
            <Form.Item
              name="old_password"
              label="Old Password"
              rules={[
                {
                  required: true,
                  message: "Please input your old password!",
                },
              ]}
            >
              <Input.Password />
            </Form.Item>
            <Form.Item
              name="new_password"
              label="New Password"
              rules={[
                {
                  required: true,
                  message: "Please input your password!",
                },
                {
                  validator: validatePassword,
                },
              ]}
              hasFeedback
            >
              <Input.Password />
            </Form.Item>
            <Form.Item
              name="confirm_password"
              label="Confirm Password"
              rules={[
                {
                  required: true,
                  message: "Please input your confirm password!",
                },
                validateConfirmPassword,
              ]}
            >
              <Input.Password />
            </Form.Item>
          </Form>
        </Modal>
        {/* Modal for sending otp  */}
        <Modal
          title={isEmailVerify ? "Verify email address" : "Verify phone number"}
          centered
          open={verifyModal}
          onOk={() => form.submit()}
          okText="Send"
          okButtonProps={{
            loading: isEmailVerify ? emailLoading : phoneLoading,
            className: "send_btn",
          }}
          onCancel={() => setVerifyModal(false)}
          width={500}
        >
          <Form layout="vertical" onFinish={handleGetOtp} form={form}>
            {isEmailVerify ? (
              <Form.Item
                name="new_email"
                rules={[
                  {
                    required: true,
                    message: "",
                  },
                ]}
              >
                <Input placeholder="Enter new email address" />
              </Form.Item>
            ) : (
              <Form.Item
                name="new_phone"
                rules={[
                  {
                    required: true,
                    message: "",
                  },
                ]}
              >
                <InputNumber
                  type="Number"
                  style={{ width: "100%" }}
                  placeholder="Enter new phone number"
                />
              </Form.Item>
            )}
          </Form>
          <div className="flex justify-end">
            <span
              style={{ color: "green", cursor: "pointer" }}
              onClick={() => setIsEmailVerify(!isEmailVerify)}
            >
              {isEmailVerify ? "Verify phone" : "Verify email"}
            </span>
          </div>
        </Modal>
        {/* Modal for verify otp  */}
        <Modal
          centered
          open={otpModal}
          okText="Verify"
          okButtonProps={{
            style: {
              display: "none",
            },
          }}
          cancelButtonProps={{
            style: {
              display: "none",
            },
          }}
          width={"fit-content"}
          onCancel={() => setOtpModal(false)}
        >
          <div className="otp_container">
            <form ref={formRef} className="form" onSubmit={handleOtpSubmit}>
              {" "}
              <div className="title">OTP</div>{" "}
              <div className="title">Verification Code</div>{" "}
              <p className="message">
                We have sent a verification code to your{" "}
                {isEmailVerify ? "email" : "phone"}
              </p>{" "}
              <div className="otp_inputs">
                <input
                  id="input1"
                  name="input1"
                  type="text"
                  min={0}
                  max={9}
                  pattern="[0-9]"
                  maxLength={1}
                  ref={inputRefs[0]}
                  onChange={handleInput1}
                  onKeyDown={(e) => handleKeyDown(e, 0)}
                  required
                />{" "}
                <input
                  id="input2"
                  name="input2"
                  type="text"
                  min={0}
                  max={9}
                  pattern="[0-9]"
                  maxLength={1}
                  ref={inputRefs[1]}
                  onChange={handleInput2}
                  onKeyDown={(e) => handleKeyDown(e, 1)}
                  required
                />{" "}
                <input
                  id="input3"
                  name="input3"
                  type="text"
                  min={0}
                  max={9}
                  pattern="[0-9]"
                  maxLength={1}
                  ref={inputRefs[2]}
                  onChange={handleInput3}
                  onKeyDown={(e) => handleKeyDown(e, 2)}
                  required
                />{" "}
                <input
                  id="input4"
                  name="input4"
                  type="text"
                  min={0}
                  max={9}
                  pattern="[0-9]"
                  maxLength={1}
                  ref={inputRefs[3]}
                  onKeyDown={(e) => handleKeyDown(e, 3)}
                  required
                />{" "}
              </div>{" "}
              <div className="submit_buttons mt-3 flex justify-end gap-2 w-100">
                <Button onClick={() => setOtpModal(false)}>Cancel</Button>
                <Button
                  className="send_btn"
                  type="primary"
                  htmlType="submit"
                  loading={verifyLoading}
                >
                  verify me
                </Button>
              </div>
            </form>
          </div>
        </Modal>

        <form action="" onSubmit={handleSignIn}>
          <div className="login_wrapper">
            <div className="login_left">
              <img src="./loginicon.png" alt="logo" className="auth_img" />
            </div>
            <div className="login_right">
              <div className="">
                <div className="form_card_heading">
                  <div className="logo_div">
                    <NavLink to="/">
                      <img src="./psmsLogo.png" alt="logo" />
                    </NavLink>
                  </div>
                </div>
                <div className="login_form_group">
                  <label htmlFor="">Username</label>
                  <div className="form_input">
                    <input
                      type="number"
                      placeholder=""
                      onChange={(e) => setUsername(e.target.value)}
                      required
                    />
                  </div>
                </div>
                <div className="login_form_group">
                  <label htmlFor="">Password</label>
                  <div className="form_input">
                    <input
                      type={passwordShown ? "text" : "password"}
                      placeholder=""
                      onChange={(e) => setPassword(e.target.value)}
                      required
                    />
                    {passwordShown ? (
                      <ion-icon
                        name="eye"
                        onClick={() => setPasswordShown(!passwordShown)}
                      ></ion-icon>
                    ) : (
                      <ion-icon
                        name="eye-off"
                        onClick={() => setPasswordShown(!passwordShown)}
                      ></ion-icon>
                    )}
                  </div>
                </div>
                <div className="login_form_group">
                  <button
                    className="login_btn"
                    type="submit"
                    disabled={isLoading}
                  >
                    {isLoading && <Spin className="mr-2" size="small" />}
                    Login
                  </button>
                </div>
                <div className="login_form_group">
                  <div className="form_check">
                    <div className="remember_me">
                      <input type="checkbox" />
                      <span>Remember me</span>
                    </div>
                    <NavLink to="/forgetPassword">
                      <p>Forgot Password?</p>
                    </NavLink>
                  </div>
                </div>
                <div className="form_link">
                  <span>
                    Don't have an account yet?
                    <NavLink to="/signup">Register Now</NavLink>
                  </span>
                </div>
              </div>
            </div>
          </div>
        </form>
      </div>
    </div>
  );
};
export default LoginForm;