import { useState, useEffect } from 'react' import { createPortal } from 'react-dom' import { X, Send, Trash2, Edit2 } from 'lucide-react' import { commentPost, getPosts, deleteComment, editComment } from '../utils/api' import { hapticFeedback, showConfirm } from '../utils/telegram' import { decodeHtmlEntities } from '../utils/htmlEntities' import './CommentsModal.css' export default function CommentsModal({ post, onClose, onUpdate, currentUser }) { // ВСЕ хуки должны вызываться всегда, до любых условных возвратов const [comment, setComment] = useState('') const [loading, setLoading] = useState(false) const [comments, setComments] = useState([]) const [fullPost, setFullPost] = useState(null) const [loadingPost, setLoadingPost] = useState(false) const [editingCommentId, setEditingCommentId] = useState(null) const [editText, setEditText] = useState('') // Загрузить полные данные поста с комментариями // ВАЖНО: useEffect всегда вызывается, даже если post отсутствует useEffect(() => { // Если пост не передан, очищаем состояние и выходим if (!post || !post._id) { setFullPost(null) setComments([]) setLoadingPost(false) return } // Сначала установим переданные данные setFullPost(post) const initialComments = (post.comments || []).filter(c => { return c && c.author && (typeof c.author === 'object') }) setComments(initialComments) // Затем загрузим полные данные для обновления let cancelled = false const loadFullPost = async () => { try { setLoadingPost(true) // Загрузить посты с фильтром по автору поста для оптимизации const authorId = post?.author?._id || post?.author const response = authorId ? await getPosts({ userId: authorId, limit: 100 }) : await getPosts({ limit: 200 }) // Проверяем, что запрос не был отменен if (cancelled) return const foundPost = response.posts?.find(p => p._id === post._id) if (foundPost) { // Проверяем, что комментарии populate'ены с авторами const commentsWithAuthors = (foundPost.comments || []).filter(c => { return c && c.author && (typeof c.author === 'object') }) setComments(commentsWithAuthors) setFullPost(foundPost) } } catch (error) { console.error('[CommentsModal] Ошибка загрузки поста:', error) // Оставляем переданные данные } finally { if (!cancelled) { setLoadingPost(false) } } } loadFullPost() // Cleanup функция для отмены запроса при размонтировании return () => { cancelled = true } }, [post?._id]) // Используем просто post?._id без || null // Проверка на существование поста ПОСЛЕ хуков const displayPost = fullPost || post const hasValidPost = post && post._id && displayPost && displayPost.author const handleSubmit = async () => { if (!comment.trim() || loading || !post || !post._id) return try { setLoading(true) hapticFeedback('light') const result = await commentPost(post._id, comment) console.log('[CommentsModal] Результат добавления комментария:', result) if (result && result.comments && Array.isArray(result.comments)) { // Фильтруем комментарии с авторами (проверяем, что author - объект) const commentsWithAuthors = result.comments.filter(c => { return c && c.author && (typeof c.author === 'object') }) console.log('[CommentsModal] Отфильтрованные комментарии:', commentsWithAuthors.length, 'из', result.comments.length) setComments(commentsWithAuthors) // Обновить полный пост if (fullPost) { setFullPost({ ...fullPost, comments: commentsWithAuthors }) } setComment('') hapticFeedback('success') if (onUpdate) { onUpdate() } } else { console.error('[CommentsModal] Неожиданный формат ответа:', result) hapticFeedback('error') } } catch (error) { console.error('[CommentsModal] Ошибка добавления комментария:', error) hapticFeedback('error') } finally { setLoading(false) } } const formatDate = (date) => { if (!date) return 'только что' const d = new Date(date) const now = new Date() const diff = Math.floor((now - d) / 1000) // секунды if (diff < 60) return 'только что' if (diff < 3600) return `${Math.floor(diff / 60)} мин` if (diff < 86400) return `${Math.floor(diff / 3600)} ч` return d.toLocaleDateString('ru-RU', { day: 'numeric', month: 'short' }) } const handleOverlayClick = (e) => { // Закрывать только при клике на overlay, не на контент if (e.target === e.currentTarget) { onClose() } } const handleDeleteComment = async (commentId) => { if (!post || !post._id) return const confirmed = await showConfirm('Удалить этот комментарий?') if (!confirmed) return try { hapticFeedback('light') const result = await deleteComment(post._id, commentId) if (result && result.comments && Array.isArray(result.comments)) { const commentsWithAuthors = result.comments.filter(c => { return c && c.author && (typeof c.author === 'object') }) setComments(commentsWithAuthors) if (fullPost) { setFullPost({ ...fullPost, comments: commentsWithAuthors }) } hapticFeedback('success') if (onUpdate) { onUpdate() } } } catch (error) { console.error('[CommentsModal] Ошибка удаления комментария:', error) hapticFeedback('error') } } const handleStartEdit = (comment) => { setEditingCommentId(comment._id) setEditText(comment.content) } const handleCancelEdit = () => { setEditingCommentId(null) setEditText('') } const handleSaveEdit = async (commentId) => { if (!post || !post._id || !editText.trim()) return try { hapticFeedback('light') const result = await editComment(post._id, commentId, editText.trim()) if (result && result.comments && Array.isArray(result.comments)) { const commentsWithAuthors = result.comments.filter(c => { return c && c.author && (typeof c.author === 'object') }) setComments(commentsWithAuthors) if (fullPost) { setFullPost({ ...fullPost, comments: commentsWithAuthors }) } setEditingCommentId(null) setEditText('') hapticFeedback('success') if (onUpdate) { onUpdate() } } } catch (error) { console.error('[CommentsModal] Ошибка редактирования комментария:', error) hapticFeedback('error') } } const isCommentAuthor = (comment) => { if (!currentUser || !comment.author) return false const authorId = comment.author._id || comment.author const userId = currentUser.id || currentUser._id return authorId === userId } const isModerator = () => { return currentUser && (currentUser.role === 'moderator' || currentUser.role === 'admin') } // ВСЕГДА рендерим createPortal, даже если пост не валиден // Это критично для соблюдения правил хуков return createPortal(
Загрузка...
Пока нет комментариев
Будьте первым!{decodeHtmlEntities(c.content)}
)}