shoc-frontend-new/src/components/layout/app-topbar-user-menu.tsx

190 lines
5.1 KiB
TypeScript
Raw Normal View History

import { useState } from "react";
import { useNavigate } from "react-router";
import LogoutOutlinedIcon from "@mui/icons-material/LogoutOutlined";
import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
import {
Box,
Divider,
ListItemIcon,
ListItemText,
Menu,
MenuItem,
Typography,
} from "@mui/material";
import { ChevronDown } from "lucide-react";
import { useAuthContext } from "@/providers/auth-context";
import { getPrimaryUserRole } from "@/lib/auth/user-utils";
type UserMenuHeaderProps = {
initials: string;
fullname: string;
email: string;
};
function UserMenuHeader({ initials, fullname, email }: UserMenuHeaderProps) {
return (
<Box className="flex items-center gap-3 px-4 py-3">
<Box
className="flex shrink-0 items-center justify-center"
sx={{
width: 40,
height: 40,
borderRadius: "50%",
background: "linear-gradient(135deg, var(--accent), var(--primary))",
fontSize: "0.875rem",
fontWeight: 600,
color: "#ffffff",
border: "2px solid var(--color-header-avatar-ring)",
}}
>
{initials}
</Box>
<Box>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{fullname}
</Typography>
<Typography variant="caption" color="text.secondary">
{email}
</Typography>
</Box>
</Box>
);
}
export function AppTopbarUserMenu() {
const navigate = useNavigate();
const { user, logout } = useAuthContext();
const [userAnchor, setUserAnchor] = useState<null | HTMLElement>(null);
const handleLogout = () => {
setUserAnchor(null);
logout();
};
const userInitials = user?.fullname
? user.fullname
.split(" ")
.map((part) => part[0])
.join("")
.slice(0, 2)
.toUpperCase()
: "U";
const userRole = getPrimaryUserRole(user?.userRoles);
return (
<>
<Box
component="button"
type="button"
aria-haspopup="true"
aria-expanded={Boolean(userAnchor)}
onClick={(event) => setUserAnchor(event.currentTarget)}
sx={{
display: "flex",
alignItems: "center",
flexShrink: 0,
gap: "10px",
py: "5px",
pl: "6px",
pr: "10px",
borderRadius: "9999px",
border: "none",
cursor: "pointer",
backgroundColor: "transparent",
color: "var(--color-header-text)",
transition: "background-color var(--duration-fast, 150ms) var(--ease-default, ease)",
"&:hover": {
backgroundColor: "var(--color-header-chip-hover)",
},
}}
>
<Box
className="flex shrink-0 items-center justify-center"
sx={{
width: 34,
height: 34,
borderRadius: "50%",
background: "linear-gradient(135deg, var(--accent), var(--primary))",
boxShadow: "0 0 0 2px var(--color-header-avatar-ring)",
fontFamily: "var(--font-display)",
fontSize: 12,
fontWeight: 700,
color: "#ffffff",
}}
>
{userInitials}
</Box>
<Box className="text-left leading-tight">
<Typography
component="p"
sx={{
fontFamily: "var(--font-sans)",
fontSize: 13,
fontWeight: 600,
color: "#ffffff",
lineHeight: 1.25,
}}
>
{user?.fullname || "User"}
</Typography>
<Typography
component="p"
sx={{
fontSize: 11,
color: "var(--color-header-text-muted)",
lineHeight: 1.25,
}}
>
{userRole}
</Typography>
</Box>
<ChevronDown
size={15}
className="shrink-0"
style={{ color: "var(--color-header-text-muted)" }}
/>
</Box>
<Menu
anchorEl={userAnchor}
open={Boolean(userAnchor)}
onClose={() => setUserAnchor(null)}
transformOrigin={{ horizontal: "right", vertical: "top" }}
anchorOrigin={{ horizontal: "right", vertical: "bottom" }}
slotProps={{
paper: {
sx: { width: 260, mt: 1, boxShadow: "var(--shadow-lg)" },
},
}}
>
<UserMenuHeader
initials={userInitials}
fullname={user?.fullname || "User"}
email={user?.email || ""}
/>
<Divider />
<MenuItem
onClick={() => {
setUserAnchor(null);
navigate("/settings/dropdowns");
}}
>
<ListItemIcon>
<SettingsOutlinedIcon fontSize="small" />
</ListItemIcon>
<ListItemText>Settings</ListItemText>
</MenuItem>
<MenuItem onClick={handleLogout} sx={{ color: "error.main" }}>
<ListItemIcon>
<LogoutOutlinedIcon fontSize="small" color="error" />
</ListItemIcon>
<ListItemText>Logout</ListItemText>
</MenuItem>
</Menu>
</>
);
}