diff --git a/Client/src/App.jsx b/Client/src/App.jsx
index 82c8203a8..a248dd5b0 100644
--- a/Client/src/App.jsx
+++ b/Client/src/App.jsx
@@ -5,7 +5,7 @@ import { useDispatch } from "react-redux";
import "react-toastify/dist/ReactToastify.css";
import { ToastContainer } from "react-toastify";
import NotFound from "./Pages/NotFound";
-import Login from "./Pages/Auth/Login";
+import Login from "./Pages/Auth/Login/Login";
import Register from "./Pages/Auth/Register/Register";
import Account from "./Pages/Account";
import Monitors from "./Pages/Monitors/Home";
diff --git a/Client/src/Pages/Auth/Login.jsx b/Client/src/Pages/Auth/Login.jsx
deleted file mode 100644
index 677637302..000000000
--- a/Client/src/Pages/Auth/Login.jsx
+++ /dev/null
@@ -1,446 +0,0 @@
-import { useState, useEffect, useRef } from "react";
-import { useNavigate } from "react-router-dom";
-import { Box, Button, Stack, Typography } from "@mui/material";
-import { useTheme } from "@emotion/react";
-import { credentials } from "../../Validation/validation";
-import { login } from "../../Features/Auth/authSlice";
-import LoadingButton from "@mui/lab/LoadingButton";
-import { useDispatch, useSelector } from "react-redux";
-import { createToast } from "../../Utils/toastUtils";
-import { networkService } from "../../main";
-import TextInput from "../../Components/Inputs/TextInput";
-import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments";
-import Background from "../../assets/Images/background-grid.svg?react";
-import Logo from "../../assets/icons/bwu-icon.svg?react";
-import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
-import PropTypes from "prop-types";
-import { logger } from "../../Utils/Logger";
-import "./index.css";
-const DEMO = import.meta.env.VITE_APP_DEMO;
-
-/**
- * Renders the first step of the login process.
- *
- * @param {Object} props
- * @param {Object} props.form - Form state object.
- * @param {Object} props.errors - Object containing form validation errors.
- * @param {Function} props.onSubmit - Callback function to handle form submission.
- * @param {Function} props.onChange - Callback function to handle form input changes.
- * @param {Function} props.onBack - Callback function to handle "Back" button click.
- * @returns {JSX.Element}
- */
-const StepOne = ({ form, errors, onSubmit, onChange }) => {
- const theme = useTheme();
- const inputRef = useRef(null);
-
- useEffect(() => {
- if (inputRef.current) {
- inputRef.current.focus();
- }
- }, []);
-
- return (
- <>
-
-
- Log In
- Enter your email address
-
-
- (e.target.value = e.target.value.toLowerCase())}
- onChange={onChange}
- error={errors.email ? true : false}
- helperText={errors.email}
- ref={inputRef}
- />
-
-
-
-
-
- >
- );
-};
-
-StepOne.propTypes = {
- form: PropTypes.object.isRequired,
- errors: PropTypes.object.isRequired,
- onSubmit: PropTypes.func.isRequired,
- onChange: PropTypes.func.isRequired,
-};
-
-/**
- * Renders the second step of the login process, including a password input field.
- *
- * @param {Object} props
- * @param {Object} props.form - Form state object.
- * @param {Object} props.errors - Object containing form validation errors.
- * @param {Function} props.onSubmit - Callback function to handle form submission.
- * @param {Function} props.onChange - Callback function to handle form input changes.
- * @param {Function} props.onBack - Callback function to handle "Back" button click.
- * @returns {JSX.Element}
- */
-const StepTwo = ({ form, errors, onSubmit, onChange, onBack }) => {
- const theme = useTheme();
- const navigate = useNavigate();
- const inputRef = useRef(null);
- const authState = useSelector((state) => state.auth);
-
- useEffect(() => {
- if (inputRef.current) {
- inputRef.current.focus();
- }
- }, []);
-
- const handleNavigate = () => {
- if (form.email !== "" && !errors.email) {
- sessionStorage.setItem("email", form.email);
- }
- navigate("/forgot-password");
- };
-
- return (
- <>
-
-
- Log In
- Enter your password
-
-
- }
- />
-
-
-
- Continue
-
-
-
-
-
- Forgot password?
-
-
- Reset password
-
-
-
- >
- );
-};
-
-StepTwo.propTypes = {
- form: PropTypes.object.isRequired,
- errors: PropTypes.object.isRequired,
- onSubmit: PropTypes.func.isRequired,
- onChange: PropTypes.func.isRequired,
- onBack: PropTypes.func.isRequired,
-};
-
-const Login = () => {
- const dispatch = useDispatch();
- const navigate = useNavigate();
- const theme = useTheme();
-
- const authState = useSelector((state) => state.auth);
- const { authToken } = authState;
-
- const idMap = {
- "login-email-input": "email",
- "login-password-input": "password",
- };
-
- const [form, setForm] = useState({
- email: DEMO !== undefined ? "uptimedemo@demo.com" : "",
- password: DEMO !== undefined ? "Demouser1!" : "",
- });
- const [errors, setErrors] = useState({});
- const [step, setStep] = useState(0);
-
- useEffect(() => {
- if (authToken) {
- navigate("/monitors");
- return;
- }
- networkService
- .doesSuperAdminExist()
- .then((response) => {
- if (response.data.data === false) {
- navigate("/register");
- }
- })
- .catch((error) => {
- logger.error(error);
- });
- }, [authToken, navigate]);
-
- const handleChange = (event) => {
- const { value, id } = event.target;
- const name = idMap[id];
- setForm((prev) => ({
- ...prev,
- [name]: value,
- }));
-
- const { error } = credentials.validate({ [name]: value }, { abortEarly: false });
-
- setErrors((prev) => {
- const prevErrors = { ...prev };
- if (error) prevErrors[name] = error.details[0].message;
- else delete prevErrors[name];
- return prevErrors;
- });
- };
-
- const handleSubmit = async (event) => {
- event.preventDefault();
-
- if (step === 0) {
- const { error } = credentials.validate(
- { email: form.email },
- { abortEarly: false }
- );
- if (error) {
- setErrors((prev) => ({ ...prev, email: error.details[0].message }));
- createToast({ body: error.details[0].message });
- } else {
- setStep(1);
- }
- } else if (step === 1) {
- const { error } = credentials.validate(form, { abortEarly: false });
-
- if (error) {
- // validation errors
- const newErrors = {};
- error.details.forEach((err) => {
- newErrors[err.path[0]] = err.message;
- });
- setErrors(newErrors);
- createToast({
- body:
- error.details && error.details.length > 0
- ? error.details[0].message
- : "Error validating data.",
- });
- } else {
- const action = await dispatch(login(form));
- if (action.payload.success) {
- navigate("/monitors");
- createToast({
- body: "Welcome back! You're successfully logged in.",
- });
- } else {
- if (action.payload) {
- if (action.payload.msg === "Incorrect password")
- setErrors({
- password: "The password you provided does not match our records",
- });
- // dispatch errors
- createToast({
- body: action.payload.msg,
- });
- } else {
- // unknown errors
- createToast({
- body: "Unknown error.",
- });
- }
- }
- }
- }
- };
-
- return (
-
-
-
-
-
-
- BlueWave Uptime
-
- .MuiStack-root": {
- border: 1,
- borderRadius: theme.spacing(5),
- borderColor: theme.palette.border.light,
- backgroundColor: theme.palette.background.main,
- padding: {
- xs: theme.spacing(12),
- sm: theme.spacing(20),
- },
- },
- }}
- >
- {step === 0 ? (
-
- ) : (
- step === 1 && (
- setStep(0)}
- />
- )
- )}
-
-
- );
-};
-
-export default Login;
diff --git a/Client/src/Pages/Auth/Login/Components/EmailStep.jsx b/Client/src/Pages/Auth/Login/Components/EmailStep.jsx
new file mode 100644
index 000000000..f78193186
--- /dev/null
+++ b/Client/src/Pages/Auth/Login/Components/EmailStep.jsx
@@ -0,0 +1,95 @@
+import { useRef, useEffect } from "react";
+import { Box, Button, Stack, Typography } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import TextInput from "../../../../Components/Inputs/TextInput";
+import PropTypes from "prop-types";
+
+/**
+ * Renders the email step of the login process which includes an email field.
+ *
+ * @param {Object} props
+ * @param {Object} props.form - Form state object.
+ * @param {Object} props.errors - Object containing form validation errors.
+ * @param {Function} props.onSubmit - Callback function to handle form submission.
+ * @param {Function} props.onChange - Callback function to handle form input changes.
+ * @param {Function} props.onBack - Callback function to handle "Back" button click.
+ * @returns {JSX.Element}
+ */
+const EmailStep = ({ form, errors, onSubmit, onChange }) => {
+ const theme = useTheme();
+ const inputRef = useRef(null);
+
+ useEffect(() => {
+ if (inputRef.current) {
+ inputRef.current.focus();
+ }
+ }, []);
+
+ return (
+ <>
+
+
+ Log In
+ Enter your email address
+
+
+ (e.target.value = e.target.value.toLowerCase())}
+ onChange={onChange}
+ error={errors.email ? true : false}
+ helperText={errors.email}
+ ref={inputRef}
+ />
+
+
+
+
+
+ >
+ );
+};
+
+EmailStep.propTypes = {
+ form: PropTypes.object.isRequired,
+ errors: PropTypes.object.isRequired,
+ onSubmit: PropTypes.func.isRequired,
+ onChange: PropTypes.func.isRequired,
+};
+
+export default EmailStep;
\ No newline at end of file
diff --git a/Client/src/Pages/Auth/Login/Components/PasswordStep.jsx b/Client/src/Pages/Auth/Login/Components/PasswordStep.jsx
new file mode 100644
index 000000000..34df4959d
--- /dev/null
+++ b/Client/src/Pages/Auth/Login/Components/PasswordStep.jsx
@@ -0,0 +1,159 @@
+import { useRef, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
+import { Box, Button, Stack, Typography } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import LoadingButton from "@mui/lab/LoadingButton";
+import { useSelector } from "react-redux";
+import TextInput from "../../../../Components/Inputs/TextInput";
+import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments";
+import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
+import PropTypes from "prop-types";
+
+/**
+ * Renders the password step of the login process, including a password input field.
+ *
+ * @param {Object} props
+ * @param {Object} props.form - Form state object.
+ * @param {Object} props.errors - Object containing form validation errors.
+ * @param {Function} props.onSubmit - Callback function to handle form submission.
+ * @param {Function} props.onChange - Callback function to handle form input changes.
+ * @param {Function} props.onBack - Callback function to handle "Back" button click.
+ * @returns {JSX.Element}
+ */
+const PasswordStep = ({ form, errors, onSubmit, onChange, onBack }) => {
+ const theme = useTheme();
+ const navigate = useNavigate();
+ const inputRef = useRef(null);
+ const authState = useSelector((state) => state.auth);
+
+ useEffect(() => {
+ if (inputRef.current) {
+ inputRef.current.focus();
+ }
+ }, []);
+
+ const handleNavigate = () => {
+ if (form.email !== "" && !errors.email) {
+ sessionStorage.setItem("email", form.email);
+ }
+ navigate("/forgot-password");
+ };
+
+ return (
+ <>
+
+
+ Log In
+ Enter your password
+
+
+ }
+ />
+
+
+
+ Continue
+
+
+
+
+
+ Forgot password?
+
+
+ Reset password
+
+
+
+ >
+ );
+};
+
+PasswordStep.propTypes = {
+ form: PropTypes.object.isRequired,
+ errors: PropTypes.object.isRequired,
+ onSubmit: PropTypes.func.isRequired,
+ onChange: PropTypes.func.isRequired,
+ onBack: PropTypes.func.isRequired,
+};
+
+export default PasswordStep
\ No newline at end of file
diff --git a/Client/src/Pages/Auth/Login/Login.jsx b/Client/src/Pages/Auth/Login/Login.jsx
new file mode 100644
index 000000000..1b3a420ea
--- /dev/null
+++ b/Client/src/Pages/Auth/Login/Login.jsx
@@ -0,0 +1,213 @@
+import { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
+import { Box, Stack, Typography } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import { credentials } from "../../../Validation/validation";
+import { login } from "../../../Features/Auth/authSlice";
+import { useDispatch, useSelector } from "react-redux";
+import { createToast } from "../../../Utils/toastUtils";
+import { networkService } from "../../../main";
+import Background from "../../../assets/Images/background-grid.svg?react";
+import Logo from "../../../assets/icons/bwu-icon.svg?react";
+import { logger } from "../../../Utils/Logger";
+import "../index.css";
+import EmailStep from "./Components/EmailStep";
+import PasswordStep from "./Components/PasswordStep";
+
+const DEMO = import.meta.env.VITE_APP_DEMO;
+
+/**
+ * Displays the login page.
+ */
+
+const Login = () => {
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+ const theme = useTheme();
+
+ const authState = useSelector((state) => state.auth);
+ const { authToken } = authState;
+
+ const idMap = {
+ "login-email-input": "email",
+ "login-password-input": "password",
+ };
+
+ const [form, setForm] = useState({
+ email: DEMO !== undefined ? "uptimedemo@demo.com" : "",
+ password: DEMO !== undefined ? "Demouser1!" : "",
+ });
+ const [errors, setErrors] = useState({});
+ const [step, setStep] = useState(0);
+
+ useEffect(() => {
+ if (authToken) {
+ navigate("/monitors");
+ return;
+ }
+ networkService
+ .doesSuperAdminExist()
+ .then((response) => {
+ if (response.data.data === false) {
+ navigate("/register");
+ }
+ })
+ .catch((error) => {
+ logger.error(error);
+ });
+ }, [authToken, navigate]);
+
+ const handleChange = (event) => {
+ const { value, id } = event.target;
+ const name = idMap[id];
+ setForm((prev) => ({
+ ...prev,
+ [name]: value,
+ }));
+
+ const { error } = credentials.validate({ [name]: value }, { abortEarly: false });
+
+ setErrors((prev) => {
+ const prevErrors = { ...prev };
+ if (error) prevErrors[name] = error.details[0].message;
+ else delete prevErrors[name];
+ return prevErrors;
+ });
+ };
+
+ const handleSubmit = async (event) => {
+ event.preventDefault();
+
+ if (step === 0) {
+ const { error } = credentials.validate(
+ { email: form.email },
+ { abortEarly: false }
+ );
+ if (error) {
+ setErrors((prev) => ({ ...prev, email: error.details[0].message }));
+ createToast({ body: error.details[0].message });
+ } else {
+ setStep(1);
+ }
+ } else if (step === 1) {
+ const { error } = credentials.validate(form, { abortEarly: false });
+
+ if (error) {
+ // validation errors
+ const newErrors = {};
+ error.details.forEach((err) => {
+ newErrors[err.path[0]] = err.message;
+ });
+ setErrors(newErrors);
+ createToast({
+ body:
+ error.details && error.details.length > 0
+ ? error.details[0].message
+ : "Error validating data.",
+ });
+ } else {
+ const action = await dispatch(login(form));
+ if (action.payload.success) {
+ navigate("/monitors");
+ createToast({
+ body: "Welcome back! You're successfully logged in.",
+ });
+ } else {
+ if (action.payload) {
+ if (action.payload.msg === "Incorrect password")
+ setErrors({
+ password: "The password you provided does not match our records",
+ });
+ // dispatch errors
+ createToast({
+ body: action.payload.msg,
+ });
+ } else {
+ // unknown errors
+ createToast({
+ body: "Unknown error.",
+ });
+ }
+ }
+ }
+ }
+ };
+
+ return (
+
+
+
+
+
+
+ BlueWave Uptime
+
+ .MuiStack-root": {
+ border: 1,
+ borderRadius: theme.spacing(5),
+ borderColor: theme.palette.border.light,
+ backgroundColor: theme.palette.background.main,
+ padding: {
+ xs: theme.spacing(12),
+ sm: theme.spacing(20),
+ },
+ },
+ }}
+ >
+ {step === 0 ? (
+
+ ) : (
+ step === 1 && (
+ setStep(0)}
+ />
+ )
+ )}
+
+
+ );
+};
+
+export default Login;