// src/hooks/useFeedActions.js import { useState, useCallback, useRef } from "react"; import { useDeletePost } from "./useDeletePost"; import { useFavorites } from "./useFavorites"; import { usePostStatus } from "./usePostStatus"; /** * Объединяет действия над объявлениями (лайк, удаление, отметка "найдено") * и общий паттерн "выполнить действие -> перезагрузить ленту и мои объявления". * * Авторизация (Authorization header + авто-обновление токена) обрабатывается * внутри apiFetch — сам token сюда передавать не нужно, только isAuthenticated * как ранняя проверка перед запросом. * * Каждое действие (deletePost/toggleFavorite/markAsFound) возвращает * { success, error } напрямую из вызова — это исключает гонку между * обновлением error state дочернего хука и его чтением здесь же в том же * тике (React batching иначе мог бы подставить устаревшее значение). * * @param {Function} refresh — перезагрузить текущий список (лента/город) * @param {Function} loadMyPosts — перезагрузить "мои объявления" * @param {Function} onAuthRequired — вызывается когда нужна авторизация * @param {boolean} isAuthenticated */ export function useFeedActions({ refresh, loadMyPosts, onAuthRequired, isAuthenticated, t, setLiked, setLikeCountAdj }) { 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); if (result.success) { afterMutation(); } else { setError(result.error); } }, [t, deletePost, afterMutation]); const handleMarkFound = useCallback(async (postId) => { if (!window.confirm(t('feed.confirmMarkFound'))) return; const result = await markAsFound(postId); if (result.success) { afterMutation(); } else { setError(result.error); } }, [t, markAsFound, afterMutation]); const clearError = useCallback(() => setError(""), []); return { toggleLike, handleDelete, handleMarkFound, handleNewAd: afterMutation, handleEditSave: afterMutation, syncLiked: syncLikedRef, error, clearError, }; }