diff --git a/Client/src/Pages/Auth/CheckEmail.jsx b/Client/src/Pages/Auth/CheckEmail.jsx index d6ff54fef..88a4afa1f 100644 --- a/Client/src/Pages/Auth/CheckEmail.jsx +++ b/Client/src/Pages/Auth/CheckEmail.jsx @@ -1,15 +1,15 @@ -import "./index.css"; -import background from "../../assets/Images/background_pattern_decorative.png"; -import React, { useEffect, useState } from "react"; -import EmailIcon from "../../assets/icons/email.svg?react"; -import Button from "../../Components/Button"; -import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; -import { Stack, Typography } from "@mui/material"; +import { Box, Stack, Typography } from "@mui/material"; +import { useEffect, useState } from "react"; import { useTheme } from "@emotion/react"; import { useDispatch } from "react-redux"; import { useNavigate } from "react-router"; import { createToast } from "../../Utils/toastUtils"; import { forgotPassword } from "../../Features/Auth/authSlice"; +import Button from "../../Components/Button"; +import background from "../../assets/Images/background_pattern_decorative.png"; +import EmailIcon from "../../assets/icons/email.svg?react"; +import Logo from "../../assets/icons/bwu-icon.svg?react"; +import "./index.css"; const CheckEmail = () => { const theme = useTheme(); @@ -79,28 +79,68 @@ const CheckEmail = () => { } }; + const handleNavigate = () => { + sessionStorage.removeItem("email"); + navigate("/login"); + }; + return ( -
+ background pattern -
- - - - Check your email - - - We sent a password reset link to{" "} - - {email || "username@email.com"} + + + BlueWave Uptime + + .MuiStack-root": { + border: 1, + borderRadius: theme.shape.borderRadius, + borderColor: theme.palette.otherColors.graishWhite, + backgroundColor: theme.palette.otherColors.white, + padding: { + xs: theme.gap.large, + sm: theme.gap.xl, + }, + }, + }} + > + + + + Check your email + + We sent a password reset link to{" "} + + {email || "username@email.com"} + - - - -
+ + + Go back to — + + Log In + + + ); }; diff --git a/Client/src/Pages/Auth/ForgotPassword.jsx b/Client/src/Pages/Auth/ForgotPassword.jsx index 86db72076..e1f6ae56e 100644 --- a/Client/src/Pages/Auth/ForgotPassword.jsx +++ b/Client/src/Pages/Auth/ForgotPassword.jsx @@ -1,18 +1,17 @@ -import "./index.css"; -import background from "../../assets/Images/background_pattern_decorative.png"; -import Logomark from "../../assets/icons/key.svg?react"; -import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; -import { useState } from "react"; -import { credentials } from "../../Validation/validation"; -import { useNavigate } from "react-router-dom"; -import Field from "../../Components/Inputs/Field"; +import { Box, Stack, Typography } from "@mui/material"; +import { useTheme } from "@emotion/react"; import { createToast } from "../../Utils/toastUtils"; import { useDispatch, useSelector } from "react-redux"; import { forgotPassword } from "../../Features/Auth/authSlice"; +import { useEffect, useState } from "react"; +import { credentials } from "../../Validation/validation"; +import { useNavigate } from "react-router-dom"; +import Field from "../../Components/Inputs/Field"; import ButtonSpinner from "../../Components/ButtonSpinner"; -import { Stack, Typography } from "@mui/material"; -import { useTheme } from "@emotion/react"; -import Button from "../../Components/Button"; +import Logo from "../../assets/icons/bwu-icon.svg?react"; +import Key from "../../assets/icons/key.svg?react"; +import background from "../../assets/Images/background_pattern_decorative.png"; +import "./index.css"; const ForgotPassword = () => { const navigate = useNavigate(); @@ -25,9 +24,10 @@ const ForgotPassword = () => { email: "", }); - const idMap = { - "forgot-password-email-input": "email", - }; + useEffect(() => { + const email = sessionStorage.getItem("email"); + email && setForm({ email: sessionStorage.getItem("email") }); + }, []); const handleSubmit = async (event) => { event.preventDefault(); @@ -40,11 +40,9 @@ const ForgotPassword = () => { error.details && error.details.length > 0 ? error.details[0].message : "Error validating data."; - setErrors(err); + setErrors({ email: err }); createToast({ - variant: "info", body: err, - hasIcon: false, }); } else { const action = await dispatch(forgotPassword(form)); @@ -71,72 +69,108 @@ const ForgotPassword = () => { }; const handleChange = (event) => { - const { value, id } = event.target; - const name = idMap[id]; - setForm((prev) => ({ - ...prev, - [name]: value, - })); + const { value } = event.target; + setForm({ email: value }); const { error } = credentials.validate( - { [name]: value }, + { email: value }, { abortEarly: false } ); - setErrors((prev) => { - const prevErrors = { ...prev }; - if (error) prevErrors[name] = error.details[0].message; - else delete prevErrors[name]; - return prevErrors; - }); + if (error) setErrors({ email: error.details[0].message }); + else delete errors.email; + }; + + const handleNavigate = () => { + sessionStorage.removeItem("email"); + navigate("/login"); }; return ( -
+ background pattern -
- - - - Forgot password? - - - No worries, we'll send you reset instructions. - + + + BlueWave Uptime + + .MuiStack-root": { + border: 1, + borderRadius: theme.shape.borderRadius, + borderColor: theme.palette.otherColors.graishWhite, + backgroundColor: theme.palette.otherColors.white, + padding: { + xs: theme.gap.large, + sm: theme.gap.xl, + }, + }, + }} + > + + + + Forgot password? + + No worries, we'll send you reset instructions. + + + + + + + + - - - -
+ + + Go back to — + + Log In + + + ); }; diff --git a/Client/src/Pages/Auth/Login.jsx b/Client/src/Pages/Auth/Login.jsx index eef972ec4..8f378286f 100644 --- a/Client/src/Pages/Auth/Login.jsx +++ b/Client/src/Pages/Auth/Login.jsx @@ -154,6 +154,13 @@ const StepTwo = ({ form, errors, onSubmit, onChange, onBack }) => { } }, []); + const handleNavigate = () => { + if (form.email !== "" && !errors.email) { + sessionStorage.setItem("email", form.email); + } + navigate("/forgot-password"); + }; + return ( <> @@ -209,7 +216,7 @@ const StepTwo = ({ form, errors, onSubmit, onChange, onBack }) => { component="span" ml={theme.gap.xs} sx={{ userSelect: "none" }} - onClick={() => navigate("/forgot-password")} + onClick={handleNavigate} > Reset password diff --git a/Client/src/Pages/Auth/NewPasswordConfirmed.jsx b/Client/src/Pages/Auth/NewPasswordConfirmed.jsx index f73e5edcf..12a6995f9 100644 --- a/Client/src/Pages/Auth/NewPasswordConfirmed.jsx +++ b/Client/src/Pages/Auth/NewPasswordConfirmed.jsx @@ -1,14 +1,14 @@ -import "./index.css"; -import background from "../../assets/Images/background_pattern_decorative.png"; -import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; -import ConfirmIcon from "../../assets/icons/confirm-icon.svg?react"; -import Button from "../../Components/Button"; -import { Stack, Typography } from "@mui/material"; +import { Box, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router"; import { useDispatch } from "react-redux"; import { clearAuthState } from "../../Features/Auth/authSlice"; import { clearUptimeMonitorState } from "../../Features/UptimeMonitors/uptimeMonitorsSlice"; +import Button from "../../Components/Button"; +import background from "../../assets/Images/background_pattern_decorative.png"; +import ConfirmIcon from "../../assets/icons/confirm-icon.svg?react"; +import Logo from "../../assets/icons/bwu-icon.svg?react"; +import "./index.css"; const NewPasswordConfirmed = () => { const theme = useTheme(); @@ -22,39 +22,74 @@ const NewPasswordConfirmed = () => { }; return ( -
+ background pattern -
- - - - Password reset - - - Your password has been successfully reset. Click below to log in - magically. - - - + + + BlueWave Uptime + + .MuiStack-root": { + border: 1, + borderRadius: theme.shape.borderRadius, + borderColor: theme.palette.otherColors.graishWhite, + backgroundColor: theme.palette.otherColors.white, + padding: { + xs: theme.gap.large, + sm: theme.gap.xl, + }, + }, + }} + > + + + + Password reset + + Your password has been successfully reset. Click below to log in + magically. + +
+
+ + Go back to — + + Log In + + + ); }; diff --git a/Client/src/Pages/Auth/Register/Register.jsx b/Client/src/Pages/Auth/Register/Register.jsx index 77b3dd2fc..855cc3c54 100644 --- a/Client/src/Pages/Auth/Register/Register.jsx +++ b/Client/src/Pages/Auth/Register/Register.jsx @@ -107,18 +107,16 @@ const StepOne = ({ form, errors, onSubmit, onChange, onBack }) => { />
- - - + @@ -267,21 +265,19 @@ const StepThree = ({ form, errors, onSubmit, onChange, onBack }) => { />
- - - + - + { const navigate = useNavigate(); @@ -105,100 +104,138 @@ const SetNewPassword = () => { }; return ( -
+ background pattern -
- - - - Set new password - - - Your new password must be different to previously used passwords. - - - - - - - - - - + + + BlueWave Uptime + + .MuiStack-root": { + border: 1, + borderRadius: theme.shape.borderRadius, + borderColor: theme.palette.otherColors.graishWhite, + backgroundColor: theme.palette.otherColors.white, + padding: { + xs: theme.gap.large, + sm: theme.gap.xl, + }, + }, + }} + > + + + + Set new password + + Your new password must be different to previously used passwords. + + + + + + +
+ + + + + + + +
-
+
+ + Go back to — + navigate("/login")} + sx={{ userSelect: "none" }} + > + Log In + + +
); }; diff --git a/Client/src/Pages/Auth/index.css b/Client/src/Pages/Auth/index.css index 577f27799..f7f2c211e 100644 --- a/Client/src/Pages/Auth/index.css +++ b/Client/src/Pages/Auth/index.css @@ -1,80 +1,3 @@ -/* ////// */ -/* SHARED */ -/* ////// */ -.set-new-password-page, -.password-confirmed-page, -.forgot-password-page, -.check-email-page { - display: flex; - justify-content: center; - height: var(--env-var-height-1); -} -.set-new-password-page h1.MuiTypography-root, -.password-confirmed-page h1.MuiTypography-root, -.forgot-password-page h1.MuiTypography-root, -.check-email-page h1.MuiTypography-root { - font-size: var(--env-var-font-size-large); - color: var(--env-var-color-1); - font-weight: 600; -} -.set-new-password-page p.MuiTypography-root, -.set-new-password-page button, -.password-confirmed-page p.MuiTypography-root, -.password-confirmed-page button, -.forgot-password-page p.MuiTypography-root, -.forgot-password-page button, -.check-email-page p.MuiTypography-root, -.check-email-page button, -.check-email-page span.MuiTypography-root { - font-size: var(--env-var-font-size-medium); -} -.set-new-password-page button:not(.MuiIconButton-root), -.password-confirmed-page button:not(.MuiIconButton-root), -.forgot-password-page button:not(.MuiIconButton-root), -.check-email-page button:not(.MuiIconButton-root) { - height: 34px; - border-radius: var(--env-var-radius-2); - line-height: 0; -} - -.set-new-password-form, -.password-confirmed-form, -.forgot-password-form, -.check-email-form { - margin-top: 65px; - width: var(--env-var-width-2); -} -.set-new-password-page p.MuiTypography-root, -.password-confirmed-page p.MuiTypography-root, -.forgot-password-page p.MuiTypography-root, -.check-email-page p.MuiTypography-root { - color: var(--env-var-color-2); -} - -.set-new-password-page button:not(.MuiIconButton-root) svg, -.password-confirmed-page button:not(.MuiIconButton-root) svg, -.forgot-password-page button svg, -.check-email-page button svg { - margin-right: 5px; -} - -.set-new-password-page - .MuiFormControl-root:has(#register-password-input) - + span.MuiTypography-root, -.password-confirmed-page - .MuiFormControl-root:has(#register-password-input) - + span.MuiTypography-root { - display: none; - visibility: hidden; -} - -/* /////////// */ -/* Check Email */ -/* /////////// */ -.check-email-page span.MuiTypography-root { - font-weight: 600; -} - /* AUTH */ .auth { height: 100vh; @@ -106,13 +29,13 @@ cursor: pointer; transition: opacity 300ms ease-in; } -.auth p > span { +.auth p > span:not(.email-sent-to) { text-decoration: underline; text-underline-offset: 2px; cursor: pointer; transition: all 200ms; } -.auth p > span:hover { +.auth p > span:not(.email-sent-to):hover { color: var(--env-var-color-2); text-underline-offset: 4px; } @@ -135,6 +58,13 @@ .auth .check span.MuiTypography-root { font-size: var(--env-var-font-size-medium); } +.auth form + form { + margin: var(--env-var-spacing-1-plus) 0; +} +.auth .email-sent-to { + font-weight: 600; + cursor: default; +} .auth > .MuiStack-root:nth-of-type(1) { height: var(--env-var-nav-bar-height); @@ -144,3 +74,10 @@ width: 100%; max-width: 800px; } + +.forgot-password-page h1, +.check-email-page h1, +.password-confirmed-page h1, +.set-new-password-page h1 { + font-size: var(--env-var-font-size-large-plus); +} diff --git a/Client/src/Pages/Incidents/IncidentTable/index.jsx b/Client/src/Pages/Incidents/IncidentTable/index.jsx index 95230bb72..44a30decc 100644 --- a/Client/src/Pages/Incidents/IncidentTable/index.jsx +++ b/Client/src/Pages/Incidents/IncidentTable/index.jsx @@ -9,6 +9,8 @@ import { Pagination, PaginationItem, Paper, + Typography, + Box, } from "@mui/material"; import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; @@ -105,41 +107,55 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => { return ( <> - - - - - Monitor Name - Status - Date & Time - Message - - - - {checks.map((check) => { - const status = check.status === true ? "up" : "down"; - - return ( - - {monitors[check.monitorId]?.name} - - - - - {new Date(check.createdAt).toLocaleString()} - - {check.statusCode} + {checks?.length === 0 && selectedMonitor === "0" ? ( + + No incidents recorded yet. + + ) : checks?.length === 0 ? ( + + + The monitor you have selected has no recorded incidents yet. + + + ) : ( + <> + +
+ + + Monitor Name + Status + Date & Time + Message - ); - })} - -
-
- {paginationComponent} + + + {checks.map((check) => { + const status = check.status === true ? "up" : "down"; + + return ( + + {monitors[check.monitorId]?.name} + + + + + {new Date(check.createdAt).toLocaleString()} + + {check.statusCode} + + ); + })} + + + + {paginationComponent} + + )} ); }; diff --git a/Client/src/Pages/Incidents/index.css b/Client/src/Pages/Incidents/index.css index 1f176d470..286552d22 100644 --- a/Client/src/Pages/Incidents/index.css +++ b/Client/src/Pages/Incidents/index.css @@ -17,6 +17,12 @@ .incidents .MuiPagination-root { background-color: var(--env-var-color-8); } +.incidents > .MuiBox-root { + background-color: var(--env-var-color-8); + border: solid 1px var(--env-var-color-6); + border-radius: var(--env-var-radius-1); + padding: 60px; +} /* remove later */ .incidents .MuiSelect-select { diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 4168880d5..918077a91 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -5,7 +5,6 @@ import { useSelector } from "react-redux"; import { useNavigate, useParams } from "react-router-dom"; import axiosInstance from "../../../Utils/axiosConfig"; import MonitorDetailsAreaChart from "../../../Components/Charts/MonitorDetailsAreaChart"; -import { StatusLabel } from "../../../Components/Label"; import ButtonGroup from "@mui/material/ButtonGroup"; import Button from "../../../Components/Button"; import WestRoundedIcon from "@mui/icons-material/WestRounded"; @@ -119,7 +118,7 @@ const DetailsPage = () => { const fetchMonitor = useCallback(async () => { try { const res = await axiosInstance.get( - `/monitors/${monitorId}?sortOrder=asc&filter=${dateRange}&numToDisplay=50&normalize=true`, + `/monitors/stats/${monitorId}?filter=${dateRange}&numToDisplay=50&normalize=true`, { headers: { Authorization: `Bearer ${authToken}`, @@ -139,67 +138,6 @@ const DetailsPage = () => { const theme = useTheme(); - /** - * Function to calculate uptime duration based on the most recent check. - * @param {Array} checks Array of check objects. - * @returns {number} Uptime duration in ms. - */ - - // TODO: This can be done more efficiently by iteratting backwards - // and breaking when the first down check is found, calculate current time - downtime - const calculateUptimeDuration = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - - const latestCheck = new Date(checks[checks.length - 1].createdAt); - let latestDownCheck = 0; - - // Checks are ordered oldest -> newest - // So we iterate backwards and find the first down check - for (let i = checks.length - 1; i >= 0; i--) { - if (checks[i].status === false) { - latestDownCheck = new Date(checks[i].createdAt); - break; - } - } - - // If no down check is found, uptime is from the first check to now - if (latestDownCheck === 0) { - return Date.now() - new Date(checks[0].createdAt); - } - - // Otherwise the uptime is from the last check to the last down check - return latestCheck - latestDownCheck; - }; - - /** - * Helper function to get duration since last check - * @param {Array} checks Array of check objects. - * @returns {number} Timestamp of the most recent check. - */ - const getLastChecked = (checks) => { - if (!checks || checks.length === 0) { - return 0; // Handle case when no checks are available - } - // Data is sorted oldest -> newest, so last check is the most recent - return new Date() - new Date(checks[checks.length - 1].createdAt); - }; - - /** - * Helper function to count incidents (checks with status === false). - * @param {Array} checks Array of check objects. - * @returns {number} Number of incidents. - */ - const countIncidents = (checks) => { - if (!checks || checks.length === 0) { - return 0; // Handle case when no checks are available - } - return checks.reduce((acc, check) => { - return check.status === false ? (acc += 1) : acc; - }, 0); - }; - let loading = Object.keys(monitor).length === 0; return ( @@ -243,7 +181,7 @@ const DetailsPage = () => { {" "} Checking every {formatDurationRounded(monitor?.interval)}. Last time checked{" "} - {formatDurationRounded(getLastChecked(monitor?.checks))} ago. + {formatDurationRounded(monitor?.lastChecked)} ago.