2025-11-03 20:35:01 +00:00
|
|
|
|
import { useState, useEffect } from 'react'
|
2025-12-07 22:29:08 +00:00
|
|
|
|
import { useSearchParams, useNavigate } from 'react-router-dom'
|
2025-12-11 00:21:17 +00:00
|
|
|
|
import { getPosts, getPost } from '../utils/api'
|
2025-11-03 20:35:01 +00:00
|
|
|
|
import PostCard from '../components/PostCard'
|
|
|
|
|
|
import CreatePostModal from '../components/CreatePostModal'
|
2025-12-07 22:29:08 +00:00
|
|
|
|
import { Plus, Settings } from 'lucide-react'
|
2025-11-03 20:35:01 +00:00
|
|
|
|
import { hapticFeedback } from '../utils/telegram'
|
|
|
|
|
|
import './Feed.css'
|
|
|
|
|
|
|
|
|
|
|
|
export default function Feed({ user }) {
|
2025-11-04 21:51:05 +00:00
|
|
|
|
const [searchParams] = useSearchParams()
|
2025-12-07 22:29:08 +00:00
|
|
|
|
const navigate = useNavigate()
|
2025-11-03 20:35:01 +00:00
|
|
|
|
const [posts, setPosts] = useState([])
|
|
|
|
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
|
|
const [showCreateModal, setShowCreateModal] = useState(false)
|
|
|
|
|
|
const [filter, setFilter] = useState('all')
|
|
|
|
|
|
const [page, setPage] = useState(1)
|
|
|
|
|
|
const [hasMore, setHasMore] = useState(true)
|
2025-11-04 21:51:05 +00:00
|
|
|
|
const [highlightPostId, setHighlightPostId] = useState(null)
|
2025-11-03 20:35:01 +00:00
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2025-11-04 21:51:05 +00:00
|
|
|
|
// Проверить параметр post в URL
|
|
|
|
|
|
const postId = searchParams.get('post')
|
|
|
|
|
|
if (postId) {
|
|
|
|
|
|
setHighlightPostId(postId)
|
|
|
|
|
|
// Загрузить конкретный пост если его нет в списке
|
|
|
|
|
|
loadSpecificPost(postId)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
loadPosts()
|
|
|
|
|
|
}
|
2025-11-04 22:41:35 +00:00
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
2025-11-04 21:51:05 +00:00
|
|
|
|
}, [filter, searchParams])
|
|
|
|
|
|
|
|
|
|
|
|
const loadSpecificPost = async (postId) => {
|
|
|
|
|
|
try {
|
2025-12-11 00:57:03 +00:00
|
|
|
|
setLoading(true)
|
|
|
|
|
|
|
2025-12-11 00:21:17 +00:00
|
|
|
|
// Сначала проверить, есть ли пост уже в загруженных
|
|
|
|
|
|
const existingPost = posts.find(p => p._id === postId)
|
2025-11-04 21:51:05 +00:00
|
|
|
|
|
2025-12-11 00:21:17 +00:00
|
|
|
|
if (existingPost) {
|
|
|
|
|
|
// Если пост уже загружен, просто прокрутить к нему
|
2025-12-11 00:57:03 +00:00
|
|
|
|
setLoading(false)
|
2025-12-11 00:21:17 +00:00
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
const element = document.getElementById(`post-${postId}`)
|
|
|
|
|
|
if (element) {
|
|
|
|
|
|
element.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 100)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Попытаться загрузить конкретный пост по ID
|
2025-12-11 00:57:03 +00:00
|
|
|
|
let foundPost = null
|
2025-12-11 00:21:17 +00:00
|
|
|
|
try {
|
2025-12-11 00:57:03 +00:00
|
|
|
|
foundPost = await getPost(postId)
|
|
|
|
|
|
console.log('[Feed] Загружен конкретный пост:', foundPost?._id)
|
2025-12-11 00:21:17 +00:00
|
|
|
|
} catch (postError) {
|
2025-12-11 00:57:03 +00:00
|
|
|
|
console.error('[Feed] Ошибка загрузки конкретного поста:', postError)
|
2025-12-11 00:21:17 +00:00
|
|
|
|
// Если не удалось загрузить конкретный пост, попробуем найти в ленте
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-11 00:57:03 +00:00
|
|
|
|
// Загрузить ленту постов
|
2025-12-11 00:21:17 +00:00
|
|
|
|
const data = await getPosts({ filter, page: 1 })
|
2025-12-11 00:57:03 +00:00
|
|
|
|
console.log('[Feed] Загружена лента, найдено постов:', data.posts.length)
|
2025-12-11 00:21:17 +00:00
|
|
|
|
|
2025-12-11 00:57:03 +00:00
|
|
|
|
// Если конкретный пост был загружен, добавить его в начало
|
|
|
|
|
|
if (foundPost) {
|
|
|
|
|
|
// Проверить, нет ли его уже в ленте
|
|
|
|
|
|
const inFeed = data.posts.find(p => p._id === postId)
|
|
|
|
|
|
if (!inFeed) {
|
|
|
|
|
|
// Если поста нет в ленте, добавить его в начало
|
|
|
|
|
|
setPosts([foundPost, ...data.posts])
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Если пост уже в ленте, просто использовать ленту
|
|
|
|
|
|
setPosts(data.posts)
|
|
|
|
|
|
}
|
2025-11-04 21:51:05 +00:00
|
|
|
|
} else {
|
2025-12-11 00:57:03 +00:00
|
|
|
|
// Если конкретный пост не загружен, использовать только ленту
|
|
|
|
|
|
setPosts(data.posts)
|
2025-11-04 21:51:05 +00:00
|
|
|
|
}
|
2025-12-11 00:57:03 +00:00
|
|
|
|
|
|
|
|
|
|
setHasMore(1 < data.totalPages)
|
|
|
|
|
|
setPage(1)
|
|
|
|
|
|
|
|
|
|
|
|
// Прокрутить к посту после загрузки
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
const element = document.getElementById(`post-${postId}`)
|
|
|
|
|
|
if (element) {
|
|
|
|
|
|
element.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.warn('[Feed] Элемент поста не найден после загрузки:', postId)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 300)
|
2025-11-04 21:51:05 +00:00
|
|
|
|
} catch (error) {
|
2025-12-11 00:57:03 +00:00
|
|
|
|
console.error('[Feed] Ошибка загрузки поста:', error)
|
2025-11-04 21:51:05 +00:00
|
|
|
|
await loadPosts()
|
2025-12-11 00:57:03 +00:00
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false)
|
2025-11-04 21:51:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-03 20:35:01 +00:00
|
|
|
|
|
|
|
|
|
|
const loadPosts = async (pageNum = 1) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
setLoading(true)
|
2025-12-07 22:15:00 +00:00
|
|
|
|
const params = {
|
|
|
|
|
|
filter: filter, // 'all', 'interests', 'following'
|
|
|
|
|
|
page: pageNum
|
2025-11-03 20:35:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const data = await getPosts(params)
|
|
|
|
|
|
|
2025-12-01 05:40:27 +00:00
|
|
|
|
// Фильтруем посты без автора (защита от ошибок)
|
|
|
|
|
|
const validPosts = data.posts.filter(post => post.author)
|
|
|
|
|
|
|
2025-11-03 20:35:01 +00:00
|
|
|
|
if (pageNum === 1) {
|
2025-12-01 05:40:27 +00:00
|
|
|
|
setPosts(validPosts)
|
2025-11-03 20:35:01 +00:00
|
|
|
|
} else {
|
2025-12-01 05:40:27 +00:00
|
|
|
|
setPosts(prev => [...prev, ...validPosts])
|
2025-11-03 20:35:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setHasMore(pageNum < data.totalPages)
|
|
|
|
|
|
setPage(pageNum)
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Ошибка загрузки постов:', error)
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleCreatePost = () => {
|
|
|
|
|
|
hapticFeedback('light')
|
|
|
|
|
|
setShowCreateModal(true)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handlePostCreated = (newPost) => {
|
|
|
|
|
|
setPosts(prev => [newPost, ...prev])
|
|
|
|
|
|
setShowCreateModal(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleLoadMore = () => {
|
|
|
|
|
|
if (!loading && hasMore) {
|
|
|
|
|
|
loadPosts(page + 1)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="feed-page">
|
|
|
|
|
|
{/* Хедер */}
|
|
|
|
|
|
<div className="feed-header">
|
2025-11-20 21:32:48 +00:00
|
|
|
|
<h1>Nakama</h1>
|
2025-11-03 20:35:01 +00:00
|
|
|
|
<button className="create-btn" onClick={handleCreatePost}>
|
|
|
|
|
|
<Plus size={20} />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Фильтры */}
|
|
|
|
|
|
<div className="feed-filters">
|
|
|
|
|
|
<button
|
|
|
|
|
|
className={`filter-btn ${filter === 'all' ? 'active' : ''}`}
|
2025-12-07 22:15:00 +00:00
|
|
|
|
onClick={() => {
|
|
|
|
|
|
setFilter('all')
|
|
|
|
|
|
setPage(1)
|
|
|
|
|
|
}}
|
2025-11-03 20:35:01 +00:00
|
|
|
|
>
|
|
|
|
|
|
Все
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
2025-12-07 22:15:00 +00:00
|
|
|
|
className={`filter-btn ${filter === 'interests' ? 'active' : ''}`}
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
setFilter('interests')
|
|
|
|
|
|
setPage(1)
|
|
|
|
|
|
}}
|
2025-11-03 20:35:01 +00:00
|
|
|
|
>
|
2025-12-07 22:15:00 +00:00
|
|
|
|
По интересам
|
2025-11-03 20:35:01 +00:00
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
2025-12-07 22:15:00 +00:00
|
|
|
|
className={`filter-btn ${filter === 'following' ? 'active' : ''}`}
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
setFilter('following')
|
|
|
|
|
|
setPage(1)
|
|
|
|
|
|
}}
|
2025-11-03 20:35:01 +00:00
|
|
|
|
>
|
2025-12-07 22:15:00 +00:00
|
|
|
|
Подписки
|
2025-11-03 20:35:01 +00:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Посты */}
|
|
|
|
|
|
<div className="feed-content">
|
|
|
|
|
|
{loading && posts.length === 0 ? (
|
|
|
|
|
|
<div className="loading-state">
|
|
|
|
|
|
<div className="spinner" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : posts.length === 0 ? (
|
|
|
|
|
|
<div className="empty-state">
|
2025-12-07 22:29:08 +00:00
|
|
|
|
{filter === 'interests' ? (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<p>Нет постов по вашим интересам</p>
|
|
|
|
|
|
<p className="empty-state-hint">
|
|
|
|
|
|
Проверьте выбранные теги в настройках профиля
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="btn-primary"
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
hapticFeedback('light')
|
|
|
|
|
|
navigate('/profile')
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Settings size={18} style={{ marginRight: '8px' }} />
|
|
|
|
|
|
Открыть настройки
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</>
|
|
|
|
|
|
) : filter === 'following' ? (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<p>Нет постов от подписок</p>
|
|
|
|
|
|
<p className="empty-state-hint">
|
|
|
|
|
|
Подпишитесь на пользователей, чтобы видеть их посты здесь
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<button className="btn-primary" onClick={handleCreatePost}>
|
|
|
|
|
|
Создать пост
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<p>Пока нет постов</p>
|
|
|
|
|
|
<button className="btn-primary" onClick={handleCreatePost}>
|
|
|
|
|
|
Создать первый пост
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
2025-11-03 20:35:01 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<>
|
|
|
|
|
|
{posts.map(post => (
|
2025-11-04 21:51:05 +00:00
|
|
|
|
<div
|
|
|
|
|
|
key={post._id}
|
|
|
|
|
|
className={highlightPostId === post._id ? 'post-highlight' : ''}
|
|
|
|
|
|
id={`post-${post._id}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
<PostCard post={post} currentUser={user} onUpdate={loadPosts} />
|
|
|
|
|
|
</div>
|
2025-11-03 20:35:01 +00:00
|
|
|
|
))}
|
|
|
|
|
|
|
|
|
|
|
|
{hasMore && (
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="load-more-btn"
|
|
|
|
|
|
onClick={handleLoadMore}
|
|
|
|
|
|
disabled={loading}
|
|
|
|
|
|
>
|
|
|
|
|
|
{loading ? 'Загрузка...' : 'Загрузить ещё'}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Модальное окно создания поста */}
|
|
|
|
|
|
{showCreateModal && (
|
|
|
|
|
|
<CreatePostModal
|
|
|
|
|
|
user={user}
|
|
|
|
|
|
onClose={() => setShowCreateModal(false)}
|
|
|
|
|
|
onPostCreated={handlePostCreated}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|