mirror of
https://github.com/JustAnimeCore/JustAnime.git
synced 2026-04-17 22:01:45 +00:00
fixed url
This commit is contained in:
@@ -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 (
|
||||
<div
|
||||
className="anime-card w-full flex flex-col"
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{/* Image card linking to watch page */}
|
||||
<div className="anime-card w-full flex flex-col">
|
||||
{/* Image card linking to watch page for recent anime, or info page otherwise */}
|
||||
<Link
|
||||
href={watchLink}
|
||||
href={isRecent ? watchLink : infoLink}
|
||||
className="block w-full rounded-lg overflow-hidden transition-transform duration-300 hover:scale-[1.02] group"
|
||||
prefetch={false}
|
||||
>
|
||||
|
||||
@@ -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 }) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Watch Button - Full Width on Mobile */}
|
||||
{info.stats?.episodes && (info.stats.episodes.sub > 0 || info.stats.episodes.dub > 0) && (
|
||||
{/* Watch Button - Mobile */}
|
||||
{firstEpisodeId && (
|
||||
<Link
|
||||
href={watchUrl}
|
||||
className="bg-[#ffffff] text-[var(--background)] px-4 py-2.5 rounded-xl mt-3 hover:opacity-90 transition-opacity flex items-center justify-center font-medium text-sm w-full shadow-lg"
|
||||
@@ -273,7 +312,7 @@ export default function AnimeDetails({ anime }) {
|
||||
</div>
|
||||
|
||||
{/* Watch Button - Desktop */}
|
||||
{info.stats?.episodes && (info.stats.episodes.sub > 0 || info.stats.episodes.dub > 0) && (
|
||||
{firstEpisodeId && (
|
||||
<Link
|
||||
href={watchUrl}
|
||||
className="bg-[#ffffff] text-[var(--background)] px-6 py-3 rounded-xl mt-4 hover:opacity-90 transition-opacity flex items-center justify-center font-medium text-base w-full shadow-lg"
|
||||
|
||||
@@ -24,10 +24,7 @@ export default function EpisodeList({ episodes, currentEpisode, onEpisodeClick,
|
||||
setActiveEpisodeId(urlEpisodeId);
|
||||
|
||||
// Find the episode and update page
|
||||
// Compare with both ?ep= format and plain format
|
||||
const episode = episodes.find(ep => {
|
||||
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) => {
|
||||
|
||||
@@ -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 (
|
||||
<div className="w-full mb-6 md:mb-10 spotlight-carousel">
|
||||
@@ -228,8 +274,9 @@ const SpotlightCarousel = ({ items = [] }) => {
|
||||
|
||||
{/* Buttons - Below title on mobile, right side on desktop */}
|
||||
<div className="flex items-center space-x-2 md:space-x-4 mt-1 md:mt-0 md:absolute md:bottom-8 md:right-8">
|
||||
{/* Watch button - Uses episodeIds[anime.id] if available, otherwise links to anime details */}
|
||||
<Link
|
||||
href={watchUrl}
|
||||
href={episodeIds[anime.id] ? `/watch/${episodeIds[anime.id]}` : `/anime/${anime.id}`}
|
||||
className="bg-white hover:bg-gray-200 text-[#0a0a0a] font-medium text-xs md:text-base px-3 md:px-6 py-1.5 md:py-2 rounded flex items-center space-x-1.5 md:space-x-2 transition-colors"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-3.5 w-3.5 md:h-5 md:w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
|
||||
Reference in New Issue
Block a user