Files
Checkmate/Client/src/Components/Sidebar/index.jsx
T

666 lines
21 KiB
React
Raw Normal View History

2024-08-16 13:16:04 -04:00
import React, { useEffect, useState } from "react";
2024-08-15 20:34:34 -04:00
import {
Box,
2024-08-16 12:13:05 -04:00
Collapse,
2024-08-16 14:17:11 -04:00
Divider,
2024-08-16 12:53:41 -04:00
IconButton,
2024-08-15 20:34:34 -04:00
List,
ListItemButton,
ListItemIcon,
ListItemText,
ListSubheader,
2024-08-19 14:01:26 -04:00
Menu,
MenuItem,
2024-08-15 20:34:34 -04:00
Stack,
Tooltip,
Typography,
} from "@mui/material";
2024-07-26 12:39:18 -04:00
import { useLocation, useNavigate } from "react-router";
import { useTheme } from "@emotion/react";
import { useDispatch, useSelector } from "react-redux";
import { clearAuthState } from "../../Features/Auth/authSlice";
2024-08-28 22:14:55 -04:00
import { setMode, toggleSidebar } from "../../Features/UI/uiSlice";
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";
2024-07-26 12:39:18 -04:00
import Support from "../../assets/icons/support.svg?react";
2024-08-16 12:19:03 -04:00
import Dashboard from "../../assets/icons/dashboard.svg?react";
import Account from "../../assets/icons/user-edit.svg?react";
2024-07-26 12:39:18 -04:00
import StatusPages from "../../assets/icons/status-pages.svg?react";
import Maintenance from "../../assets/icons/maintenance.svg?react";
import Monitors from "../../assets/icons/monitors.svg?react";
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";
2024-08-16 12:13:05 -04:00
import ArrowDown from "../../assets/icons/down-arrow.svg?react";
import ArrowUp from "../../assets/icons/up-arrow.svg?react";
2024-08-19 13:23:17 -04:00
import ArrowRight from "../../assets/icons/right-arrow.svg?react";
import ArrowLeft from "../../assets/icons/left-arrow.svg?react";
2024-08-28 22:14:55 -04:00
import DotsVertical from "../../assets/icons/dots-vertical.svg?react";
2024-07-26 12:39:18 -04:00
import "./index.css";
const menu = [
2024-08-16 12:13:05 -04:00
{
name: "Dashboard",
2024-08-16 12:19:03 -04:00
icon: <Dashboard />,
2024-08-16 12:13:05 -04:00
nested: [
{ name: "Monitors", path: "monitors", icon: <Monitors /> },
{ name: "Pagespeed", path: "pagespeed", icon: <PageSpeed /> },
],
},
2024-07-26 12:39:18 -04:00
{ name: "Incidents", path: "incidents", icon: <Incidents /> },
2024-08-23 11:29:28 -07:00
// { name: "Status pages", path: "status", icon: <StatusPages /> },
2024-07-26 12:39:18 -04:00
{ name: "Maintenance", path: "maintenance", icon: <Maintenance /> },
2024-08-23 11:29:28 -07:00
// { name: "Integrations", path: "integrations", icon: <Integrations /> },
2024-08-16 12:13:05 -04:00
{
name: "Account",
2024-08-16 12:19:03 -04:00
icon: <Account />,
2024-08-16 12:13:05 -04:00
nested: [
{ name: "Profile", path: "account/profile", icon: <UserSvg /> },
2024-08-16 12:19:03 -04:00
{ name: "Password", path: "account/password", icon: <LockSvg /> },
{ name: "Team", path: "account/team", icon: <TeamSvg /> },
2024-08-16 12:13:05 -04:00
],
},
2024-08-15 20:34:34 -04:00
];
const other = [
2024-07-26 12:39:18 -04:00
{ name: "Support", path: "support", icon: <Support /> },
{ name: "Settings", path: "settings", icon: <Settings /> },
];
/**
* @component
* Sidebar component serves as a sidebar containing a menu.
*
* @returns {JSX.Element} The JSX element representing the Sidebar component.
*/
2024-07-26 12:39:18 -04:00
function Sidebar() {
const theme = useTheme();
const navigate = useNavigate();
const location = useLocation();
const dispatch = useDispatch();
const authState = useSelector((state) => state.auth);
const collapsed = useSelector((state) => state.ui.sidebar.collapsed);
2024-08-16 12:13:05 -04:00
const [open, setOpen] = useState({ Dashboard: false, Account: false });
2024-08-19 14:01:26 -04:00
const [anchorEl, setAnchorEl] = useState(null);
const [popup, setPopup] = useState();
2024-09-06 11:10:08 -07:00
const { user } = useSelector((state) => state.auth);
2024-09-06 11:51:55 -07:00
// Remove demo password if demo
if (user.role.includes("demo")) {
2024-09-06 11:10:08 -07:00
menu[3].nested = menu[3].nested.filter((item) => {
return item.name !== "Password";
});
}
2024-08-19 14:01:26 -04:00
const openPopup = (event, id) => {
setAnchorEl(event.currentTarget);
setPopup(id);
};
const closePopup = () => {
setAnchorEl(null);
};
/**
* Handles logging out the user
*
*/
const logout = async () => {
2024-08-15 11:39:45 -07:00
// Clear auth state
dispatch(clearAuthState());
dispatch(clearUptimeMonitorState());
navigate("/login");
};
2024-08-16 13:16:04 -04:00
useEffect(() => {
if (
location.pathname.includes("monitors") ||
location.pathname.includes("pagespeed")
)
setOpen((prev) => ({ ...prev, Dashboard: true }));
else if (location.pathname.includes("/account"))
setOpen((prev) => ({ ...prev, Account: true }));
}, []);
2024-07-26 12:39:18 -04:00
return (
2024-08-19 13:23:17 -04:00
<Stack
component="aside"
2024-08-22 17:13:29 -04:00
className={collapsed ? "collapsed" : "expanded"}
2024-08-27 20:11:59 -04:00
py={theme.spacing(6)}
2024-08-27 17:01:28 -04:00
gap={theme.spacing(6)}
sx={{
2024-08-27 20:11:59 -04:00
border: 1,
borderColor: theme.palette.border.light,
borderRadius: theme.shape.borderRadius,
backgroundColor: theme.palette.background.main,
2024-08-27 17:01:28 -04:00
"& .selected-path, & .MuiListItemButton-root:hover": {
backgroundColor: theme.palette.background.accent,
},
"& .MuiList-root svg path": {
stroke: theme.palette.text.tertiary,
},
"& p, & span, & .MuiListSubheader-root": {
color: theme.palette.text.secondary,
},
}}
2024-08-19 13:23:17 -04:00
>
2024-08-27 17:01:28 -04:00
<Stack pt={theme.spacing(6)} pb={theme.spacing(12)} pl={theme.spacing(8)}>
<Stack direction="row" alignItems="center" gap={theme.spacing(4)}>
2024-08-22 15:51:22 -04:00
<Stack
justifyContent="center"
alignItems="center"
2024-08-27 17:01:28 -04:00
minWidth={theme.spacing(16)}
minHeight={theme.spacing(16)}
pl="1px"
fontSize={18}
2024-08-22 15:51:22 -04:00
color="white"
sx={{
position: "relative",
backgroundColor: theme.palette.primary.main,
2024-08-27 17:01:28 -04:00
borderRadius: theme.shape.borderRadius,
2024-08-22 15:51:22 -04:00
userSelect: "none",
}}
>
BU
</Stack>
2024-08-22 17:13:29 -04:00
<Typography
component="span"
2024-08-27 17:01:28 -04:00
mt={theme.spacing(2)}
2024-08-22 17:13:29 -04:00
sx={{ opacity: 0.8, fontWeight: 500 }}
>
BlueWave Uptime
</Typography>
2024-08-22 15:51:22 -04:00
</Stack>
2024-08-19 13:23:17 -04:00
<IconButton
sx={{
position: "absolute",
2024-08-22 15:51:22 -04:00
top: 60,
2024-08-19 13:23:17 -04:00
right: 0,
2024-08-22 15:51:22 -04:00
transform: `translate(50%, 0)`,
2024-08-27 17:01:28 -04:00
backgroundColor: theme.palette.background.fill,
border: 1,
borderColor: theme.palette.border.light,
p: theme.spacing(2.5),
2024-08-19 13:23:17 -04:00
"& svg": {
2024-08-27 17:01:28 -04:00
width: theme.spacing(8),
height: theme.spacing(8),
2024-08-19 13:23:17 -04:00
"& path": {
2024-08-27 17:01:28 -04:00
stroke: theme.palette.text.secondary,
2024-08-19 13:23:17 -04:00
},
},
"&:focus": { outline: "none" },
"&:hover": {
2024-08-27 17:01:28 -04:00
backgroundColor: theme.palette.border.light,
borderColor: theme.palette.border.light,
2024-08-19 13:23:17 -04:00
},
}}
2024-08-22 17:13:29 -04:00
onClick={() => {
setOpen({ Dashboard: false, Account: false });
dispatch(toggleSidebar());
}}
2024-08-19 13:23:17 -04:00
>
{collapsed ? <ArrowRight /> : <ArrowLeft />}
</IconButton>
2024-08-19 14:37:25 -04:00
</Stack>
2024-08-15 20:34:34 -04:00
{/* menu */}
<List
component="nav"
aria-labelledby="nested-menu-subheader"
2024-08-16 14:17:11 -04:00
disablePadding
2024-08-15 20:34:34 -04:00
subheader={
2024-08-16 14:17:11 -04:00
<ListSubheader
component="div"
id="nested-menu-subheader"
2024-08-19 13:23:17 -04:00
sx={{
2024-08-27 17:01:28 -04:00
pt: theme.spacing(4),
px: collapsed ? theme.spacing(2) : theme.spacing(4),
backgroundColor: "transparent",
2024-08-19 13:23:17 -04:00
}}
2024-08-16 14:17:11 -04:00
>
2024-08-15 20:34:34 -04:00
Menu
</ListSubheader>
}
2024-08-27 17:01:28 -04:00
sx={{ px: theme.spacing(6) }}
2024-08-15 20:34:34 -04:00
>
2024-08-16 12:13:05 -04:00
{menu.map((item) =>
item.path ? (
2024-08-19 15:00:11 -04:00
<Tooltip
2024-08-19 15:04:04 -04:00
key={item.path}
2024-08-19 15:00:11 -04:00
placement="right"
title={collapsed ? item.name : ""}
slotProps={{
popper: {
modifiers: [
{
name: "offset",
options: {
offset: [0, -16],
},
},
],
},
2024-08-16 12:13:05 -04:00
}}
2024-08-20 17:34:54 -04:00
disableInteractive
2024-08-16 12:13:05 -04:00
>
2024-08-19 14:01:26 -04:00
<ListItemButton
2024-08-19 14:30:44 -04:00
className={
2024-08-19 15:00:11 -04:00
location.pathname.includes(item.path) ? "selected-path" : ""
2024-08-19 14:30:44 -04:00
}
2024-08-19 15:00:11 -04:00
onClick={() => navigate(`/${item.path}`)}
2024-08-19 14:01:26 -04:00
sx={{
2024-08-22 17:13:29 -04:00
height: "37px",
2024-08-27 17:01:28 -04:00
gap: theme.spacing(4),
borderRadius: theme.shape.borderRadius,
px: theme.spacing(4),
2024-08-19 14:01:26 -04:00
}}
>
<ListItemIcon sx={{ minWidth: 0 }}>{item.icon}</ListItemIcon>
2024-08-22 17:13:29 -04:00
<ListItemText>{item.name}</ListItemText>
2024-08-19 14:01:26 -04:00
</ListItemButton>
2024-08-19 15:00:11 -04:00
</Tooltip>
) : collapsed ? (
<React.Fragment key={item.name}>
<Tooltip
placement="right"
title={collapsed ? item.name : ""}
slotProps={{
popper: {
modifiers: [
{
name: "offset",
options: {
offset: [0, -16],
},
},
],
},
}}
2024-08-20 17:34:54 -04:00
disableInteractive
2024-08-19 15:00:11 -04:00
>
<ListItemButton
className={
Boolean(anchorEl) && popup === item.name
? "selected-path"
: ""
}
onClick={(event) => openPopup(event, item.name)}
sx={{
position: "relative",
2024-08-27 17:01:28 -04:00
gap: theme.spacing(4),
borderRadius: theme.shape.borderRadius,
px: theme.spacing(4),
2024-08-19 15:00:11 -04:00
}}
>
<ListItemIcon sx={{ minWidth: 0 }}>{item.icon}</ListItemIcon>
2024-08-22 17:13:29 -04:00
<ListItemText>{item.name}</ListItemText>
2024-08-19 15:00:11 -04:00
</ListItemButton>
</Tooltip>
2024-08-19 14:01:26 -04:00
<Menu
className="sidebar-popup"
anchorEl={anchorEl}
open={Boolean(anchorEl) && popup === item.name}
onClose={closePopup}
anchorOrigin={{
vertical: "top",
horizontal: "right",
}}
2024-08-27 17:01:28 -04:00
slotProps={{
paper: {
sx: {
mt: theme.spacing(-2),
2024-08-28 22:14:55 -04:00
ml: theme.spacing(1),
2024-08-27 17:01:28 -04:00
},
},
}}
MenuListProps={{ sx: { px: 1, py: 2 } }}
2024-08-27 17:01:28 -04:00
sx={{
ml: theme.spacing(8),
"& .selected-path": {
backgroundColor: theme.palette.background.accent,
},
}}
2024-08-19 14:01:26 -04:00
>
{item.nested.map((child) => {
if (
child.name === "Team" &&
authState.user?.role &&
2024-08-27 09:13:48 -07:00
!authState.user.role.includes("superadmin")
2024-08-19 14:01:26 -04:00
) {
return null;
}
return (
<MenuItem
className={
location.pathname.includes(child.path)
? "selected-path"
: ""
}
key={child.path}
2024-08-19 14:30:44 -04:00
onClick={() => {
navigate(`/${child.path}`);
closePopup();
}}
2024-08-19 14:01:26 -04:00
sx={{
2024-08-27 17:01:28 -04:00
gap: theme.spacing(4),
opacity: 0.9,
"& svg": {
"& path": {
stroke: theme.palette.other.icon,
strokeWidth: 1.1,
},
},
2024-08-19 14:01:26 -04:00
}}
>
{child.icon}
{child.name}
</MenuItem>
);
})}
</Menu>
</React.Fragment>
2024-08-16 12:13:05 -04:00
) : (
2024-08-16 12:40:15 -04:00
<React.Fragment key={item.name}>
2024-08-16 12:13:05 -04:00
<ListItemButton
onClick={() =>
setOpen((prev) => ({
...prev,
[`${item.name}`]: !prev[`${item.name}`],
}))
}
sx={{
2024-08-27 17:01:28 -04:00
gap: theme.spacing(4),
borderRadius: theme.shape.borderRadius,
px: theme.spacing(4),
2024-08-16 12:13:05 -04:00
}}
>
<ListItemIcon sx={{ minWidth: 0 }}>{item.icon}</ListItemIcon>
<ListItemText>{item.name}</ListItemText>
{open[`${item.name}`] ? <ArrowUp /> : <ArrowDown />}
</ListItemButton>
2024-08-16 12:40:15 -04:00
<Collapse in={open[`${item.name}`]} timeout="auto">
2024-08-16 12:19:03 -04:00
<List
component="div"
disablePadding
2024-08-27 17:01:28 -04:00
sx={{ pl: theme.spacing(12) }}
2024-08-16 12:19:03 -04:00
>
2024-08-16 12:40:15 -04:00
{item.nested.map((child) => {
if (
child.name === "Team" &&
authState.user?.role &&
2024-08-27 09:13:48 -07:00
!authState.user.role.includes("superadmin")
2024-08-16 12:40:15 -04:00
) {
return null;
}
return (
<ListItemButton
className={
location.pathname.includes(child.path)
? "selected-path"
: ""
}
key={child.path}
onClick={() => navigate(`/${child.path}`)}
sx={{
2024-08-27 17:01:28 -04:00
gap: theme.spacing(4),
borderRadius: theme.shape.borderRadius,
pl: theme.spacing(4),
"&::before": {
content: `""`,
position: "absolute",
top: 0,
left: "-7px",
height: "100%",
borderLeft: 1,
2024-08-28 22:14:55 -04:00
borderLeftColor: theme.palette.other.line,
2024-08-27 17:01:28 -04:00
},
"&:last-child::before": {
height: "50%",
},
"&::after": {
content: `""`,
position: "absolute",
top: "45%",
left: "-8px",
height: "3px",
width: "3px",
borderRadius: "50%",
2024-08-28 22:14:55 -04:00
backgroundColor: theme.palette.other.line,
2024-08-27 17:01:28 -04:00
},
"&.selected-path::after": {
backgroundColor: theme.palette.text.tertiary,
transform: "scale(1.2)",
},
2024-08-16 12:40:15 -04:00
}}
>
<ListItemIcon sx={{ minWidth: 0 }}>
{child.icon}
</ListItemIcon>
<ListItemText>{child.name}</ListItemText>
</ListItemButton>
);
})}
2024-08-16 12:13:05 -04:00
</List>
</Collapse>
2024-08-16 12:40:15 -04:00
</React.Fragment>
2024-08-16 12:13:05 -04:00
)
)}
2024-08-15 20:34:34 -04:00
</List>
<Divider sx={{ my: theme.spacing(4) }} />
2024-08-15 20:34:34 -04:00
{/* other */}
<List
component="nav"
aria-labelledby="nested-other-subheader"
subheader={
2024-08-16 14:17:11 -04:00
<ListSubheader
component="div"
id="nested-other-subheader"
2024-08-19 13:23:17 -04:00
sx={{
2024-08-27 17:01:28 -04:00
pt: theme.spacing(4),
px: collapsed ? 0 : theme.spacing(4),
backgroundColor: "transparent",
2024-08-19 13:23:17 -04:00
}}
2024-08-16 14:17:11 -04:00
>
2024-08-15 20:34:34 -04:00
Other
</ListSubheader>
}
2024-08-27 17:01:28 -04:00
sx={{ px: theme.spacing(6) }}
2024-08-15 20:34:34 -04:00
>
{other.map((item) => (
2024-08-19 15:00:11 -04:00
<Tooltip
2024-08-19 15:04:04 -04:00
key={item.path}
2024-08-19 15:00:11 -04:00
placement="right"
title={collapsed ? item.name : ""}
slotProps={{
popper: {
modifiers: [
{
name: "offset",
options: {
offset: [0, -16],
},
},
],
},
2024-08-15 20:34:34 -04:00
}}
2024-08-20 17:34:54 -04:00
disableInteractive
2024-08-15 20:34:34 -04:00
>
2024-08-19 15:00:11 -04:00
<ListItemButton
className={
location.pathname.includes(item.path) ? "selected-path" : ""
}
onClick={() =>
item.path === "support"
? window.open(
2024-08-23 14:34:26 -04:00
"https://github.com/bluewave-labs/bluewave-uptime/issues",
2024-08-19 15:00:11 -04:00
"_blank",
"noreferrer"
)
: navigate(`/${item.path}`)
}
sx={{
2024-08-27 17:01:28 -04:00
gap: theme.spacing(4),
borderRadius: theme.shape.borderRadius,
px: theme.spacing(4),
2024-08-19 15:00:11 -04:00
}}
>
<ListItemIcon sx={{ minWidth: 0 }}>{item.icon}</ListItemIcon>
2024-08-22 17:13:29 -04:00
<ListItemText>{item.name}</ListItemText>
2024-08-19 15:00:11 -04:00
</ListItemButton>
</Tooltip>
2024-08-15 20:34:34 -04:00
))}
</List>
<Divider sx={{ mt: "auto" }} />
2024-08-19 13:23:17 -04:00
2024-08-16 12:53:41 -04:00
<Stack
direction="row"
2024-08-19 13:23:17 -04:00
height="50px"
2024-08-16 12:53:41 -04:00
alignItems="center"
2024-08-27 17:01:28 -04:00
py={theme.spacing(4)}
px={theme.spacing(8)}
gap={theme.spacing(2)}
borderRadius={theme.shape.borderRadius}
>
2024-08-19 14:30:44 -04:00
{collapsed ? (
<>
<Tooltip
title="Options"
slotProps={{
popper: {
modifiers: [
{
name: "offset",
options: {
offset: [0, -10],
},
},
],
},
}}
2024-08-20 17:34:54 -04:00
disableInteractive
2024-08-19 14:30:44 -04:00
>
<IconButton
onClick={(event) => openPopup(event, "logout")}
sx={{ p: 0, "&:focus": { outline: "none" } }}
>
<Avatar small={true} />
</IconButton>
</Tooltip>
</>
) : (
2024-08-19 13:23:17 -04:00
<>
2024-08-19 14:30:44 -04:00
<Avatar small={true} />
2024-08-27 17:01:28 -04:00
<Box ml={theme.spacing(2)}>
2024-08-19 13:23:17 -04:00
<Typography component="span" fontWeight={500}>
{authState.user?.firstName} {authState.user?.lastName}
</Typography>
<Typography sx={{ textTransform: "capitalize" }}>
{authState.user?.role}
</Typography>
</Box>
2024-08-28 22:14:55 -04:00
<Tooltip title="Controls" disableInteractive>
2024-08-19 13:23:17 -04:00
<IconButton
2024-08-28 22:14:55 -04:00
sx={{
ml: "auto",
mr: "-8px",
"&:focus": { outline: "none" },
"& svg": {
width: "20px",
height: "20px",
},
"& svg path": {
stroke: theme.palette.other.icon,
},
}}
onClick={(event) => openPopup(event, "logout")}
2024-08-19 13:23:17 -04:00
>
2024-08-28 22:14:55 -04:00
<DotsVertical />
2024-08-19 13:23:17 -04:00
</IconButton>
</Tooltip>
</>
)}
2024-08-28 22:14:55 -04:00
<Menu
className="sidebar-popup"
anchorEl={anchorEl}
open={Boolean(anchorEl) && popup === "logout"}
onClose={closePopup}
anchorOrigin={{
vertical: "top",
horizontal: "right",
}}
slotProps={{
paper: {
sx: {
marginTop: theme.spacing(-4),
marginLeft: collapsed ? theme.spacing(2) : 0,
},
},
}}
MenuListProps={{
sx: {
p: 2,
2024-08-30 12:53:23 -04:00
"& li": { m: 0 },
2024-08-28 22:14:55 -04:00
"& li:has(.MuiBox-root):hover": {
2024-08-30 12:53:23 -04:00
backgroundColor: "transparent",
2024-08-28 22:14:55 -04:00
},
},
}}
sx={{
ml: theme.spacing(8),
}}
>
{collapsed && (
<MenuItem sx={{ cursor: "default", minWidth: "150px" }}>
<Box mb={theme.spacing(2)}>
<Typography component="span" fontWeight={500} fontSize={13}>
{authState.user?.firstName} {authState.user?.lastName}
</Typography>
<Typography sx={{ textTransform: "capitalize", fontSize: 12 }}>
{authState.user?.role}
</Typography>
</Box>
</MenuItem>
)}
{collapsed && <Divider />}
2024-08-30 12:53:23 -04:00
<MenuItem
onClick={() => {
dispatch(setMode("light"));
closePopup();
}}
>
Light
</MenuItem>
<MenuItem
onClick={() => {
dispatch(setMode("dark"));
closePopup();
}}
>
Dark
</MenuItem>
<Divider />
2024-08-28 22:14:55 -04:00
<MenuItem
onClick={logout}
sx={{
gap: theme.spacing(4),
borderRadius: theme.shape.borderRadius,
pl: theme.spacing(4),
}}
>
<LogoutSvg />
Log out
</MenuItem>
</Menu>
2024-08-16 12:53:41 -04:00
</Stack>
2024-07-26 12:39:18 -04:00
</Stack>
);
}
export default Sidebar;