import { useEffect, useMemo, useRef, useState } from 'react' import Avatar from './Avatar' import { IconSearch, IconUserPlus, IconUsers } from './icons' import { api } from '../lib/api' import { userTitle } from '../lib/format' import type { Contact, User } from '../lib/types' type DialogMode = 'dm' | 'group' interface NewChatDialogProps { onClose: () => void onStartChat: (user: User) => Promise onCreateGroup: (name: string, memberIds: number[]) => Promise onContactsChanged: () => void } export default function NewChatDialog({ onClose, onStartChat, onCreateGroup, onContactsChanged, }: NewChatDialogProps) { const [mode, setMode] = useState('dm') const [query, setQuery] = useState('') const [results, setResults] = useState([]) const [resultQuery, setResultQuery] = useState('') const [contacts, setContacts] = useState([]) const [loadingContacts, setLoadingContacts] = useState(true) const [error, setError] = useState(null) const [busyId, setBusyId] = useState(null) const [groupName, setGroupName] = useState('') const [selectedIds, setSelectedIds] = useState>(() => new Set()) const [creating, setCreating] = useState(false) const inputRef = useRef(null) useEffect(() => { inputRef.current?.focus() const onKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') onClose() } window.addEventListener('keydown', onKeyDown) return () => window.removeEventListener('keydown', onKeyDown) }, [onClose]) useEffect(() => { let cancelled = false api .listContacts() .then((list) => { if (!cancelled) setContacts(list) }) .catch(() => { if (!cancelled) setContacts([]) }) .finally(() => { if (!cancelled) setLoadingContacts(false) }) return () => { cancelled = true } }, []) useEffect(() => { const term = query.trim() if (term.length === 0) return let cancelled = false const timer = window.setTimeout(() => { api .searchUsers(term) .then((users) => { if (cancelled) return setResults(users) setResultQuery(term) }) .catch(() => { if (cancelled) return setResults([]) setResultQuery(term) }) }, 250) return () => { cancelled = true window.clearTimeout(timer) } }, [query]) const contactIds = useMemo(() => new Set(contacts.map((contact) => contact.user.id)), [contacts]) const trimmed = query.trim() const searching = trimmed.length > 0 && resultQuery !== trimmed const list = trimmed.length > 0 ? results : contacts.map((contact) => contact.user) const runAction = async (user: User, action: () => Promise) => { setBusyId(user.id) setError(null) try { await action() } catch (err) { setError(err instanceof Error ? err.message : 'Не удалось выполнить действие') } finally { setBusyId(null) } } const handleStart = (user: User) => runAction(user, async () => { await onStartChat(user) onClose() }) const handleAdd = (user: User) => runAction(user, async () => { await api.addContact(user.id) setContacts((prev) => prev.some((contact) => contact.user.id === user.id) ? prev : [...prev, { user, added_at: new Date().toISOString() }], ) onContactsChanged() }) const toggleMember = (userId: number) => { setSelectedIds((prev) => { const next = new Set(prev) if (next.has(userId)) next.delete(userId) else next.add(userId) return next }) } const handleCreateGroup = async () => { const name = groupName.trim() if (!name || selectedIds.size === 0) return setCreating(true) setError(null) try { await onCreateGroup(name, [...selectedIds]) onClose() } catch (err) { setError(err instanceof Error ? err.message : 'Не удалось создать группу') } finally { setCreating(false) } } const canCreate = groupName.trim().length > 0 && selectedIds.size > 0 && !creating return (

{mode === 'dm' ? 'Найдите человека или выберите из контактов' : 'Соберите группу и добавьте участников'}

{(['dm', 'group'] as const).map((value) => ( ))}
{mode === 'group' && (
setGroupName(event.target.value)} placeholder="Название группы" maxLength={80} className="retro-input w-full py-1.5 pr-2 pl-7 text-xs" />
)}
setQuery(event.target.value)} placeholder="Имя или @username" className="retro-input w-full py-1.5 pr-2 pl-7 text-xs" />
{error &&

{error}

}
{(loadingContacts && trimmed.length === 0) || (searching && list.length === 0) ? (
{[0, 1, 2].map((row) => (
))}
) : list.length === 0 ? (
{trimmed.length > 0 ? 'Пользователи не найдены' : 'Список контактов пуст'}
) : (
    {list.map((user) => { const isContact = contactIds.has(user.id) const busy = busyId === user.id const selected = selectedIds.has(user.id) return (
  • {mode === 'group' && ( )}

    {userTitle(user)}

    @{user.username}

    {mode === 'dm' && ( <> {!isContact && ( )} )}
  • ) })}
)}
{mode === 'group' && (
Выбрано: {selectedIds.size}
)} ) }