mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-05 22:22:08 +00:00
feat(layout): redesign sidebar, topbar, and admin shell viewport
This commit is contained in:
parent
59e32152c6
commit
7b8ff5e04b
3 changed files with 280 additions and 215 deletions
|
|
@ -3,31 +3,28 @@ import { Outlet } from "react-router-dom";
|
|||
import { Box } from "@mui/material";
|
||||
|
||||
import { ProtectedRoute } from "@/components/auth/protected-route";
|
||||
import {
|
||||
AppSidebar,
|
||||
SIDEBAR_COLLAPSED_WIDTH,
|
||||
SIDEBAR_WIDTH,
|
||||
} from "@/components/layout/app-sidebar";
|
||||
import { AppSidebar } from "@/components/layout/app-sidebar";
|
||||
import { getSidebarWidth } from "@/config/menu";
|
||||
import { AppTopbar } from "@/components/layout/app-topbar";
|
||||
|
||||
export default function AdminShellLayout() {
|
||||
const [navCollapsed, setNavCollapsed] = useState(false);
|
||||
const sidebarWidth = navCollapsed ? SIDEBAR_COLLAPSED_WIDTH : SIDEBAR_WIDTH;
|
||||
const sidebarWidth = getSidebarWidth(navCollapsed);
|
||||
|
||||
return (
|
||||
<ProtectedRoute>
|
||||
<Box className="flex min-h-screen w-full bg-background">
|
||||
<Box className="flex h-screen w-full overflow-hidden bg-background">
|
||||
<AppSidebar collapsed={navCollapsed} />
|
||||
|
||||
<Box
|
||||
className="flex min-h-screen min-w-0 flex-1 flex-col"
|
||||
className="flex h-screen min-w-0 flex-1 flex-col overflow-hidden"
|
||||
sx={{ width: `calc(100% - ${sidebarWidth}px)` }}
|
||||
>
|
||||
<AppTopbar onToggleNav={() => setNavCollapsed((value) => !value)} />
|
||||
|
||||
<Box
|
||||
component="main"
|
||||
className="flex-1 p-2.5"
|
||||
className="flex-1 overflow-auto p-2.5"
|
||||
sx={{ backgroundColor: "var(--color-bg-page)" }}
|
||||
>
|
||||
<Outlet />
|
||||
|
|
|
|||
|
|
@ -1,76 +1,117 @@
|
|||
import { useState } from "react";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import {
|
||||
Box,
|
||||
Drawer,
|
||||
List,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { Box, Drawer } from "@mui/material";
|
||||
|
||||
import { navGroups, type NavItemDef } from "@/config/menu";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { BrandLockup, BrandMark } from "@/components/common/brand-lockup";
|
||||
import { getSidebarWidth, navGroups, type NavItemDef } from "@/config/menu";
|
||||
|
||||
const SIDEBAR_WIDTH = 220;
|
||||
const SIDEBAR_COLLAPSED_WIDTH = 56;
|
||||
function getNavItemAppearance(isActive: boolean, isHover: boolean) {
|
||||
if (isActive) {
|
||||
return {
|
||||
itemColor: "var(--primary)",
|
||||
iconColor: "var(--primary)",
|
||||
itemBg: "var(--color-sidebar-item-active-bg)",
|
||||
};
|
||||
}
|
||||
|
||||
if (isHover) {
|
||||
return {
|
||||
itemColor: "var(--primary-hover)",
|
||||
iconColor: "var(--primary-hover)",
|
||||
itemBg: "var(--sidebar-accent)",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
itemColor: "var(--foreground)",
|
||||
iconColor: "var(--muted-foreground)",
|
||||
itemBg: "transparent",
|
||||
};
|
||||
}
|
||||
|
||||
type NavItemProps = NavItemDef & {
|
||||
collapsed: boolean;
|
||||
end?: boolean;
|
||||
};
|
||||
|
||||
function NavItem({ label, icon: Icon, path, collapsed, end }: NavItemProps) {
|
||||
function NavItem({ label, icon: Icon, path, collapsed, end, badge }: NavItemProps) {
|
||||
const [hovered, setHovered] = useState(false);
|
||||
|
||||
return (
|
||||
<NavLink to={path} end={end} className="no-underline">
|
||||
{({ isActive }) => (
|
||||
<ListItemButton
|
||||
title={collapsed ? label : undefined}
|
||||
className={cn("mb-1 rounded-md", isActive && !collapsed && "font-semibold")}
|
||||
sx={{
|
||||
justifyContent: collapsed ? "center" : "flex-start",
|
||||
px: collapsed ? 1 : 1.75,
|
||||
py: 1.25,
|
||||
color: "var(--color-sidebar-text)",
|
||||
transition: "background-color var(--duration-fast, 150ms) var(--ease-default, ease)",
|
||||
"&:hover": {
|
||||
backgroundColor: "var(--color-sidebar-hover-bg)",
|
||||
color: "var(--color-sidebar-text-hover)",
|
||||
},
|
||||
...(isActive && collapsed
|
||||
? { backgroundColor: "var(--color-sidebar-hover-bg)" }
|
||||
: isActive
|
||||
? {
|
||||
backgroundColor: "var(--color-sidebar-active-bg)",
|
||||
color: "var(--color-sidebar-active-text)",
|
||||
borderLeft: "3px solid var(--primary)",
|
||||
pl: "13px",
|
||||
}
|
||||
: {}),
|
||||
}}
|
||||
>
|
||||
<ListItemIcon
|
||||
<NavLink to={path} end={end} className="no-underline" title={collapsed ? label : undefined}>
|
||||
{({ isActive }) => {
|
||||
const isHover = hovered && !isActive;
|
||||
const { itemColor, iconColor, itemBg } = getNavItemAppearance(isActive, isHover);
|
||||
|
||||
return (
|
||||
<Box
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
sx={{
|
||||
minWidth: collapsed ? 0 : 34,
|
||||
justifyContent: "center",
|
||||
color: "inherit",
|
||||
mr: collapsed ? 0 : 0.5,
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
height: 40,
|
||||
gap: "12px",
|
||||
px: collapsed ? 0 : "12px",
|
||||
justifyContent: collapsed ? "center" : "flex-start",
|
||||
borderRadius: "6px",
|
||||
cursor: "pointer",
|
||||
fontFamily: "var(--font-sans)",
|
||||
fontSize: 14,
|
||||
fontWeight: isActive ? 600 : 500,
|
||||
color: itemColor,
|
||||
backgroundColor: itemBg,
|
||||
boxShadow: isActive ? "var(--color-sidebar-item-active-shadow)" : "none",
|
||||
transition: "all var(--duration-fast, 150ms) var(--ease-default, ease)",
|
||||
mb: "4px",
|
||||
textAlign: "left",
|
||||
}}
|
||||
>
|
||||
<Icon size={18} />
|
||||
</ListItemIcon>
|
||||
{!collapsed && (
|
||||
<ListItemText
|
||||
primary={label}
|
||||
primaryTypographyProps={{
|
||||
fontSize: "0.875rem",
|
||||
fontWeight: "inherit",
|
||||
noWrap: true,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ListItemButton>
|
||||
)}
|
||||
{isActive && (
|
||||
<Box
|
||||
component="span"
|
||||
sx={{
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
top: 8,
|
||||
bottom: 8,
|
||||
width: 3,
|
||||
borderRadius: "0 3px 3px 0",
|
||||
backgroundColor: "var(--primary)",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Icon size={19} className="shrink-0" style={{ color: iconColor }} />
|
||||
{!collapsed && (
|
||||
<>
|
||||
<Box className="truncate flex-1">{label}</Box>
|
||||
{badge != null && (
|
||||
<Box
|
||||
component="span"
|
||||
className="inline-flex shrink-0 items-center justify-center"
|
||||
sx={{
|
||||
height: 18,
|
||||
minWidth: 18,
|
||||
px: "6px",
|
||||
borderRadius: "9999px",
|
||||
backgroundColor: "var(--accent)",
|
||||
color: "#ffffff",
|
||||
fontFamily: "var(--font-sans)",
|
||||
fontSize: 10,
|
||||
fontWeight: 700,
|
||||
lineHeight: 1,
|
||||
}}
|
||||
>
|
||||
{badge}
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}}
|
||||
</NavLink>
|
||||
);
|
||||
}
|
||||
|
|
@ -80,7 +121,7 @@ type AppSidebarProps = {
|
|||
};
|
||||
|
||||
export function AppSidebar({ collapsed }: AppSidebarProps) {
|
||||
const width = collapsed ? SIDEBAR_COLLAPSED_WIDTH : SIDEBAR_WIDTH;
|
||||
const width = getSidebarWidth(collapsed);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
|
|
@ -93,83 +134,76 @@ export function AppSidebar({ collapsed }: AppSidebarProps) {
|
|||
boxSizing: "border-box",
|
||||
backgroundColor: "var(--color-sidebar-bg)",
|
||||
borderRight: "1px solid var(--color-sidebar-border)",
|
||||
transition: "width var(--duration-normal, 250ms) var(--ease-default, ease)",
|
||||
borderRadius: 0,
|
||||
transition: "width 200ms var(--ease-default, ease)",
|
||||
overflowX: "hidden",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
className="flex flex-col"
|
||||
sx={{
|
||||
height: "100vh",
|
||||
px: collapsed ? 1 : 2.5,
|
||||
py: 2.5,
|
||||
}}
|
||||
>
|
||||
<Box className="flex h-full flex-col overflow-hidden">
|
||||
<Box
|
||||
className={cn("mb-7 flex items-center", collapsed && "justify-center")}
|
||||
sx={{ minHeight: 40 }}
|
||||
className="flex shrink-0 items-center"
|
||||
sx={{
|
||||
height: "var(--spacing-topbar)",
|
||||
gap: "10px",
|
||||
px: collapsed ? 0 : "16px",
|
||||
justifyContent: collapsed ? "center" : "flex-start",
|
||||
backgroundColor: "var(--card)",
|
||||
borderBottom: "1px solid var(--color-sidebar-border)",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="img"
|
||||
src="/logo.png"
|
||||
alt="SeaHaven Industries"
|
||||
sx={{
|
||||
maxWidth: collapsed ? 32 : 140,
|
||||
height: "auto",
|
||||
display: "block",
|
||||
transition: "max-width var(--duration-normal, 250ms) var(--ease-default, ease)",
|
||||
mx: collapsed ? "auto" : 0,
|
||||
}}
|
||||
/>
|
||||
{collapsed ? <BrandMark size="compact" /> : <BrandLockup size="compact" />}
|
||||
</Box>
|
||||
|
||||
<List disablePadding className="flex-1 overflow-y-auto overflow-x-hidden">
|
||||
<Box component="nav" className="flex-1 overflow-y-auto overflow-x-hidden" sx={{ py: 1 }}>
|
||||
{navGroups.map((group, groupIndex) => (
|
||||
<Box key={group.title} component="li" sx={{ listStyle: "none" }}>
|
||||
{!collapsed && (
|
||||
<Typography
|
||||
className="text-label"
|
||||
<Box key={group.title || `group-${groupIndex}`}>
|
||||
{collapsed ? (
|
||||
groupIndex > 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
height: 1,
|
||||
backgroundColor: "var(--color-sidebar-border)",
|
||||
mx: "14px",
|
||||
my: "10px",
|
||||
}}
|
||||
/>
|
||||
)
|
||||
) : group.title ? (
|
||||
<Box
|
||||
component="p"
|
||||
className="text-nav-group"
|
||||
sx={{
|
||||
px: 1.75,
|
||||
mt: groupIndex === 0 ? 0 : 2.25,
|
||||
mb: 0.75,
|
||||
color: "var(--color-sidebar-label)",
|
||||
m: 0,
|
||||
px: "10px",
|
||||
pt: "11px",
|
||||
pb: "6px",
|
||||
}}
|
||||
>
|
||||
{group.title}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ height: 8 }} />
|
||||
)}
|
||||
|
||||
{collapsed && groupIndex > 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
borderTop: "1px solid var(--color-sidebar-border)",
|
||||
mt: 1,
|
||||
pt: 1,
|
||||
mx: 0.5,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<List disablePadding component="ul">
|
||||
{group.items.map(({ label, icon, path }) => (
|
||||
<NavItem
|
||||
key={path}
|
||||
label={label}
|
||||
icon={icon}
|
||||
path={path}
|
||||
collapsed={collapsed}
|
||||
end={path === "/"}
|
||||
/>
|
||||
<Box component="ul" sx={{ listStyle: "none", m: 0, p: "0 8px" }}>
|
||||
{group.items.map(({ label, icon, path, badge }) => (
|
||||
<Box component="li" key={path} sx={{ listStyle: "none" }}>
|
||||
<NavItem
|
||||
label={label}
|
||||
icon={icon}
|
||||
path={path}
|
||||
badge={badge}
|
||||
collapsed={collapsed}
|
||||
end={path === "/"}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
</Box>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
export { SIDEBAR_COLLAPSED_WIDTH, SIDEBAR_WIDTH };
|
||||
|
|
|
|||
|
|
@ -1,17 +1,11 @@
|
|||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline";
|
||||
import LogoutOutlinedIcon from "@mui/icons-material/LogoutOutlined";
|
||||
import MenuIcon from "@mui/icons-material/Menu";
|
||||
import NotificationsNoneOutlinedIcon from "@mui/icons-material/NotificationsNoneOutlined";
|
||||
import PersonOutlineOutlinedIcon from "@mui/icons-material/PersonOutlineOutlined";
|
||||
import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
|
||||
import {
|
||||
AppBar,
|
||||
Avatar,
|
||||
Box,
|
||||
Divider,
|
||||
IconButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Menu,
|
||||
|
|
@ -19,8 +13,10 @@ import {
|
|||
Toolbar,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { ChevronDown, Menu as MenuIcon } from "lucide-react";
|
||||
|
||||
import { useAuthContext } from "@/providers/auth-context";
|
||||
import { getPrimaryUserRole } from "@/lib/auth/user-utils";
|
||||
|
||||
type AppTopbarProps = {
|
||||
onToggleNav: () => void;
|
||||
|
|
@ -30,7 +26,6 @@ export function AppTopbar({ onToggleNav }: AppTopbarProps) {
|
|||
const navigate = useNavigate();
|
||||
const { user, logout } = useAuthContext();
|
||||
|
||||
const [notifAnchor, setNotifAnchor] = useState<null | HTMLElement>(null);
|
||||
const [userAnchor, setUserAnchor] = useState<null | HTMLElement>(null);
|
||||
|
||||
const handleLogout = () => {
|
||||
|
|
@ -47,100 +42,133 @@ export function AppTopbar({ onToggleNav }: AppTopbarProps) {
|
|||
.toUpperCase()
|
||||
: "U";
|
||||
|
||||
const userRole = getPrimaryUserRole(user?.userRoles);
|
||||
|
||||
return (
|
||||
<AppBar
|
||||
position="sticky"
|
||||
elevation={0}
|
||||
square
|
||||
sx={{
|
||||
height: "var(--spacing-topbar)",
|
||||
background: "var(--color-header-gradient)",
|
||||
boxShadow: "var(--color-header-shadow)",
|
||||
color: "var(--color-header-text)",
|
||||
borderRadius: 0,
|
||||
}}
|
||||
>
|
||||
<Toolbar
|
||||
disableGutters
|
||||
className="flex justify-between px-4"
|
||||
sx={{ minHeight: "var(--spacing-topbar) !important" }}
|
||||
>
|
||||
<Box className="flex items-center">
|
||||
<IconButton
|
||||
onClick={onToggleNav}
|
||||
aria-label="Toggle navigation"
|
||||
sx={{
|
||||
color: "var(--color-header-icon)",
|
||||
mr: 1.5,
|
||||
"&:hover": { backgroundColor: "var(--color-header-surface-hover)" },
|
||||
}}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
<Box className="flex items-center gap-1">
|
||||
<IconButton
|
||||
title="Notifications"
|
||||
onClick={(event) => setNotifAnchor(event.currentTarget)}
|
||||
sx={{
|
||||
color: "var(--color-header-text-muted)",
|
||||
"&:hover": {
|
||||
color: "var(--color-header-text)",
|
||||
backgroundColor: "var(--color-header-chip-hover)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<NotificationsNoneOutlinedIcon fontSize="small" />
|
||||
</IconButton>
|
||||
|
||||
<Divider
|
||||
orientation="vertical"
|
||||
flexItem
|
||||
sx={{
|
||||
mx: 0.5,
|
||||
borderColor: "var(--color-header-surface)",
|
||||
alignSelf: "center",
|
||||
height: 24,
|
||||
}}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
title="Account"
|
||||
onClick={(event) => setUserAnchor(event.currentTarget)}
|
||||
sx={{
|
||||
color: "var(--color-header-text-muted)",
|
||||
"&:hover": {
|
||||
color: "var(--color-header-text)",
|
||||
backgroundColor: "var(--color-header-chip-hover)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<PersonOutlineOutlinedIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Toolbar>
|
||||
|
||||
<Menu
|
||||
anchorEl={notifAnchor}
|
||||
open={Boolean(notifAnchor)}
|
||||
onClose={() => setNotifAnchor(null)}
|
||||
transformOrigin={{ horizontal: "right", vertical: "top" }}
|
||||
anchorOrigin={{ horizontal: "right", vertical: "bottom" }}
|
||||
slotProps={{
|
||||
paper: {
|
||||
sx: { width: 300, mt: 1, boxShadow: "var(--shadow-lg)" },
|
||||
},
|
||||
className="flex items-center"
|
||||
sx={{
|
||||
minHeight: "var(--spacing-topbar) !important",
|
||||
height: "var(--spacing-topbar)",
|
||||
gap: "18px",
|
||||
px: "16px",
|
||||
pr: "22px",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ px: 2, py: 1.5, borderBottom: "1px solid var(--border)" }}>
|
||||
<Typography variant="subtitle2">Notifications</Typography>
|
||||
<Box
|
||||
component="button"
|
||||
type="button"
|
||||
onClick={onToggleNav}
|
||||
aria-label="Toggle navigation"
|
||||
title="Toggle sidebar"
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
flexShrink: 0,
|
||||
width: 38,
|
||||
height: 38,
|
||||
borderRadius: "6px",
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
backgroundColor: "var(--color-header-surface)",
|
||||
color: "var(--color-header-icon)",
|
||||
transition: "background-color var(--duration-fast, 150ms) var(--ease-default, ease)",
|
||||
"&:hover": {
|
||||
backgroundColor: "var(--color-header-surface-hover)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<MenuIcon size={18} />
|
||||
</Box>
|
||||
<Box className="flex flex-col items-center gap-2 px-4 py-6 text-muted-foreground">
|
||||
<CheckCircleOutlineIcon sx={{ fontSize: 28, color: "var(--muted)" }} />
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
No new notifications
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ flex: 1 }} />
|
||||
|
||||
<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>
|
||||
</Toolbar>
|
||||
|
||||
<Menu
|
||||
anchorEl={userAnchor}
|
||||
|
|
@ -155,19 +183,25 @@ export function AppTopbar({ onToggleNav }: AppTopbarProps) {
|
|||
}}
|
||||
>
|
||||
<Box className="flex items-center gap-3 px-4 py-3">
|
||||
<Avatar
|
||||
<Box
|
||||
className="flex shrink-0 items-center justify-center"
|
||||
sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
bgcolor: "var(--accent)",
|
||||
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)",
|
||||
}}
|
||||
>
|
||||
{userInitials}
|
||||
</Avatar>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="body2" fontWeight={600}>
|
||||
{user?.fullname || "User"}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{user?.email || ""}
|
||||
</Typography>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue