diff --git a/assets/icons/logouticon.svg b/assets/icons/logouticon.svg new file mode 100644 index 0000000..003d2c8 --- /dev/null +++ b/assets/icons/logouticon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/components/Dashboard/common/DashboardNavigation.jsx b/src/components/Dashboard/common/DashboardNavigation.jsx index de2bcf7..aa4021e 100644 --- a/src/components/Dashboard/common/DashboardNavigation.jsx +++ b/src/components/Dashboard/common/DashboardNavigation.jsx @@ -5,18 +5,14 @@ import { Flex, Tag, Space, - Dropdown, Button, Tooltip, Badge, Divider, - Typography + Typography, + Popover } from 'antd' -import { - LogoutOutlined, - MailOutlined, - LoadingOutlined -} from '@ant-design/icons' +import { LoadingOutlined } from '@ant-design/icons' import { AuthContext } from '../context/AuthContext' import { SpotlightContext } from '../context/SpotlightContext' import { ApiServerContext } from '../context/ApiServerContext' @@ -25,6 +21,7 @@ import { useNavigate, useLocation } from 'react-router-dom' import { Header } from 'antd/es/layout/layout' import { useMediaQuery } from 'react-responsive' import KeyboardShortcut from './KeyboardShortcut' +import UserProfilePopover from './UserProfilePopover' import FarmControlLogo from '../../Logos/FarmControlLogo' import FarmControlLogoSmall from '../../Logos/FarmControlLogoSmall' @@ -45,7 +42,7 @@ import DashboardWindowButtons from './DashboardWindowButtons' const { Text } = Typography const DashboardNavigation = () => { - const { logout, userProfile } = useContext(AuthContext) + const { userProfile } = useContext(AuthContext) const { showSpotlight } = useContext(SpotlightContext) const { connecting, connected } = useContext(ApiServerContext) const { toggleNotificationCenter, unreadCount } = @@ -93,34 +90,11 @@ const DashboardNavigation = () => { [] ) - const userMenuItems = { - items: [ - { - key: 'username', - label: userProfile?.username, - icon: , - disabled: true - }, - { - key: 'email', - label: userProfile?.email, - icon: , - disabled: true - }, - { - key: 'logout', - label: 'Logout', - icon: - } - ], - onClick: (key) => { - if (key === 'profile') { - navigate('/profile') - } else if (key === 'logout') { - logout() - } - } - } + const [userPopoverOpen, setUserPopoverOpen] = useState(false) + + const userPopoverContent = ( + setUserPopoverOpen(false)} /> + ) useEffect(() => { const pathParts = location.pathname.split('/').filter(Boolean) @@ -322,11 +296,18 @@ const DashboardNavigation = () => { )} {userProfile ? ( - + }> {!isMobile && (userProfile?.name || userProfile.username)} - + ) : null} diff --git a/src/components/Dashboard/common/UserProfilePopover.jsx b/src/components/Dashboard/common/UserProfilePopover.jsx new file mode 100644 index 0000000..b7b2f5f --- /dev/null +++ b/src/components/Dashboard/common/UserProfilePopover.jsx @@ -0,0 +1,158 @@ +import PropTypes from 'prop-types' +import { createElement } from 'react' +import { Flex, Typography, Button, Space, Dropdown, Divider } from 'antd' +import { UserOutlined } from '@ant-design/icons' +import { useContext } from 'react' +import { useNavigate } from 'react-router-dom' +import LogoutIcon from '../../Icons/LogoutIcon' +import { User } from '../../../database/models/User' +import { AuthContext } from '../context/AuthContext' + +const { Text } = Typography + +const ICON_ACTION_NAMES = ['info', 'edit'] + +const UserProfilePopover = ({ onClose }) => { + const { userProfile, profileImageUrl, logout } = useContext(AuthContext) + const navigate = useNavigate() + + const modelActions = User.actions || [] + const iconActions = modelActions.filter((a) => ICON_ACTION_NAMES.includes(a.name)) + const dropdownActions = modelActions.filter( + (a) => !ICON_ACTION_NAMES.includes(a.name) + ) + + const objectData = { ...userProfile, _user: userProfile } + + const runAction = (action) => { + if (action.name === 'logout') { + logout() + } else if (action.url && userProfile?._id) { + const url = action.url(userProfile._id) + navigate(url) + } + onClose?.() + } + + const isActionDisabled = (action) => { + if (action.disabled && typeof action.disabled === 'function') { + return action.disabled(objectData) + } + return false + } + + const username = userProfile?.username + const fullName = [userProfile?.firstName, userProfile?.lastName] + .filter(Boolean) + .join(' ') + const email = userProfile?.email + + const dropdownItems = [ + ...dropdownActions.map((action) => ({ + key: action.name, + label: action.label, + icon: action.icon ? createElement(action.icon) : undefined, + disabled: isActionDisabled(action), + onClick: () => !isActionDisabled(action) && runAction(action) + })), + { type: 'divider' }, + { + key: 'logout', + label: 'Logout', + icon: createElement(LogoutIcon), + onClick: () => runAction({ name: 'logout' }) + } + ] + + const actionButton = + dropdownItems.length > 1 ? ( + + + + ) : null + + return ( + + + {profileImageUrl ? ( + + ) : ( + + )} + + {fullName && ( + + {fullName} + + )} + {email && ( + + + @{username} + + + {email} + + + )} + {!username && !fullName && !email && ( + Unknown user + )} + + + {iconActions.map((action) => ( +