import { useFloating } from '@floating-ui/react'; import clsx from 'clsx'; import throttle from 'lodash/throttle'; import React, { useEffect, useMemo, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { Link } from 'react-router-dom'; import { fetchOwnAccounts, logOut, switchAccount } from 'soapbox/actions/auth'; import Account from 'soapbox/components/account'; import { MenuDivider } from 'soapbox/components/ui'; import { useAppDispatch, useAppSelector, useClickOutside, useFeatures } from 'soapbox/hooks'; import { makeGetAccount } from 'soapbox/selectors'; import ThemeToggle from './theme-toggle'; import type { Account as AccountEntity } from 'soapbox/types/entities'; const messages = defineMessages({ add: { id: 'profile_dropdown.add_account', defaultMessage: 'Add an existing account' }, theme: { id: 'profile_dropdown.theme', defaultMessage: 'Theme' }, logout: { id: 'profile_dropdown.logout', defaultMessage: 'Log out @{acct}' }, }); interface IProfileDropdown { account: AccountEntity children: React.ReactNode } type IMenuItem = { text: string | React.ReactElement | null to?: string toggle?: JSX.Element icon?: string action?: (event: React.MouseEvent) => void } const getAccount = makeGetAccount(); const ProfileDropdown: React.FC = ({ account, children }) => { const dispatch = useAppDispatch(); const features = useFeatures(); const intl = useIntl(); const [visible, setVisible] = useState(false); const { x, y, strategy, refs } = useFloating({ placement: 'bottom-end' }); const authUsers = useAppSelector((state) => state.auth.users); const otherAccounts = useAppSelector((state) => authUsers.map((authUser: any) => getAccount(state, authUser.id)!)); const handleLogOut = () => { dispatch(logOut()); }; const handleSwitchAccount = (account: AccountEntity) => { return () => { dispatch(switchAccount(account.id)); }; }; const fetchOwnAccountThrottled = throttle(() => { dispatch(fetchOwnAccounts()); }, 2000); const renderAccount = (account: AccountEntity) => { return ( ); }; const menu: IMenuItem[] = useMemo(() => { const menu: IMenuItem[] = []; menu.push({ text: renderAccount(account), to: `/@${account.acct}` }); otherAccounts.forEach((otherAccount: AccountEntity) => { if (otherAccount && otherAccount.id !== account.id) { menu.push({ text: renderAccount(otherAccount), action: handleSwitchAccount(otherAccount), }); } }); menu.push({ text: null }); menu.push({ text: intl.formatMessage(messages.theme), toggle: }); menu.push({ text: null }); menu.push({ text: intl.formatMessage(messages.add), to: '/login/add', icon: require('@tabler/icons/plus.svg'), }); menu.push({ text: intl.formatMessage(messages.logout, { acct: account.acct }), to: '/logout', action: handleLogOut, icon: require('@tabler/icons/logout.svg'), }); return menu; }, [account, authUsers, features]); const toggleVisible = () => setVisible(!visible); useEffect(() => { fetchOwnAccountThrottled(); }, [account, authUsers]); useClickOutside(refs, () => { setVisible(false); }); return ( <> {visible && (
{menu.map((menuItem, i) => ( ))}
)} ); }; interface MenuItemProps { className?: string menuItem: IMenuItem } const MenuItem: React.FC = ({ className, menuItem }) => { const baseClassName = clsx(className, 'block w-full cursor-pointer truncate px-4 py-2.5 text-left text-sm text-gray-700 hover:bg-gray-100 rtl:text-right dark:text-gray-500 dark:hover:bg-gray-800'); if (menuItem.toggle) { return (
{menuItem.text} {menuItem.toggle}
); } else if (!menuItem.text) { return ; } else if (menuItem.action) { return ( ); } else if (menuItem.to) { return ( {menuItem.text} ); } else { throw menuItem; } }; export default ProfileDropdown;