From 58352267fae299e79913341e81156e4a7d85f197 Mon Sep 17 00:00:00 2001 From: SlimusMinus Date: Thu, 24 Sep 2026 00:20:41 +0300 Subject: [PATCH] deleted full likes --- src/components/PostDetailModal.jsx | 26 +------- src/components/RightSidebar.jsx | 2 +- src/components/Sidebar.jsx | 2 +- src/components/feed/FeedContent.jsx | 12 +--- src/hooks/useDeleteChat.js | 2 +- src/hooks/useFavorites.js | 93 ----------------------------- src/hooks/useFeedActions.jsx | 53 ++-------------- src/locales/en.json | 10 +--- src/locales/ru.json | 10 +--- src/pages/MainFeed.tsx | 67 ++------------------- src/styles/card.css | 6 -- 11 files changed, 18 insertions(+), 265 deletions(-) delete mode 100644 src/hooks/useFavorites.js diff --git a/src/components/PostDetailModal.jsx b/src/components/PostDetailModal.jsx index 48aaa87..17e2164 100644 --- a/src/components/PostDetailModal.jsx +++ b/src/components/PostDetailModal.jsx @@ -3,19 +3,15 @@ import { useTranslation } from "react-i18next"; import { API_URL } from "../config/authConstants"; import { apiFetch } from "../api/apiClient"; import { useAuthContext } from "../context/AuthContext"; -import { useFavorites } from "../hooks/useFavorites"; import PostOnFeed from "../pages/PostOnFeed"; import CloseIcon from "./icons/CloseIcon"; export default function PostDetailModal({ postId, onClose, onImageClick, onLocationClick, onAuthRequired }) { const { t } = useTranslation(); - const { user, isAuthenticated } = useAuthContext(); - const { toggleFavorite } = useFavorites(); + const { user } = useAuthContext(); const [post, setPost] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - const [liked, setLiked] = useState(false); - const [likeCountAdj, setLikeCountAdj] = useState(0); useEffect(() => { if (!postId) return; @@ -30,8 +26,6 @@ export default function PostDetailModal({ postId, onClose, onImageClick, onLocat const data = await res.json(); if (!cancelled) { setPost({ ...data, type: data.type ? data.type.toLowerCase() : "loss" }); - setLiked(!!data.liked); - setLikeCountAdj(0); } } catch (e) { if (!cancelled) setError(e.message); @@ -44,21 +38,6 @@ export default function PostDetailModal({ postId, onClose, onImageClick, onLocat return () => { cancelled = true; }; }, [postId]); - const handleLike = async (id) => { - if (!isAuthenticated) { - if (onAuthRequired) onAuthRequired(); - return; - } - const prev = liked; - setLiked((v) => !v); - setLikeCountAdj((v) => (prev ? v - 1 : v + 1)); - const result = await toggleFavorite(id); - if (!result.success) { - setLiked(prev); - setLikeCountAdj((v) => (prev ? v + 1 : v - 1)); - } - }; - const isOwner = post && user && (post.authorId === user.id || post.userId === user.id); return ( @@ -82,9 +61,6 @@ export default function PostDetailModal({ postId, onClose, onImageClick, onLocat {post && ( (b.likeCount || 0) - (a.likeCount || 0)) + .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)) .slice(0, 4); const profileName = isAuthenticated diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx index 4fe8eb8..93bbec5 100644 --- a/src/components/Sidebar.jsx +++ b/src/components/Sidebar.jsx @@ -44,7 +44,7 @@ export default function Sidebar({ const HelpIcon = NavIcons.help; const handleNavItem = (itemKey) => { - if (!isAuthenticated && (itemKey === "myads" || itemKey === "favorites")) { + if (!isAuthenticated && itemKey === "myads") { onLoginClick(); return; } diff --git a/src/components/feed/FeedContent.jsx b/src/components/feed/FeedContent.jsx index 25d5b17..675d3db 100644 --- a/src/components/feed/FeedContent.jsx +++ b/src/components/feed/FeedContent.jsx @@ -13,9 +13,6 @@ export default function FeedContent({ filteredAds, activeNav, searchPrompt, - liked, - likeCountAdj, - onLike, onEdit, onDelete, onMarkFound, @@ -26,7 +23,7 @@ export default function FeedContent({ t, }) { // Реклама вставляется только на главной ленте (showAds). useMemo — чтобы - // при ре-рендерах (виждет-обновления, лайки и т.п.) позиции слотов не + // при ре-рендерах (виджет-обновления и т.п.) позиции слотов не // пересчитывались и уже загруженные объявления не терялись. const feedItems = useMemo( () => withAds(filteredAds, showAds ? AD_EVERY : 0), @@ -66,9 +63,7 @@ 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 + : citySearchActive ? { title: t('feed.emptyCityTitle'), sub: t('feed.emptyCitySub') } : { title: t('feed.emptyAllTitle'), sub: t('feed.emptyAllSub') }; @@ -90,9 +85,6 @@ export default function FeedContent({ } - */ - const toggleFavorite = useCallback(async (postId) => { - setError(""); - try { - const response = await apiFetch(`${API_URL}/api/v1/favorites/${postId}/toggle`, { - method: 'POST', - }); - - if (!response.ok) { - throw new Error(t('favorites.toggleError')); - } - - return { success: true, error: null }; - } catch (err) { - console.error('Error toggling favorite:', err); - const message = err.message || t('favorites.toggleError'); - setError(message); - return { success: false, error: message }; - } - }, [t]); - - /** - * Загружает список лайкнутых постов текущего пользователя. - * @returns {Promise} - массив ID лайкнутых постов - */ - const loadFavorites = useCallback(async () => { - setError(""); - try { - const response = await apiFetch(`${API_URL}/api/v1/favorites`); - - if (!response.ok) { - throw new Error(t('favorites.loadError')); - } - - const data = await response.json(); - return Array.isArray(data) ? data.map(post => post.id) : []; - } catch (err) { - console.error('Error loading favorites:', err); - setError(err.message || t('favorites.loadError')); - return []; - } - }, [t]); - - /** - * Загружает полные посты из избранного текущего пользователя. - * @returns {Promise} - массив постов (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, - }; -} \ No newline at end of file diff --git a/src/hooks/useFeedActions.jsx b/src/hooks/useFeedActions.jsx index 8aef7d2..d6fc0ab 100644 --- a/src/hooks/useFeedActions.jsx +++ b/src/hooks/useFeedActions.jsx @@ -1,18 +1,17 @@ // src/hooks/useFeedActions.js -import { useState, useCallback, useRef } from "react"; +import { useState, useCallback } from "react"; import { useDeletePost } from "./useDeletePost"; -import { useFavorites } from "./useFavorites"; import { usePostStatus } from "./usePostStatus"; /** - * Объединяет действия над объявлениями (лайк, удаление, отметка "найдено") + * Объединяет действия над объявлениями (удаление, отметка "найдено") * и общий паттерн "выполнить действие -> перезагрузить ленту и мои объявления". * * Авторизация (Authorization header + авто-обновление токена) обрабатывается * внутри apiFetch — сам token сюда передавать не нужно, только isAuthenticated * как ранняя проверка перед запросом. * - * Каждое действие (deletePost/toggleFavorite/markAsFound) возвращает + * Каждое действие (deletePost/markAsFound) возвращает * { success, error } напрямую из вызова — это исключает гонку между * обновлением error state дочернего хука и его чтением здесь же в том же * тике (React batching иначе мог бы подставить устаревшее значение). @@ -22,59 +21,17 @@ import { usePostStatus } from "./usePostStatus"; * @param {Function} onAuthRequired — вызывается когда нужна авторизация * @param {boolean} isAuthenticated */ -export function useFeedActions({ refresh, loadMyPosts, onAuthRequired, isAuthenticated, t, setLiked, setLikeCountAdj }) { +export function useFeedActions({ refresh, loadMyPosts, onAuthRequired, isAuthenticated, t }) { const { deletePost } = useDeletePost(); - const { toggleFavorite } = useFavorites(); const { markAsFound } = usePostStatus(); const [error, setError] = useState(""); - const likedRef = useRef(new Set()); - - const syncLikedRef = useCallback((prev) => { - likedRef.current = prev instanceof Set ? prev : new Set(prev); - return prev; - }, []); const afterMutation = useCallback(() => { refresh(); loadMyPosts(); }, [refresh, loadMyPosts]); - const toggleLike = useCallback(async (id) => { - if (!isAuthenticated) { - onAuthRequired(); - return; - } - if (setLiked) { - const wasCurrentlyLiked = likedRef.current.has(id); - const delta = wasCurrentlyLiked ? -1 : 1; - - setLiked(prev => { - const next = new Set(prev); - if (next.has(id)) next.delete(id); else next.add(id); - likedRef.current = next; - return next; - }); - if (setLikeCountAdj) { - setLikeCountAdj(prev => ({ ...prev, [id]: (prev[id] || 0) + delta })); - } - - const result = await toggleFavorite(id); - if (!result.success) { - setLiked(prev => { - const next = new Set(prev); - if (next.has(id)) next.delete(id); else next.add(id); - likedRef.current = next; - return next; - }); - if (setLikeCountAdj) { - setLikeCountAdj(prev => ({ ...prev, [id]: (prev[id] || 0) - delta })); - } - setError(result.error); - } - } - }, [isAuthenticated, onAuthRequired, toggleFavorite, setLiked, setLikeCountAdj]); - const handleDelete = useCallback(async (postId) => { if (!window.confirm(t('feed.confirmDelete'))) return; const result = await deletePost(postId); @@ -98,12 +55,10 @@ export function useFeedActions({ refresh, loadMyPosts, onAuthRequired, isAuthent const clearError = useCallback(() => setError(""), []); return { - toggleLike, handleDelete, handleMarkFound, handleNewAd: afterMutation, handleEditSave: afterMutation, - syncLiked: syncLikedRef, error, clearError, }; diff --git a/src/locales/en.json b/src/locales/en.json index 5cd4192..b7c94eb 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -5,7 +5,6 @@ "nav": { "home": "Home", "myAds": "My ads", - "favorites": "Favorites", "items": "Items", "pets": "Pets", "chat": "Chat", @@ -139,8 +138,6 @@ "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
about a loss or find", "searchPromptTitle": "Enter search parameters", @@ -173,8 +170,7 @@ "itemFound": "✓ Returned to owner", "showOnMap": "Show on map — {{address}}", "contactError": "Unable to open the chat. Please try again", - "reward": "Reward", - "likes": "Likes: {{count}}" + "reward": "Reward" }, "chat": { "title": "Chat", @@ -511,10 +507,6 @@ "deleteError": "Failed to delete the chat", "deleteErrorWithStatus": "Failed to delete chat (error {{status}})" }, - "favorites": { - "toggleError": "Failed to change the favorite status.", - "loadError": "Failed to load favorites" - }, "map": { "backToListings": "Back to listings" } diff --git a/src/locales/ru.json b/src/locales/ru.json index 61e9b6b..c3208c0 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -5,7 +5,6 @@ "nav": { "home": "Главная", "myAds": "Мои объявления", - "favorites": "Избранное", "items": "Вещи", "pets": "Питомцы", "chat": "Чаты по объявлениям", @@ -140,8 +139,6 @@ "loadError": "Не удалось загрузить ленту", "emptyMyAdsTitle": "У вас нет объявлений", "emptyMyAdsSub": "Создайте первое — нажмите кнопку выше", - "emptyFavoritesTitle": "В избранном пока пусто", - "emptyFavoritesSub": "Нажимайте ❤️ на объявлениях, чтобы сохранить их здесь", "emptyAllTitle": "Объявлений пока нет", "emptyAllSub": "Будьте первым — разместите объявление
о потере или находке", "searchPromptTitle": "Введите параметры поиска", @@ -174,8 +171,7 @@ "itemFound": "✓ Вернулось к владельцу", "showOnMap": "Показать на карте — {{address}}", "contactError": "Не удалось открыть чат. Попробуйте ещё раз", - "reward": "Вознаграждение", - "likes": "Нравится: {{count}}" + "reward": "Вознаграждение" }, "chat": { "title": "Чат", @@ -512,10 +508,6 @@ "deleteError": "Не удалось удалить чат", "deleteErrorWithStatus": "Не удалось удалить чат (ошибка {{status}})" }, - "favorites": { - "toggleError": "Не удалось изменить статус избранного", - "loadError": "Не удалось загрузить избранное" - }, "map": { "backToListings": "Назад к объявлениям" } diff --git a/src/pages/MainFeed.tsx b/src/pages/MainFeed.tsx index 6b77e62..7407f6d 100644 --- a/src/pages/MainFeed.tsx +++ b/src/pages/MainFeed.tsx @@ -26,7 +26,6 @@ import { useCityFeed } from "../hooks/useCityFeed"; import { useInfiniteScroll } from "../hooks/useInfiniteScroll"; import { useModals } from "../hooks/useModals"; import { useFeedActions } from "../hooks/useFeedActions"; -import { useFavorites } from "../hooks/useFavorites"; import { useTranslation } from "react-i18next"; import { useProfile } from "../hooks/useProfile"; import { useFullscreenAdTrigger } from "../hooks/useFullscreenAdTrigger"; @@ -56,7 +55,7 @@ type AnyAds = any[]; export default function MainFeed() { const navigate = useNavigate(); const location = useLocation(); - const { user, token, isAuthenticated, updateUser } = useAuthContext() as { + const { user, isAuthenticated, updateUser } = useAuthContext() as { user: { id?: number; name?: string; @@ -67,13 +66,11 @@ export default function MainFeed() { phone?: string; avatar?: string; } | null; - token: string | null; isAuthenticated: boolean; updateUser: (data: unknown) => void; }; const { fetchProfile } = useProfile(); const { cities } = useCities() as { cities: string[]; loading: boolean; error: string }; - const { loadFavorites, loadFavoritePosts, error: favoritesError } = useFavorites(); const { t } = useTranslation(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const modals = useModals() as any; @@ -83,11 +80,7 @@ export default function MainFeed() { const [typeFilter, setTypeFilter] = useState<"loss" | "found" | null>(null); const [selectedCities, setSelectedCities] = useState([]); const [searchQuery, setSearchQuery] = useState(""); - const [liked, setLiked] = useState>(new Set()); - const [likeCountAdj, setLikeCountAdj] = useState>({}); - const [favoritePosts, setFavoritePosts] = useState([]); const profileFetchedFor = useRef(null); - const [favoritesLoading, setFavoritesLoading] = useState(false); const feedRef = useRef(null); // Состояние ленты перед переходом на карту — чтобы вернуть юзера на то же место @@ -138,18 +131,11 @@ export default function MainFeed() { if (activeNav === "myads") { return Promise.resolve(myPosts.loadMyPosts()); } - if (activeNav === "favorites") { - setFavoritesLoading(true); - return loadFavoritePosts().then((posts: AnyAds) => { - setFavoritePosts(posts); - setFavoritesLoading(false); - }); - } if (selectedCities.length > 0) { return cityFeed.searchCities(selectedCities); } return feed.loadFeed(); - }, [activeNav, myPosts, selectedCities, cityFeed, feed, loadFavoritePosts]); + }, [activeNav, myPosts, selectedCities, cityFeed, feed]); // eslint-disable-next-line @typescript-eslint/no-explicit-any const actions = useFeedActions({ @@ -158,8 +144,6 @@ export default function MainFeed() { onAuthRequired: () => modals.open("auth"), isAuthenticated, t, - setLiked, - setLikeCountAdj, } as any); // ── «Стеклянная шапка»: закреплена наверху, высоту зоны затухания маски @@ -185,22 +169,6 @@ export default function MainFeed() { const fadeHeight = measuredFade; - // Загружаем лайки пользователя при авторизации - useEffect(() => { - if (isAuthenticated && token) { - loadFavorites().then((favoriteIds: number[]) => { - const s = new Set(favoriteIds); - actions.syncLiked(s); - setLiked(s); - }); - } else { - actions.syncLiked(new Set()); - setLiked(new Set()); - } - setLikeCountAdj({}); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isAuthenticated, token, loadFavorites]); - // Открываем модалку аутентификации если пришли с лендинга useEffect(() => { if (location.state?.openAuth) { @@ -260,20 +228,6 @@ 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: AnyAds) => { - 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 @@ -368,8 +322,6 @@ export default function MainFeed() { let result: AnyAds; if (activeNav === "myads") { result = myPosts.myAds; - } else if (activeNav === "favorites") { - result = favoritePosts; } else if (selectedCities.length > 0) { result = cityFeed.cityAds; } else { @@ -386,18 +338,14 @@ export default function MainFeed() { result = result.filter(ad => ad.type === typeFilter); } return result; - }, [activeNav, myPosts.myAds, favoritePosts, cityFeed.cityAds, feed.ads, selectedCities, typeFilter]); + }, [activeNav, myPosts.myAds, cityFeed.cityAds, feed.ads, selectedCities, typeFilter]); const listLoading = activeNav === "myads" ? myPosts.loading - : activeNav === "favorites" - ? favoritesLoading - : feed.loading; + : feed.loading; const listError = activeNav === "myads" ? myPosts.error - : activeNav === "favorites" - ? favoritesError - : feed.error; + : feed.error; return (