added functional my favorites posts
This commit is contained in:
@@ -35,7 +35,7 @@ export default function Sidebar({
|
||||
const HelpIcon = NavIcons.help;
|
||||
|
||||
const handleNavItem = (itemKey) => {
|
||||
if (!isAuthenticated && itemKey === "myads") {
|
||||
if (!isAuthenticated && (itemKey === "myads" || itemKey === "favorites")) {
|
||||
onLoginClick();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -52,13 +52,15 @@ export default function FeedContent({
|
||||
if (!filteredAds.length) {
|
||||
const emptyMsg = activeNav === "myads"
|
||||
? { title: t('feed.emptyMyAdsTitle'), sub: t('feed.emptyMyAdsSub') }
|
||||
: activeNav === "favorites"
|
||||
? { title: t('feed.emptyFavoritesTitle'), sub: t('feed.emptyFavoritesSub'), icon: "❤️" }
|
||||
: citySearchActive
|
||||
? { title: t('feed.emptyCityTitle'), sub: t('feed.emptyCitySub') }
|
||||
: { title: t('feed.emptyAllTitle'), sub: t('feed.emptyAllSub') };
|
||||
|
||||
return (
|
||||
<div className="f-center">
|
||||
<div className="f-center-icon">🔍</div>
|
||||
<div className="f-center-icon">{emptyMsg.icon || "🔍"}</div>
|
||||
<div className="f-center-title">{emptyMsg.title}</div>
|
||||
<div className="f-center-sub" dangerouslySetInnerHTML={{ __html: emptyMsg.sub }} />
|
||||
</div>
|
||||
|
||||
@@ -58,4 +58,9 @@ export const NavIcons = {
|
||||
<line x1="8" y1="12" x2="16" y2="12" />
|
||||
</svg>
|
||||
),
|
||||
heart: () => (
|
||||
<svg width={20} height={20} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" />
|
||||
</svg>
|
||||
),
|
||||
};
|
||||
@@ -4,6 +4,7 @@ export const NAV_ITEMS = [
|
||||
{ icon: "box", key: "items", labelKey: "nav.items" },
|
||||
{ icon: "paw", key: "pets", labelKey: "nav.pets" },
|
||||
{ icon: "file", key: "myads", labelKey: "nav.myAds" },
|
||||
{ icon: "heart", key: "favorites", labelKey: "nav.favorites" },
|
||||
{ icon: "plus", key: "create", labelKey: "nav.create" },
|
||||
];
|
||||
|
||||
|
||||
@@ -59,9 +59,36 @@ export function useFavorites() {
|
||||
}
|
||||
}, [t]);
|
||||
|
||||
/**
|
||||
* Загружает полные посты из избранного текущего пользователя.
|
||||
* @returns {Promise<Array>} - массив постов (PostResponse)
|
||||
*/
|
||||
const loadFavoritePosts = useCallback(async () => {
|
||||
setError("");
|
||||
try {
|
||||
const response = await apiFetch(`${API_URL}/api/v1/favorites/posts`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(t('favorites.loadError'));
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const posts = Array.isArray(data) ? data : [];
|
||||
return posts.map(post => ({
|
||||
...post,
|
||||
type: post.type ? post.type.toLowerCase() : "loss",
|
||||
}));
|
||||
} catch (err) {
|
||||
console.error('Error loading favorite posts:', err);
|
||||
setError(err.message || t('favorites.loadError'));
|
||||
return [];
|
||||
}
|
||||
}, [t]);
|
||||
|
||||
return {
|
||||
toggleFavorite,
|
||||
loadFavorites,
|
||||
loadFavoritePosts,
|
||||
error,
|
||||
};
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
"nav": {
|
||||
"home": "Home",
|
||||
"myAds": "My ads",
|
||||
"favorites": "Favorites",
|
||||
"items": "Items",
|
||||
"pets": "Pets",
|
||||
"chat": "Chat",
|
||||
@@ -125,6 +126,8 @@
|
||||
"loadError": "Failed to load feed",
|
||||
"emptyMyAdsTitle": "You have no ads",
|
||||
"emptyMyAdsSub": "Create your first one — click the button above",
|
||||
"emptyFavoritesTitle": "No favorites yet",
|
||||
"emptyFavoritesSub": "Tap the ❤️ on any listing to save it here",
|
||||
"emptyAllTitle": "No ads yet",
|
||||
"emptyAllSub": "Be the first — post an ad<br />about a loss or find",
|
||||
"searchPromptTitle": "Enter search parameters",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"nav": {
|
||||
"home": "Главная",
|
||||
"myAds": "Мои объявления",
|
||||
"favorites": "Избранное",
|
||||
"items": "Вещи",
|
||||
"pets": "Питомцы",
|
||||
"chat": "Чат",
|
||||
@@ -126,6 +127,8 @@
|
||||
"loadError": "Не удалось загрузить ленту",
|
||||
"emptyMyAdsTitle": "У вас нет объявлений",
|
||||
"emptyMyAdsSub": "Создайте первое — нажмите кнопку выше",
|
||||
"emptyFavoritesTitle": "В избранном пока пусто",
|
||||
"emptyFavoritesSub": "Нажимайте ❤️ на объявлениях, чтобы сохранить их здесь",
|
||||
"emptyAllTitle": "Объявлений пока нет",
|
||||
"emptyAllSub": "Будьте первым — разместите объявление<br />о потере или находке",
|
||||
"searchPromptTitle": "Введите параметры поиска",
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function MainFeed() {
|
||||
const location = useLocation();
|
||||
const { user, token, isAuthenticated } = useAuthContext();
|
||||
const { cities } = useCities();
|
||||
const { loadFavorites } = useFavorites();
|
||||
const { loadFavorites, loadFavoritePosts, error: favoritesError } = useFavorites();
|
||||
const { t } = useTranslation();
|
||||
const modals = useModals();
|
||||
|
||||
@@ -47,6 +47,8 @@ export default function MainFeed() {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [liked, setLiked] = useState(new Set());
|
||||
const [likeCountAdj, setLikeCountAdj] = useState({});
|
||||
const [favoritePosts, setFavoritePosts] = useState([]);
|
||||
const [favoritesLoading, setFavoritesLoading] = useState(false);
|
||||
|
||||
const feedRef = useRef(null);
|
||||
|
||||
@@ -144,6 +146,20 @@ export default function MainFeed() {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [activeNav, isAuthenticated, user?.id]);
|
||||
|
||||
// Загрузка избранных постов при переходе во вкладку «Избранное»
|
||||
useEffect(() => {
|
||||
if (activeNav !== "favorites") return;
|
||||
if (!isAuthenticated || !token) return;
|
||||
let cancelled = false;
|
||||
setFavoritesLoading(true);
|
||||
loadFavoritePosts().then(posts => {
|
||||
if (cancelled) return;
|
||||
setFavoritePosts(posts);
|
||||
setFavoritesLoading(false);
|
||||
});
|
||||
return () => { cancelled = true; };
|
||||
}, [activeNav, isAuthenticated, token, loadFavoritePosts]);
|
||||
|
||||
const sentinelRef = useInfiniteScroll(
|
||||
() => feed.loadFeed(feed.page + 1, searchQuery, true),
|
||||
activeNav === "home" && selectedCities.length === 0 && feed.hasMore && !feed.loading && !feed.loadingMore
|
||||
@@ -194,6 +210,8 @@ export default function MainFeed() {
|
||||
let result;
|
||||
if (activeNav === "myads") {
|
||||
result = myPosts.myAds;
|
||||
} else if (activeNav === "favorites") {
|
||||
result = favoritePosts;
|
||||
} else if (selectedCities.length > 0) {
|
||||
result = cityFeed.cityAds;
|
||||
} else {
|
||||
@@ -210,10 +228,18 @@ export default function MainFeed() {
|
||||
result = result.filter(ad => ad.type === typeFilter);
|
||||
}
|
||||
return result;
|
||||
}, [activeNav, myPosts.myAds, cityFeed.cityAds, feed.ads, selectedCities, typeFilter]);
|
||||
}, [activeNav, myPosts.myAds, favoritePosts, cityFeed.cityAds, feed.ads, selectedCities, typeFilter]);
|
||||
|
||||
const listLoading = activeNav === "myads" ? myPosts.loading : feed.loading;
|
||||
const listError = activeNav === "myads" ? myPosts.error : feed.error;
|
||||
const listLoading = activeNav === "myads"
|
||||
? myPosts.loading
|
||||
: activeNav === "favorites"
|
||||
? favoritesLoading
|
||||
: feed.loading;
|
||||
const listError = activeNav === "myads"
|
||||
? myPosts.error
|
||||
: activeNav === "favorites"
|
||||
? favoritesError
|
||||
: feed.error;
|
||||
|
||||
return (
|
||||
<div className={`f-app${isSearchView ? " f-search-view" : ""}${isMapView ? " f-map-view" : ""}`}>
|
||||
|
||||
Reference in New Issue
Block a user