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 { useDispatch, useSelector } from "react-redux"; import { createToast } from "../../Utils/toastUtils"; import { networkService } from "../../main"; import Field from "../../Components/Inputs/Field"; import Background from "../../assets/Images/background-grid.svg?react"; import Logo from "../../assets/icons/bwu-icon.svg?react"; import Mail from "../../assets/icons/mail.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; /** * Displays the initial landing page. * * @param {Object} props * @param {Function} props.onContinue - Callback function to handle "Continue with Email" button click. * @returns {JSX.Element} */ const LandingPage = ({ onContinue }) => { const theme = useTheme(); return ( <> Log In We are pleased to see you again! By continuing, you agree to our{" "} { window.open( "https://bluewavelabs.ca/terms-of-service-open-source", "_blank", "noreferrer" ); }} sx={{ "&:hover": { color: theme.palette.text.tertiary, }, }} > Terms of Service {" "} and{" "} { window.open( "https://bluewavelabs.ca/privacy-policy-open-source", "_blank", "noreferrer" ); }} sx={{ "&:hover": { color: theme.palette.text.tertiary, }, }} > Privacy Policy. ); }; LandingPage.propTypes = { onContinue: PropTypes.func.isRequired, }; /** * 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, onBack }) => { 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} ref={inputRef} /> ); }; StepOne.propTypes = { form: PropTypes.object.isRequired, errors: PropTypes.object.isRequired, onSubmit: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired, onBack: 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); 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 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 === 1) { 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(2); } } else if (step === 2) { 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 ? ( setStep(1)} /> ) : step === 1 ? ( setStep(0)} /> ) : ( step === 2 && ( setStep(1)} /> ) )} ); }; export default Login;