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 */}