Files
dashfront/src/components/UserMenu.jsx
2026-10-08 01:44:40 +02:00

190 lines
6.2 KiB
JavaScript

import { PureComponent } from 'react';
import Avatar from '@mui/material/Avatar';
import Badge from '@mui/material/Badge';
import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import LogoutIcon from '@mui/icons-material/Logout';
import SettingsIcon from '@mui/icons-material/Settings';
import HelpOutlineRoundedIcon from '@mui/icons-material/HelpOutlineRounded';
import SystemUpdateAltIcon from '@mui/icons-material/SystemUpdateAlt';
import Switch from '@mui/material/Switch';
import Divider from '@mui/material/Divider';
import authAutomat, { bootstrapAuthMe } from '../automats/authAutomat';
import { helpAutomat, toggleHelp, pwaAutomat, openPwaDialog } from '../automats';
import SettingsDialog from './settings/SettingsDialog';
import { notifyError } from '../utils/notify';
class UserMenu extends PureComponent {
constructor(props) {
super(props);
this.state = {
anchorEl: null,
settingsOpen: false,
user: authAutomat.state.user,
helpEnabled: helpAutomat.state?.enabled ?? false,
pwaUpdateAvailable: pwaAutomat.state.needRefresh,
};
this.handleOpenMenu = this.handleOpenMenu.bind(this);
this.handleContextMenu = this.handleContextMenu.bind(this);
this.handleCloseMenu = this.handleCloseMenu.bind(this);
this.handleOpenSettings = this.handleOpenSettings.bind(this);
this.handleCloseSettings = this.handleCloseSettings.bind(this);
this.handleToggleHelp = this.handleToggleHelp.bind(this);
this.handleOpenPwaUpdate = this.handleOpenPwaUpdate.bind(this);
this.handleLogout = this.handleLogout.bind(this);
}
componentDidMount() {
this.unsubscribeAuth = authAutomat.subscribe(this, 'user');
this.unsubscribeHelp = helpAutomat.subscribe(this, (state) => ({
helpEnabled: Boolean(state?.enabled),
}));
this.unsubscribePwa = pwaAutomat.subscribe(this, (state) => ({
pwaUpdateAvailable: Boolean(state?.needRefresh),
}));
}
componentWillUnmount() {
this.unsubscribeAuth?.();
this.unsubscribeHelp?.();
this.unsubscribePwa?.();
}
handleOpenPwaUpdate() {
this.setState({ anchorEl: null });
openPwaDialog();
}
handleOpenMenu(event) {
this.setState({ anchorEl: event.currentTarget });
}
handleContextMenu(event) {
event?.preventDefault?.();
this.setState({ anchorEl: event.currentTarget });
}
handleCloseMenu() {
this.setState({ anchorEl: null });
}
handleOpenSettings() {
this.setState({ anchorEl: null, settingsOpen: true });
}
handleCloseSettings() {
this.setState({ settingsOpen: false });
}
handleToggleHelp(e) {
e?.preventDefault?.();
e?.stopPropagation?.();
toggleHelp();
}
async handleLogout() {
this.setState({ anchorEl: null });
try {
await authAutomat.actions.logout();
await bootstrapAuthMe();
} catch (err) {
notifyError(err);
}
}
render() {
const { user, anchorEl, settingsOpen, helpEnabled, pwaUpdateAvailable } = this.state;
const open = Boolean(anchorEl);
const pictureSrc = (() => {
const pic = user?.picture;
if (!pic || typeof pic !== 'string') return undefined;
if (pic.startsWith('http://') || pic.startsWith('https://') || pic.startsWith('data:')) {
return pic;
}
const mime = pic.startsWith('/9j/') ? 'image/jpeg' : 'image/png';
return `data:${mime};base64,${pic}`;
})();
return (
<>
<Box sx={{ px: 1.5, py: 1.5, alignSelf: 'flex-start' }}>
<IconButton
onClick={this.handleOpenMenu}
onContextMenu={this.handleContextMenu}
onMouseDown={(e) => {
if (e.button === 0) e.preventDefault();
}}
sx={{ p: 0, width: 32, height: 32, borderRadius: '50%' }}
>
<Badge
color="primary"
variant="dot"
invisible={!pwaUpdateAvailable}
overlap="circular"
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
>
<Avatar src={pictureSrc} alt={user?.email} sx={{ width: 32, height: 32 }}>
{user?.email?.[0]?.toUpperCase()}
</Avatar>
</Badge>
</IconButton>
</Box>
<Menu
anchorEl={anchorEl}
open={open}
onClose={this.handleCloseMenu}
anchorOrigin={{ vertical: 'top', horizontal: 'left' }}
transformOrigin={{ vertical: 'bottom', horizontal: 'left' }}
>
{pwaUpdateAvailable && (
<MenuItem onClick={this.handleOpenPwaUpdate} sx={{ bgcolor: 'action.hover' }}>
<ListItemIcon>
<SystemUpdateAltIcon fontSize="small" color="primary" />
</ListItemIcon>
<ListItemText
primary="Update verfügbar"
secondary="Jetzt aktualisieren"
primaryTypographyProps={{ fontWeight: 600, color: 'primary.main' }}
/>
</MenuItem>
)}
<MenuItem disabled>{user?.email}</MenuItem>
<MenuItem disabled={!user?.selectedCompanyId} onClick={this.handleOpenSettings}>
<ListItemIcon>
<SettingsIcon fontSize="small" />
</ListItemIcon>
<ListItemText>Einstellungen</ListItemText>
</MenuItem>
<MenuItem onClick={this.handleToggleHelp}>
<ListItemIcon>
<HelpOutlineRoundedIcon fontSize="small" />
</ListItemIcon>
<ListItemText>Hilfe</ListItemText>
<Switch
edge="end"
size="small"
checked={helpEnabled}
sx={{ pointerEvents: 'none' }}
/>
</MenuItem>
<Divider />
<MenuItem onClick={this.handleLogout}>
<ListItemIcon>
<LogoutIcon fontSize="small" />
</ListItemIcon>
<ListItemText>Sign out</ListItemText>
</MenuItem>
</Menu>
<SettingsDialog open={settingsOpen} onClose={this.handleCloseSettings} />
</>
);
}
}
export default UserMenu;