redesign app layout: removed navbar and added profile menu to sidebar

This commit is contained in:
Daniel Cojocea
2024-08-13 20:17:18 -04:00
parent 72d3635f12
commit baeacd15a2
7 changed files with 182 additions and 247 deletions

View File

@@ -18,7 +18,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,7 +35,7 @@ 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,
display: "inline-flex",
"&::before": {
@@ -52,7 +52,8 @@ const Avatar = ({ src, small, sx }) => {
...sx,
}}
>
{user.firstName?.charAt(0)}{user.lastName?.charAt(0)}
{user.firstName?.charAt(0)}
{!small && user.lastName?.charAt(0)}
</MuiAvatar>
);
};

View File

@@ -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;
}

View File

@@ -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: <UserSvg />,
Team: <TeamSvg />,
Password: <LockSvg />,
Logout: <LogoutSvg />,
};
function AddBorderOnScroll(props) {
const { children, window } = props;
const trigger = useScrollTrigger({
target: window ? window() : undefined,
disableHysteresis: true,
threshold: 0,
});
return (
<AppBar
className={trigger ? "scrolled" : ""}
position="sticky"
sx={{
boxShadow: "none",
transition: "all 0.3s ease",
borderBottom: "1px solid transparent",
"&.scrolled": {
borderBottom: "1px solid #eaecf0",
backgroundColor: "white",
},
}}
>
{children}
</AppBar>
);
}
/**
* NavBar component
*
* A responsive navigation bar component with a user menu.
*
* @component
* @example
* return (
* <NavBar />
* )
*/
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<HTMLElement>} 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 (
<AddBorderOnScroll>
<Toolbar disableGutters sx={{ alignSelf: "flex-end", paddingX: "25px" }}>
<Tooltip title="Open settings">
<IconButton
id="icon-button"
onClick={handleOpenUserMenu}
sx={{ p: 0 }}
>
<Stack direction="row" alignItems="center" gap="8px">
<Avatar small={true} />
<Box
className="icon-button-toggle-title"
sx={{ mr: "3px", lineHeight: 2 }}
>
{authState.user?.firstName} {authState.user?.lastName}
</Box>
<KeyboardArrowDownIcon sx={{ mt: "2px" }} />
</Stack>
</IconButton>
</Tooltip>
<Menu
sx={{
mt: theme.spacing(5.5),
borderRadius: "4px",
}}
id="menu-appbar"
anchorEl={anchorElUser}
anchorOrigin={{
vertical: "top",
horizontal: "right",
}}
keepMounted
transformOrigin={{
vertical: "top",
horizontal: "right",
}}
open={Boolean(anchorElUser)}
onClose={handleCloseUserMenu}
>
{settings.map((setting) => (
<MenuItem
id="menu-item"
key={setting}
onClick={() => handleCloseUserMenu(setting)}
sx={{ width: "150px" }}
>
{icons[setting]}
<Typography
fontSize="var(--env-var-font-size-medium)"
textAlign="center"
marginLeft="8px"
sx={{ fontWeight: 400, color: "#344054" }}
>
{setting}
</Typography>
</MenuItem>
))}
</Menu>
</Toolbar>
</AddBorderOnScroll>
);
}
export default NavBar;

View File

@@ -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);
}

View File

@@ -1,7 +1,15 @@
import { Stack, Typography } from "@mui/material";
import { useState } from "react";
import { Box, 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 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,6 +19,7 @@ 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";
@@ -32,10 +41,80 @@ const menu = [
{ name: "Settings", path: "settings", icon: <Settings /> },
];
const icons = {
Profile: <UserSvg />,
Team: <TeamSvg />,
Password: <LockSvg />,
Logout: <LogoutSvg />,
};
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<HTMLElement>} 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 (
<Stack component="aside" gap={theme.gap.xs}>
@@ -48,6 +127,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 +140,66 @@ function Sidebar() {
)
: navigate(`/${item.path}`)
}
sx={{ p: `${theme.gap.small} ${theme.gap.medium}` }}
>
{item.icon}
<Typography component="span">{item.name}</Typography>
</Stack>
))}
<Tooltip
title="Open settings"
slotProps={{
popper: {
modifiers: [
{
name: "offset",
options: {
offset: [0, -14],
},
},
],
},
}}
>
<Stack
direction="row"
alignItems="center"
py={theme.gap.small}
px={theme.gap.medium}
gap={theme.gap.xs}
borderRadius={`${theme.shape.borderRadius}px`}
onClick={handleOpenUserMenu}
>
<Avatar small={true} />
<Typography component="span" ml={theme.gap.xs}>
{authState.user?.firstName} {authState.user?.lastName}
</Typography>
<Arrow style={{marginTop: "2px"}}/>
</Stack>
</Tooltip>
<Menu
className="sidebar-menu"
anchorEl={anchorElUser}
anchorOrigin={{
vertical: "top",
horizontal: "center",
}}
keepMounted
open={Boolean(anchorElUser)}
onClose={handleCloseUserMenu}
autoFocus={false}
sx={{
ml: theme.gap.xxl,
}}
>
{settings.map((setting) => (
<MenuItem key={setting} onClick={() => handleCloseUserMenu(setting)}>
{icons[setting]}
<Typography component="span" ml={theme.gap.small}>
{setting}
</Typography>
</MenuItem>
))}
</Menu>
</Stack>
);
}

View File

@@ -29,7 +29,7 @@ const Account = ({ open = "profile" }) => {
if (!user.role.includes("admin")) tabList = ["Profile", "Password"];
return (
<Box className="account">
<Box className="account" pt={theme.gap.xl}>
<TabContext value={tab}>
<Box
sx={{

View File

@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 9L12 15L18 9" stroke="#667085" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 212 B