mirror of
https://github.com/p-stream/p-stream.git
synced 2026-04-21 05:32:23 +00:00
add trackt latest and 4k
This commit is contained in:
parent
3713a85720
commit
5cc4485629
5 changed files with 409 additions and 62 deletions
|
|
@ -901,6 +901,12 @@
|
||||||
"movies": "{{category}} Movies",
|
"movies": "{{category}} Movies",
|
||||||
"tvshows": "{{category}} Shows",
|
"tvshows": "{{category}} Shows",
|
||||||
"inCinemas": "In Cinemas",
|
"inCinemas": "In Cinemas",
|
||||||
|
"popular": "Most Popular",
|
||||||
|
"nowPlaying": "In Cinemas",
|
||||||
|
"topRated": "Top Rated",
|
||||||
|
"latestReleases": "Latest Releases",
|
||||||
|
"4kReleases": "4K Releases",
|
||||||
|
"onTheAir": "On The Air",
|
||||||
"popularOn": "Popular {{type}} on {{provider}}",
|
"popularOn": "Popular {{type}} on {{provider}}",
|
||||||
"editorPicksMovies": "Editor Picks Movies",
|
"editorPicksMovies": "Editor Picks Movies",
|
||||||
"editorPicksShows": "Editor Picks Shows",
|
"editorPicksShows": "Editor Picks Shows",
|
||||||
|
|
|
||||||
75
src/backend/metadata/traktApi.ts
Normal file
75
src/backend/metadata/traktApi.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { MWMediaType } from "./types/mw";
|
||||||
|
|
||||||
|
export interface TraktLatestResponse {
|
||||||
|
tmdb_ids: number[];
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TraktReleaseResponse {
|
||||||
|
tmdb_id: number;
|
||||||
|
title: string;
|
||||||
|
year?: number;
|
||||||
|
type: "movie" | "episode";
|
||||||
|
season?: number;
|
||||||
|
episode?: number;
|
||||||
|
quality?: string;
|
||||||
|
source?: string;
|
||||||
|
group?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TraktContentType = "movie" | "episode";
|
||||||
|
|
||||||
|
export const TRAKT_BASE_URL = "https://airdate.up.railway.app";
|
||||||
|
|
||||||
|
export async function getLatestReleases(): Promise<TraktLatestResponse> {
|
||||||
|
const response = await fetch(`${TRAKT_BASE_URL}/latest`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch latest releases: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getLatest4KReleases(): Promise<TraktLatestResponse> {
|
||||||
|
const response = await fetch(`${TRAKT_BASE_URL}/latest4k`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to fetch latest 4K releases: ${response.statusText}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getReleaseDetails(
|
||||||
|
id: string,
|
||||||
|
season?: number,
|
||||||
|
episode?: number,
|
||||||
|
): Promise<TraktReleaseResponse> {
|
||||||
|
let url = `${TRAKT_BASE_URL}/release/${id}`;
|
||||||
|
if (season !== undefined && episode !== undefined) {
|
||||||
|
url += `/${season}/${episode}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch release details: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAppleTVReleases(): Promise<TraktLatestResponse> {
|
||||||
|
const response = await fetch(`${TRAKT_BASE_URL}/appletv`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to fetch Apple TV releases: ${response.statusText}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertToMediaType(type: TraktContentType): MWMediaType {
|
||||||
|
return type === "movie" ? MWMediaType.MOVIE : MWMediaType.SERIES;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertFromMediaType(type: MWMediaType): TraktContentType {
|
||||||
|
return type === MWMediaType.MOVIE ? "movie" : "episode";
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,10 @@ import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { useWindowSize } from "react-use";
|
import { useWindowSize } from "react-use";
|
||||||
|
|
||||||
import { get } from "@/backend/metadata/tmdb";
|
import { get } from "@/backend/metadata/tmdb";
|
||||||
|
import {
|
||||||
|
getLatest4KReleases,
|
||||||
|
getLatestReleases,
|
||||||
|
} from "@/backend/metadata/traktApi";
|
||||||
import { Button } from "@/components/buttons/Button";
|
import { Button } from "@/components/buttons/Button";
|
||||||
import { Dropdown, OptionItem } from "@/components/form/Dropdown";
|
import { Dropdown, OptionItem } from "@/components/form/Dropdown";
|
||||||
import { Icon, Icons } from "@/components/Icon";
|
import { Icon, Icons } from "@/components/Icon";
|
||||||
|
|
@ -22,7 +26,7 @@ import { ProgressMediaItem, useProgressStore } from "@/stores/progress";
|
||||||
import { getTmdbLanguageCode } from "@/utils/language";
|
import { getTmdbLanguageCode } from "@/utils/language";
|
||||||
import { MediaItem } from "@/utils/mediaTypes";
|
import { MediaItem } from "@/utils/mediaTypes";
|
||||||
|
|
||||||
import { Genre, categories, tvCategories } from "./common";
|
import { Genre, Movie, categories, tvCategories } from "./common";
|
||||||
import {
|
import {
|
||||||
EDITOR_PICKS_MOVIES,
|
EDITOR_PICKS_MOVIES,
|
||||||
EDITOR_PICKS_TV_SHOWS,
|
EDITOR_PICKS_TV_SHOWS,
|
||||||
|
|
@ -123,6 +127,18 @@ export function MoreContent({ onShowDetails }: MoreContentProps) {
|
||||||
const isTVShow = mediaType === "tv";
|
const isTVShow = mediaType === "tv";
|
||||||
let endpoint = "";
|
let endpoint = "";
|
||||||
|
|
||||||
|
// Map category URLs to their corresponding TMDB endpoints
|
||||||
|
const categoryEndpointMap: { [key: string]: string } = {
|
||||||
|
// Movie categories
|
||||||
|
"now-playing-movie": "/movie/now_playing",
|
||||||
|
"top-rated-movie": "/movie/top_rated",
|
||||||
|
"most-popular-movie": "/movie/popular",
|
||||||
|
// TV categories
|
||||||
|
"on-the-air-tv": "/tv/on_the_air",
|
||||||
|
"top-rated-tv": "/tv/top_rated",
|
||||||
|
"most-popular-tv": "/tv/popular",
|
||||||
|
};
|
||||||
|
|
||||||
// Handle recommendations separately
|
// Handle recommendations separately
|
||||||
if (contentType === "recommendations") {
|
if (contentType === "recommendations") {
|
||||||
// Get title from progress store instead of fetching details
|
// Get title from progress store instead of fetching details
|
||||||
|
|
@ -141,10 +157,21 @@ export function MoreContent({ onShowDetails }: MoreContentProps) {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const processedResults = results.results.map((item: any) => {
|
||||||
|
const isItemTVShow = Boolean(item.first_air_date);
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
type: isItemTVShow ? "show" : "movie",
|
||||||
|
// Keep both dates in the raw data
|
||||||
|
first_air_date: item.first_air_date,
|
||||||
|
release_date: item.release_date,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
if (append) {
|
if (append) {
|
||||||
setMedias((prev) => [...prev, ...results.results]);
|
setMedias((prev) => [...prev, ...processedResults]);
|
||||||
} else {
|
} else {
|
||||||
setMedias(results.results);
|
setMedias(processedResults);
|
||||||
}
|
}
|
||||||
setHasMore(page < results.total_pages);
|
setHasMore(page < results.total_pages);
|
||||||
setCurrentPage(page);
|
setCurrentPage(page);
|
||||||
|
|
@ -153,35 +180,90 @@ export function MoreContent({ onShowDetails }: MoreContentProps) {
|
||||||
|
|
||||||
// Handle editor picks separately
|
// Handle editor picks separately
|
||||||
if (category?.includes("editor-picks")) {
|
if (category?.includes("editor-picks")) {
|
||||||
const editorPicks = isTVShow
|
const isEditorPicksTV = category.includes("tv");
|
||||||
|
const editorPicks = isEditorPicksTV
|
||||||
? EDITOR_PICKS_TV_SHOWS
|
? EDITOR_PICKS_TV_SHOWS
|
||||||
: EDITOR_PICKS_MOVIES;
|
: EDITOR_PICKS_MOVIES;
|
||||||
|
|
||||||
// Fetch details for all editor picks
|
// Fetch details for all editor picks
|
||||||
const promises = editorPicks.map((item) =>
|
const promises = editorPicks.map((item) =>
|
||||||
get<any>(`/${isTVShow ? "tv" : "movie"}/${item.id}`, {
|
get<any>(`/${isEditorPicksTV ? "tv" : "movie"}/${item.id}`, {
|
||||||
api_key: conf().TMDB_READ_API_KEY,
|
api_key: conf().TMDB_READ_API_KEY,
|
||||||
language: formattedLanguage,
|
language: formattedLanguage,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const results = await Promise.all(promises);
|
const results = await Promise.all(promises);
|
||||||
setMedias(results);
|
const validResults = results.map((item) => ({
|
||||||
|
...item,
|
||||||
|
type: isEditorPicksTV ? "show" : "movie",
|
||||||
|
release_date: isEditorPicksTV
|
||||||
|
? item.first_air_date
|
||||||
|
: item.release_date,
|
||||||
|
}));
|
||||||
|
setMedias(validResults);
|
||||||
setHasMore(false);
|
setHasMore(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the correct endpoint based on the type
|
// Handle Trakt categories separately
|
||||||
if (contentType === "category") {
|
if (
|
||||||
const categoryList = isTVShow ? tvCategories : categories;
|
category?.includes("latest-releases") ||
|
||||||
const categoryData = categoryList.find((c) => c.urlPath === id);
|
category?.includes("4k-releases")
|
||||||
if (categoryData) {
|
) {
|
||||||
endpoint = categoryData.endpoint;
|
try {
|
||||||
} else {
|
const traktFunction = category?.includes("latest-releases")
|
||||||
endpoint = isTVShow ? "/discover/tv" : "/discover/movie";
|
? getLatestReleases
|
||||||
|
: getLatest4KReleases;
|
||||||
|
|
||||||
|
const traktData = await traktFunction();
|
||||||
|
const moviePromises = traktData.tmdb_ids
|
||||||
|
.slice((page - 1) * 20, page * 20)
|
||||||
|
.map((tmdbId: number) =>
|
||||||
|
get<any>(`/movie/${tmdbId}`, {
|
||||||
|
api_key: conf().TMDB_READ_API_KEY,
|
||||||
|
language: formattedLanguage,
|
||||||
|
}).catch(() => null),
|
||||||
|
);
|
||||||
|
|
||||||
|
const results = await Promise.all(moviePromises);
|
||||||
|
const validMovies = results
|
||||||
|
.filter((movie: any): movie is Movie => movie !== null)
|
||||||
|
.map((movie: Movie) => {
|
||||||
|
const isItemTVShow = Boolean(movie.first_air_date);
|
||||||
|
return {
|
||||||
|
...movie,
|
||||||
|
type: isItemTVShow ? "show" : "movie",
|
||||||
|
// Keep both dates in the raw data
|
||||||
|
first_air_date: movie.first_air_date,
|
||||||
|
release_date: movie.release_date,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (append) {
|
||||||
|
setMedias((prev) => [...prev, ...validMovies]);
|
||||||
|
} else {
|
||||||
|
setMedias(validMovies);
|
||||||
|
}
|
||||||
|
setHasMore(traktData.tmdb_ids.length > page * 20);
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error fetching ${category}:`, error);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
// Determine the correct endpoint based on the category
|
||||||
|
if (category && categoryEndpointMap[category]) {
|
||||||
|
endpoint = categoryEndpointMap[category];
|
||||||
|
} else if (contentType === "provider") {
|
||||||
endpoint = isTVShow ? "/discover/tv" : "/discover/movie";
|
endpoint = isTVShow ? "/discover/tv" : "/discover/movie";
|
||||||
|
} else if (contentType === "genre") {
|
||||||
|
endpoint = isTVShow ? "/discover/tv" : "/discover/movie";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!endpoint) {
|
||||||
|
console.error("No endpoint found for category:", category);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const allResults: any[] = [];
|
const allResults: any[] = [];
|
||||||
|
|
@ -203,7 +285,18 @@ export function MoreContent({ onShowDetails }: MoreContentProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await get<any>(endpoint, params);
|
const data = await get<any>(endpoint, params);
|
||||||
allResults.push(...data.results);
|
const processedResults = data.results.map((item: any) => {
|
||||||
|
const isItemTVShow = Boolean(item.first_air_date);
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
type: isItemTVShow ? "show" : "movie",
|
||||||
|
// Keep both dates in the raw data
|
||||||
|
first_air_date: item.first_air_date,
|
||||||
|
release_date: item.release_date,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
allResults.push(...processedResults);
|
||||||
|
|
||||||
// Check if we've reached the end
|
// Check if we've reached the end
|
||||||
if (currentPageNum >= data.total_pages) {
|
if (currentPageNum >= data.total_pages) {
|
||||||
|
|
@ -259,12 +352,36 @@ export function MoreContent({ onShowDetails }: MoreContentProps) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (category === "editor-picks-tv" || category === "editor-picks-movie") {
|
if (category?.includes("editor-picks")) {
|
||||||
return category === "editor-picks-tv"
|
return category.includes("tv")
|
||||||
? t("discover.carousel.title.editorPicksShows")
|
? t("discover.carousel.title.editorPicksShows")
|
||||||
: t("discover.carousel.title.editorPicksMovies");
|
: t("discover.carousel.title.editorPicksMovies");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (category?.includes("latest-releases")) {
|
||||||
|
return t("discover.carousel.title.latestReleases");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (category?.includes("4k-releases")) {
|
||||||
|
return t("discover.carousel.title.4kReleases");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map category URLs to their display titles
|
||||||
|
const categoryTitleMap: { [key: string]: string } = {
|
||||||
|
// Movie categories
|
||||||
|
"now-playing-movie": t("discover.carousel.title.nowPlaying"),
|
||||||
|
"top-rated-movie": t("discover.carousel.title.topRated"),
|
||||||
|
"most-popular-movie": t("discover.carousel.title.popular"),
|
||||||
|
// TV categories
|
||||||
|
"on-the-air-tv": t("discover.carousel.title.onTheAir"),
|
||||||
|
"top-rated-tv": t("discover.carousel.title.topRated"),
|
||||||
|
"most-popular-tv": t("discover.carousel.title.popular"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (category && categoryTitleMap[category]) {
|
||||||
|
return categoryTitleMap[category];
|
||||||
|
}
|
||||||
|
|
||||||
if (!contentType || !id) return "";
|
if (!contentType || !id) return "";
|
||||||
|
|
||||||
if (contentType === "provider") {
|
if (contentType === "provider") {
|
||||||
|
|
@ -620,34 +737,52 @@ export function MoreContent({ onShowDetails }: MoreContentProps) {
|
||||||
{renderGenreButtons()}
|
{renderGenreButtons()}
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-8 sm:grid-cols-3 md:grid-cols-4 xl:grid-cols-6 3xl:grid-cols-8 4xl:grid-cols-10 pt-8">
|
<div className="grid grid-cols-2 gap-8 sm:grid-cols-3 md:grid-cols-4 xl:grid-cols-6 3xl:grid-cols-8 4xl:grid-cols-10 pt-8">
|
||||||
{medias.map((media) => (
|
{medias.map((media) => {
|
||||||
<div
|
// Determine if this is a TV show based on the presence of first_air_date
|
||||||
key={media.id}
|
const isTVShow = Boolean(media.first_air_date);
|
||||||
style={{ userSelect: "none" }}
|
const releaseDate = isTVShow
|
||||||
onContextMenu={(e: React.MouseEvent<HTMLDivElement>) =>
|
? media.first_air_date
|
||||||
e.preventDefault()
|
: media.release_date;
|
||||||
}
|
const year = releaseDate
|
||||||
>
|
? parseInt(releaseDate.split("-")[0], 10)
|
||||||
<MediaCard
|
: undefined;
|
||||||
media={{
|
|
||||||
id: media.id.toString(),
|
const mediaItem: MediaItem = {
|
||||||
title: media.title || media.name || "",
|
id: media.id.toString(),
|
||||||
poster: `https://image.tmdb.org/t/p/w342${media.poster_path}`,
|
title: media.title || media.name || "",
|
||||||
type: mediaType === "tv" ? "show" : "movie",
|
poster: `https://image.tmdb.org/t/p/w342${media.poster_path}`,
|
||||||
year:
|
type: isTVShow ? "show" : "movie",
|
||||||
mediaType === "tv"
|
year,
|
||||||
? media.first_air_date
|
release_date: releaseDate ? new Date(releaseDate) : undefined,
|
||||||
? parseInt(media.first_air_date.split("-")[0], 10)
|
};
|
||||||
: undefined
|
|
||||||
: media.release_date
|
console.log("Final MediaCard item:", {
|
||||||
? parseInt(media.release_date.split("-")[0], 10)
|
id: mediaItem.id,
|
||||||
: undefined,
|
title: mediaItem.title,
|
||||||
}}
|
year: mediaItem.year,
|
||||||
onShowDetails={handleShowDetails}
|
release_date: mediaItem.release_date?.toISOString(),
|
||||||
linkable={!category?.includes("upcoming")}
|
type: mediaItem.type,
|
||||||
/>
|
raw_first_air_date: media.first_air_date,
|
||||||
</div>
|
raw_release_date: media.release_date,
|
||||||
))}
|
isTVShow,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={media.id}
|
||||||
|
style={{ userSelect: "none" }}
|
||||||
|
onContextMenu={(e: React.MouseEvent<HTMLDivElement>) =>
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MediaCard
|
||||||
|
media={mediaItem}
|
||||||
|
onShowDetails={handleShowDetails}
|
||||||
|
linkable={!category?.includes("upcoming")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
{hasMore && (
|
{hasMore && (
|
||||||
<div className="flex justify-center mt-8">
|
<div className="flex justify-center mt-8">
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,14 @@ export function MediaCarousel({
|
||||||
: t("discover.carousel.title.editorPicksMovies");
|
: t("discover.carousel.title.editorPicksMovies");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (categoryName === "Latest Releases") {
|
||||||
|
return t("discover.carousel.title.latestReleases");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (categoryName === "4K Releases") {
|
||||||
|
return t("discover.carousel.title.4kReleases");
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
categoryName.includes("Movies on") ||
|
categoryName.includes("Movies on") ||
|
||||||
categoryName.includes("Shows on")
|
categoryName.includes("Shows on")
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,10 @@ import { useEffect, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { get } from "@/backend/metadata/tmdb";
|
import { get } from "@/backend/metadata/tmdb";
|
||||||
|
import {
|
||||||
|
getLatest4KReleases,
|
||||||
|
getLatestReleases,
|
||||||
|
} from "@/backend/metadata/traktApi";
|
||||||
import { WideContainer } from "@/components/layout/WideContainer";
|
import { WideContainer } from "@/components/layout/WideContainer";
|
||||||
import { DetailsModal } from "@/components/overlays/details/DetailsModal";
|
import { DetailsModal } from "@/components/overlays/details/DetailsModal";
|
||||||
import { useModal } from "@/components/overlays/Modal";
|
import { useModal } from "@/components/overlays/Modal";
|
||||||
|
|
@ -163,6 +167,11 @@ export function DiscoverContent() {
|
||||||
const [selectedTVSource, setSelectedTVSource] = useState<string>("");
|
const [selectedTVSource, setSelectedTVSource] = useState<string>("");
|
||||||
const progressStore = useProgressStore();
|
const progressStore = useProgressStore();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const [latestReleases, setLatestReleases] = useState<Movie[]>([]);
|
||||||
|
const [latest4KReleases, setLatest4KReleases] = useState<Movie[]>([]);
|
||||||
|
const [isLoadingLatest, setIsLoadingLatest] = useState(false);
|
||||||
|
const [isLoading4K, setIsLoading4K] = useState(false);
|
||||||
|
const [isTraktAvailable, setIsTraktAvailable] = useState(false);
|
||||||
|
|
||||||
const carouselRefs = useRef<{ [key: string]: HTMLDivElement | null }>({});
|
const carouselRefs = useRef<{ [key: string]: HTMLDivElement | null }>({});
|
||||||
|
|
||||||
|
|
@ -485,6 +494,98 @@ export function DiscoverContent() {
|
||||||
t,
|
t,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Fetch latest releases from Trakt
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchLatestReleases = async () => {
|
||||||
|
if (!isMoviesTab) return;
|
||||||
|
setIsLoadingLatest(true);
|
||||||
|
try {
|
||||||
|
const traktData = await getLatestReleases();
|
||||||
|
const moviePromises = traktData.tmdb_ids.slice(0, 20).map(
|
||||||
|
(id) =>
|
||||||
|
get<any>(`/movie/${id}`, {
|
||||||
|
api_key: conf().TMDB_READ_API_KEY,
|
||||||
|
language: formattedLanguage,
|
||||||
|
}).catch(() => null), // Handle failed TMDB fetches gracefully
|
||||||
|
);
|
||||||
|
|
||||||
|
const results = await Promise.all(moviePromises);
|
||||||
|
const validMovies = results
|
||||||
|
.filter((movie): movie is Movie => movie !== null)
|
||||||
|
.map((movie) => ({
|
||||||
|
...movie,
|
||||||
|
type: "movie" as const,
|
||||||
|
}));
|
||||||
|
setLatestReleases(validMovies);
|
||||||
|
setIsTraktAvailable(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching latest releases:", error);
|
||||||
|
setIsTraktAvailable(false);
|
||||||
|
// Fallback to TMDB if Trakt fails
|
||||||
|
const data = await get<any>("/movie/now_playing", {
|
||||||
|
api_key: conf().TMDB_READ_API_KEY,
|
||||||
|
language: formattedLanguage,
|
||||||
|
});
|
||||||
|
setLatestReleases(
|
||||||
|
data.results.map((movie: any) => ({
|
||||||
|
...movie,
|
||||||
|
type: "movie" as const,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setIsLoadingLatest(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchLatestReleases();
|
||||||
|
}, [isMoviesTab, formattedLanguage]);
|
||||||
|
|
||||||
|
// Fetch 4K releases from Trakt
|
||||||
|
useEffect(() => {
|
||||||
|
const fetch4KReleases = async () => {
|
||||||
|
if (!isMoviesTab) return;
|
||||||
|
setIsLoading4K(true);
|
||||||
|
try {
|
||||||
|
const traktData = await getLatest4KReleases();
|
||||||
|
const moviePromises = traktData.tmdb_ids.slice(0, 20).map(
|
||||||
|
(id) =>
|
||||||
|
get<any>(`/movie/${id}`, {
|
||||||
|
api_key: conf().TMDB_READ_API_KEY,
|
||||||
|
language: formattedLanguage,
|
||||||
|
}).catch(() => null), // Handle failed TMDB fetches gracefully
|
||||||
|
);
|
||||||
|
|
||||||
|
const results = await Promise.all(moviePromises);
|
||||||
|
const validMovies = results
|
||||||
|
.filter((movie): movie is Movie => movie !== null)
|
||||||
|
.map((movie) => ({
|
||||||
|
...movie,
|
||||||
|
type: "movie" as const,
|
||||||
|
}));
|
||||||
|
setLatest4KReleases(validMovies);
|
||||||
|
setIsTraktAvailable(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching 4K releases:", error);
|
||||||
|
setIsTraktAvailable(false);
|
||||||
|
// Fallback to TMDB if Trakt fails
|
||||||
|
const data = await get<any>("/movie/popular", {
|
||||||
|
api_key: conf().TMDB_READ_API_KEY,
|
||||||
|
language: formattedLanguage,
|
||||||
|
});
|
||||||
|
setLatest4KReleases(
|
||||||
|
data.results.map((movie: any) => ({
|
||||||
|
...movie,
|
||||||
|
type: "movie" as const,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setIsLoading4K(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch4KReleases();
|
||||||
|
}, [isMoviesTab, formattedLanguage]);
|
||||||
|
|
||||||
const handleShowDetails = async (media: MediaItem | FeaturedMedia) => {
|
const handleShowDetails = async (media: MediaItem | FeaturedMedia) => {
|
||||||
setDetailsData({
|
setDetailsData({
|
||||||
id: Number(media.id),
|
id: Number(media.id),
|
||||||
|
|
@ -513,14 +614,25 @@ export function DiscoverContent() {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* In Cinemas */}
|
{/* Latest Releases or In Cinemas */}
|
||||||
<LazyMediaCarousel
|
{isTraktAvailable ? (
|
||||||
category={categories[0].name}
|
<LazyMediaCarousel
|
||||||
isTVShow={false}
|
medias={isLoadingLatest ? undefined : latestReleases}
|
||||||
carouselRefs={carouselRefs}
|
category="Latest Releases"
|
||||||
onShowDetails={handleShowDetails}
|
isTVShow={false}
|
||||||
moreContent
|
carouselRefs={carouselRefs}
|
||||||
/>
|
onShowDetails={handleShowDetails}
|
||||||
|
moreContent
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<LazyMediaCarousel
|
||||||
|
category={categories[0].name}
|
||||||
|
isTVShow={false}
|
||||||
|
carouselRefs={carouselRefs}
|
||||||
|
onShowDetails={handleShowDetails}
|
||||||
|
moreContent
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Top Rated */}
|
{/* Top Rated */}
|
||||||
<LazyMediaCarousel
|
<LazyMediaCarousel
|
||||||
|
|
@ -531,14 +643,25 @@ export function DiscoverContent() {
|
||||||
moreContent
|
moreContent
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Popular */}
|
{/* 4K Releases or Popular */}
|
||||||
<LazyMediaCarousel
|
{isTraktAvailable ? (
|
||||||
category={categories[2].name}
|
<LazyMediaCarousel
|
||||||
isTVShow={false}
|
medias={isLoading4K ? undefined : latest4KReleases}
|
||||||
carouselRefs={carouselRefs}
|
category="4K Releases"
|
||||||
onShowDetails={handleShowDetails}
|
isTVShow={false}
|
||||||
moreContent
|
carouselRefs={carouselRefs}
|
||||||
/>
|
onShowDetails={handleShowDetails}
|
||||||
|
moreContent
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<LazyMediaCarousel
|
||||||
|
category={categories[2].name}
|
||||||
|
isTVShow={false}
|
||||||
|
carouselRefs={carouselRefs}
|
||||||
|
onShowDetails={handleShowDetails}
|
||||||
|
moreContent
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Provider Movies */}
|
{/* Provider Movies */}
|
||||||
<LazyMediaCarousel
|
<LazyMediaCarousel
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue