import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useCallback, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { guildMembersQueryKey } from '@/api/guilds'; import { banGuildMember, guildBansQueryKey, kickGuildMember, timeoutGuildMember, } from '@/api/moderation'; import { blockUser } from '@/api/friends'; import { Menu, MenuGroupLabel, MenuItem, MenuSeparator } from '@/components/ui/Menu'; import { useFriendsStore } from '@/stores/friends'; import { useApiErrorMessage } from '@/lib/useApiErrorMessage'; /** Пресеты тайм-аута в минутах: 0 — снять ограничение. */ const TIMEOUT_CHOICES = [1, 10, 60, 1440, 0] as const; interface MemberModerationMenuProps { guildId: string; userId: string; userName: string; canKick: boolean; canBan: boolean; canTimeout: boolean; } /** * Меню модерации участника в списке сервера (AGENT.md 7.17): тайм-аут, * исключение и бан с причиной. Пункты скрыты без соответствующих прав, а * сервер всё равно проверяет их повторно. */ export function MemberModerationMenu({ guildId, userId, userName, canKick, canBan, canTimeout, }: MemberModerationMenuProps) { const { t } = useTranslation(); const describeError = useApiErrorMessage(); const queryClient = useQueryClient(); const [open, setOpen] = useState(false); const [reason, setReason] = useState(''); const anchorRef = useRef(null); const close = useCallback(() => setOpen(false), []); const refresh = (): void => { void queryClient.invalidateQueries({ queryKey: guildMembersQueryKey(guildId) }); void queryClient.invalidateQueries({ queryKey: guildBansQueryKey(guildId) }); }; const timeout = useMutation({ mutationFn: (minutes: number) => timeoutGuildMember( guildId, userId, minutes === 0 ? '2000-01-01T00:00:00Z' : new Date(Date.now() + minutes * 60_000).toISOString(), ), onSuccess: () => { refresh(); close(); }, }); const reloadRelationships = useFriendsStore((state) => state.reload); const blockedUsers = useFriendsStore((state) => state.blocked); const blocked = blockedUsers.some((entry) => entry.user_id === userId); const block = useMutation({ mutationFn: () => blockUser(userId), onSuccess: () => { void reloadRelationships(); }, }); const kick = useMutation({ mutationFn: () => kickGuildMember(guildId, userId), onSuccess: () => { refresh(); close(); }, }); const ban = useMutation({ mutationFn: () => banGuildMember(guildId, userId, reason.trim()), onSuccess: () => { refresh(); close(); }, }); if (!canKick && !canBan && !canTimeout) { return null; } const error = timeout.error ?? kick.error ?? ban.error; const pending = timeout.isPending || kick.isPending || ban.isPending; return ( <> {canTimeout ? ( <> {t('settings.servers.members.timeout')} {TIMEOUT_CHOICES.map((minutes) => ( timeout.mutate(minutes)} > {minutes === 0 ? t('settings.servers.members.timeoutClear') : t('settings.servers.bans.timeoutMinutes', { count: minutes })} ))} ) : null} {canBan ? ( <> {t('settings.servers.members.banReason')} ban.mutate()} > {t('settings.servers.members.ban')} ) : null} {canKick ? ( kick.mutate()} > {t('settings.servers.members.kick')} ) : null} {/* Личная блокировка не зависит от прав на сервере (AGENT.md 7.2). */} block.mutate()} > {t(blocked ? 'friends.blocked.alreadyBlocked' : 'friends.list.block')} {error === null || error === undefined ? null : (

{describeError(error)}

)}
); }