mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-19 16:08:39 -05:00
auth footer
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import { Avatar as MuiAvatar } from "@mui/material";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTheme } from "@mui/material";
|
||||
import type { RootState } from "@/Types/state";
|
||||
|
||||
interface AvatarProps {
|
||||
src?: string;
|
||||
small?: boolean;
|
||||
sx?: object;
|
||||
onClick?: Function;
|
||||
}
|
||||
|
||||
export const Avatar = ({ src, small, sx, onClick = () => {} }: AvatarProps) => {
|
||||
const { user } = useSelector((state: RootState) => state.auth);
|
||||
const theme = useTheme();
|
||||
if (!user) return null;
|
||||
|
||||
const style = small ? { width: 32, height: 32 } : { width: 64, height: 64 };
|
||||
const border = small ? 1 : 3;
|
||||
|
||||
const [image, setImage] = useState<string>();
|
||||
useEffect(() => {
|
||||
if (user.avatarImage) {
|
||||
setImage(`data:image/png;base64,${user.avatarImage}`);
|
||||
}
|
||||
}, [user?.avatarImage]);
|
||||
|
||||
return (
|
||||
<MuiAvatar
|
||||
onClick={onClick}
|
||||
alt={`${user?.firstName} ${user?.lastName}`}
|
||||
src={src ? src : user?.avatarImage ? image : undefined}
|
||||
sx={{
|
||||
fontSize: small ? "16px" : "22px",
|
||||
fontWeight: 400,
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
display: "inline-flex",
|
||||
|
||||
...style,
|
||||
...sx,
|
||||
}}
|
||||
>
|
||||
{user.firstName?.charAt(0)}
|
||||
{user.lastName?.charAt(0) || ""}
|
||||
</MuiAvatar>
|
||||
);
|
||||
};
|
||||
@@ -20,3 +20,4 @@ export * from "./Tabs";
|
||||
export * from "./SplitBox";
|
||||
export * from "./TextLink";
|
||||
export * from "./OfflineBanner";
|
||||
export * from "./Avatar";
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
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, Icon } from "@/Components/v2/design-elements";
|
||||
import Menu from "@mui/material/Menu";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import { MoreVertical, LogOut } from "lucide-react";
|
||||
|
||||
import { useTheme } from "@mui/material";
|
||||
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";
|
||||
import type { RootState } from "@/Types/state.js";
|
||||
|
||||
const getFilteredAccountMenuItems = (user: any, items: any[]) => {
|
||||
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: any, t: Function) => {
|
||||
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,
|
||||
}: {
|
||||
collapsed: boolean;
|
||||
accountMenuItems: any[];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const authState = useSelector((state: RootState) => state.auth);
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
|
||||
const openPopup = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const closePopup = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
dispatch(clearAuthState());
|
||||
navigate("/login");
|
||||
};
|
||||
const renderAccountMenuItems = (user: any, items: any[]) => {
|
||||
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),
|
||||
}}
|
||||
>
|
||||
{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: any) => {
|
||||
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
|
||||
icon={MoreVertical}
|
||||
size={22}
|
||||
/>
|
||||
</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
|
||||
icon={LogOut}
|
||||
size={20}
|
||||
/>
|
||||
{t("menu.logOut", "Log out")}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
AuthFooter.propTypes = {
|
||||
collapsed: PropTypes.bool,
|
||||
accountMenuItems: PropTypes.array,
|
||||
};
|
||||
|
||||
export default AuthFooter;
|
||||
@@ -21,25 +21,53 @@ import {
|
||||
|
||||
export const getMenu = (t: Function) => {
|
||||
return [
|
||||
{ name: t("menu.uptime"), path: "uptime", icon: <Icon icon={Globe} /> },
|
||||
{ name: t("menu.pagespeed"), path: "pagespeed", icon: <Icon icon={Gauge} /> },
|
||||
{
|
||||
name: t("menu.infrastructure"),
|
||||
name: t("components.sidebar.menu.uptime"),
|
||||
path: "uptime",
|
||||
icon: <Icon icon={Globe} />,
|
||||
},
|
||||
{
|
||||
name: t("components.sidebar.menu.pagespeed"),
|
||||
path: "pagespeed",
|
||||
icon: <Icon icon={Gauge} />,
|
||||
},
|
||||
{
|
||||
name: t("components.sidebar.menu.infrastructure"),
|
||||
path: "infrastructure",
|
||||
icon: <Icon icon={Link} />,
|
||||
},
|
||||
{
|
||||
name: t("menu.notifications"),
|
||||
name: t("components.sidebar.menu.notifications"),
|
||||
path: "notifications",
|
||||
icon: <Icon icon={Bell} />,
|
||||
},
|
||||
{ name: t("menu.checks"), path: "checks", icon: <Icon icon={FileText} /> },
|
||||
{ name: t("menu.incidents"), path: "incidents", icon: <Icon icon={AlertTriangle} /> },
|
||||
{ name: t("menu.statusPages"), path: "status", icon: <Icon icon={Wifi} /> },
|
||||
{ name: t("menu.maintenance"), path: "maintenance", icon: <Icon icon={Wrench} /> },
|
||||
{ name: t("menu.logs"), path: "logs", icon: <Icon icon={Database} /> },
|
||||
{
|
||||
name: t("menu.settings"),
|
||||
name: t("components.sidebar.menu.checks"),
|
||||
path: "checks",
|
||||
icon: <Icon icon={FileText} />,
|
||||
},
|
||||
{
|
||||
name: t("components.sidebar.menu.incidents"),
|
||||
path: "incidents",
|
||||
icon: <Icon icon={AlertTriangle} />,
|
||||
},
|
||||
{
|
||||
name: t("components.sidebar.menu.statusPages"),
|
||||
path: "status",
|
||||
icon: <Icon icon={Wifi} />,
|
||||
},
|
||||
{
|
||||
name: t("components.sidebar.menu.maintenance"),
|
||||
path: "maintenance",
|
||||
icon: <Icon icon={Wrench} />,
|
||||
},
|
||||
{
|
||||
name: t("components.sidebar.menu.logs"),
|
||||
path: "logs",
|
||||
icon: <Icon icon={Database} />,
|
||||
},
|
||||
{
|
||||
name: t("components.sidebar.menu.settings"),
|
||||
icon: <Icon icon={Settings} />,
|
||||
path: "settings",
|
||||
},
|
||||
@@ -48,13 +76,45 @@ export const getMenu = (t: Function) => {
|
||||
|
||||
export const getBottomMenu = (t: Function) => {
|
||||
return [
|
||||
{ name: t("menu.support"), path: "support", icon: <Icon icon={HelpCircle} /> },
|
||||
{
|
||||
name: t("menu.discussions"),
|
||||
name: t("components.sidebar.bottomMenu.support"),
|
||||
path: "support",
|
||||
icon: <Icon icon={HelpCircle} />,
|
||||
},
|
||||
{
|
||||
name: t("components.sidebar.bottomMenu.discussions"),
|
||||
path: "discussions",
|
||||
icon: <Icon icon={MessageCircle} />,
|
||||
},
|
||||
{ name: t("menu.docs"), path: "docs", icon: <Icon icon={FileText} /> },
|
||||
{ name: t("menu.changelog"), path: "changelog", icon: <Icon icon={Code} /> },
|
||||
{
|
||||
name: t("components.sidebar.bottomMenu.docs"),
|
||||
path: "docs",
|
||||
icon: <Icon icon={FileText} />,
|
||||
},
|
||||
{
|
||||
name: t("components.sidebar.bottomMenu.changelog"),
|
||||
path: "changelog",
|
||||
icon: <Icon icon={Code} />,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const getAccountMenu = (t: Function) => {
|
||||
return [
|
||||
{
|
||||
name: t("components.sidebar.accountMenu.profile"),
|
||||
path: "account/profile",
|
||||
icon: <Icon icon={User} />,
|
||||
},
|
||||
{
|
||||
name: t("components.sidebar.accountMenu.password"),
|
||||
path: "account/password",
|
||||
icon: <Icon icon={Lock} />,
|
||||
},
|
||||
{
|
||||
name: t("components.sidebar.accountMenu.team"),
|
||||
path: "account/team",
|
||||
icon: <Icon icon={Users} />,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
@@ -54,7 +54,7 @@ export const StarPrompt = ({
|
||||
variant="subtitle2"
|
||||
mt={theme.spacing(3)}
|
||||
>
|
||||
{t("starPromptTitle")}
|
||||
{t("components.sidebar.starPrompt.title")}
|
||||
</Typography>
|
||||
<IconButton
|
||||
onClick={handleClose}
|
||||
@@ -81,7 +81,7 @@ export const StarPrompt = ({
|
||||
px: theme.spacing(4),
|
||||
}}
|
||||
>
|
||||
{t("starPromptDescription")}
|
||||
{t("components.sidebar.starPrompt.description")}
|
||||
</Typography>
|
||||
|
||||
<Box
|
||||
|
||||
@@ -4,7 +4,7 @@ import List from "@mui/material/List";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import { useSidebar } from "@/Hooks/useSidebar.js";
|
||||
import { Logo } from "@/Components/v2/sidebar/Logo";
|
||||
import { getMenu, getBottomMenu } from "@/Components/v2/sidebar/Menu";
|
||||
import { getMenu, getBottomMenu, getAccountMenu } from "@/Components/v2/sidebar/Menu";
|
||||
import { NavItem } from "@/Components/v2/sidebar/NavItem";
|
||||
import { StarPrompt } from "@/Components/v2/sidebar/StarPrompt";
|
||||
|
||||
@@ -31,6 +31,7 @@ export const Sidebar = () => {
|
||||
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
|
||||
const menu = getMenu(t);
|
||||
const bottomMenu = getBottomMenu(t);
|
||||
const accountMenu = getAccountMenu(t);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setCollapsed({ collapsed: isSmall }));
|
||||
@@ -109,6 +110,23 @@ export const Sidebar = () => {
|
||||
})}
|
||||
</List>
|
||||
<Divider sx={{ borderColor: theme.palette.divider }} />
|
||||
<List
|
||||
component="nav"
|
||||
disablePadding
|
||||
sx={{ px: theme.spacing(6) }}
|
||||
>
|
||||
{accountMenu.map((item) => {
|
||||
const selected = location.pathname.startsWith(`/${item.path}`);
|
||||
return (
|
||||
<NavItem
|
||||
key={item.path}
|
||||
item={item}
|
||||
selected={selected}
|
||||
onClick={() => handleNavClick(item.path)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -333,10 +333,38 @@
|
||||
"reconnected": "Connection restored"
|
||||
},
|
||||
"sidebar": {
|
||||
"menu": {},
|
||||
"bottomMenu": {}
|
||||
"menu": {
|
||||
"uptime": "Uptime",
|
||||
"pagespeed": "Pagespeed",
|
||||
"infrastructure": "Infrastructure",
|
||||
"notifications": "Notifications",
|
||||
"checks": "Checks",
|
||||
"incidents": "Incidents",
|
||||
"statusPages": "Status pages",
|
||||
"maintenance": "Maintenance",
|
||||
"logs": "Logs",
|
||||
"settings": "Settings"
|
||||
},
|
||||
"bottomMenu": {
|
||||
"support": "Support",
|
||||
"discussions": "Discussions",
|
||||
"docs": "Docs",
|
||||
"changelog": "Changelog"
|
||||
},
|
||||
"accountMenu": {
|
||||
"profile": "Profile",
|
||||
"password": "Password",
|
||||
"team": "Team"
|
||||
},
|
||||
"starPrompt": {
|
||||
"title": "Star Checkmate",
|
||||
"description": "See the latest releases and help grow the community on GitHub"
|
||||
}
|
||||
},
|
||||
"starPrompt": {}
|
||||
"starPrompt": {
|
||||
"title": "Star Checkmate",
|
||||
"description": "See the latest releases and help grow the community on GitHub"
|
||||
}
|
||||
},
|
||||
"configure": "Configure",
|
||||
"confirmPassword": "Confirm password",
|
||||
|
||||
Reference in New Issue
Block a user