import { useCallback, useRef, useState } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router'; import { logout } from '@/api/auth'; import { currentUserQueryKey, updateProfile } from '@/api/users'; import { userStatuses, type UserStatus } from '@/api/types'; import { Avatar } from '@/components/ui/Avatar'; import { Menu, MenuGroupLabel, MenuItem, MenuSeparator } from '@/components/ui/Menu'; import { useCurrentUser } from '@/lib/hooks'; import { useApiErrorMessage } from '@/lib/useApiErrorMessage'; import { useGatewayStore } from '@/stores/gateway'; import { useSessionStore } from '@/stores/session'; /** Статусы, которые можно выставить из меню: «не в сети» сервер выводит сам. */ const selectableStatuses = userStatuses.filter((value) => value !== 'offline'); /** * Панель пользователя внизу слева: аватар, имя и статус. Клик по аватару, * имени или шестерёнке открывает всплывающее вверх меню с настройками, * сменой статуса и выходом (см. `Menu`). */ export function UserPanel() { const { t } = useTranslation(); const navigate = useNavigate(); const queryClient = useQueryClient(); const currentUser = useCurrentUser(); const describeError = useApiErrorMessage(); const sessionUser = useSessionStore((state) => state.user); const disconnect = useGatewayStore((state) => state.disconnect); const [menuOpen, setMenuOpen] = useState(false); const triggerRef = useRef(null); const name = sessionUser?.display_name ?? currentUser.data?.display_name ?? ''; const username = sessionUser?.username ?? currentUser.data?.username ?? ''; const displayName = name === '' ? username : name; const status: UserStatus = sessionUser?.status ?? currentUser.data?.status ?? 'online'; const customStatus = sessionUser?.custom_status ?? ''; const emoji = sessionUser?.custom_status_emoji ?? ''; const seed = currentUser.data?.id ?? sessionUser?.id ?? username; const avatarFileId = sessionUser?.avatar_file_id ?? currentUser.data?.avatar_file_id; const signOut = useMutation({ mutationFn: () => logout(), onSuccess: () => { // Сессия в cookie уже погашена — сбрасываем локальное состояние. disconnect(); useSessionStore.getState().reset(); queryClient.clear(); void navigate('/login', { replace: true }); }, }); const changeStatus = useMutation({ mutationFn: (next: UserStatus) => updateProfile({ status: next }), onSuccess: (updated) => { queryClient.setQueryData(currentUserQueryKey, updated); useSessionStore.getState().patchUser(updated); setMenuOpen(false); }, }); const statusLabel = userStatuses.includes(status) ? t(`user.status.${status}`) : t('user.status.offline'); const onCopyId = useCallback(() => { if (currentUser.data === undefined) { return; } void navigator.clipboard?.writeText(currentUser.data.id); }, [currentUser.data]); const toggleMenu = useCallback(() => setMenuOpen((value) => !value), []); const closeMenu = useCallback(() => setMenuOpen(false), []); const goTo = (path: string) => (): void => { setMenuOpen(false); void navigate(path); }; const error = changeStatus.error ?? signOut.error; const errorText = error === null || error === undefined ? null : describeError(error); return ( ); }