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;
Source