import type { ICustomUserStatus, IUser, UserStatus as UserStatusEnum } from '@rocket.chat/core-typings'; import { Box, Icon, RadioButton } from '@rocket.chat/fuselage'; import type { GenericMenuItemProps } from '@rocket.chat/ui-client'; import { clientCallbacks } from '@rocket.chat/ui-client'; import { useEndpoint, useSetting, useStream } from '@rocket.chat/ui-contexts'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useCallback, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useCustomStatusModalHandler } from './useCustomStatusModalHandler'; import { useStatusVisibilityModalHandler } from './useStatusVisibilityModalHandler'; import MarkdownText from '../../../../components/MarkdownText'; import { UserStatus } from '../../../../components/UserStatus'; import { useExpirationText } from '../../../../hooks/useExpirationText'; import { useFireGlobalEvent } from '../../../../hooks/useFireGlobalEvent'; import { userStatuses } from '../../../../lib/userStatuses'; import type { UserStatusDescriptor } from '../../../../lib/userStatuses'; import { mapCustomUserStatusFromApi } from '../../../../lib/utils/mapCustomUserStatusFromApi'; import { useStatusDisabledModal } from '../../../../views/admin/customUserStatus/hooks/useStatusDisabledModal'; export const useStatusItems = (user?: IUser): GenericMenuItemProps[] => { // We should lift this up to somewhere else if we want to use it in other places userStatuses.invisibleAllowed = useSetting('Accounts_AllowInvisibleStatusOption', true); const queryClient = useQueryClient(); const stream = useStream('notify-logged'); const listCustomUserStatusEndpoint = useEndpoint('GET', '/v1/custom-user-status.list'); const listCustomUserStatus = useCallback(async (): Promise => { const all: ICustomUserStatus[] = []; const count = 100; let offset = 0; // REST endpoint is paginated; loop until total reached. while (true) { const { statuses, total } = await listCustomUserStatusEndpoint({ count, offset }); all.push(...statuses.map(mapCustomUserStatusFromApi)); if (all.length >= total || statuses.length === 0) break; offset += statuses.length; } return all; }, [listCustomUserStatusEndpoint]); useEffect( () => userStatuses.watch(stream, () => { queryClient.setQueryData(['user-statuses'], Array.from(userStatuses)); }), [queryClient, stream], ); const { t } = useTranslation(); const presenceDisabled = useSetting('Presence_broadcast_disabled', false); const allowUserStatusMessageChange = useSetting('Accounts_AllowUserStatusMessageChange', true); const fireGlobalStatusEvent = useFireGlobalEvent('user-status-manually-set'); const setStatus = useEndpoint('POST', '/v1/users.setStatus'); const setStatusMutation = useMutation({ mutationFn: async (status: UserStatusDescriptor) => { void setStatus({ status: status.statusType, ...(allowUserStatusMessageChange && { message: userStatuses.isValidType(status.id) ? '' : status.name }), }); void clientCallbacks.run('userStatusManuallySet', status); await fireGlobalStatusEvent.mutateAsync(status); }, }); const { data: statuses } = useQuery({ queryKey: ['user-statuses'], queryFn: async () => { await userStatuses.sync(listCustomUserStatus); return Array.from(userStatuses); }, staleTime: Infinity, }); const handleStatusDisabledModal = useStatusDisabledModal(); const handleCustomStatus = useCustomStatusModalHandler(); const handleStatusVisibility = useStatusVisibilityModalHandler(); const statusVisibilityEnabled = useSetting('Accounts_StatusVisibility_Enabled', false); const customStatusExpiration = useExpirationText(user?.statusExpiresAt); return useMemo(() => { if (presenceDisabled) { return [ { id: 'presence-disabled', content: ( {t('User_status_disabled')} {t('Learn_more')} ), }, ]; } const items: GenericMenuItemProps[] = []; // Top: user's currently-active custom status (display only — clicking does nothing, already selected). if (user?.statusText || customStatusExpiration) { const contentValue = user?.statusText || (user?.status ? t(user.status) : undefined); items.push({ id: 'current-custom-status', status: , content: ( <> {contentValue && } {customStatusExpiration && ( {customStatusExpiration} )} ), addon: , }); } // "Custom..." action opens the edit modal if (allowUserStatusMessageChange) { items.push({ id: 'custom-status-edit', icon: 'edit', content: t('Custom_Status'), onClick: handleCustomStatus, }); } const isPresetSelected = (statusType: UserStatusEnum): boolean => !user?.statusText && !customStatusExpiration && user?.status === statusType; const presetItems = (statuses ?? []) .filter((s) => userStatuses.isValidType(s.id)) .map((status): GenericMenuItemProps => ({ id: status.id, status: , content: , addon: , onClick: () => setStatusMutation.mutate(status), })); // Admin-defined custom statuses const customItems = allowUserStatusMessageChange ? (statuses ?? []) .filter((s) => !userStatuses.isValidType(s.id)) .map((status): GenericMenuItemProps => ({ id: status.id, status: , content: ( ), addon: , onClick: () => setStatusMutation.mutate(status), })) : []; const actionItems: GenericMenuItemProps[] = []; if (statusVisibilityEnabled) { actionItems.push({ id: 'status-visibility-edit', icon: 'eye-off', content: t('Accounts_StatusVisibility_HideFrom'), onClick: handleStatusVisibility, }); } return [...items, ...presetItems, ...customItems, ...actionItems]; }, [ presenceDisabled, allowUserStatusMessageChange, t, handleStatusDisabledModal, user?.status, user?.statusText, customStatusExpiration, statuses, handleCustomStatus, handleStatusVisibility, statusVisibilityEnabled, setStatusMutation, ]); };