frontend/src/user/UserMenu.tsx
2025-06-25 09:45:02 -06:00

295 lines
No EOL
8.9 KiB
TypeScript

import { Avatar, Box, Button, Checkbox, Divider, ListItemIcon, ListItemText, Menu, MenuItem, PaletteColor, Theme, Tooltip, useTheme } from "@mui/material";
import { useGlobalState } from "../providers/StateContainer";
import { getSecondaryColour, getSignatureAccentColour } from "../services/whiteLabel";
import { makeStyles } from "@mui/styles";
import { LockOpen, Person, Lock, Settings, SupervisedUserCircle as TeamIcon, ExitToApp, PersonAdd } from "@mui/icons-material";
import { useState } from "react";
import UserTeamName from "./UserTeamName";
import { useAuth0 } from "@auth0/auth0-react";
import UserSettings from "./UserSettings";
import UserAvatar from "./UserAvatar";
import { purple } from "@mui/material/colors";
import { useNavigate } from "react-router-dom";
import TeamDialog from "teams/TeamDialog";
import AccessObject from "./AccessObject";
import { useSnackbar, useUserAPI } from "hooks";
// import { useThemeMode } from "theme/AppThemeProvider";
const useStyles = makeStyles((theme: Theme) => ({
accessIcon: {
color: "var(--status-ok)"
},
userAvatar: {
color: "#fff",
backgroundColor: getSecondaryColour()["700" as keyof PaletteColor],
[theme.breakpoints.down("xs")]: {
width: "32px",
height: "32px"
}
},
userAvatar2: {
color: "#fff",
backgroundColor: theme.palette.secondary["700" as keyof PaletteColor],
[theme.breakpoints.down("xs")]: {
width: "30px",
height: "30px"
},
position: "relative",
top: -6,
left: 0
},
teamAvatar: {
color: "#fff",
backgroundColor: theme.palette.secondary["700" as keyof PaletteColor],
[theme.breakpoints.down("xs")]: {
width: "30px",
height: "30px"
},
position: "absolute",
top: -34,
left: 8
},
profileButton: {
maxWidth: "50vw"
},
profileName: {
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
textTransform: "capitalize",
color: getSignatureAccentColour(),
marginBottom: -3,
marginTop: -3
},
red: {
color: "var(--status-alert)"
},
rightIcon: {
marginLeft: theme.spacing(1)
},
signIn: {
color: getSignatureAccentColour(),
borderColor: getSignatureAccentColour()
},
}));
export default function UserMenu() {
// const { toggleMode } = useThemeMode()
const [{ user, team, as }, dispatch] = useGlobalState();
const { loginWithRedirect } = useAuth0();
const classes = useStyles();
const theme = useTheme();
const userAPI = useUserAPI()
const snackbar = useSnackbar()
const name = user.name();
const picture = user.settings.avatar;
const [lockIsHovered, setLockIsHovered] = useState<boolean>(false);
const [userMenuIsOpen, setUserMenuIsOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState<any>(null);
const [userSettingsIsOpen, setUserSettingsIsOpen] = useState<boolean>(false);
const hasTeams = user.hasFeature ? user.hasFeature("teams") : false;
const [teamDialogIsOpen, setTeamDialogIsOpen] = useState<boolean>(false);
const [accessDialogOpen, setAccessDialogOpen] = useState<boolean>(false);
const navigate = useNavigate()
const openTeamDialog = () => {
setTeamDialogIsOpen(true);
closeUserMenu();
};
const lockHover = () => {
setLockIsHovered(true);
};
const lockNoHover = () => {
setLockIsHovered(false);
};
const handleLogin = () => {
loginWithRedirect()
}
const openUserMenu = (event: any) => {
setAnchorEl(event.currentTarget);
setUserMenuIsOpen(true);
};
const closeUserMenu = () => {
setUserMenuIsOpen(false);
setAnchorEl(null);
};
const openUserSettingsDialog = () => {
setUserMenuIsOpen(false);
setAnchorEl(null);
setUserSettingsIsOpen(true)
}
const closeUserSettingsDialog = () => {
setUserSettingsIsOpen(false)
}
const openAccessObject = () => {
setAccessDialogOpen(true);
closeUserMenu();
};
const handleLogout = () => {
closeUserMenu();
navigate("/logout");
};
const unauthenticatedUserMenu = () => {
return (
<>
<Button
variant="outlined"
aria-label="Sign In"
onClick={handleLogin}
onMouseEnter={lockHover}
onMouseLeave={lockNoHover}
size="small"
className={classes.signIn}
>
Sign In
{lockIsHovered ? (
<LockOpen className={classes.rightIcon} fontSize="small" />
) : (
<Lock className={classes.rightIcon} fontSize="small" />
)}
</Button>
</>
);
};
if (user.id().length < 1) return unauthenticatedUserMenu()
const hasAdmin = user.hasFeature ? user.hasFeature("admin") : false;
return (
<>
<Button
id="tour-user-menu"
aria-owns={userMenuIsOpen ? "userMenu" : undefined}
aria-haspopup="true"
onClick={openUserMenu}
className={classes.profileButton}>
<Box style={{ display: "flex", flexDirection: "column" }}>
<UserTeamName user={user} team={team} />
</Box>
<Box style={{ marginLeft: theme.spacing(1) }}>
{picture && hasTeams && team.id().length > 0 ? (
<div>
<UserAvatar
user={user}
className={classes.userAvatar2}
style={{ zIndex: as === "" ? 1000 : 0 }}
/>
<div style={{ position: "relative" }}>
<Avatar
src={team.settings.avatar}
className={classes.teamAvatar}
style={{ background: purple[500] }}>
<TeamIcon style={{ fontSize: 38 }} />
</Avatar>
</div>
</div>
) : picture ? (
<Avatar alt={name} src={picture} className={classes.userAvatar}>
{!picture && name}
</Avatar>
) : (
<Avatar alt={name} src={picture} className={classes.userAvatar}>
<Person />
</Avatar>
)}
</Box>
</Button>
<Menu
id="userMenu"
anchorEl={anchorEl}
open={userMenuIsOpen}
onClose={closeUserMenu}
disableAutoFocusItem>
<MenuItem onClick={openUserSettingsDialog} aria-label="Open User Settings" dense>
<ListItemIcon>
<Settings />
</ListItemIcon>
<ListItemText primary="User Settings" />
</MenuItem>
{/* <MenuItem onClick={toggleMode} aria-label="Toggle Theme" dense>
<ListItemIcon>
<ThemeIcon />
</ListItemIcon>
<ListItemText primary="Toggle Theme" />
</MenuItem> */}
{hasTeams && (
<MenuItem onClick={openTeamDialog} aria-label="Open Team Menu" dense>
<ListItemIcon>
<TeamIcon />
</ListItemIcon>
<ListItemText primary="Select Team" />
</MenuItem>
)}
{hasTeams && (
<MenuItem
onClick={() => {
if (as.length > 1) {
user.settings.useTeam = false;
dispatch({ key: "as", value: "" });
userAPI.updateUser(user.id(), user.protobuf()).then(_resp => {
snackbar.info("Will no longer view as team by default");
});
} else {
user.settings.useTeam = true;
dispatch({ key: "as", value: team.key() });
userAPI.updateUser(user.id(), user.protobuf()).then(_resp => {
snackbar.info("Will now view as " + team.name() + " by default");
});
}
}}
disabled={user.settings.defaultTeam.length < 1}
aria-label="Open Team Menu"
dense>
<ListItemIcon>
<Checkbox
disabled={user.settings.defaultTeam.length < 1}
checked={as.length > 1}
style={{ margin: 0, padding: 0 }}
/>
</ListItemIcon>
<ListItemText primary="View Site as Team" />
</MenuItem>
)}
<Divider />
{hasAdmin && (
<Tooltip title="Access Object">
<MenuItem onClick={() => openAccessObject()} aria-label="Access Object" dense>
<ListItemIcon>
<PersonAdd className={classes.accessIcon} />
</ListItemIcon>
<ListItemText primary="Access Object" />
</MenuItem>
</Tooltip>
)}
<Divider />
<MenuItem onClick={handleLogout} aria-label="Sign Out" dense>
<ListItemIcon>
<ExitToApp className={classes.red} />
</ListItemIcon>
<ListItemText primary="Sign Out" />
</MenuItem>
</Menu>
<TeamDialog open={teamDialogIsOpen} setOpen={setTeamDialogIsOpen} />
<UserSettings isOpen={userSettingsIsOpen} closeDialogCallback={closeUserSettingsDialog} />
<AccessObject
isOpen={accessDialogOpen}
closeDialogCallback={() => setAccessDialogOpen(false)}
/>
</>
)
}