From 479a4d54df4e3a1c71839bbd34b7f29a58d7cd9a Mon Sep 17 00:00:00 2001 From: Shreeshanth shetty Date: Mon, 23 Feb 2026 17:30:47 +0530 Subject: [PATCH] Stabilize fallback source metadata after merge updates --- backend/server.js | 2 +- backend/src/routes/recommendationRoutes.js | 323 ++++++++++++------ package.json | 2 +- src/components/HomeComponents/Navbar.jsx | 2 +- src/components/HomeComponents/ThreeScene.jsx | 2 +- .../Recommendations/SimilarAnime.jsx | 14 +- src/context/AuthContext.jsx | 30 +- src/context/SocketContext.jsx | 8 +- src/pages/AIRecommendations.jsx | 9 +- src/pages/Community.jsx | 56 +-- src/pages/Discover.jsx | 4 +- src/pages/ProfilePage2.jsx | 2 +- src/pages/Trending.jsx | 2 +- src/pages/login/ResetPassword.jsx | 24 +- src/pages/login/loginPage.jsx | 2 +- src/pages/login/signuppage.jsx | 2 +- 16 files changed, 309 insertions(+), 175 deletions(-) diff --git a/backend/server.js b/backend/server.js index 05fa325..360617c 100644 --- a/backend/server.js +++ b/backend/server.js @@ -60,7 +60,7 @@ const onlineUsers = new Map(); // Socket.IO connection handler io.on("connection", (socket) => { - const userId = socket.user._id; + const userId = socket.user.id || socket.user._id; console.log(`User connected: ${userId}`); // Add user to online users diff --git a/backend/src/routes/recommendationRoutes.js b/backend/src/routes/recommendationRoutes.js index 4f1e62e..8fce39f 100644 --- a/backend/src/routes/recommendationRoutes.js +++ b/backend/src/routes/recommendationRoutes.js @@ -1,98 +1,246 @@ import express from "express"; import axios from "axios"; +import { verifyToken } from "../middleware/authMiddlesware.js"; const router = express.Router(); +const REQUEST_TIMEOUT_MS = 30000; -// Configuration for Python Flask API -const PYTHON_API_URL = - process.env.PYTHON_API_URL || "http://localhost:5002/api/recommend"; +const PYTHON_RECOMMEND_API_URL = + process.env.PYTHON_RECOMMEND_API_URL || + process.env.RECOMMENDATION_API_URL || + process.env.PYTHON_API_URL || + "http://localhost:5002/api/recommend"; -/** - * @route GET /api/recommendations/user/:userId - * @desc Get personalized recommendations for a user - * @access Private - */ -router.get("/user/:userId", async (req, res) => { - try { - const { userId } = req.params; - const { top_n = 12, min_score = 0 } = req.query; +const PYTHON_HEALTH_URL = + process.env.PYTHON_HEALTH_URL || + process.env.RECOMMENDATION_HEALTH_URL || + "http://localhost:5002/api/health"; + +const STATIC_FALLBACK_RECOMMENDATIONS = [ + { + anime_id: 5114, + title: "Fullmetal Alchemist: Brotherhood", + genres: ["Action", "Adventure", "Drama"], + image_url: "https://cdn.myanimelist.net/images/anime/1208/94745.jpg", + score: 9.1, + }, + { + anime_id: 9253, + title: "Steins;Gate", + genres: ["Sci-Fi", "Thriller"], + image_url: "https://cdn.myanimelist.net/images/anime/1935/127974.jpg", + score: 9.0, + }, + { + anime_id: 1535, + title: "Death Note", + genres: ["Mystery", "Psychological", "Supernatural"], + image_url: "https://cdn.myanimelist.net/images/anime/9/9453.jpg", + score: 8.6, + }, + { + anime_id: 16498, + title: "Attack on Titan", + genres: ["Action", "Drama", "Fantasy"], + image_url: "https://cdn.myanimelist.net/images/anime/10/47347.jpg", + score: 8.5, + }, + { + anime_id: 11061, + title: "Hunter x Hunter (2011)", + genres: ["Action", "Adventure", "Fantasy"], + image_url: "https://cdn.myanimelist.net/images/anime/1337/99013.jpg", + score: 9.0, + }, + { + anime_id: 20, + title: "Naruto", + genres: ["Action", "Adventure", "Shounen"], + image_url: "https://cdn.myanimelist.net/images/anime/13/17405.jpg", + score: 8.0, + }, +]; + +let popularFallbackCache = { + data: null, + expiresAt: 0, +}; - console.log(`🤖 Fetching recommendations for user: ${userId}`); +const buildStaticFallback = (limit = 12) => + STATIC_FALLBACK_RECOMMENDATIONS.slice(0, limit).map((anime, index) => ({ + ...anime, + popularity_score: Math.max(0, 100 - index), + hybrid_score: Math.max(0.55, 1 - index / Math.max(limit, 1)), + reason_for_recommendation: "Reliable fallback recommendation", + _fallbackSource: "static", + })); - // Call Python Flask API - const response = await axios.get(`${PYTHON_API_URL}/user/${userId}`, { - params: { - top_n: parseInt(top_n), - }, - timeout: 30000, // 30 second timeout +const getPopularFallback = async (limit = 12) => { + const now = Date.now(); + if (popularFallbackCache.data && popularFallbackCache.expiresAt > now) { + return popularFallbackCache.data.slice(0, limit); + } + + try { + const { data } = await axios.get("https://api.jikan.moe/v4/top/anime", { + params: { filter: "bypopularity", limit: Math.max(limit, 12) }, + timeout: 8000, }); - console.log( - `✅ Received ${ - response.data.recommendations?.length || 0 - } recommendations` + const mapped = (data.data || []).map((anime, index) => ({ + anime_id: anime.mal_id, + title: anime.title, + genres: (anime.genres || []).map((g) => g.name), + synopsis: anime.synopsis, + image_url: + anime.images?.jpg?.large_image_url || anime.images?.jpg?.image_url, + score: anime.score, + scored_by: anime.scored_by, + popularity_rank: anime.popularity, + popularity_score: Math.max(0, 100 - index), + hybrid_score: Math.max(0.5, 1 - index / Math.max(limit, 1)), + reason_for_recommendation: "Popular fallback recommendation", + _fallbackSource: "jikan", + })); + + if (!mapped.length) { + return buildStaticFallback(limit); + } + + popularFallbackCache = { + data: mapped, + expiresAt: now + 10 * 60 * 1000, + }; + + return mapped.slice(0, limit); + } catch { + return buildStaticFallback(limit); + } +}; + + +const extractFallbackSource = (items = []) => items[0]?._fallbackSource || "unknown"; + +const sanitizeFallbackItems = (items = []) => + items.map(({ _fallbackSource, ...item }) => item); + +const getSimilarFallback = async (animeId, limit = 6) => { + try { + const { data } = await axios.get( + `https://api.jikan.moe/v4/anime/${animeId}/recommendations`, + { + params: { limit }, + timeout: 8000, + } + ); + + const mapped = (data.data || []).slice(0, limit).map((item) => ({ + anime_id: item.entry?.mal_id, + title: item.entry?.title, + image_url: + item.entry?.images?.jpg?.large_image_url || + item.entry?.images?.jpg?.image_url, + content_similarity: Math.min(1, Math.max(0.1, (item.votes || 1) / 100)), + reason_for_recommendation: + item.content || "Recommended by similar viewers", + _fallbackSource: "jikan", + })); + + if (mapped.length) { + return mapped; + } + } catch { + // no-op and continue to static fallback + } + + return buildStaticFallback(limit).map((anime) => ({ + anime_id: anime.anime_id, + title: anime.title, + image_url: anime.image_url, + content_similarity: anime.hybrid_score, + reason_for_recommendation: "Similar taste fallback recommendation", + _fallbackSource: "static", + })); +}; + +router.get("/user/:userId", verifyToken, async (req, res) => { + const requestedUserId = req.params.userId; + const tokenUserId = req.user?.id; + const effectiveUserId = tokenUserId || requestedUserId; + + try { + const { top_n = 12, min_score = 0 } = req.query; + const parsedTopN = Number.parseInt(top_n, 10) || 12; + const parsedMinScore = Number.parseFloat(min_score) || 0; + + const response = await axios.get( + `${PYTHON_RECOMMEND_API_URL}/user/${effectiveUserId}`, + { + params: { + top_n: parsedTopN, + min_score: parsedMinScore, + }, + timeout: REQUEST_TIMEOUT_MS, + } ); return res.json({ success: true, - user_id: response.data.user_id, + user_id: response.data.user_id || effectiveUserId, recommendations: response.data.recommendations || [], count: response.data.count || 0, is_cold_start: response.data.is_cold_start || false, + source: "ml-service", message: "Recommendations fetched successfully", }); } catch (error) { console.error("❌ Error fetching recommendations:", error.message); - console.error("Error details:", error.response?.data); - // Handle different error scenarios - if (error.code === "ECONNREFUSED") { + try { + const parsedTopN = Number.parseInt(req.query.top_n || 12, 10) || 12; + const parsedMinScore = Number.parseFloat(req.query.min_score || 0) || 0; + + const fallbackRecommendations = (await getPopularFallback(parsedTopN)).filter( + (anime) => (anime.hybrid_score || 0) >= parsedMinScore + ); + const fallbackSource = extractFallbackSource(fallbackRecommendations); + + return res.status(200).json({ + success: true, + user_id: effectiveUserId, + recommendations: sanitizeFallbackItems(fallbackRecommendations), + count: fallbackRecommendations.length, + is_cold_start: true, + source: "fallback", + fallback_source: fallbackSource, + warning: + "Recommendation service unavailable. Showing fallback recommendations.", + }); + } catch (fallbackError) { return res.status(503).json({ success: false, error: - "Recommendation service is currently unavailable. Please try again later.", - recommendations: [], - }); - } - - if (error.response?.status === 404) { - return res.status(404).json({ - success: false, - error: "No recommendations found. Try watching more anime first!", + "Recommendation service is currently unavailable and fallback failed.", + details: fallbackError.message, recommendations: [], }); } - - return res.status(500).json({ - success: false, - error: error.response?.data?.error || "Failed to fetch recommendations", - recommendations: [], - }); } }); -/** - * @route GET /api/recommendations/similar/:animeId - * @desc Get anime similar to a specific anime - * @access Public - */ router.get("/similar/:animeId", async (req, res) => { try { const { animeId } = req.params; - const { top_n = 6 } = req.query; + const parsedTopN = Number.parseInt(req.query.top_n || 6, 10) || 6; - console.log(`🔍 Fetching similar anime for ID: ${animeId}`); - - // Call Python Flask API - const response = await axios.get(`${PYTHON_API_URL}/similar/${animeId}`, { - params: { - top_n: parseInt(top_n), - }, - timeout: 30000, - }); - - console.log( - `✅ Received ${response.data.similar?.length || 0} similar anime` + const response = await axios.get( + `${PYTHON_RECOMMEND_API_URL}/similar/${animeId}`, + { + params: { + top_n: parsedTopN, + }, + timeout: REQUEST_TIMEOUT_MS, + } ); return res.json({ @@ -100,49 +248,38 @@ router.get("/similar/:animeId", async (req, res) => { anime_id: response.data.anime_id, similar: response.data.similar || [], count: response.data.count || 0, + source: "ml-service", message: "Similar anime fetched successfully", }); } catch (error) { console.error("❌ Error fetching similar anime:", error.message); - console.error("Error details:", error.response?.data); - - if (error.code === "ECONNREFUSED") { - return res.status(503).json({ - success: false, - error: "Recommendation service is currently unavailable", - similar: [], - }); - } - if (error.response?.status === 404) { - return res.status(404).json({ - success: false, - error: "Anime not found in recommendation system", - similar: [], - }); - } + const fallbackSimilar = await getSimilarFallback( + req.params.animeId, + Number.parseInt(req.query.top_n || 6, 10) || 6 + ); + const fallbackSource = extractFallbackSource(fallbackSimilar); - return res.status(500).json({ - success: false, - error: error.response?.data?.error || "Failed to fetch similar anime", - similar: [], + return res.json({ + success: true, + anime_id: req.params.animeId, + similar: sanitizeFallbackItems(fallbackSimilar), + count: fallbackSimilar.length, + source: "fallback", + fallback_source: fallbackSource, + warning: + "Recommendation service unavailable. Showing fallback similar anime.", }); } }); -/** - * @route POST /api/recommendations/initialize - * @desc Initialize/refresh the recommendation system - * @access Private (Admin only) - */ router.post("/initialize", async (req, res) => { try { - // Call Python Flask API to reinitialize const response = await axios.post( - `${PYTHON_API_URL}/initialize`, + `${PYTHON_RECOMMEND_API_URL}/initialize`, {}, { - timeout: 60000, // 60 second timeout for initialization + timeout: 60000, } ); @@ -153,8 +290,6 @@ router.post("/initialize", async (req, res) => { "Recommendation system initialized successfully", }); } catch (error) { - console.error("Error initializing recommendation system:", error.message); - return res.status(500).json({ success: false, message: "Failed to initialize recommendation system", @@ -163,14 +298,9 @@ router.post("/initialize", async (req, res) => { } }); -/** - * @route GET /api/recommendations/health - * @desc Check health status of recommendation service - * @access Public - */ router.get("/health", async (req, res) => { try { - const response = await axios.get(`${PYTHON_API_URL}/health`, { + const response = await axios.get(PYTHON_HEALTH_URL, { timeout: 5000, }); @@ -178,6 +308,7 @@ router.get("/health", async (req, res) => { success: true, status: "healthy", service: response.data, + endpoint: PYTHON_HEALTH_URL, }); } catch (error) { return res.status(503).json({ diff --git a/package.json b/package.json index 33b8d21..a9e53fe 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ }, "scripts": { "start": "react-scripts start", - "build": "react-scripts build", + "build": "GENERATE_SOURCEMAP=false react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, diff --git a/src/components/HomeComponents/Navbar.jsx b/src/components/HomeComponents/Navbar.jsx index d78347d..416480f 100644 --- a/src/components/HomeComponents/Navbar.jsx +++ b/src/components/HomeComponents/Navbar.jsx @@ -54,7 +54,7 @@ export default function AniMatchNavbar() {
- + AniMatch logo
diff --git a/src/components/HomeComponents/ThreeScene.jsx b/src/components/HomeComponents/ThreeScene.jsx index cade84b..f9244b7 100644 --- a/src/components/HomeComponents/ThreeScene.jsx +++ b/src/components/HomeComponents/ThreeScene.jsx @@ -32,7 +32,7 @@ function AnimeCube() { }, [size.width]); return ( - + diff --git a/src/components/Recommendations/SimilarAnime.jsx b/src/components/Recommendations/SimilarAnime.jsx index dc58dd1..8f7f363 100644 --- a/src/components/Recommendations/SimilarAnime.jsx +++ b/src/components/Recommendations/SimilarAnime.jsx @@ -9,6 +9,7 @@ const SimilarAnimeCard = ({ anime, index, onClick }) => { const [jikanImage, setJikanImage] = useState(null); // Fetch image from Jikan API if original fails + // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => { if (imgError && anime.anime_id && !jikanImage) { fetch(`https://api.jikan.moe/v4/anime/${anime.anime_id}`) @@ -122,12 +123,6 @@ const SimilarAnime = ({ animeId, currentAnimeTitle }) => { const [error, setError] = useState(null); const navigate = useNavigate(); - useEffect(() => { - if (animeId) { - fetchSimilarAnime(); - } - }, [animeId]); - const fetchSimilarAnime = async () => { try { setLoading(true); @@ -165,6 +160,13 @@ const SimilarAnime = ({ animeId, currentAnimeTitle }) => { } }; + // eslint-disable-next-line react-hooks/exhaustive-deps + useEffect(() => { + if (animeId) { + fetchSimilarAnime(); + } + }, [animeId]); // eslint-disable-line react-hooks/exhaustive-deps + const handleAnimeClick = (id) => { navigate(`/anime/${id}`); // Scroll to top diff --git a/src/context/AuthContext.jsx b/src/context/AuthContext.jsx index 816f6f8..382a50c 100644 --- a/src/context/AuthContext.jsx +++ b/src/context/AuthContext.jsx @@ -145,7 +145,7 @@ export const AuthProvider = ({ children }) => { } const data = await response.json(); - return data.token; + return data; } catch (error) { console.error("Token exchange error:", error); throw error; @@ -162,13 +162,33 @@ export const AuthProvider = ({ children }) => { try { console.log("🔄 Processing Supabase auth for:", session.user.email); - const customToken = await exchangeToken(session.user); + const exchangeData = await exchangeToken(session.user); + const customToken = exchangeData?.token; + + if (!customToken) { + throw new Error("Token exchange returned no token"); + } if (mountedRef.current) { localStorage.setItem("token", customToken); setToken(customToken); - setUser(session.user); setAuthMethod("supabase"); + + // Always resolve user from backend profile so IDs are consistent + // across JWT and Supabase accounts (MongoDB user id). + if (exchangeData?.user) { + setUser(exchangeData.user); + } + + try { + const profile = await fetchUserProfile(customToken); + if (profile && mountedRef.current) { + setUser(profile); + } + } catch (profileError) { + console.warn("âš ī¸ Profile fetch after Supabase exchange failed:", profileError.message); + } + if (window.location.pathname === "/login") { navigate("/home"); } @@ -186,6 +206,7 @@ export const AuthProvider = ({ children }) => { } }; + // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => { let mounted = true; mountedRef.current = true; @@ -245,7 +266,9 @@ export const AuthProvider = ({ children }) => { mounted = false; mountedRef.current = false; }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => { const { data: { subscription }, @@ -284,6 +307,7 @@ export const AuthProvider = ({ children }) => { return () => { subscription.unsubscribe(); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [navigate, authMethod, user]); const isAuthenticated = !!user; diff --git a/src/context/SocketContext.jsx b/src/context/SocketContext.jsx index beb3432..f80637e 100644 --- a/src/context/SocketContext.jsx +++ b/src/context/SocketContext.jsx @@ -4,8 +4,8 @@ import { useAuth } from "./AuthContext"; const SocketContext = createContext(); -const SOCKET_SERVER_URL = - process.env.REACT_APP_API_URL || "http://localhost:5000"; +const rawApiUrl = process.env.REACT_APP_API_URL || "http://localhost:5001/api"; +const SOCKET_SERVER_URL = rawApiUrl.replace(/\/api\/?$/, ""); export const useSocket = () => { return useContext(SocketContext); @@ -18,7 +18,7 @@ export const SocketProvider = ({ children }) => { // Initialize socket connection useEffect(() => { - if (!token || !user?._id) return; + if (!token || !(user?._id || user?.id)) return; // Create socket connection with auth token socketRef.current = io(SOCKET_SERVER_URL, { @@ -58,7 +58,7 @@ export const SocketProvider = ({ children }) => { socketRef.current = null; } }; - }, [token, user?._id]); + }, [token, user?._id, user?.id]); // Function to join a group room const joinGroup = (groupId) => { diff --git a/src/pages/AIRecommendations.jsx b/src/pages/AIRecommendations.jsx index 9af662c..bac9fc3 100644 --- a/src/pages/AIRecommendations.jsx +++ b/src/pages/AIRecommendations.jsx @@ -14,6 +14,7 @@ const AIRecommendations = () => { minScore: 0, }); + // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => { console.log("🔍 AIRecommendations useEffect:", { authLoading, @@ -38,9 +39,11 @@ const AIRecommendations = () => { console.log("â„šī¸ No user logged in"); setLoading(false); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [user?._id, user?.id, authLoading, filters]); const fetchRecommendations = async () => { + let timeoutId; try { setLoading(true); setError(null); @@ -50,7 +53,7 @@ const AIRecommendations = () => { // Add timeout to prevent infinite loading const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout + timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout const response = await api.get(`/recommendations/user/${userId}`, { params: { @@ -60,7 +63,6 @@ const AIRecommendations = () => { signal: controller.signal, }); - clearTimeout(timeoutId); if (response.data.success) { setRecommendations(response.data.recommendations || []); @@ -89,6 +91,9 @@ const AIRecommendations = () => { ); } } finally { + if (timeoutId) { + clearTimeout(timeoutId); + } setLoading(false); } }; diff --git a/src/pages/Community.jsx b/src/pages/Community.jsx index 3136bc5..f447e0d 100644 --- a/src/pages/Community.jsx +++ b/src/pages/Community.jsx @@ -27,7 +27,7 @@ import { const Community = () => { const { token, user } = useAuth(); const socketContext = useSocket(); - const { socket, emit, on, off } = socketContext || {}; + const { socket, emit, on } = socketContext || {}; const [activeGroup, setActiveGroup] = useState(null); const [groups, setGroups] = useState([]); const [posts, setPosts] = useState([]); @@ -49,10 +49,8 @@ const Community = () => { const [uploadingImage, setUploadingImage] = useState(false); const [typingUsers, setTypingUsers] = useState({}); const [isTyping, setIsTyping] = useState(false); - const [onlineUsers, setOnlineUsers] = useState(new Set()); const typingTimeoutRef = useRef(null); const messagesEndRef = useRef(null); - const fileInputRef = useRef(null); // Socket.IO event handlers useEffect(() => { @@ -112,19 +110,6 @@ const Community = () => { }); }; - // Handle online status - const handleUserOnline = ({ userId }) => { - setOnlineUsers((prev) => new Set([...prev, userId])); - }; - - const handleUserOffline = ({ userId }) => { - setOnlineUsers((prev) => { - const newSet = new Set(prev); - newSet.delete(userId); - return newSet; - }); - }; - // Set up event listeners using the context's 'on' function const cleanupFunctions = [ on("new-post", handleNewPost), @@ -132,8 +117,6 @@ const Community = () => { on("new-comment", handleNewComment), on("reaction", handleReaction), on("user-typing", handleUserTyping), - on("user-online", handleUserOnline), - on("user-offline", handleUserOffline), ]; // Clean up event listeners @@ -276,7 +259,7 @@ const Community = () => { const formData = new FormData(); formData.append("image", img.file); - const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:3001/api"; + const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:5001/api"; const xhr = new XMLHttpRequest(); // Track upload progress @@ -344,7 +327,7 @@ const Community = () => { try { const apiUrl = - process.env.REACT_APP_API_URL || "http://localhost:3001/api"; + process.env.REACT_APP_API_URL || "http://localhost:5001/api"; const res = await fetch(`${apiUrl}/groups`, { headers: { Authorization: `Bearer ${token}` }, }); @@ -369,7 +352,7 @@ const Community = () => { if (!token) return; try { - const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:3001/api"; + const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:5001/api"; const response = await fetch(`${apiUrl}/notifications`, { headers: { Authorization: `Bearer ${token}` }, }); @@ -388,7 +371,7 @@ const Community = () => { if (!token || !notificationId) return; try { - const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:3001/api"; + const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:5001/api"; await fetch(`${apiUrl}/notifications/${notificationId}/read`, { method: "PATCH", headers: { Authorization: `Bearer ${token}` }, @@ -406,7 +389,7 @@ const Community = () => { try { setLoading(true); - const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:3001/api"; + const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:5001/api"; const res = await fetch(`${apiUrl}/posts/group/${groupId}`, { headers: { Authorization: `Bearer ${token}` }, }); @@ -433,7 +416,7 @@ const Community = () => { try { const apiUrl = - process.env.REACT_APP_API_URL || "http://localhost:3001/api"; + process.env.REACT_APP_API_URL || "http://localhost:5001/api"; const res = await fetch(`${apiUrl}/groups/create`, { method: "POST", headers: { @@ -469,7 +452,7 @@ const Community = () => { try { const apiUrl = - process.env.REACT_APP_API_URL || "http://localhost:3001/api"; + process.env.REACT_APP_API_URL || "http://localhost:5001/api"; const res = await fetch(`${apiUrl}/groups/${groupId}/join`, { method: "POST", headers: { @@ -524,7 +507,7 @@ const Community = () => { console.log("📤 Sending post data:", postData); - const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:3001/api"; + const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:5001/api"; const res = await fetch(`${apiUrl}/posts/create`, { method: "POST", headers: { @@ -586,7 +569,7 @@ const Community = () => { if (!postId || !reactionType || !token || !emit) return; try { - const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:3001/api"; + const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:5001/api"; await fetch(`${apiUrl}/posts/${postId}/react`, { method: "POST", headers: { @@ -609,7 +592,7 @@ const Community = () => { const content = newComment[postId].trim(); try { - const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:3001/api"; + const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:5001/api"; const response = await fetch(`${apiUrl}/posts/${postId}/comment`, { method: "POST", headers: { @@ -635,7 +618,7 @@ const Community = () => { } try { - const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:3001/api"; + const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:5001/api"; await fetch(`${apiUrl}/posts/${postId}/comment`, { method: "POST", headers: { @@ -734,17 +717,6 @@ const Community = () => { ); }; - const renderOnlineUsers = () => { - const onlineCount = onlineUsers.size; - if (onlineCount === 0) return null; - - return ( -
- {onlineCount} {onlineCount === 1 ? "user" : "users"} online -
- ); - }; - const handlePostInputChange = (e) => { const value = e.target.value; setNewPost(value); @@ -954,7 +926,7 @@ const Community = () => { try { console.log("đŸ—‘ī¸ Deleting post:", postId); - const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:3001/api"; + const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:5001/api"; const res = await fetch(`${apiUrl}/posts/${postId}`, { method: "DELETE", headers: { @@ -987,7 +959,7 @@ const Community = () => { if (!postId || !commentId || !token) return; try { - const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:3001/api"; + const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:5001/api"; const res = await fetch( `${apiUrl}/posts/${postId}/comments/${commentId}`, { diff --git a/src/pages/Discover.jsx b/src/pages/Discover.jsx index 629229a..be544db 100644 --- a/src/pages/Discover.jsx +++ b/src/pages/Discover.jsx @@ -120,11 +120,11 @@ const Discover = () => { }, 500); return () => clearTimeout(timeoutId); - }, [searchQuery, filters]); + }, [searchAnime, searchQuery, filters]); useEffect(() => { searchAnime(1, true); - }, []); + }, [searchAnime]); const handleFilterChange = (filterName, value) => { setFilters((prev) => ({ ...prev, [filterName]: value })); diff --git a/src/pages/ProfilePage2.jsx b/src/pages/ProfilePage2.jsx index 83573ce..57fa8bb 100644 --- a/src/pages/ProfilePage2.jsx +++ b/src/pages/ProfilePage2.jsx @@ -21,7 +21,7 @@ const ProfilePage = () => { const { user, logout } = useAuth(); const [library, setLibrary] = useState([]); const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); + const [, setError] = useState(null); useEffect(() => { const fetchLibrary = async () => { diff --git a/src/pages/Trending.jsx b/src/pages/Trending.jsx index 733a1d1..07539b1 100644 --- a/src/pages/Trending.jsx +++ b/src/pages/Trending.jsx @@ -1,4 +1,4 @@ -import { AlertCircle, Loader2, Star, TrendingUp } from "lucide-react"; +import { AlertCircle, Loader2, Star } from "lucide-react"; import { useEffect, useState } from "react"; import { Link } from "react-router-dom"; import AniMatchNavbar from "../components/HomeComponents/Navbar"; diff --git a/src/pages/login/ResetPassword.jsx b/src/pages/login/ResetPassword.jsx index 3b441ad..13919c9 100644 --- a/src/pages/login/ResetPassword.jsx +++ b/src/pages/login/ResetPassword.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useCallback } from "react"; import axios from "axios"; import { useParams, useNavigate } from "react-router-dom"; import { Lock, EyeOff, Eye, AlertCircle, CheckCircle } from "lucide-react"; @@ -18,16 +18,7 @@ export default function ResetPassword() { const { token } = useParams(); const navigate = useNavigate(); - useEffect(() => { - if (token) { - verifyToken(); - } else { - setIsValidToken(false); - setMessage("No reset token provided"); - } - }, [token]); - - const verifyToken = async () => { + const verifyToken = useCallback(async () => { try { console.log("=== FRONTEND TOKEN VERIFICATION ==="); console.log("1. Token from URL:", token); @@ -64,7 +55,16 @@ export default function ResetPassword() { console.error("6. Debug info from server:", err.response.data.debug); } } - }; + }, [token]); + + useEffect(() => { + if (token) { + verifyToken(); + } else { + setIsValidToken(false); + setMessage("No reset token provided"); + } + }, [token, verifyToken]); const validateForm = () => { const newErrors = {}; diff --git a/src/pages/login/loginPage.jsx b/src/pages/login/loginPage.jsx index f17aee2..0f64af6 100644 --- a/src/pages/login/loginPage.jsx +++ b/src/pages/login/loginPage.jsx @@ -116,7 +116,7 @@ export default function LoginPage() { setMessage(""); try { - const res = await axios.post( + await axios.post( `${ process.env.REACT_APP_API_URL || "http://localhost:5001/api" }/auth/forgot-password`, diff --git a/src/pages/login/signuppage.jsx b/src/pages/login/signuppage.jsx index cf228cd..f71f966 100644 --- a/src/pages/login/signuppage.jsx +++ b/src/pages/login/signuppage.jsx @@ -83,7 +83,7 @@ export default function SignUpPage() { try { await new Promise((resolve) => setTimeout(resolve, 1500)); - const res = await axios.post( + await axios.post( `${ process.env.REACT_APP_API_URL || "http://localhost:5001/api" }/auth/register`,