mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-19 16:08:39 -05:00
cleanup
This commit is contained in:
@@ -1,36 +0,0 @@
|
||||
import Icon from "../Icon";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const ArrowLeft = ({ type, color = "#667085", ...props }) => {
|
||||
if (type === "double") {
|
||||
return (
|
||||
<Icon
|
||||
name="ChevronsLeft"
|
||||
color={color}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
} else if (type === "long") {
|
||||
return (
|
||||
<Icon
|
||||
name="ArrowLeft"
|
||||
color={color}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Icon
|
||||
name="ChevronLeft"
|
||||
color={color}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
ArrowLeft.propTypes = {
|
||||
color: PropTypes.string,
|
||||
type: PropTypes.oneOf(["double", "long", "default"]),
|
||||
};
|
||||
export default ArrowLeft;
|
||||
@@ -1,73 +0,0 @@
|
||||
import { Avatar as MuiAvatar } from "@mui/material";
|
||||
import PropTypes from "prop-types";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTheme } from "@emotion/react";
|
||||
/**
|
||||
* @component
|
||||
* @param {Object} props
|
||||
* @param {string} props.src - Path to image for avatar
|
||||
* @param {boolean} props.small - Specifies if avatar should be large
|
||||
* @param {Object} [props.sx] - Additional styles to apply to the button.
|
||||
* @returns {JSX.Element}
|
||||
* @example
|
||||
* // Render a red label
|
||||
* <Avatar src="assets/img" first="Alex" last="Holliday" small />
|
||||
*/
|
||||
|
||||
const Avatar = ({ src, small, sx, onClick = () => {} }) => {
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
const theme = useTheme();
|
||||
|
||||
const style = small ? { width: 32, height: 32 } : { width: 64, height: 64 };
|
||||
const border = small ? 1 : 3;
|
||||
|
||||
const [image, setImage] = useState();
|
||||
useEffect(() => {
|
||||
if (user.avatarImage) {
|
||||
setImage(`data:image/png;base64,${user.avatarImage}`);
|
||||
}
|
||||
}, [user?.avatarImage]);
|
||||
|
||||
return (
|
||||
<MuiAvatar
|
||||
onClick={onClick}
|
||||
alt={`${user?.firstName} ${user?.lastName}`}
|
||||
/* TODO What is the /static/images/avatar/2.jpg ?*/
|
||||
src={src ? src : user?.avatarImage ? image : "/static/images/avatar/2.jpg"}
|
||||
sx={{
|
||||
fontSize: small ? "16px" : "22px",
|
||||
fontWeight: 400,
|
||||
color: theme.palette.accent.contrastText,
|
||||
backgroundColor: theme.palette.accent.main, // Same BG color as checkmate BG in sidebar
|
||||
display: "inline-flex",
|
||||
/*
|
||||
TODO not sure what this is for*/
|
||||
"&::before": {
|
||||
content: `""`,
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: `100%`,
|
||||
height: `100%`,
|
||||
border: `${border}px solid rgba(255,255,255,0.2)`,
|
||||
borderRadius: "50%",
|
||||
},
|
||||
...style,
|
||||
...sx,
|
||||
}}
|
||||
>
|
||||
{user.firstName?.charAt(0)}
|
||||
{user.lastName?.charAt(0) || ""}
|
||||
</MuiAvatar>
|
||||
);
|
||||
};
|
||||
|
||||
Avatar.propTypes = {
|
||||
src: PropTypes.string,
|
||||
small: PropTypes.bool,
|
||||
sx: PropTypes.object,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Avatar;
|
||||
@@ -1,6 +0,0 @@
|
||||
.label {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
line-height: normal;
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { Box } from "@mui/material";
|
||||
import { useTheme } from "@mui/material";
|
||||
import "./index.css";
|
||||
|
||||
/**
|
||||
* @typedef {Object} Styles
|
||||
* @param {string} [color] - The text color
|
||||
* @param {string} [backgroundColor] - The background color
|
||||
* @param {string} [borderColor] - The border color
|
||||
*/
|
||||
|
||||
/**
|
||||
* @component
|
||||
* @param {Object} props
|
||||
* @param {string} props.label - The label of the label
|
||||
* @param {Styles} props.styles - CSS Styles passed from parent component
|
||||
* @param {React.ReactNode} children - Children passed from parent component
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
|
||||
const BaseLabel = ({ label, styles, children }) => {
|
||||
const theme = useTheme();
|
||||
// Grab the default borderRadius from the theme to match button style
|
||||
const { borderRadius } = theme.shape;
|
||||
// Calculate padding for the label to mimic button. Appears to scale correctly, not 100% sure though.
|
||||
const padding = theme.spacing(3, 5);
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="label"
|
||||
sx={{
|
||||
borderRadius: borderRadius,
|
||||
border: `1px solid ${theme.palette.primary.lowContrast}`,
|
||||
color: theme.palette.primary.contrastText,
|
||||
padding: padding,
|
||||
...styles,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
{label}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
BaseLabel.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
styles: PropTypes.shape({
|
||||
color: PropTypes.string,
|
||||
backgroundColor: PropTypes.string,
|
||||
}),
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
// Produces a lighter color based on a hex color and a percent
|
||||
// lightenColor("#067647", 20) will produce a color 20% lighter than #067647
|
||||
const lightenColor = (color, percent) => {
|
||||
let r = parseInt(color.substring(1, 3), 16);
|
||||
let g = parseInt(color.substring(3, 5), 16);
|
||||
let b = parseInt(color.substring(5, 7), 16);
|
||||
|
||||
const amt = Math.round((255 * percent) / 100);
|
||||
|
||||
r = r + amt <= 255 ? r + amt : 255;
|
||||
g = g + amt <= 255 ? g + amt : 255;
|
||||
b = b + amt <= 255 ? b + amt : 255;
|
||||
|
||||
r = r.toString(16).padStart(2, "0");
|
||||
g = g.toString(16).padStart(2, "0");
|
||||
b = b.toString(16).padStart(2, "0");
|
||||
|
||||
return `#${r}${g}${b}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* @component
|
||||
* @param {Object} props
|
||||
* @param {string} props.label - The label of the label
|
||||
* @param {string} props.color - The color of the label, specified in #RRGGBB format
|
||||
* @returns {JSX.Element}
|
||||
* @example
|
||||
* // Render a red label
|
||||
* <ColoredLabel label="Label" color="#FF0000" />
|
||||
*/
|
||||
|
||||
const ColoredLabel = ({ label, color }) => {
|
||||
const theme = useTheme();
|
||||
// If an invalid color is passed, default to the labelGray color
|
||||
if (typeof color !== "string" || !/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(color)) {
|
||||
color = theme.palette.primary.lowContrast;
|
||||
}
|
||||
|
||||
// Calculate lighter shades for border and bg
|
||||
const borderColor = lightenColor(color, 20);
|
||||
const bgColor = lightenColor(color, 75);
|
||||
|
||||
return (
|
||||
<BaseLabel
|
||||
label={label}
|
||||
styles={{
|
||||
color: color,
|
||||
borderColor: borderColor,
|
||||
backgroundColor: bgColor,
|
||||
}}
|
||||
></BaseLabel>
|
||||
);
|
||||
};
|
||||
|
||||
ColoredLabel.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
color: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
/**
|
||||
* @component
|
||||
* @param {Object} props
|
||||
* @param {'up' | 'down' | 'paused' | 'pending' | 'cannot resolve' | 'published' | 'unpublished'} props.status - The status for the label
|
||||
* @param {string} props.text - The text of the label
|
||||
* @returns {JSX.Element}
|
||||
* @example
|
||||
* // Render an active label
|
||||
* <StatusLabel status="up" text="Active" />
|
||||
*/
|
||||
|
||||
const statusToTheme = {
|
||||
up: "success",
|
||||
down: "error",
|
||||
paused: "warning",
|
||||
pending: "warning",
|
||||
"cannot resolve": "error",
|
||||
published: "success",
|
||||
unpublished: "error",
|
||||
};
|
||||
|
||||
const StatusLabel = ({ status, text, customStyles }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const themeColor = statusToTheme[status];
|
||||
|
||||
return (
|
||||
<BaseLabel
|
||||
label={text}
|
||||
styles={{
|
||||
color: theme.palette[themeColor].main,
|
||||
borderColor: theme.palette[themeColor].lowContrast,
|
||||
...customStyles,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
bgcolor={theme.palette[themeColor].lowContrast}
|
||||
borderRadius="0%"
|
||||
marginRight="1px"
|
||||
/>
|
||||
</BaseLabel>
|
||||
);
|
||||
};
|
||||
|
||||
StatusLabel.propTypes = {
|
||||
status: PropTypes.oneOf([
|
||||
"up",
|
||||
"down",
|
||||
"paused",
|
||||
"pending",
|
||||
"cannot resolve",
|
||||
"published",
|
||||
"unpublished",
|
||||
]),
|
||||
text: PropTypes.string,
|
||||
customStyles: PropTypes.object,
|
||||
};
|
||||
|
||||
export { BaseLabel, ColoredLabel, StatusLabel };
|
||||
@@ -1,29 +0,0 @@
|
||||
.home-layout {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
overflow-x: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* TODO go for this approach for responsiveness. The aside needs to be taken care of */
|
||||
/* @media (max-width: 1000px) {
|
||||
.home-layout {
|
||||
flex-direction: column !important;
|
||||
}
|
||||
} */
|
||||
|
||||
.home-layout > .home-content-wrapper {
|
||||
min-height: calc(100vh - var(--env-var-spacing-2) * 2);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.home-content-wrapper {
|
||||
padding: var(--env-var-spacing-2);
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import Sidebar from "../../Sidebar/index.jsx";
|
||||
import { Outlet } from "react-router";
|
||||
import { Box, Stack } from "@mui/material";
|
||||
import { useSidebar } from "@/Hooks/useSidebar.js";
|
||||
|
||||
import "./index.css";
|
||||
|
||||
const HomeLayout = () => {
|
||||
const { width, transition } = useSidebar();
|
||||
|
||||
return (
|
||||
<Stack
|
||||
className="home-layout"
|
||||
flexDirection="row"
|
||||
>
|
||||
<Sidebar />
|
||||
<Box
|
||||
sx={{
|
||||
width,
|
||||
flexShrink: 0,
|
||||
transition,
|
||||
}}
|
||||
/>
|
||||
<Stack className="home-content-wrapper">
|
||||
<Outlet />
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default HomeLayout;
|
||||
@@ -1,276 +0,0 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Avatar from "../../Avatar/index.jsx";
|
||||
import Menu from "@mui/material/Menu";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import Icon from "../../Icon";
|
||||
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router";
|
||||
import { clearAuthState } from "../../../../Features/Auth/authSlice.js";
|
||||
import { useDispatch } from "react-redux";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const getFilteredAccountMenuItems = (user, items) => {
|
||||
if (!user) return [];
|
||||
|
||||
let filtered = [...items];
|
||||
|
||||
if (user.role?.includes("demo")) {
|
||||
filtered = filtered.filter((item) => item.name !== "Password");
|
||||
}
|
||||
|
||||
if (!user.role?.includes("superadmin")) {
|
||||
filtered = filtered.filter((item) => item.name !== "Team");
|
||||
}
|
||||
|
||||
return filtered;
|
||||
};
|
||||
|
||||
const getRoleDisplayText = (user, t) => {
|
||||
if (!user?.role) return "";
|
||||
|
||||
if (user.role.includes("superadmin")) return t("roles.superAdmin");
|
||||
if (user.role.includes("admin")) return t("roles.admin");
|
||||
if (user.role.includes("user")) return t("roles.teamMember");
|
||||
if (user.role.includes("demo")) return t("roles.demoUser");
|
||||
|
||||
return user.role;
|
||||
};
|
||||
|
||||
const AuthFooter = ({ collapsed, accountMenuItems }) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const authState = useSelector((state) => state.auth);
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
|
||||
const openPopup = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const closePopup = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
dispatch(clearAuthState());
|
||||
navigate("/login");
|
||||
};
|
||||
const renderAccountMenuItems = (user, items) => {
|
||||
const filteredItems = getFilteredAccountMenuItems(user, items);
|
||||
|
||||
return filteredItems.map((item) => (
|
||||
<MenuItem
|
||||
key={item.name}
|
||||
onClick={() => {
|
||||
closePopup();
|
||||
navigate(item.path);
|
||||
}}
|
||||
sx={{
|
||||
gap: theme.spacing(2),
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
pl: theme.spacing(4),
|
||||
color: theme.palette.primary.contrastTextTertiary,
|
||||
"& svg": {
|
||||
stroke: theme.palette.primary.contrastTextTertiary,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{item.icon}
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
));
|
||||
};
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
height="var(--env-var-side-bar-auth-footer-height)"
|
||||
alignItems="center"
|
||||
py={theme.spacing(4)}
|
||||
px={theme.spacing(8)}
|
||||
gap={theme.spacing(2)}
|
||||
borderRadius={theme.shape.borderRadius}
|
||||
boxSizing={"border-box"}
|
||||
>
|
||||
<Avatar
|
||||
small={true}
|
||||
onClick={(e) => collapsed && openPopup(e)}
|
||||
sx={{
|
||||
cursor: collapsed ? "pointer" : "default",
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack
|
||||
direction={"row"}
|
||||
alignItems={"center"}
|
||||
gap={theme.spacing(2)}
|
||||
minWidth={0}
|
||||
maxWidth={collapsed ? 0 : "100%"}
|
||||
sx={{
|
||||
opacity: collapsed ? 0 : 1,
|
||||
transition: "opacity 300ms ease, max-width 300ms ease",
|
||||
transitionDelay: collapsed ? "0ms" : "300ms",
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
ml={theme.spacing(2)}
|
||||
sx={{
|
||||
maxWidth: "50%",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
color={theme.palette.primary.contrastText}
|
||||
fontWeight={500}
|
||||
lineHeight={1}
|
||||
fontSize={"var(--env-var-font-size-medium)"}
|
||||
sx={{
|
||||
display: "block",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
>
|
||||
{authState.user?.firstName} {authState.user?.lastName}
|
||||
</Typography>
|
||||
<Typography
|
||||
color={theme.palette.primary.contrastText}
|
||||
fontSize={"var(--env-var-font-size-small)"}
|
||||
textOverflow="ellipsis"
|
||||
overflow="hidden"
|
||||
whiteSpace="nowrap"
|
||||
sx={{ textTransform: "capitalize", opacity: 0.8 }}
|
||||
>
|
||||
{getRoleDisplayText(authState.user, t)}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Tooltip
|
||||
title={t("navControls")}
|
||||
disableInteractive
|
||||
>
|
||||
<IconButton
|
||||
sx={{
|
||||
ml: "50px",
|
||||
"&:focus": { outline: "none" },
|
||||
alignSelf: "center",
|
||||
}}
|
||||
onClick={(event) => openPopup(event)}
|
||||
>
|
||||
<Icon
|
||||
name="MoreVertical"
|
||||
size={22}
|
||||
color="primary.contrastTextTertiary"
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
<Menu
|
||||
className="sidebar-popup"
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={closePopup}
|
||||
disableScrollLock
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
}}
|
||||
slotProps={{
|
||||
paper: {
|
||||
sx: {
|
||||
marginTop: theme.spacing(-4),
|
||||
marginLeft: collapsed ? theme.spacing(2) : 0,
|
||||
},
|
||||
},
|
||||
}}
|
||||
MenuListProps={{
|
||||
sx: {
|
||||
p: 2,
|
||||
"& li": { m: 0 },
|
||||
"& li:has(.MuiBox-root):hover": {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
},
|
||||
}}
|
||||
sx={{
|
||||
ml: theme.spacing(4),
|
||||
}}
|
||||
>
|
||||
{collapsed && (
|
||||
<MenuItem sx={{ cursor: "default", minWidth: "50%" }}>
|
||||
<Box
|
||||
mb={theme.spacing(2)}
|
||||
sx={{
|
||||
minWidth: "50%",
|
||||
maxWidth: "max-content",
|
||||
overflow: "visible",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
component="span"
|
||||
fontWeight={500}
|
||||
fontSize={13}
|
||||
sx={{
|
||||
display: "block",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "visible",
|
||||
// wordBreak: "break-word",
|
||||
textOverflow: "clip",
|
||||
}}
|
||||
>
|
||||
{authState.user?.firstName} {authState.user?.lastName}
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
textTransform: "capitalize",
|
||||
fontSize: 12,
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "visible",
|
||||
// wordBreak: "break-word",
|
||||
}}
|
||||
>
|
||||
{authState.user?.role}
|
||||
</Typography>
|
||||
</Box>
|
||||
</MenuItem>
|
||||
)}
|
||||
{/* TODO Do we need two dividers? */}
|
||||
{collapsed && <Divider />}
|
||||
{/* <Divider /> */}
|
||||
{renderAccountMenuItems(authState.user, accountMenuItems)}
|
||||
<MenuItem
|
||||
onClick={logout}
|
||||
sx={{
|
||||
gap: theme.spacing(4),
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
pl: theme.spacing(4),
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name="LogOut"
|
||||
size={20}
|
||||
color="primary.contrastTextTertiary"
|
||||
/>
|
||||
{t("menu.logOut", "Log out")}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
AuthFooter.propTypes = {
|
||||
collapsed: PropTypes.bool,
|
||||
accountMenuItems: PropTypes.array,
|
||||
};
|
||||
|
||||
export default AuthFooter;
|
||||
@@ -1,55 +0,0 @@
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import ArrowRight from "../../ArrowRight/index.jsx";
|
||||
import ArrowLeft from "../../ArrowLeft/index.jsx";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { toggleSidebar } from "../../../../Features/UI/uiSlice.js";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const CollapseButton = ({ collapsed }) => {
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
const arrowIcon = collapsed ? (
|
||||
<ArrowRight
|
||||
height={theme.spacing(8)}
|
||||
width={theme.spacing(8)}
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
/>
|
||||
) : (
|
||||
<ArrowLeft
|
||||
height={theme.spacing(8)}
|
||||
width={theme.spacing(8)}
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<IconButton
|
||||
sx={{
|
||||
position: "absolute",
|
||||
/* TODO 60 is a magic number. if logo chnges size this might break */
|
||||
top: 60,
|
||||
right: 0,
|
||||
transform: `translate(50%, 0)`,
|
||||
backgroundColor: theme.palette.tertiary.main,
|
||||
border: `1px solid ${theme.palette.primary.lowContrast}`,
|
||||
p: theme.spacing(2.5),
|
||||
|
||||
"&:focus": { outline: "none" },
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.primary.lowContrast,
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
dispatch(toggleSidebar());
|
||||
}}
|
||||
>
|
||||
{arrowIcon}
|
||||
</IconButton>
|
||||
);
|
||||
};
|
||||
|
||||
CollapseButton.propTypes = {
|
||||
collapsed: PropTypes.bool.isRequired,
|
||||
};
|
||||
export default CollapseButton;
|
||||
@@ -1,67 +0,0 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useNavigate } from "react-router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const Logo = ({ collapsed }) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<Stack
|
||||
pt={theme.spacing(6)}
|
||||
pb={theme.spacing(12)}
|
||||
pl={theme.spacing(8)}
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(4)}
|
||||
onClick={() => navigate("/")}
|
||||
sx={{ cursor: "pointer" }}
|
||||
>
|
||||
<Typography
|
||||
pl={theme.spacing("1px")}
|
||||
minWidth={theme.spacing(16)}
|
||||
minHeight={theme.spacing(16)}
|
||||
display={"flex"}
|
||||
justifyContent={"center"}
|
||||
alignItems={"center"}
|
||||
backgroundColor={theme.palette.accent.main}
|
||||
borderRadius={theme.shape.borderRadius}
|
||||
color={theme.palette.accent.contrastText}
|
||||
fontSize={18}
|
||||
>
|
||||
C
|
||||
</Typography>
|
||||
<Box
|
||||
overflow={"hidden"}
|
||||
sx={{
|
||||
transition: "opacity 900ms ease, width 900ms ease",
|
||||
opacity: collapsed ? 0 : 1,
|
||||
whiteSpace: "nowrap",
|
||||
width: collapsed ? 0 : "100%",
|
||||
}}
|
||||
>
|
||||
{" "}
|
||||
<Typography
|
||||
lineHeight={1}
|
||||
mt={theme.spacing(2)}
|
||||
color={theme.palette.primary.contrastText}
|
||||
fontSize={"var(--env-var-font-size-medium-plus)"}
|
||||
sx={{ fontWeight: 500 }}
|
||||
>
|
||||
{t("common.appName")}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
Logo.propTypes = {
|
||||
collapsed: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Logo;
|
||||
@@ -1,96 +0,0 @@
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import ListItemButton from "@mui/material/ListItemButton";
|
||||
import ListItemIcon from "@mui/material/ListItemIcon";
|
||||
import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const NavItem = ({ item, collapsed, selected, onClick }) => {
|
||||
const theme = useTheme();
|
||||
const iconStroke = selected
|
||||
? theme.palette.primary.contrastText
|
||||
: theme.palette.primary.contrastTextTertiary;
|
||||
|
||||
const buttonBgColor = selected ? theme.palette.secondary.main : "transparent";
|
||||
const buttonBgHoverColor = selected
|
||||
? theme.palette.secondary.main
|
||||
: theme.palette.tertiary.main;
|
||||
const fontWeight = selected ? 600 : 400;
|
||||
return (
|
||||
<Tooltip
|
||||
placement="right"
|
||||
title={collapsed ? item.name : ""}
|
||||
slotProps={{
|
||||
popper: {
|
||||
modifiers: [
|
||||
{
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: [0, -16],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}}
|
||||
disableInteractive
|
||||
>
|
||||
<ListItemButton
|
||||
sx={{
|
||||
backgroundColor: buttonBgColor,
|
||||
"&:hover": {
|
||||
backgroundColor: buttonBgHoverColor,
|
||||
},
|
||||
height: 37,
|
||||
gap: theme.spacing(4),
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
px: theme.spacing(4),
|
||||
pl: theme.spacing(5),
|
||||
}}
|
||||
onClick={onClick}
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: 0,
|
||||
color: iconStroke,
|
||||
"& svg": {
|
||||
height: 20,
|
||||
width: 20,
|
||||
opacity: 0.81,
|
||||
stroke: iconStroke,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{item.icon}
|
||||
</ListItemIcon>
|
||||
<Box
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
transition: "opacity 900ms ease",
|
||||
opacity: collapsed ? 0 : 1,
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body1"
|
||||
color={theme.palette.primary.contrastText}
|
||||
sx={{
|
||||
fontWeight: fontWeight,
|
||||
opacity: 0.9,
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
</Typography>
|
||||
</Box>
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
NavItem.propTypes = {
|
||||
item: PropTypes.object,
|
||||
collapsed: PropTypes.bool,
|
||||
selected: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
export default NavItem;
|
||||
@@ -1,152 +0,0 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
|
||||
import List from "@mui/material/List";
|
||||
import Logo from "./components/logo.jsx";
|
||||
import CollapseButton from "./components/collapseButton.jsx";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import NavItem from "./components/navItem.jsx";
|
||||
import AuthFooter from "./components/authFooter.jsx";
|
||||
|
||||
import StarPrompt from "../StarPrompt/index.jsx";
|
||||
import Icon from "../Icon";
|
||||
|
||||
// Utils
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router";
|
||||
import { useSidebar } from "@/Hooks/useSidebar.js";
|
||||
|
||||
const URL_MAP = {
|
||||
support: "https://discord.com/invite/NAb6H3UTjK",
|
||||
discussions: "https://github.com/bluewave-labs/checkmate/discussions",
|
||||
docs: "https://bluewavelabs.gitbook.io/checkmate",
|
||||
changelog: "https://github.com/bluewave-labs/checkmate/releases",
|
||||
};
|
||||
|
||||
const getMenu = (t) => [
|
||||
{ name: t("menu.uptime"), path: "uptime", icon: <Icon name="Globe" /> },
|
||||
{ name: t("menu.pagespeed"), path: "pagespeed", icon: <Icon name="Gauge" /> },
|
||||
{ name: t("menu.infrastructure"), path: "infrastructure", icon: <Icon name="Link" /> },
|
||||
{
|
||||
name: t("menu.notifications"),
|
||||
path: "notifications",
|
||||
icon: <Icon name="Bell" />,
|
||||
},
|
||||
{ name: t("menu.checks"), path: "checks", icon: <Icon name="FileText" /> },
|
||||
{ name: t("menu.incidents"), path: "incidents", icon: <Icon name="AlertTriangle" /> },
|
||||
{ name: t("menu.statusPages"), path: "status", icon: <Icon name="Wifi" /> },
|
||||
{ name: t("menu.maintenance"), path: "maintenance", icon: <Icon name="Wrench" /> },
|
||||
{ name: t("menu.logs"), path: "logs", icon: <Icon name="Database" /> },
|
||||
{
|
||||
name: t("menu.settings"),
|
||||
icon: <Icon name="Settings" />,
|
||||
path: "settings",
|
||||
},
|
||||
];
|
||||
|
||||
const getOtherMenuItems = (t) => [
|
||||
{ name: t("menu.support"), path: "support", icon: <Icon name="HelpCircle" /> },
|
||||
{
|
||||
name: t("menu.discussions"),
|
||||
path: "discussions",
|
||||
icon: <Icon name="MessageCircle" />,
|
||||
},
|
||||
{ name: t("menu.docs"), path: "docs", icon: <Icon name="FileText" /> },
|
||||
{ name: t("menu.changelog"), path: "changelog", icon: <Icon name="Code" /> },
|
||||
];
|
||||
|
||||
const getAccountMenuItems = (t) => [
|
||||
{ name: t("menu.profile"), path: "account/profile", icon: <Icon name="User" /> },
|
||||
{ name: t("menu.password"), path: "account/password", icon: <Icon name="Lock" /> },
|
||||
{ name: t("menu.team"), path: "account/team", icon: <Icon name="Users" /> },
|
||||
];
|
||||
|
||||
const Sidebar = () => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { collapsed, width, transition } = useSidebar();
|
||||
|
||||
const menu = getMenu(t);
|
||||
const otherMenuItems = getOtherMenuItems(t);
|
||||
const accountMenuItems = getAccountMenuItems(t);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
height="100vh"
|
||||
width={width}
|
||||
component="aside"
|
||||
position="fixed"
|
||||
top={0}
|
||||
left={0}
|
||||
paddingTop={theme.spacing(6)}
|
||||
paddingBottom={theme.spacing(6)}
|
||||
gap={theme.spacing(6)}
|
||||
sx={{
|
||||
transition,
|
||||
backgroundColor: theme.palette.background.main,
|
||||
borderRight: `1px solid ${theme.palette.primary.lowContrast}`,
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<CollapseButton collapsed={collapsed} />
|
||||
<Logo collapsed={collapsed} />
|
||||
<List
|
||||
component="nav"
|
||||
aria-labelledby="nested-menu-subheader"
|
||||
disablePadding
|
||||
sx={{
|
||||
px: theme.spacing(6),
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
{menu.map((item) => {
|
||||
const selected = location.pathname.startsWith(`/${item.path}`);
|
||||
return (
|
||||
<NavItem
|
||||
key={item.path}
|
||||
item={item}
|
||||
collapsed={collapsed}
|
||||
selected={selected}
|
||||
onClick={() => navigate(`/${item.path}`)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
{!collapsed && <StarPrompt />}
|
||||
<List
|
||||
component="nav"
|
||||
disablePadding
|
||||
sx={{ px: theme.spacing(6) }}
|
||||
>
|
||||
{otherMenuItems.map((item) => {
|
||||
const selected = location.pathname.startsWith(`/${item.path}`);
|
||||
|
||||
return (
|
||||
<NavItem
|
||||
key={item.path}
|
||||
item={item}
|
||||
collapsed={collapsed}
|
||||
selected={selected}
|
||||
onClick={() => {
|
||||
const url = URL_MAP[item.path];
|
||||
if (url) {
|
||||
window.open(url, "_blank", "noreferrer");
|
||||
} else {
|
||||
navigate(`/${item.path}`);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
<Divider sx={{ mt: "auto", borderColor: theme.palette.primary.lowContrast }} />
|
||||
<AuthFooter
|
||||
collapsed={collapsed}
|
||||
accountMenuItems={accountMenuItems}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
@@ -1,110 +0,0 @@
|
||||
import React from "react";
|
||||
import { Typography, IconButton, Stack, Box } from "@mui/material";
|
||||
import Icon from "../Icon";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { setStarPromptOpen } from "../../../Features/UI/uiSlice.js";
|
||||
|
||||
const StarPrompt = ({ repoUrl = "https://github.com/bluewave-labs/checkmate" }) => {
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const isOpen = useSelector((state) => state.ui?.starPromptOpen ?? true);
|
||||
const mode = useSelector((state) => state.ui.mode);
|
||||
|
||||
const handleClose = () => {
|
||||
dispatch(setStarPromptOpen(false));
|
||||
};
|
||||
|
||||
const handleStarClick = () => {
|
||||
window.open(repoUrl, "_blank");
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction="column"
|
||||
sx={{
|
||||
width: "100%",
|
||||
padding: `${theme.spacing(6)} ${theme.spacing(6)}`,
|
||||
borderTop: `1px solid ${theme.palette.primary.lowContrast}`,
|
||||
borderBottom: `1px solid ${theme.palette.primary.lowContrast}`,
|
||||
borderRadius: 0,
|
||||
gap: theme.spacing(1.5),
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
width="100%"
|
||||
pl={theme.spacing(4)}
|
||||
>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
sx={{
|
||||
color:
|
||||
mode === "dark"
|
||||
? theme.palette.primary.contrastText
|
||||
: theme.palette.text.primary,
|
||||
mt: theme.spacing(3),
|
||||
}}
|
||||
>
|
||||
{t("starPromptTitle")}
|
||||
</Typography>
|
||||
<IconButton
|
||||
onClick={handleClose}
|
||||
size="small"
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
padding: 0,
|
||||
marginTop: theme.spacing(-5),
|
||||
"&:hover": {
|
||||
backgroundColor: "transparent",
|
||||
opacity: 0.8,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name="X"
|
||||
size={20}
|
||||
/>
|
||||
</IconButton>
|
||||
</Stack>
|
||||
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
color: theme.palette.primary.contrastTextTertiary,
|
||||
fontSize: "0.938rem",
|
||||
lineHeight: 1.5,
|
||||
mb: 1,
|
||||
px: theme.spacing(4),
|
||||
}}
|
||||
>
|
||||
{t("starPromptDescription")}
|
||||
</Typography>
|
||||
|
||||
<Box
|
||||
component="img"
|
||||
src={`https://img.shields.io/github/stars/bluewave-labs/checkmate?label=checkmate&style=social${mode === "dark" ? "&color=white" : ""}`}
|
||||
alt="GitHub stars"
|
||||
onClick={handleStarClick}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
transform: "scale(0.65)",
|
||||
transformOrigin: "left center",
|
||||
"&:hover": {
|
||||
opacity: 0.8,
|
||||
},
|
||||
pl: theme.spacing(4),
|
||||
filter: mode === "dark" ? "invert(1)" : "none",
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default StarPrompt;
|
||||
@@ -51,7 +51,7 @@ export const AuthFooter = ({ collapsed, accountMenuItems }: AuthFooterProps) =>
|
||||
if (role.includes("superadmin"))
|
||||
return t("components.sidebar.authFooter.roles.superAdmin");
|
||||
if (role.includes("admin")) return t("components.sidebar.authFooter.roles.admin");
|
||||
if (role.includes("user")) return t("components.sidebar.authFooter.roles.teamMember");
|
||||
if (role.includes("user")) return t("components.sidebar.authFooter.roles.user");
|
||||
if (role.includes("demo")) return t("components.sidebar.authFooter.roles.demoUser");
|
||||
return role;
|
||||
};
|
||||
|
||||
@@ -366,7 +366,7 @@
|
||||
"roles": {
|
||||
"superAdmin": "Super admin",
|
||||
"admin": "Admin",
|
||||
"teamMember": "Team member",
|
||||
"user": "User",
|
||||
"demoUser": "Demo user"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user