import { useState } from 'react' import { useNavigate } from 'react-router-dom' import { createPortal } from 'react-dom' import { Heart, MessageCircle, MoreVertical, ChevronLeft, ChevronRight, Download, Send, X, ZoomIn, Share2 } from 'lucide-react' import { likePost, deletePost, sendPhotoToTelegram } from '../utils/api' import { hapticFeedback, showConfirm, openTelegramLink } from '../utils/telegram' import { decodeHtmlEntities } from '../utils/htmlEntities' import CommentsModal from './CommentsModal' import PostMenu from './PostMenu' import MusicAttachment from './MusicAttachment' import './PostCard.css' const TAG_COLORS = { furry: '#FF8A33', anime: '#4A90E2', other: '#A0A0A0' } const TAG_NAMES = { furry: 'Furry', anime: 'Anime', other: 'Other' } export default function PostCard({ post, currentUser, onUpdate, onAuthRequired }) { const navigate = useNavigate() const [liked, setLiked] = useState(post.likes?.includes(currentUser.id) || false) const [likesCount, setLikesCount] = useState(post.likes?.length || 0) const [currentImageIndex, setCurrentImageIndex] = useState(0) const [showFullView, setShowFullView] = useState(false) const [showComments, setShowComments] = useState(false) const [showMenu, setShowMenu] = useState(false) const [menuButtonPosition, setMenuButtonPosition] = useState(null) const isGuest = currentUser?.isGuest === true // Проверка на существование автора if (!post.author) { console.warn('[PostCard] Post without author:', post._id) return null // Не показываем посты без автора } // Поддержка и старого поля imageUrl и нового images // Фильтруем старые URL из Telegram API const allImages = post.images && post.images.length > 0 ? post.images : (post.imageUrl ? [post.imageUrl] : []) const images = allImages.filter(img => { // Игнорируем старые URL из Telegram API if (img && typeof img === 'string' && img.includes('api.telegram.org/file/bot')) { console.warn('[PostCard] Skipping old Telegram URL:', img) return false } return true }) const handleLike = async () => { // Проверка: гость не может лайкать if (isGuest) { if (onAuthRequired) { onAuthRequired('Войдите, чтобы лайкать посты') } hapticFeedback('error') return } try { hapticFeedback('light') const result = await likePost(post._id) setLiked(result.liked) setLikesCount(result.likes) if (result.liked) { hapticFeedback('success') } } catch (error) { console.error('Ошибка лайка:', error) } } const handleDelete = async () => { const confirmed = await showConfirm('Удалить этот пост?') if (confirmed) { try { await deletePost(post._id) hapticFeedback('success') onUpdate() } catch (error) { console.error('Ошибка удаления:', error) } } } const formatDate = (date) => { const d = new Date(date) return d.toLocaleDateString('ru-RU', { day: 'numeric', month: 'short' }) } const goToProfile = () => { if (post.author?._id) { navigate(`/user/${post.author._id}`) } } const openFullView = () => { if (images.length > 0) { setShowFullView(true) hapticFeedback('light') } } const handleNext = () => { if (currentImageIndex < images.length - 1) { setCurrentImageIndex(currentImageIndex + 1) hapticFeedback('light') } } const handlePrev = () => { if (currentImageIndex > 0) { setCurrentImageIndex(currentImageIndex - 1) hapticFeedback('light') } } const handleDownloadImage = async () => { try { hapticFeedback('light') const imageUrl = images[currentImageIndex] await sendPhotoToTelegram(imageUrl) hapticFeedback('success') } catch (error) { console.error('Ошибка отправки фото:', error) hapticFeedback('error') } } const handleRepost = () => { try { hapticFeedback('light') // Получить имя бота из переменных окружения или использовать дефолтное const botName = import.meta.env.VITE_TELEGRAM_BOT_NAME || 'NakamaSpaceBot' // Создать deeplink для открытия поста в миниапп // Используем startapp для миниаппов - это правильный формат для передачи параметра в миниапп const deeplink = `https://t.me/${botName}?startapp=post_${post._id}` // Открыть нативное окно "Поделиться" в Telegram const shareUrl = `https://t.me/share/url?url=${encodeURIComponent(deeplink)}&text=${encodeURIComponent('Смотри пост в Nakama!')}` openTelegramLink(shareUrl) hapticFeedback('success') } catch (error) { console.error('Ошибка репоста:', error) hapticFeedback('error') } } return (
{/* Хедер поста */}
{post.author?.username { e.target.src = '/default-avatar.png' }} />
{post.author?.firstName || ''} {post.author?.lastName || ''} {!post.author?.firstName && !post.author?.lastName && 'Пользователь'}
@{post.author?.username || post.author?.firstName || 'user'} · {formatDate(post.createdAt)}
{/* Контент */} {post.content && (
{decodeHtmlEntities(post.content)}
)} {/* Изображения */} {images.length > 0 && (
{`Image {images.length > 1 && ( <> {/* Левая зона для переключения на предыдущее изображение */}
{ e.stopPropagation() if (currentImageIndex > 0) { handlePrev() } else { openFullView() } }} style={{ cursor: 'pointer' }} /> {/* Правая зона для переключения на следующее изображение */}
{ e.stopPropagation() if (currentImageIndex < images.length - 1) { handleNext() } else { openFullView() } }} style={{ cursor: 'pointer' }} />
{images.map((_, index) => ( { e.stopPropagation(); setCurrentImageIndex(index); }} /> ))}
)} {/* Индикатор что можно открыть fullview */}
{ e.stopPropagation(); openFullView(); }}>
)} {/* Музыкальный трек (после изображений, компактно) */} {post.attachedTrack && (
)} {/* Теги */}
{post.tags.map((tag, index) => { // Для известных тегов используем цвета и имена из объектов // Для неизвестных тегов используем дефолтный цвет и само имя тега const tagColor = TAG_COLORS[tag] || '#A0A0A0' // Дефолтный серый цвет // Если нет имени в TAG_NAMES, используем сам тег с заглавной первой буквой const tagName = TAG_NAMES[tag] || (tag.charAt(0).toUpperCase() + tag.slice(1)) return ( {tagName} ) })} {post.isNSFW && ( NSFW )}
{/* Действия */}
{/* Fullview модал через Portal */} {showFullView && createPortal(
e.stopPropagation()} onTouchStart={(e) => e.stopPropagation()} onClick={(e) => { e.stopPropagation() e.preventDefault() setShowFullView(false) }} >
e.stopPropagation()}> {currentImageIndex + 1} / {images.length}
e.stopPropagation()}> {`Full e.stopPropagation()} /> {images.length > 1 && ( <> {/* Левая зона для переключения на предыдущее изображение */}
{ e.stopPropagation() if (currentImageIndex > 0) { handlePrev() } }} /> {/* Правая зона для переключения на следующее изображение */}
{ e.stopPropagation() if (currentImageIndex < images.length - 1) { handleNext() } }} /> {/* Кнопки навигации - всегда видимые, но неактивные когда нельзя переключить */} )}
{images.length > 1 && (
e.stopPropagation()}> {images.map((_, index) => ( { e.stopPropagation(); setCurrentImageIndex(index); }} /> ))}
)}
, document.body )} {/* Модалка комментариев через Portal */} {showComments && createPortal( setShowComments(false)} onUpdate={onUpdate} />, document.body )} {/* Модалка меню через Portal */} {showMenu && createPortal( { setShowMenu(false) setMenuButtonPosition(null) }} onDelete={async () => { await handleDelete() setShowMenu(false) setMenuButtonPosition(null) }} onUpdate={onUpdate} buttonPosition={menuButtonPosition} />, document.body )}
) }