diff --git a/Client/src/Components/Avatar/index.jsx b/Client/src/Components/Avatar/index.jsx
index 4f4351508..2a9f70a08 100644
--- a/Client/src/Components/Avatar/index.jsx
+++ b/Client/src/Components/Avatar/index.jsx
@@ -3,6 +3,27 @@ import PropTypes from "prop-types";
import { useSelector } from "react-redux";
import { useEffect, useState } from "react";
+/**
+ * Generates a color based on the input string.
+ * @param {string} string - The input string to generate the color from.
+ * @returns {string}
+ */
+const stringToColor = (string) => {
+ let hash = 0;
+ let i;
+ for (i = 0; i < string.length; i += 1) {
+ hash = string.charCodeAt(i) + ((hash << 5) - hash);
+ }
+
+ let color = "#";
+ for (i = 0; i < 3; i += 1) {
+ const value = (hash >> (i * 8)) & 0xff;
+ color += `00${value.toString(16)}`.slice(-2);
+ }
+
+ return color;
+};
+
/**
* @component
* @param {Object} props
@@ -18,7 +39,7 @@ import { useEffect, useState } from "react";
const Avatar = ({ src, small, sx }) => {
const { user } = useSelector((state) => state.auth);
- const style = small ? { width: 25, height: 25 } : { width: 64, height: 64 };
+ const style = small ? { width: 32, height: 32 } : { width: 64, height: 64 };
const border = small ? 1 : 3;
const [image, setImage] = useState();
@@ -35,8 +56,9 @@ const Avatar = ({ src, small, sx }) => {
src ? src : user?.avatarImage ? image : "/static/images/avatar/2.jpg"
}
sx={{
- fontSize: small ? "13px" : "22px",
+ fontSize: small ? "16px" : "22px",
fontWeight: 400,
+ backgroundColor: stringToColor(`${user?.firstName} ${user?.lastName}`),
display: "inline-flex",
"&::before": {
content: `""`,
@@ -52,7 +74,8 @@ const Avatar = ({ src, small, sx }) => {
...sx,
}}
>
- {user.firstName?.charAt(0)}{user.lastName?.charAt(0)}
+ {user.firstName?.charAt(0)}
+ {user.lastName?.charAt(0)}
);
};
diff --git a/Client/src/Components/NavBar/index.css b/Client/src/Components/NavBar/index.css
deleted file mode 100644
index fcedc53a2..000000000
--- a/Client/src/Components/NavBar/index.css
+++ /dev/null
@@ -1,34 +0,0 @@
-/* NavBar Component Styles*/
-.MuiToolbar-root {
- min-height: var(--env-var-nav-bar-height) !important;
-}
-
-.icon-button-toggle-title {
- font-size: var(--env-var-font-size-medium);
- color: var(--env-var-color-5);
-}
-
-.icon-button-toggle-pic {
- width: 10px;
- height: 5px;
-}
-
-#icon-button {
- -webkit-transition: none;
- transition: none;
- outline: none;
- box-shadow: none;
- background-color: transparent;
-}
-
-#menu-appbar svg {
- width: 16px;
- height: 16px;
- color: var(--env-var-color-25);
-}
-
-#bw-uptime-logo-dashboard {
- width: fit-content;
- height: 16px;
- margin-left: 15px;
-}
diff --git a/Client/src/Components/NavBar/index.jsx b/Client/src/Components/NavBar/index.jsx
deleted file mode 100644
index 95123d975..000000000
--- a/Client/src/Components/NavBar/index.jsx
+++ /dev/null
@@ -1,203 +0,0 @@
-import "./index.css";
-import { cloneElement, useState } from "react";
-import AppBar from "@mui/material/AppBar";
-import Box from "@mui/material/Box";
-import Toolbar from "@mui/material/Toolbar";
-import IconButton from "@mui/material/IconButton";
-import Typography from "@mui/material/Typography";
-import Menu from "@mui/material/Menu";
-import Avatar from "../Avatar";
-import Tooltip from "@mui/material/Tooltip";
-import MenuItem from "@mui/material/MenuItem";
-import { useTheme } from "@mui/material/styles";
-import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
-import { clearAuthState } from "../../Features/Auth/authSlice";
-import { clearUptimeMonitorState } from "../../Features/UptimeMonitors/uptimeMonitorsSlice";
-
-import { useDispatch, useSelector } from "react-redux";
-import { useNavigate } from "react-router-dom";
-import LockSvg from "../../assets/icons/lock.svg?react";
-import UserSvg from "../../assets/icons/user.svg?react";
-import TeamSvg from "../../assets/icons/user-two.svg?react";
-import LogoutSvg from "../../assets/icons/logout.svg?react";
-import { Stack, useScrollTrigger } from "@mui/material";
-import axiosIntance from "../../Utils/axiosConfig";
-import axios from "axios";
-
-const icons = {
- Profile: ,
- Team: ,
- Password: ,
- Logout: ,
-};
-
-function AddBorderOnScroll(props) {
- const { children, window } = props;
- const trigger = useScrollTrigger({
- target: window ? window() : undefined,
- disableHysteresis: true,
- threshold: 0,
- });
-
- return (
-
- {children}
-
- );
-}
-
-/**
- * NavBar component
- *
- * A responsive navigation bar component with a user menu.
- *
- * @component
- * @example
- * return (
- *
- * )
- */
-function NavBar() {
- const theme = useTheme();
- const [anchorElUser, setAnchorElUser] = useState(null);
- const dispatch = useDispatch();
- const navigate = useNavigate();
- const authState = useSelector((state) => state.auth);
-
- // Initialize settings and update based on user role
- let settings = ["Profile", "Password", "Team", "Logout"];
- if (authState.user?.role && !authState.user.role.includes("admin")) {
- settings = ["Profile", "Password", "Logout"];
- }
-
- /**
- * Handles opening the user menu.
- *
- * @param {React.MouseEvent} event - The event triggered by clicking the user menu button.
- */
- const handleOpenUserMenu = (event) => {
- setAnchorElUser(event.currentTarget);
- };
-
- /**
- * Handles logging out the user
- *
- */
- const logout = async () => {
- // Clear auth state
- dispatch(clearAuthState());
- dispatch(clearUptimeMonitorState());
- // Make request to BE to remove JWT from user
- await axiosIntance.post(
- "/auth/logout",
- { email: authState.user.email },
- {
- headers: {
- Authorization: `Bearer ${authState.authToken}`,
- "Content-Type": "application/json",
- },
- }
- );
- navigate("/login");
- };
-
- /**
- * Handles closing the user menu.
- */
- const handleCloseUserMenu = (setting) => {
- setAnchorElUser(null);
- switch (setting) {
- case "Profile":
- navigate("/account/profile");
- break;
- case "Team":
- navigate("/account/team");
- break;
- case "Password":
- navigate("/account/password");
- break;
- case "Logout":
- logout();
- break;
- default:
- break;
- }
- };
-
- return (
-
-
-
-
-
-
-
- {authState.user?.firstName} {authState.user?.lastName}
-
-
-
-
-
-
-
-
- );
-}
-
-export default NavBar;
diff --git a/Client/src/Components/Sidebar/index.css b/Client/src/Components/Sidebar/index.css
index 6fa9d2134..4840043c3 100644
--- a/Client/src/Components/Sidebar/index.css
+++ b/Client/src/Components/Sidebar/index.css
@@ -18,11 +18,14 @@ aside span.MuiTypography-root {
color: var(--env-var-color-5);
line-height: 1;
}
-aside .MuiStack-root:nth-last-child(2) {
+aside .MuiStack-root:nth-last-child(3) {
margin-top: auto;
+}
+aside .MuiStack-root:last-child,
+aside .MuiStack-root:nth-last-child(3) {
position: relative;
}
-aside .MuiStack-root:nth-last-child(2):before {
+aside .MuiStack-root:last-child:before {
content: "";
position: absolute;
top: -10px;
@@ -31,5 +34,35 @@ aside .MuiStack-root:nth-last-child(2):before {
border-top: solid 1px var(--env-var-color-6);
}
aside .MuiStack-root:last-child {
- margin-bottom: 10px;
+ margin-top: 15px;
+}
+
+.sidebar-menu {
+ margin-top: -20px;
+}
+.sidebar-menu .MuiPaper-root {
+ box-shadow: var(--env-var-shadow-1);
+ border: solid 1px var(--env-var-color-6);
+ border-radius: var(--env-var-radius-1);
+ gap: 1px;
+}
+.sidebar-menu .MuiList-root {
+ min-width: 100px;
+ width: 150px;
+ padding-bottom: 0;
+}
+.sidebar-menu li.MuiButtonBase-root:last-child {
+ border-top: solid 1px var(--env-var-color-6);
+ padding: 12px 16px;
+}
+.sidebar-menu li.MuiButtonBase-root {
+ min-height: fit-content;
+}
+.sidebar-menu .MuiList-root svg {
+ width: 16px;
+ height: 16px;
+}
+.sidebar-menu span {
+ font-size: var(--env-var-font-size-medium);
+ color: var(--env-var-color-2);
}
diff --git a/Client/src/Components/Sidebar/index.jsx b/Client/src/Components/Sidebar/index.jsx
index 3b2a32394..5a5b1d980 100644
--- a/Client/src/Components/Sidebar/index.jsx
+++ b/Client/src/Components/Sidebar/index.jsx
@@ -1,7 +1,17 @@
-import { Stack, Typography } from "@mui/material";
+import { useState } from "react";
+import { Menu, MenuItem, Stack, Tooltip, Typography } from "@mui/material";
import { useLocation, useNavigate } from "react-router";
import { useTheme } from "@emotion/react";
-
+import { useDispatch, useSelector } from "react-redux";
+import { clearAuthState } from "../../Features/Auth/authSlice";
+import { clearUptimeMonitorState } from "../../Features/UptimeMonitors/uptimeMonitorsSlice";
+import { createToast } from "../../Utils/toastUtils";
+import axiosInstance from "../../Utils/axiosConfig";
+import Avatar from "../Avatar";
+import LockSvg from "../../assets/icons/lock.svg?react";
+import UserSvg from "../../assets/icons/user.svg?react";
+import TeamSvg from "../../assets/icons/user-two.svg?react";
+import LogoutSvg from "../../assets/icons/logout.svg?react";
import BWULogo from "../../assets/Images/bwl-logo.svg?react";
import Support from "../../assets/icons/support.svg?react";
import StatusPages from "../../assets/icons/status-pages.svg?react";
@@ -11,16 +21,10 @@ import Incidents from "../../assets/icons/incidents.svg?react";
import Integrations from "../../assets/icons/integrations.svg?react";
import PageSpeed from "../../assets/icons/page-speed.svg?react";
import Settings from "../../assets/icons/settings.svg?react";
+import Arrow from "../../assets/icons/down-arrow.svg?react";
import "./index.css";
-/**
- * @component
- * Sidebar component serves as a sidebar containing a menu.
- *
- * @returns {JSX.Element} The JSX element representing the Sidebar component.
- */
-
const menu = [
{ name: "Monitors", path: "monitors", icon: },
{ name: "Incidents", path: "incidents", icon: },
@@ -32,10 +36,94 @@ const menu = [
{ name: "Settings", path: "settings", icon: },
];
+const icons = {
+ Profile: ,
+ Team: ,
+ Password: ,
+ Logout: ,
+};
+
+/**
+ * @component
+ * Sidebar component serves as a sidebar containing a menu.
+ *
+ * @returns {JSX.Element} The JSX element representing the Sidebar component.
+ */
+
function Sidebar() {
const theme = useTheme();
const navigate = useNavigate();
const location = useLocation();
+ const dispatch = useDispatch();
+ const [anchorElUser, setAnchorElUser] = useState(null);
+ const authState = useSelector((state) => state.auth);
+
+ // Initialize settings and update based on user role
+ let settings = ["Profile", "Password", "Team", "Logout"];
+ if (authState.user?.role && !authState.user.role.includes("admin")) {
+ settings = ["Profile", "Password", "Logout"];
+ }
+
+ /**
+ * Handles opening the user menu.
+ *
+ * @param {React.MouseEvent} event - The event triggered by clicking the user menu button.
+ */
+ const handleOpenUserMenu = (event) => {
+ setAnchorElUser(event.currentTarget);
+ };
+
+ /**
+ * Handles logging out the user
+ *
+ */
+ const logout = async () => {
+ try {
+ // Make request to BE to remove JWT from user
+ await axiosInstance.post(
+ "/auth/logout",
+ { email: authState.user.email },
+ {
+ headers: {
+ Authorization: `Bearer ${authState.authToken}`,
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ // Clear auth state
+ dispatch(clearAuthState());
+ dispatch(clearUptimeMonitorState());
+ navigate("/login");
+ } catch (error) {
+ createToast({
+ body: error.message,
+ });
+ }
+ };
+
+ /**
+ * Handles closing the user menu.
+ */
+ const handleCloseUserMenu = (setting) => {
+ setAnchorElUser(null);
+ switch (setting) {
+ case "Profile":
+ navigate("/account/profile");
+ break;
+ case "Team":
+ navigate("/account/team");
+ break;
+ case "Password":
+ navigate("/account/password");
+ break;
+ case "Logout":
+ logout();
+ break;
+ default:
+ break;
+ }
+ };
return (
@@ -48,6 +136,8 @@ function Sidebar() {
key={item.path}
direction="row"
alignItems="center"
+ py={theme.gap.small}
+ px={theme.gap.medium}
gap={theme.gap.small}
borderRadius={`${theme.shape.borderRadius}px`}
onClick={() =>
@@ -59,12 +149,66 @@ function Sidebar() {
)
: navigate(`/${item.path}`)
}
- sx={{ p: `${theme.gap.small} ${theme.gap.medium}` }}
>
{item.icon}
{item.name}
))}
+
+
+
+
+ {authState.user?.firstName} {authState.user?.lastName}
+
+
+
+
+
);
}
diff --git a/Client/src/Layouts/HomeLayout/index.css b/Client/src/Layouts/HomeLayout/index.css
index 048700db6..a62016a60 100644
--- a/Client/src/Layouts/HomeLayout/index.css
+++ b/Client/src/Layouts/HomeLayout/index.css
@@ -1,55 +1,34 @@
-.home-layout {
- display: grid;
- position: relative;
- grid-template-areas:
- "aside header"
- "aside main";
- grid-template-columns: var(--env-var-side-bar-width) auto;
- grid-template-rows: var(--env-var-nav-bar-height) auto;
+#root:has(.home-layout) {
background-color: var(--env-var-color-30);
+}
+
+.home-layout {
+ display: flex;
+ position: relative;
+ gap: var(--env-var-spacing-2);
min-height: 100vh;
+ max-width: 1400px;
+ margin: 0 auto;
+ padding: var(--env-var-spacing-2);
}
.home-layout aside {
- grid-area: aside;
- position: fixed;
- top: 0;
- height: 100%;
- width: var(--env-var-side-bar-width);
+ position: sticky;
+ top: var(--env-var-spacing-2);
+ left: 0;
+
+ height: calc(100vh - var(--env-var-spacing-2) * 2);
+ max-width: var(--env-var-side-bar-width);
+ flex: 1;
+
+ border: 1px solid var(--color-border-0);
+ border-radius: var(--env-var-radius-1);
background-color: var(--env-var-color-8);
- border-right: 1px solid var(--color-border-0);
+
padding: var(--env-var-spacing-1) var(--env-var-spacing-1-plus);
}
-.home-layout header {
- grid-area: header;
- background-color: var(--env-var-color-30);
-}
+
.home-layout > div {
- height: 100%;
- grid-area: main;
- padding: var(--env-var-spacing-2) calc(var(--env-var-spacing-4) * 2);
- padding-bottom: var(--env-var-spacing-4);
- max-width: 100%;
-}
-.home-layout > div:not(:has([class*="fallback"])) {
- max-width: 1500px;
-}
-
-@media (max-width: 600px) {
- .home-layout {
- grid-template-areas:
- "header header"
- "main main";
- grid-template-columns: 1fr;
- grid-template-rows: 64px auto;
- }
-
- aside {
- display: none;
- visibility: hidden;
- }
-
- .home-layout > div {
- padding: var(--env-var-spacing-1);
- }
+ min-height: calc(100vh - var(--env-var-spacing-2) * 2);
+ flex: 1;
}
diff --git a/Client/src/Layouts/HomeLayout/index.jsx b/Client/src/Layouts/HomeLayout/index.jsx
index 007fb7d05..81cb2fd9c 100644
--- a/Client/src/Layouts/HomeLayout/index.jsx
+++ b/Client/src/Layouts/HomeLayout/index.jsx
@@ -1,4 +1,3 @@
-import NavBar from "../../Components/NavBar";
import Sidebar from "../../Components/Sidebar";
import { Outlet } from "react-router";
import { Box } from "@mui/material";
@@ -9,7 +8,6 @@ const HomeLayout = () => {
return (
-
);
diff --git a/Client/src/Pages/Account/index.jsx b/Client/src/Pages/Account/index.jsx
index db08ec3a2..82f37a969 100644
--- a/Client/src/Pages/Account/index.jsx
+++ b/Client/src/Pages/Account/index.jsx
@@ -29,7 +29,7 @@ const Account = ({ open = "profile" }) => {
if (!user.role.includes("admin")) tabList = ["Profile", "Password"];
return (
-
+
{
};
return (
-
+
{loading ? (
) : (
diff --git a/Client/src/Pages/Integrations/index.jsx b/Client/src/Pages/Integrations/index.jsx
index f7d693948..278754fea 100644
--- a/Client/src/Pages/Integrations/index.jsx
+++ b/Client/src/Pages/Integrations/index.jsx
@@ -108,7 +108,7 @@ const Integrations = () => {
];
return (
-
+
Integrations
Connect BlueWave Uptime to your favorite service.
diff --git a/Client/src/Pages/Monitors/Configure/index.css b/Client/src/Pages/Monitors/Configure/index.css
index a594348ab..ac9cd8a0d 100644
--- a/Client/src/Pages/Monitors/Configure/index.css
+++ b/Client/src/Pages/Monitors/Configure/index.css
@@ -75,7 +75,7 @@
.configure-monitor-form {
display: flex;
flex-direction: column;
- gap: var(--env-var-spacing-4);
+ gap: var(--env-var-spacing-2);
}
body:has(.configure-monitor) .select-dropdown .MuiMenuItem-root,
diff --git a/Client/src/Pages/Monitors/CreateMonitor/index.css b/Client/src/Pages/Monitors/CreateMonitor/index.css
index 1eb542a44..3fa38f14a 100644
--- a/Client/src/Pages/Monitors/CreateMonitor/index.css
+++ b/Client/src/Pages/Monitors/CreateMonitor/index.css
@@ -59,7 +59,7 @@
.create-monitor-form {
display: flex;
flex-direction: column;
- gap: var(--env-var-spacing-4);
+ gap: var(--env-var-spacing-2);
}
.create-monitor .MuiStack-root .MuiButtonGroup-root button {
diff --git a/Client/src/Pages/Monitors/index.jsx b/Client/src/Pages/Monitors/index.jsx
index c1e987827..f8efd7fa3 100644
--- a/Client/src/Pages/Monitors/index.jsx
+++ b/Client/src/Pages/Monitors/index.jsx
@@ -243,7 +243,7 @@ const Monitors = () => {
let loading = monitorState.isLoading && monitorState.monitors.length === 0;
return (
-
+
{loading ? (
) : (
@@ -253,7 +253,10 @@ const Monitors = () => {
justifyContent="space-between"
alignItems="center"
>
-
+
Hello, {authState.user.firstName}
{monitorState.monitors?.length !== 0 && (
@@ -302,7 +305,8 @@ const Monitors = () => {
{
onClick={() => navigate("/pagespeed")}
sx={{
backgroundColor: theme.palette.otherColors.fillGray,
- mb: theme.gap.large,
+ mb: theme.gap.medium,
px: theme.gap.ml,
"& svg.MuiSvgIcon-root": {
mr: theme.gap.small,
@@ -122,8 +122,6 @@ const CreatePageSpeed = () => {
display: "flex",
flexDirection: "column",
gap: theme.gap.large,
- // TODO
- maxWidth: "1000px",
}}
>
Create a page speed monitor
diff --git a/Client/src/Pages/PageSpeed/Details/index.jsx b/Client/src/Pages/PageSpeed/Details/index.jsx
index 963b0b31f..ed8c942af 100644
--- a/Client/src/Pages/PageSpeed/Details/index.jsx
+++ b/Client/src/Pages/PageSpeed/Details/index.jsx
@@ -363,7 +363,7 @@ const PageSpeedDetails = () => {
{
Performance report
-
+
{
let isActuallyLoading = isLoading && monitors.length === 0;
return (
-
+
{isActuallyLoading ? (
) : monitors?.length !== 0 ? (
diff --git a/Client/src/Utils/Theme.js b/Client/src/Utils/Theme.js
index c065798d2..c4faa2d1f 100644
--- a/Client/src/Utils/Theme.js
+++ b/Client/src/Utils/Theme.js
@@ -96,9 +96,10 @@ const theme = createTheme({
ml: "16px",
mlplus: "20px",
large: "24px",
+ lgplus: "32px",
xl: "40px",
xxl: "60px",
- triplexl: "120px"
+ triplexl: "120px",
},
alert: {
info: {
diff --git a/Client/src/assets/icons/down-arrow.svg b/Client/src/assets/icons/down-arrow.svg
new file mode 100644
index 000000000..7c6a715ed
--- /dev/null
+++ b/Client/src/assets/icons/down-arrow.svg
@@ -0,0 +1,3 @@
+