140 lines
4.2 KiB
JavaScript
140 lines
4.2 KiB
JavaScript
import {useState, useEffect, useCallback} from "react";
|
||
import "../styles/feed.css";
|
||
import {API_URL} from "../config/constants";
|
||
import Sidebar from "../components/Sidebar";
|
||
import RightSidebar from "../components/RightSidebar";
|
||
import AdCard from "../components/AdCard";
|
||
import PostModal from "../components/PostModal";
|
||
import MenuIcon from "../components/icons/MenuIcon"
|
||
|
||
export default function FeedPage() {
|
||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||
const [activeNav, setActiveNav] = useState("Главная");
|
||
const [modalOpen, setModalOpen] = useState(false);
|
||
const [ads, setAds] = useState([]);
|
||
const [liked, setLiked] = useState(new Set());
|
||
const [loading, setLoading] = useState(true);
|
||
const [error, setError] = useState("");
|
||
|
||
useEffect(() => {
|
||
const load = async () => {
|
||
setLoading(true);
|
||
setError("");
|
||
try {
|
||
const res = await fetch(`${API_URL}/api/v1/posts`);
|
||
if (!res.ok) {
|
||
throw new Error(`Ошибка ${res.status}`);
|
||
}
|
||
const data = await res.json();
|
||
// поддерживаем { items: [] } и просто []
|
||
setAds(Array.isArray(data) ? data : (data.items ?? []));
|
||
} catch (err) {
|
||
setError(err.message || "Не удалось загрузить объявления");
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
load();
|
||
}, []);
|
||
|
||
const toggleLike = useCallback((id) => {
|
||
setLiked(prev => {
|
||
const next = new Set(prev);
|
||
next.has(id) ? next.delete(id) : next.add(id);
|
||
return next;
|
||
});
|
||
}, []);
|
||
|
||
// Новое объявление — добавляем в начало ленты
|
||
const handleNewAd = useCallback((ad) => {
|
||
setAds(prev => [ad, ...prev]);
|
||
}, []);
|
||
|
||
const renderContent = () => {
|
||
if (loading) {
|
||
return (
|
||
<div className="f-center">
|
||
<div className="f-spinner-wrap">
|
||
<div className="f-spinner"/>
|
||
Загружаем объявления...
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
if (error) {
|
||
return (
|
||
<div className="f-center">
|
||
<div className="f-center-icon">⚠️</div>
|
||
<div className="f-center-title">Не удалось загрузить ленту</div>
|
||
<div className="f-center-sub">{error}</div>
|
||
</div>
|
||
);
|
||
}
|
||
if (!ads.length) {
|
||
return (
|
||
<div className="f-center">
|
||
<div className="f-center-icon">🔍</div>
|
||
<div className="f-center-title">Объявлений пока нет</div>
|
||
<div className="f-center-sub">
|
||
Будьте первым — разместите объявление<br/>о потере или находке
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
return ads.map(ad => (
|
||
<AdCard key={ad.id} ad={ad} liked={liked.has(ad.id)}
|
||
onLike={toggleLike}/>
|
||
));
|
||
};
|
||
|
||
return (
|
||
<>
|
||
<div className="f-app">
|
||
|
||
<Sidebar
|
||
open={sidebarOpen}
|
||
activeNav={activeNav}
|
||
onClose={() => setSidebarOpen(false)}
|
||
onNavClick={setActiveNav}
|
||
/>
|
||
|
||
<div className="f-main">
|
||
<div className="f-topbar">
|
||
<button className="f-menu-btn"
|
||
onClick={() => setSidebarOpen(v => !v)}>
|
||
<MenuIcon/>
|
||
</button>
|
||
<div className="f-topbar-center">
|
||
<button className="f-post-btn"
|
||
onClick={() => setModalOpen(true)}>
|
||
<span style={{
|
||
fontSize: "1.15rem",
|
||
fontWeight: 900,
|
||
lineHeight: 1
|
||
}}>+</span>
|
||
Разместить объявление
|
||
</button>
|
||
</div>
|
||
<div className="f-topbar-spacer"/>
|
||
</div>
|
||
|
||
<div className="f-wrap">
|
||
<div className="f-feed">
|
||
{renderContent()}
|
||
</div>
|
||
<RightSidebar/>
|
||
</div>
|
||
</div>
|
||
|
||
{modalOpen && (
|
||
<PostModal
|
||
onClose={() => setModalOpen(false)}
|
||
onSuccess={handleNewAd}
|
||
/>
|
||
)}
|
||
|
||
</div>
|
||
</>
|
||
);
|
||
}
|