// Based mfs only use only one 500 line file instead of ten 50 line files. import { useEffect, useRef, useState } from "react"; import { useNavigate } from "react-router-dom"; import { get } from "@/backend/metadata/tmdb"; import { Divider } from "@/components/utils/Divider"; import { Flare } from "@/components/utils/Flare"; import { useIsMobile } from "@/hooks/useIsMobile"; import { conf } from "@/setup/config"; import { Category, Genre, Media, Movie, TVShow, categories, tvCategories, } from "@/utils/discover"; import { Icon, Icons } from "../components/Icon"; const editorPicks = [ { id: 9342, type: "movie" }, // The Mask of Zorro { id: 293, type: "movie" }, // A River Runs Through It { id: 370172, type: "movie" }, // No Time To Die { id: 661374, type: "movie" }, // The Glass Onion { id: 207, type: "movie" }, // Dead Poets Society { id: 378785, type: "movie" }, // The Best of the Blues Brothers { id: 335984, type: "movie" }, // Blade Runner 2049 { id: 13353, type: "movie" }, // It's the Great Pumpkin, Charlie Brown { id: 27205, type: "movie" }, // Inception { id: 106646, type: "movie" }, // The Wolf of Wall Street { id: 334533, type: "movie" }, // Captain Fantastic { id: 693134, type: "movie" }, // Dune: Part Two { id: 765245, type: "movie" }, // Swan Song { id: 264660, type: "movie" }, // Ex Machina { id: 92591, type: "movie" }, // Bernie { id: 976893, type: "movie" }, // Perfect Days ]; function ScrollToTopButton() { const [isVisible, setIsVisible] = useState(false); // Throttle scroll event for performance const toggleVisibility = () => { const scrolled = window.scrollY > 300; // Show button after 300px of scrolling setIsVisible(scrolled); }; useEffect(() => { const handleScroll = () => { // Throttle the scroll event to fire every 100ms for better performance const timeout = setTimeout(toggleVisibility, 100); return () => clearTimeout(timeout); }; window.addEventListener("scroll", handleScroll); return () => { window.removeEventListener("scroll", handleScroll); }; }, []); const scrollToTop = () => { window.scrollTo({ top: 0, behavior: "smooth" }); }; return (
{/* Glow Effect (Behind the Button) */}
{/* Button */}
); } export function DiscoverContent() { const [genres, setGenres] = useState([]); const [randomMovie, setRandomMovie] = useState(null); const [genreMovies, setGenreMovies] = useState<{ [genreId: number]: Movie[]; }>({}); const [providerMovies, setProviderMovies] = useState<{ [providerId: string]: Movie[]; }>({}); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [selectedProvider, setSelectedProvider] = useState({ name: "", id: "", }); const [providerTVShows, setProviderTVShows] = useState<{ [providerId: string]: Movie[]; }>({}); const [selectedTVProvider, setSelectedTVProvider] = useState({ name: "", id: "", }); const movieProviders = [ { name: "Netflix", id: "8" }, { name: "Apple TV+", id: "2" }, { name: "Amazon Prime Video", id: "10" }, { name: "Hulu", id: "15" }, { name: "Max", id: "1899" }, { name: "Paramount Plus", id: "531" }, { name: "Disney Plus", id: "337" }, { name: "Shudder", id: "99" }, ]; const tvProviders = [ { name: "Netflix", id: "8" }, { name: "Apple TV+", id: "350" }, { name: "Paramount Plus", id: "531" }, { name: "Hulu", id: "15" }, { name: "Max", id: "1899" }, { name: "Disney Plus", id: "337" }, { name: "fubuTV", id: "257" }, ]; const [countdown, setCountdown] = useState(null); const navigate = useNavigate(); const [categoryShows, setCategoryShows] = useState<{ [categoryName: string]: Movie[]; }>({}); const [categoryMovies, setCategoryMovies] = useState<{ [categoryName: string]: Movie[]; }>({}); const [tvGenres, setTVGenres] = useState([]); const [tvShowGenres, setTVShowGenres] = useState<{ [genreId: number]: TVShow[]; }>({}); const carouselRef = useRef(null); const carouselRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); const gradientRef = useRef(null); const [countdownTimeout, setCountdownTimeout] = useState(null); const { isMobile } = useIsMobile(); const [editorPicksData, setEditorPicksData] = useState([]); // State to track selected category (movies or TV shows) const [selectedCategory, setSelectedCategory] = useState("movies"); const [isDropdownOpen, setDropdownOpen] = useState(false); // Handle category change for both event (from change event setSelectedCategory(eventOrValue.target.value); } setDropdownOpen(false); // Close dropdown after selection }; useEffect(() => { // Function to shuffle array const shuffleArray = (array: any[]) => { for (let i = array.length - 1; i > 0; i -= 1) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; }; const fetchEditorPicks = async () => { try { // Shuffle the editorPicks array const shuffledPicks = shuffleArray([...editorPicks]); const promises = shuffledPicks.map(async (pick) => { const endpoint = pick.type === "movie" ? `/movie/${pick.id}` : `/tv/${pick.id}`; const data = await get(endpoint, { api_key: conf().TMDB_READ_API_KEY, language: "en-US", }); return { ...data, type: pick.type, }; }); const results = await Promise.all(promises); setEditorPicksData(results); } catch (error) { console.error("Error fetching editor picks:", error); } }; fetchEditorPicks(); }, []); useEffect(() => { const fetchMoviesForCategory = async (category: Category) => { try { const data = await get(category.endpoint, { api_key: conf().TMDB_READ_API_KEY, language: "en-US", }); // Shuffle the movies for (let i = data.results.length - 1; i > 0; i -= 1) { const j = Math.floor(Math.random() * (i + 1)); [data.results[i], data.results[j]] = [ data.results[j], data.results[i], ]; } setCategoryMovies((prevCategoryMovies) => ({ ...prevCategoryMovies, [category.name]: data.results, })); } catch (error) { console.error( `Error fetching movies for category ${category.name}:`, error, ); } }; categories.forEach(fetchMoviesForCategory); }, []); useEffect(() => { const fetchShowsForCategory = async (category: Category) => { try { const data = await get(category.endpoint, { api_key: conf().TMDB_READ_API_KEY, language: "en-US", }); // Shuffle the TV shows for (let i = data.results.length - 1; i > 0; i -= 1) { const j = Math.floor(Math.random() * (i + 1)); [data.results[i], data.results[j]] = [ data.results[j], data.results[i], ]; } setCategoryShows((prevCategoryShows) => ({ ...prevCategoryShows, [category.name]: data.results, })); } catch (error) { console.error( `Error fetching movies for category ${category.name}:`, error, ); } }; tvCategories.forEach(fetchShowsForCategory); }, []); // Fetch TV show genres useEffect(() => { const fetchTVGenres = async () => { try { const data = await get("/genre/tv/list", { api_key: conf().TMDB_READ_API_KEY, language: "en-US", }); // Shuffle the array of genres // for (let i = data.genres.length - 1; i > 0; i -= 1) { // const j = Math.floor(Math.random() * (i + 1)); // [data.genres[i], data.genres[j]] = [data.genres[j], data.genres[i]]; // } // Fetch only the first 10 TV show genres setTVGenres(data.genres.slice(0, 10)); } catch (error) { console.error("Error fetching TV show genres:", error); } }; fetchTVGenres(); }, []); // Fetch TV shows for each genre useEffect(() => { const fetchTVShowsForGenre = async (genreId: number) => { try { const data = await get("/discover/tv", { api_key: conf().TMDB_READ_API_KEY, with_genres: genreId.toString(), language: "en-US", }); // Shuffle the TV shows for (let i = data.results.length - 1; i > 0; i -= 1) { const j = Math.floor(Math.random() * (i + 1)); [data.results[i], data.results[j]] = [ data.results[j], data.results[i], ]; } setTVShowGenres((prevTVShowGenres) => ({ ...prevTVShowGenres, [genreId]: data.results, })); } catch (error) { console.error(`Error fetching TV shows for genre ${genreId}:`, error); } }; tvGenres.forEach((genre) => fetchTVShowsForGenre(genre.id)); }, [tvGenres]); // Fetch Movie genres useEffect(() => { const fetchGenres = async () => { try { const data = await get("/genre/movie/list", { api_key: conf().TMDB_READ_API_KEY, language: "en-US", }); // Shuffle the array of genres // for (let i = data.genres.length - 1; i > 0; i -= 1) { // const j = Math.floor(Math.random() * (i + 1)); // [data.genres[i], data.genres[j]] = [data.genres[j], data.genres[i]]; // } // Fetch only the first 12 genres setGenres(data.genres.slice(0, 12)); } catch (error) { console.error("Error fetching genres:", error); } }; fetchGenres(); }, []); // Fetch movies for each genre useEffect(() => { const fetchMoviesForGenre = async (genreId: number) => { try { const movies: any[] = []; for (let page = 1; page <= 6; page += 1) { // Fetch only 6 pages const data = await get("/discover/movie", { api_key: conf().TMDB_READ_API_KEY, with_genres: genreId.toString(), language: "en-US", page: page.toString(), }); movies.push(...data.results); } // Shuffle the movies for (let i = movies.length - 1; i > 0; i -= 1) { const j = Math.floor(Math.random() * (i + 1)); [movies[i], movies[j]] = [movies[j], movies[i]]; } setGenreMovies((prevGenreMovies) => ({ ...prevGenreMovies, [genreId]: movies, })); } catch (error) { console.error(`Error fetching movies for genre ${genreId}:`, error); } }; genres.forEach((genre) => fetchMoviesForGenre(genre.id)); }, [genres]); // Fetch Movies By Provider const fetchMoviesByProvider = async (providerId: string) => { try { const movies: any[] = []; // eslint-disable-next-line no-plusplus for (let page = 1; page <= 3; page++) { const data = await get("/discover/movie", { api_key: conf().TMDB_READ_API_KEY, language: "en-US", page: page.toString(), with_watch_providers: providerId, watch_region: "US", // You can set a specific region if required }); movies.push(...data.results); } setProviderMovies((prev) => ({ ...prev, [providerId]: movies, })); } catch (error) { console.error(`Error fetching movies for provider ${providerId}:`, error); } }; useEffect(() => { const randomMovieProvider = movieProviders[Math.floor(Math.random() * movieProviders.length)]; setSelectedProvider(randomMovieProvider); // Store the selected provider fetchMoviesByProvider(randomMovieProvider.id); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Fetch TV Shows By Provider const fetchTVByProvider = async (providerId: string) => { try { const series: any[] = []; // eslint-disable-next-line no-plusplus for (let page = 1; page <= 3; page++) { const data = await get("/discover/tv", { api_key: conf().TMDB_READ_API_KEY, language: "en-US", page: page.toString(), with_watch_providers: providerId, watch_region: "US", // You can set a specific region if required }); series.push(...data.results); } setProviderTVShows((prev) => ({ ...prev, [providerId]: series, })); } catch (error) { console.error( `Error fetching tv shows for provider ${providerId}:`, error, ); } }; useEffect(() => { const randomTVProvider = tvProviders[Math.floor(Math.random() * tvProviders.length)]; setSelectedTVProvider(randomTVProvider); // Store the selected provider fetchTVByProvider(randomTVProvider.id); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); function scrollCarousel(categorySlug: string, direction: string) { const carousel = carouselRefs.current[categorySlug]; if (carousel) { const movieElements = carousel.getElementsByTagName("a"); if (movieElements.length > 0) { const movieWidth = movieElements[0].offsetWidth; const visibleMovies = Math.floor(carousel.offsetWidth / movieWidth); // Scroll 2 posters by default, but scroll 4 if more than 5 are visible let scrollAmount = movieWidth * 2; if (visibleMovies > 5) { scrollAmount = movieWidth * 4; } if (direction === "left") { carousel.scrollBy({ left: -scrollAmount, behavior: "smooth" }); } else { carousel.scrollBy({ left: scrollAmount, behavior: "smooth" }); } } } } const [movieWidth, setMovieWidth] = useState( window.innerWidth < 600 ? "150px" : "200px", ); useEffect(() => { const handleResize = () => { setMovieWidth(window.innerWidth < 600 ? "150px" : "200px"); }; window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); }; }, []); const browser = !!window.chrome; // detect chromium browser let isScrolling = false; function handleWheel(e: React.WheelEvent, _categorySlug: string) { if (isScrolling) { return; } isScrolling = true; if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) { e.stopPropagation(); e.preventDefault(); } if (browser) { setTimeout(() => { isScrolling = false; }, 345); // disable scrolling after 345 milliseconds for chromium-based browsers } else { // immediately reset isScrolling for non-chromium browsers isScrolling = false; } } useEffect(() => { if (carouselRef.current && gradientRef.current) { const carouselHeight = carouselRef.current.getBoundingClientRect().height; gradientRef.current.style.top = `${carouselHeight}px`; gradientRef.current.style.bottom = `${carouselHeight}px`; } }, [movieWidth]); function renderMovies(medias: Media[], category: string, isTVShow = false) { const categorySlug = `${category.toLowerCase().replace(/ /g, "-")}${Math.random()}`; // Convert the category to a slug const displayCategory = category === "Now Playing" ? "In Cinemas" : category === "Editor Picks" // Check for "Editor Picks" specifically ? category : category === `Popular Movies on ${selectedProvider.name}` ? `Popular Movies on ${selectedProvider.name}` : category === `Popular Shows on ${selectedTVProvider.name}` ? `Popular Shows on ${selectedTVProvider.name}` : category.includes("Movie") ? `${category}s` : isTVShow ? `${category} Shows` : `${category} Movies`; // https://tailwindcss.com/docs/border-style return (

