import { useState, useRef, useEffect } from 'react'; import { Send, Paperclip, X, Smile } from 'lucide-react'; import { wsClient } from '../api/ws'; import api from '../api/client'; import { Message } from '../types'; import { useStore } from '../store'; interface Props { chatId: string; replyTo: Message | null; editMsg: Message | null; onCancelReply: () => void; onCancelEdit: () => void; onEditDone: (msg: Message) => void; } const EMOJIS = ['😀','😂','😍','🥰','😎','👍','❤️','🔥','✅','👋','🙏','💪','🎉','💯','😊','🤔','😅','🙌','💬','📌']; export default function MessageInput({ chatId, replyTo, editMsg, onCancelReply, onCancelEdit, onEditDone }: Props) { const [text, setText] = useState(''); const [sending, setSending] = useState(false); const [showEmoji, setShowEmoji] = useState(false); const [uploading, setUploading] = useState(false); const textareaRef = useRef(null); const fileRef = useRef(null); const typingTimeout = useRef | null>(null); const { addMessage, user } = useStore(); useEffect(() => { if (editMsg) { setText(editMsg.content); textareaRef.current?.focus(); } }, [editMsg]); useEffect(() => { if (replyTo) textareaRef.current?.focus(); }, [replyTo]); function handleChange(e: React.ChangeEvent) { setText(e.target.value); autoResize(); // Typing indicator wsClient.send('typing', { chatId, typing: true }); if (typingTimeout.current) clearTimeout(typingTimeout.current); typingTimeout.current = setTimeout(() => { wsClient.send('typing', { chatId, typing: false }); }, 2000); } function autoResize() { const el = textareaRef.current; if (el) { el.style.height = 'auto'; el.style.height = Math.min(el.scrollHeight, 120) + 'px'; } } function handleKeyDown(e: React.KeyboardEvent) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } if (e.key === 'Escape') { onCancelReply(); onCancelEdit(); } } async function handleSend() { const content = text.trim(); if (!content || sending) return; setSending(true); setText(''); if (textareaRef.current) textareaRef.current.style.height = 'auto'; wsClient.send('typing', { chatId, typing: false }); try { if (editMsg) { wsClient.send('edit_message', { messageId: editMsg.id, content }); onEditDone({ ...editMsg, content, isEdited: true }); } else { const sent = wsClient.trySend('send_message', { chatId, content, type: 'text', replyToId: replyTo?.id || null, }); if (!sent) { // WS not available — use HTTP fallback const { data } = await api.post(`/api/messages/chat/${chatId}`, { content, type: 'text', replyToId: replyTo?.id || null, }); addMessage(data); } onCancelReply(); } } catch { // restore text so user doesn't lose it setText(content); } finally { setSending(false); } } async function handleFile(e: React.ChangeEvent) { const file = e.target.files?.[0]; if (!file) return; e.target.value = ''; setUploading(true); const form = new FormData(); form.append('file', file); try { const { data } = await api.post(`/api/messages/chat/${chatId}/upload`, form, { headers: { 'Content-Type': 'multipart/form-data' } }); addMessage(data); } catch { alert('Ошибка загрузки файла'); } finally { setUploading(false); } } function insertEmoji(emoji: string) { setText(t => t + emoji); setShowEmoji(false); textareaRef.current?.focus(); } const isEdit = !!editMsg; const placeholder = isEdit ? 'Редактирование...' : replyTo ? 'Ответить...' : 'Сообщение...'; return (
{/* Reply/Edit preview */} {(replyTo || editMsg) && (
{isEdit ? 'Редактирование' : `Ответ: ${replyTo?.sender?.displayName}`}
{isEdit ? editMsg?.content : replyTo?.content}
)}
{/* Emoji picker */}
{showEmoji && (
{EMOJIS.map(e => ( ))}
)}
{/* Textarea */}