From 20641701410be9ec3c4551afdeaeb445a5791049 Mon Sep 17 00:00:00 2001 From: tejaspanchall Date: Mon, 23 Jun 2025 18:39:40 +0530 Subject: [PATCH] fixed url --- src/app/watch/[episodeId]/page.js | 58 ++++---------------- src/components/AnimeCard.js | 83 +++++++++++++++++------------ src/components/AnimeDetails.js | 55 ++++++++++++++++--- src/components/EpisodeList.js | 14 +---- src/components/SpotlightCarousel.js | 59 +++++++++++++++++--- src/lib/api.js | 10 ++-- 6 files changed, 164 insertions(+), 115 deletions(-) diff --git a/src/app/watch/[episodeId]/page.js b/src/app/watch/[episodeId]/page.js index 4bbcf1b..a2a9d7c 100644 --- a/src/app/watch/[episodeId]/page.js +++ b/src/app/watch/[episodeId]/page.js @@ -68,36 +68,16 @@ export default function WatchPage() { console.log('[Watch] Raw episodeId from URL:', episodeId); // Extract animeId from the episodeId parameter - // Handle different possible formats: - // 1. anime-name?ep=episode-number (standard format) - // 2. anime-name-episode-number (legacy format) + // The API response contains episode.id in the format "anime-id?ep=episode-number" + let extractedAnimeId = episodeId; - let extractedAnimeId; - let episodeNumber; - - if (episodeId.includes('?ep=')) { - // Format: anime-name?ep=episode-number - const [baseId, queryString] = episodeId.split('?'); - extractedAnimeId = baseId; - episodeNumber = queryString.replace('ep=', ''); - console.log(`[Watch] Format detected: standard (anime-name?ep=episode-number)`); - } else if (episodeId.includes('-')) { - // Format: anime-name-episode-number - const match = episodeId.match(/^(.*?)-(\d+)$/); - if (match) { - extractedAnimeId = match[1]; - episodeNumber = match[2]; - console.log(`[Watch] Format detected: legacy (anime-name-episode-number)`); - } + // If the ID contains a query parameter, extract just the anime ID + if (episodeId.includes('?')) { + extractedAnimeId = episodeId.split('?')[0]; } - if (extractedAnimeId) { - setAnimeId(extractedAnimeId); - console.log('[Watch] Extracted anime ID:', extractedAnimeId); - console.log('[Watch] Extracted episode number:', episodeNumber); - } else { - console.warn('[Watch] Could not extract anime ID from episode ID:', episodeId); - } + setAnimeId(extractedAnimeId); + console.log('[Watch] Extracted anime ID:', extractedAnimeId); setCurrentEpisodeId(episodeId); } @@ -302,34 +282,14 @@ export default function WatchPage() { setEpisodes(episodesData.episodes); // Find current episode in episode list - // Handle both formats: anime-name?ep=episode-number or anime-name-episode-number const findCurrentEpisode = () => { - // First, try to find the episode by direct ID match + // Find the episode by direct ID match const directMatch = episodesData.episodes.find(ep => ep.id === currentEpisodeId); if (directMatch) { console.log('[Watch] Found episode by direct ID match:', directMatch.number); return directMatch; } - // As a fallback, try to match by episode number - // Extract episode number from the URL if it's in the format anime-id?ep=number - if (currentEpisodeId.includes('?ep=')) { - const [, queryString] = currentEpisodeId.split('?'); - if (queryString) { - const episodeNumber = queryString.replace('ep=', ''); - console.log('[Watch] Trying to find by episode number:', episodeNumber); - - const numberMatch = episodesData.episodes.find(ep => - ep.number && ep.number.toString() === episodeNumber.toString() - ); - - if (numberMatch) { - console.log('[Watch] Found episode by number:', numberMatch.number); - return numberMatch; - } - } - } - // If no match found, return first episode as fallback console.warn('[Watch] Could not find matching episode, falling back to first episode'); return episodesData.episodes[0]; @@ -407,7 +367,7 @@ export default function WatchPage() { // from the API response (animeId?ep=episodeNumber) // Update the URL using history API - const newUrl = `/watch/${encodeURIComponent(newEpisodeId)}`; + const newUrl = `/watch/${newEpisodeId}`; window.history.pushState({ episodeId: newEpisodeId }, '', newUrl); // Update state to trigger video reload diff --git a/src/components/AnimeCard.js b/src/components/AnimeCard.js index 4736b54..cd64546 100644 --- a/src/components/AnimeCard.js +++ b/src/components/AnimeCard.js @@ -8,7 +8,7 @@ import { fetchAnimeEpisodes } from '@/lib/api'; export default function AnimeCard({ anime, isRecent }) { const [imageError, setImageError] = useState(false); const [firstEpisodeId, setFirstEpisodeId] = useState(null); - const [isHovered, setIsHovered] = useState(false); + const [isLoading, setIsLoading] = useState(false); const timerRef = useRef(null); if (!anime) return null; @@ -18,40 +18,59 @@ export default function AnimeCard({ anime, isRecent }) { setImageError(true); }; - // Fetch first episode ID when component is hovered + // Fetch first episode ID when component mounts for recent anime useEffect(() => { const fetchFirstEpisode = async () => { - if (anime?.id && isHovered && !firstEpisodeId) { + // Only fetch for recent anime and if we don't already have the episode ID + if (isRecent && anime?.id && !firstEpisodeId && !isLoading) { + setIsLoading(true); try { + console.log(`[AnimeCard] Fetching episodes for anime: ${anime.id}`); const response = await fetchAnimeEpisodes(anime.id); + console.log(`[AnimeCard] Episodes response for ${anime.name}:`, response); + if (response.episodes && response.episodes.length > 0) { - // Get the first episode's episodeId - setFirstEpisodeId(response.episodes[0].episodeId); - console.log(`[AnimeCard] First episode ID for ${anime.name}: ${response.episodes[0].episodeId}`); + // Check for the episode ID in the format expected by the watch page + const firstEp = response.episodes[0]; + if (firstEp.id) { + setFirstEpisodeId(firstEp.id); + console.log(`[AnimeCard] First episode ID (id) for ${anime.name}: ${firstEp.id}`); + } else if (firstEp.episodeId) { + setFirstEpisodeId(firstEp.episodeId); + console.log(`[AnimeCard] First episode ID (episodeId) for ${anime.name}: ${firstEp.episodeId}`); + } else { + // Create a fallback ID if neither id nor episodeId are available + const fallbackId = `${anime.id}?ep=1`; + setFirstEpisodeId(fallbackId); + console.log(`[AnimeCard] Using fallback ID for ${anime.name}: ${fallbackId}`); + } + } else if (anime.id) { + // If no episodes found, create a fallback ID + const fallbackId = `${anime.id}?ep=1`; + setFirstEpisodeId(fallbackId); + console.log(`[AnimeCard] No episodes found for ${anime.name}, using fallback ID: ${fallbackId}`); } } catch (error) { console.error(`[AnimeCard] Error fetching episodes for ${anime.id}:`, error); + // Even on error, try to use fallback + if (anime.id) { + const fallbackId = `${anime.id}?ep=1`; + setFirstEpisodeId(fallbackId); + console.log(`[AnimeCard] Error for ${anime.name}, using fallback ID: ${fallbackId}`); + } + } finally { + setIsLoading(false); } } }; fetchFirstEpisode(); - }, [anime?.id, isHovered, firstEpisodeId]); - - const handleMouseEnter = () => { - // Clear any existing timers - if (timerRef.current) clearTimeout(timerRef.current); - // Set a small delay to prevent API calls for quick mouseovers - timerRef.current = setTimeout(() => { - setIsHovered(true); - }, 300); // Delay to prevent unnecessary API calls - }; - - const handleMouseLeave = () => { - // Clear the timer if the user moves the mouse away quickly - if (timerRef.current) clearTimeout(timerRef.current); - setIsHovered(false); - }; + + // Clean up timer if component unmounts + return () => { + if (timerRef.current) clearTimeout(timerRef.current); + }; + }, [anime?.id, anime?.name, isRecent, firstEpisodeId, isLoading]); // Get image URL with fallback const imageSrc = imageError ? '/images/placeholder.png' : anime.poster; @@ -60,21 +79,17 @@ export default function AnimeCard({ anime, isRecent }) { const infoLink = `/anime/${anime.id}`; // Build the watch URL based on the first episode ID or fallback - const watchLink = isRecent ? ( - firstEpisodeId - ? `/watch/${firstEpisodeId}` - : `/watch/${anime.id}?ep=${anime.episodes?.sub || anime.episodes?.dub || 1}` - ) : infoLink; + const watchLink = isRecent && firstEpisodeId + ? `/watch/${firstEpisodeId}` + : isRecent + ? `/anime/${anime.id}` // Temporarily link to info page while loading + : `/anime/${anime.id}`; // Non-recent anime always link to info return ( -
- {/* Image card linking to watch page */} +
+ {/* Image card linking to watch page for recent anime, or info page otherwise */} diff --git a/src/components/AnimeDetails.js b/src/components/AnimeDetails.js index 4f42f48..68c2654 100644 --- a/src/components/AnimeDetails.js +++ b/src/components/AnimeDetails.js @@ -30,14 +30,45 @@ export default function AnimeDetails({ anime }) { if (anime?.info?.id) { setIsLoadingEpisodes(true); try { + console.log(`[AnimeDetails] Fetching episodes for anime: ${anime.info.id}`); const response = await fetchAnimeEpisodes(anime.info.id); + console.log('[AnimeDetails] Episodes response:', response); + if (response.episodes && response.episodes.length > 0) { - // Get the first episode's episodeId - setFirstEpisodeId(response.episodes[0].episodeId); - console.log(`[AnimeDetails] First episode ID: ${response.episodes[0].episodeId}`); + // Log the first episode to check its structure + console.log('[AnimeDetails] First episode:', response.episodes[0]); + + // Get the first episode's id + const firstEp = response.episodes[0]; + if (firstEp.id) { + setFirstEpisodeId(firstEp.id); + console.log(`[AnimeDetails] First episode ID found: ${firstEp.id}`); + } else if (firstEp.episodeId) { + // Fallback to episodeId if id is not available + setFirstEpisodeId(firstEp.episodeId); + console.log(`[AnimeDetails] Falling back to episodeId: ${firstEp.episodeId}`); + } else { + // If no episode ID is found in the API response, create a fallback ID + const fallbackId = `${anime.info.id}?ep=1`; + setFirstEpisodeId(fallbackId); + console.log(`[AnimeDetails] Using fallback ID: ${fallbackId}`); + } + } else if (anime.info.id) { + // If no episodes found but anime ID is available, use fallback + const fallbackId = `${anime.info.id}?ep=1`; + setFirstEpisodeId(fallbackId); + console.log(`[AnimeDetails] No episodes found, using fallback ID: ${fallbackId}`); + } else { + console.warn('[AnimeDetails] No episodes found and no anime ID available'); } } catch (error) { console.error('[AnimeDetails] Error fetching episodes:', error); + // Even on error, try to use fallback + if (anime.info.id) { + const fallbackId = `${anime.info.id}?ep=1`; + setFirstEpisodeId(fallbackId); + console.log(`[AnimeDetails] Error occurred, using fallback ID: ${fallbackId}`); + } } finally { setIsLoadingEpisodes(false); } @@ -47,6 +78,11 @@ export default function AnimeDetails({ anime }) { fetchFirstEpisode(); }, [anime?.info?.id]); + // Add a useEffect to debug when and why firstEpisodeId changes + useEffect(() => { + console.log('[AnimeDetails] firstEpisodeId changed:', firstEpisodeId); + }, [firstEpisodeId]); + if (!anime?.info) { return null; } @@ -55,10 +91,13 @@ export default function AnimeDetails({ anime }) { const hasCharacters = info.characterVoiceActor?.length > 0 || info.charactersVoiceActors?.length > 0; const hasVideos = info.promotionalVideos && info.promotionalVideos.length > 0; - // Build the watch URL based on the first episode ID or fallback + // Build the watch URL based on the first episode ID const watchUrl = firstEpisodeId ? `/watch/${firstEpisodeId}` - : `/watch/${info.id}?ep=1`; // Fallback to old format if API fetch fails + : ''; // Empty string if no episodes available - this shouldn't happen with our fallback + + // Add debug log here + console.log('[AnimeDetails] Rendered with watchUrl:', watchUrl, 'firstEpisodeId:', firstEpisodeId); // Video modal for promotional videos const VideoModal = ({ video, onClose }) => { @@ -236,8 +275,8 @@ export default function AnimeDetails({ anime }) {
- {/* Watch Button - Full Width on Mobile */} - {info.stats?.episodes && (info.stats.episodes.sub > 0 || info.stats.episodes.dub > 0) && ( + {/* Watch Button - Mobile */} + {firstEpisodeId && ( {/* Watch Button - Desktop */} - {info.stats?.episodes && (info.stats.episodes.sub > 0 || info.stats.episodes.dub > 0) && ( + {firstEpisodeId && ( { - return normalizeEpisodeId(ep.id) === normalizeEpisodeId(urlEpisodeId); - }); + const episode = episodes.find(ep => ep.id === urlEpisodeId); if (episode) { const pageNumber = Math.ceil(episode.number / episodesPerPage); @@ -52,13 +49,6 @@ export default function EpisodeList({ episodes, currentEpisode, onEpisodeClick, }; }, [episodes, episodesPerPage]); - // Helper function for episode ID comparison - // The API returns episode IDs in the format: animeId?ep=episodeNumber - const normalizeEpisodeId = (id) => { - // Simply return the ID as-is since the API already provides the correct format - return id || ''; - }; - const filteredEpisodes = useMemo(() => { if (!searchQuery) return episodes; const query = searchQuery.toLowerCase(); @@ -81,7 +71,7 @@ export default function EpisodeList({ episodes, currentEpisode, onEpisodeClick, const isCurrentEpisode = (episode) => { if (!episode || !episode.id || !activeEpisodeId) return false; - return normalizeEpisodeId(episode.id) === normalizeEpisodeId(activeEpisodeId); + return episode.id === activeEpisodeId; }; const handleEpisodeSelect = (episode, e) => { diff --git a/src/components/SpotlightCarousel.js b/src/components/SpotlightCarousel.js index c30f153..35891c1 100644 --- a/src/components/SpotlightCarousel.js +++ b/src/components/SpotlightCarousel.js @@ -19,6 +19,7 @@ const SpotlightCarousel = ({ items = [] }) => { const [autoplay, setAutoplay] = useState(true); const [progress, setProgress] = useState(0); const [episodeIds, setEpisodeIds] = useState({}); + const [loadingItems, setLoadingItems] = useState({}); const intervalRef = useRef(null); const progressIntervalRef = useRef(null); @@ -30,28 +31,73 @@ const SpotlightCarousel = ({ items = [] }) => { // Fetch first episode IDs for all spotlight items useEffect(() => { const fetchEpisodeData = async () => { - const episodeData = {}; + // Create a copy to track what we're loading + const newLoadingItems = { ...loadingItems }; + const episodeData = { ...episodeIds }; for (const item of items) { - if (item.id) { + // Skip if we already have the episode ID or if it's already loading + if (item.id && !episodeData[item.id] && !newLoadingItems[item.id]) { + newLoadingItems[item.id] = true; + } + } + + // Update loading state + setLoadingItems(newLoadingItems); + + // Process items that need to be loaded + for (const item of items) { + if (item.id && !episodeData[item.id] && newLoadingItems[item.id]) { try { + console.log(`[SpotlightCarousel] Fetching episodes for anime: ${item.id}`); const response = await fetchAnimeEpisodes(item.id); + console.log(`[SpotlightCarousel] Episodes response for ${item.name}:`, response); + if (response.episodes && response.episodes.length > 0) { - episodeData[item.id] = response.episodes[0].episodeId; + // Check for episode ID in the expected format + const firstEp = response.episodes[0]; + if (firstEp.id) { + episodeData[item.id] = firstEp.id; + console.log(`[SpotlightCarousel] Found episode ID (id) for ${item.name}: ${firstEp.id}`); + } else if (firstEp.episodeId) { + episodeData[item.id] = firstEp.episodeId; + console.log(`[SpotlightCarousel] Found episode ID (episodeId) for ${item.name}: ${firstEp.episodeId}`); + } else { + // Create a fallback ID if neither id nor episodeId are available + episodeData[item.id] = `${item.id}?ep=1`; + console.log(`[SpotlightCarousel] Using fallback ID for ${item.name}: ${item.id}?ep=1`); + } + } else { + // If no episodes, use a fallback + episodeData[item.id] = `${item.id}?ep=1`; + console.log(`[SpotlightCarousel] No episodes for ${item.name}, using fallback: ${item.id}?ep=1`); } } catch (error) { console.error(`[SpotlightCarousel] Error fetching episodes for ${item.id}:`, error); + // Even on error, try to use fallback + episodeData[item.id] = `${item.id}?ep=1`; + } finally { + // Mark as no longer loading + newLoadingItems[item.id] = false; } } } + // Update states setEpisodeIds(episodeData); + setLoadingItems(newLoadingItems); }; if (items && items.length > 0) { fetchEpisodeData(); } - }, [items]); + + // Clean up function + return () => { + if (intervalRef.current) clearTimeout(intervalRef.current); + if (progressIntervalRef.current) clearInterval(progressIntervalRef.current); + }; + }, [items, episodeIds, loadingItems]); // Autoplay functionality useEffect(() => { @@ -114,7 +160,7 @@ const SpotlightCarousel = ({ items = [] }) => { // Get the watch URL for the current item const watchUrl = episodeIds[currentItem.id] ? `/watch/${episodeIds[currentItem.id]}` - : `/watch/${currentItem.id}?ep=1`; // Fallback to old format if API fetch fails + : `/anime/${currentItem.id}`; // Direct to anime info if no episode ID return (
@@ -228,8 +274,9 @@ const SpotlightCarousel = ({ items = [] }) => { {/* Buttons - Below title on mobile, right side on desktop */}
+ {/* Watch button - Uses episodeIds[anime.id] if available, otherwise links to anime details */} diff --git a/src/lib/api.js b/src/lib/api.js index 8d6efae..1a6081f 100644 --- a/src/lib/api.js +++ b/src/lib/api.js @@ -463,8 +463,8 @@ export const fetchAnimeEpisodes = async (animeId) => { return { episodes: [] }; } - // API already returns episodeId in correct format (animeId?ep=episodeNumber) - // So we simply use it directly without any formatting + // API returns episodes with episode.id in the format (animeId?ep=episodeNumber) + // We use this id directly for all episode operations return { episodes: data.data.episodes || [], @@ -485,8 +485,7 @@ export const fetchEpisodeServers = async (episodeId) => { console.log(`[API] Processing episode ID: ${episodeId}`); - // episodeId should already be in the correct format (animeId?ep=episodeNumber) - // from the API response, so we use it directly + // Use the episode.id directly - it's in the format "animeId?ep=episodeNumber" const apiUrl = `${API_BASE_URL}/episode/servers?animeEpisodeId=${encodeURIComponent(episodeId)}`; console.log(`[API Call] Fetching servers from: ${apiUrl}`); @@ -543,8 +542,7 @@ export const fetchEpisodeSources = async (episodeId, dub = false, server = 'hd-2 console.log(`[API] Processing episode ID for sources: ${episodeId}`); - // episodeId should already be in the correct format (animeId?ep=episodeNumber) - // from the API response, so we use it directly + // Use the episode.id directly - it's in the format "animeId?ep=episodeNumber" const category = dub ? 'dub' : 'sub'; const serverName = server || 'hd-2'; // Default to hd-2 if server is null or empty const apiUrl = `${API_BASE_URL}/episode/sources?animeEpisodeId=${encodeURIComponent(episodeId)}&category=${category}&server=${serverName}`;