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 36a5867..7980d38 100644 --- a/backend/src/routes/recommendationRoutes.js +++ b/backend/src/routes/recommendationRoutes.js @@ -1,5 +1,6 @@ import express from "express"; import axios from "axios"; +import { verifyToken } from "../middleware/authMiddlesware.js"; const router = express.Router(); const REQUEST_TIMEOUT_MS = 30000; @@ -66,7 +67,7 @@ router.get("/user/:userId", async (req, res) => { 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, @@ -107,7 +108,7 @@ router.get("/user/:userId", async (req, res) => { 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; const response = await axios.get( `${PYTHON_RECOMMEND_API_URL}/similar/${animeId}`, 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 00e6f4c..f80637e 100644 --- a/src/context/SocketContext.jsx +++ b/src/context/SocketContext.jsx @@ -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 debc48b..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,6 +39,7 @@ 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 () => { diff --git a/src/pages/Community.jsx b/src/pages/Community.jsx index e2512d8..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 @@ -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); 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`,