nakama/frontend/src/pages/Feed.jsx

269 lines
8.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useEffect } from 'react'
import { useSearchParams, useNavigate } from 'react-router-dom'
import { getPosts, getPost } from '../utils/api'
import PostCard from '../components/PostCard'
import CreatePostModal from '../components/CreatePostModal'
import { Plus, Settings } from 'lucide-react'
import { hapticFeedback } from '../utils/telegram'
import './Feed.css'
export default function Feed({ user }) {
const [searchParams] = useSearchParams()
const navigate = useNavigate()
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)
const [highlightPostId, setHighlightPostId] = useState(null)
useEffect(() => {
// Проверить параметр post в URL
const postId = searchParams.get('post')
if (postId) {
setHighlightPostId(postId)
// Загрузить конкретный пост если его нет в списке
loadSpecificPost(postId)
} else {
loadPosts()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [filter, searchParams])
const loadSpecificPost = async (postId) => {
try {
setLoading(true)
// Сначала проверить, есть ли пост уже в загруженных
const existingPost = posts.find(p => p._id === postId)
if (existingPost) {
// Если пост уже загружен, просто прокрутить к нему
setLoading(false)
setTimeout(() => {
const element = document.getElementById(`post-${postId}`)
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'center' })
}
}, 100)
return
}
// Попытаться загрузить конкретный пост по ID
let foundPost = null
try {
foundPost = await getPost(postId)
console.log('[Feed] Загружен конкретный пост:', foundPost?._id)
} catch (postError) {
console.error('[Feed] Ошибка загрузки конкретного поста:', postError)
// Если не удалось загрузить конкретный пост, попробуем найти в ленте
}
// Загрузить ленту постов
const data = await getPosts({ filter, page: 1 })
console.log('[Feed] Загружена лента, найдено постов:', data.posts.length)
// Если конкретный пост был загружен, добавить его в начало
if (foundPost) {
// Проверить, нет ли его уже в ленте
const inFeed = data.posts.find(p => p._id === postId)
if (!inFeed) {
// Если поста нет в ленте, добавить его в начало
setPosts([foundPost, ...data.posts])
} else {
// Если пост уже в ленте, просто использовать ленту
setPosts(data.posts)
}
} else {
// Если конкретный пост не загружен, использовать только ленту
setPosts(data.posts)
}
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)
} catch (error) {
console.error('[Feed] Ошибка загрузки поста:', error)
await loadPosts()
} finally {
setLoading(false)
}
}
const loadPosts = async (pageNum = 1) => {
try {
setLoading(true)
const params = {
filter: filter, // 'all', 'interests', 'following'
page: pageNum
}
const data = await getPosts(params)
// Фильтруем посты без автора (защита от ошибок)
const validPosts = data.posts.filter(post => post.author)
if (pageNum === 1) {
setPosts(validPosts)
} else {
setPosts(prev => [...prev, ...validPosts])
}
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">
<h1>Nakama</h1>
<button className="create-btn" onClick={handleCreatePost}>
<Plus size={20} />
</button>
</div>
{/* Фильтры */}
<div className="feed-filters">
<button
className={`filter-btn ${filter === 'all' ? 'active' : ''}`}
onClick={() => {
setFilter('all')
setPage(1)
}}
>
Все
</button>
<button
className={`filter-btn ${filter === 'interests' ? 'active' : ''}`}
onClick={() => {
setFilter('interests')
setPage(1)
}}
>
По интересам
</button>
<button
className={`filter-btn ${filter === 'following' ? 'active' : ''}`}
onClick={() => {
setFilter('following')
setPage(1)
}}
>
Подписки
</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">
{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>
</>
)}
</div>
) : (
<>
{posts.map(post => (
<div
key={post._id}
className={highlightPostId === post._id ? 'post-highlight' : ''}
id={`post-${post._id}`}
>
<PostCard post={post} currentUser={user} onUpdate={loadPosts} />
</div>
))}
{hasMore && (
<button
className="load-more-btn"
onClick={handleLoadMore}
disabled={loading}
>
{loading ? 'Загрузка...' : 'Загрузить ещё'}
</button>
)}
</>
)}
</div>
{/* Модальное окно создания поста */}
{showCreateModal && (
<CreatePostModal
user={user}
onClose={() => setShowCreateModal(false)}
onPostCreated={handlePostCreated}
/>
)}
</div>
)
}