182 lines
5.0 KiB
React
182 lines
5.0 KiB
React
|
|
import { useState } from 'react'
|
||
|
|
import { useNavigate } from 'react-router-dom'
|
||
|
|
import { Heart, MessageCircle, Share2, MoreVertical } from 'lucide-react'
|
||
|
|
import { likePost, commentPost, repostPost, deletePost } from '../utils/api'
|
||
|
|
import { hapticFeedback, showConfirm } from '../utils/telegram'
|
||
|
|
import PostMenu from './PostMenu'
|
||
|
|
import CommentsModal from './CommentsModal'
|
||
|
|
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 }) {
|
||
|
|
const navigate = useNavigate()
|
||
|
|
const [liked, setLiked] = useState(post.likes.includes(currentUser.id))
|
||
|
|
const [likesCount, setLikesCount] = useState(post.likes.length)
|
||
|
|
const [reposted, setReposted] = useState(post.reposts.includes(currentUser.id))
|
||
|
|
const [repostsCount, setRepostsCount] = useState(post.reposts.length)
|
||
|
|
const [showMenu, setShowMenu] = useState(false)
|
||
|
|
const [showComments, setShowComments] = useState(false)
|
||
|
|
|
||
|
|
const handleLike = async () => {
|
||
|
|
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 handleRepost = async () => {
|
||
|
|
try {
|
||
|
|
hapticFeedback('light')
|
||
|
|
const result = await repostPost(post._id)
|
||
|
|
setReposted(result.reposted)
|
||
|
|
setRepostsCount(result.reposts)
|
||
|
|
if (result.reposted) {
|
||
|
|
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 = () => {
|
||
|
|
navigate(`/user/${post.author._id}`)
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="post-card card fade-in">
|
||
|
|
{/* Хедер поста */}
|
||
|
|
<div className="post-header">
|
||
|
|
<div className="post-author" onClick={goToProfile}>
|
||
|
|
<img
|
||
|
|
src={post.author.photoUrl || '/default-avatar.png'}
|
||
|
|
alt={post.author.username}
|
||
|
|
className="author-avatar"
|
||
|
|
/>
|
||
|
|
<div className="author-info">
|
||
|
|
<div className="author-name">
|
||
|
|
{post.author.firstName} {post.author.lastName}
|
||
|
|
</div>
|
||
|
|
<div className="post-date">
|
||
|
|
@{post.author.username} · {formatDate(post.createdAt)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<button className="menu-btn" onClick={() => setShowMenu(true)}>
|
||
|
|
<MoreVertical size={20} />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Контент */}
|
||
|
|
{post.content && (
|
||
|
|
<div className="post-content">
|
||
|
|
{post.content}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Изображение */}
|
||
|
|
{post.imageUrl && (
|
||
|
|
<div className="post-image">
|
||
|
|
<img src={post.imageUrl} alt="Post" />
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Теги */}
|
||
|
|
<div className="post-tags">
|
||
|
|
{post.tags.map((tag, index) => (
|
||
|
|
<span
|
||
|
|
key={index}
|
||
|
|
className="post-tag"
|
||
|
|
style={{ backgroundColor: TAG_COLORS[tag] }}
|
||
|
|
>
|
||
|
|
{TAG_NAMES[tag]}
|
||
|
|
</span>
|
||
|
|
))}
|
||
|
|
{post.isNSFW && (
|
||
|
|
<span className="nsfw-badge">NSFW</span>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Действия */}
|
||
|
|
<div className="post-actions">
|
||
|
|
<button
|
||
|
|
className={`action-btn ${liked ? 'active' : ''}`}
|
||
|
|
onClick={handleLike}
|
||
|
|
>
|
||
|
|
<Heart size={20} fill={liked ? '#FF3B30' : 'none'} color={liked ? '#FF3B30' : undefined} />
|
||
|
|
<span>{likesCount}</span>
|
||
|
|
</button>
|
||
|
|
|
||
|
|
<button className="action-btn" onClick={() => setShowComments(true)}>
|
||
|
|
<MessageCircle size={20} />
|
||
|
|
<span>{post.comments.length}</span>
|
||
|
|
</button>
|
||
|
|
|
||
|
|
<button
|
||
|
|
className={`action-btn ${reposted ? 'active' : ''}`}
|
||
|
|
onClick={handleRepost}
|
||
|
|
>
|
||
|
|
<Share2 size={20} color={reposted ? '#34C759' : undefined} />
|
||
|
|
<span>{repostsCount}</span>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Меню поста */}
|
||
|
|
{showMenu && (
|
||
|
|
<PostMenu
|
||
|
|
post={post}
|
||
|
|
currentUser={currentUser}
|
||
|
|
onClose={() => setShowMenu(false)}
|
||
|
|
onDelete={handleDelete}
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Комментарии */}
|
||
|
|
{showComments && (
|
||
|
|
<CommentsModal
|
||
|
|
post={post}
|
||
|
|
onClose={() => setShowComments(false)}
|
||
|
|
onUpdate={onUpdate}
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|