{displayCategory}

{!isMobile && (
)}
); } const handleRandomMovieClick = () => { const allMovies = Object.values(genreMovies).flat(); // Flatten all movie arrays const uniqueTitles = new Set(); // Use a Set to store unique titles allMovies.forEach((movie) => uniqueTitles.add(movie.title)); // Add each title to the Set const uniqueTitlesArray = Array.from(uniqueTitles); // Convert the Set back to an array const randomIndex = Math.floor(Math.random() * uniqueTitlesArray.length); const selectedMovie = allMovies.find( (movie) => movie.title === uniqueTitlesArray[randomIndex], ); if (selectedMovie) { setRandomMovie(selectedMovie); if (countdown !== null && countdown > 0) { // Clear the countdown setCountdown(null); if (countdownTimeout) { clearTimeout(countdownTimeout); setCountdownTimeout(null); setRandomMovie(null); } } else { setCountdown(5); // Schedule navigation after 5 seconds const timeoutId = setTimeout(() => { navigate( `/media/tmdb-movie-${selectedMovie.id}-${selectedMovie.title}`, ); }, 5000); setCountdownTimeout(timeoutId); } } }; useEffect(() => { let countdownInterval: NodeJS.Timeout; if (countdown !== null && countdown > 0) { countdownInterval = setInterval(() => { setCountdown((prevCountdown) => prevCountdown !== null ? prevCountdown - 1 : prevCountdown, ); }, 1000); } return () => { clearInterval(countdownInterval); }; }, [countdown]); const renderTopMovieButtons = () => { const buttons = []; // Categories for (const [index, category] of categories.entries()) { buttons.push( , ); } return buttons; }; const renderMovieButtons = () => { const buttons = []; // Genres for (const [index, genre] of genres.entries()) { buttons.push( , ); } return buttons; }; const renderMovieProviderButtons = () => { const buttons = []; // Movie Providers // eslint-disable-next-line @typescript-eslint/no-unused-vars for (const [index, provider] of movieProviders.entries()) { buttons.push( , ); } return buttons; }; const renderTopTvButtons = () => { const buttons = []; // TV Categories for (const [index, category] of tvCategories.entries()) { buttons.push( , ); } return buttons; }; const renderTvButtons = () => { const buttons = []; // TV Genres for (const [index, genre] of tvGenres.entries()) { buttons.push( , ); } return buttons; }; const renderTvProviderButtons = () => { const buttons = []; // Movie Providers // eslint-disable-next-line @typescript-eslint/no-unused-vars for (const [index, provider] of tvProviders.entries()) { buttons.push( , ); } return buttons; }; const renderScrollButton = ( categorySlug: string, direction: "left" | "right", ) => ( ); return (
{randomMovie && (

Now Playing {randomMovie.title}{" "} in {countdown}

)}
{/* Custom dropdown button */} {/* Dropdown options */} {isDropdownOpen && (
  • handleCategoryChange("movies")} > Movies
  • handleCategoryChange("tvshows")} > TV Shows
)}
{/* Render Movies */} {selectedCategory === "movies" && ( <>
{isMobile && (
{renderScrollButton("providers", "left")}
)} {isMobile && (
{renderScrollButton("providers", "right")}
)}
{renderScrollButton("movies", "left")}
{renderScrollButton("movies", "right")}
)} {/* Render Shows */} {selectedCategory === "tvshows" && ( <>
{isMobile && (
{renderScrollButton("tv-providers", "left")}
)} {isMobile && (
{renderScrollButton("tv-providers", "right")}
)}
{renderScrollButton("tvshows", "left")}
{renderScrollButton("tvshows", "right")}
)}

Editor Picks

{/* Editor Picks Section */}
{editorPicksData.length > 0 && (
{renderMovies(editorPicksData, "Editor Picks")}
)}
{selectedCategory === "movies" && ( <>

Movies

{isMobile && (
{renderScrollButton("providers", "left")}
)} {isMobile && (
{renderScrollButton("providers", "right")}
)}
{" "} {categories.map((category) => (
{renderMovies( categoryMovies[category.name] || [], category.name, )}
))} {genres.map((genre) => (
{renderMovies(genreMovies[genre.id] || [], genre.name)}
))}
)} {selectedCategory === "tvshows" && ( <>

Shows

{isMobile && (
{renderScrollButton("tv-providers", "left")}
)} {isMobile && (
{renderScrollButton("tv-providers", "right")}
)}
{" "} {tvCategories.map((category) => ( ))} {tvGenres.map((genre) => ( ))}
)}
); } export default DiscoverContent;