add network images
BIN
public/platforms/appletv.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
public/platforms/disney.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
public/platforms/hulu.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
public/platforms/max.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
public/platforms/netflix.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
public/platforms/paramount.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
public/platforms/prime.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
|
|
@ -35,6 +35,12 @@ export interface TraktDiscoverResponse {
|
|||
count: number;
|
||||
}
|
||||
|
||||
export interface TraktNetworkResponse {
|
||||
type: string;
|
||||
platforms: string[];
|
||||
count: number;
|
||||
}
|
||||
|
||||
// Pagination utility
|
||||
export function paginateResults(
|
||||
results: TraktLatestResponse,
|
||||
|
|
@ -106,6 +112,10 @@ export const getPopularMovies = () => fetchFromTrakt("/popularmovies");
|
|||
export const getDiscoverContent = () =>
|
||||
fetchFromTrakt<TraktDiscoverResponse>("/discover");
|
||||
|
||||
// Network content
|
||||
export const getNetworkContent = (tmdbId: string) =>
|
||||
fetchFromTrakt<TraktNetworkResponse>(`/network/${tmdbId}`);
|
||||
|
||||
// Type conversion utilities
|
||||
export function convertToMediaType(type: TraktContentType): MWMediaType {
|
||||
return type === "movie" ? MWMediaType.MOVIE : MWMediaType.SERIES;
|
||||
|
|
@ -130,3 +140,14 @@ export const GENRE_TO_TRAKT_MAP = {
|
|||
"28": "action", // Action
|
||||
"18": "drama", // Drama
|
||||
} as const;
|
||||
|
||||
// Map provider names to their image filenames
|
||||
export const PROVIDER_TO_IMAGE_MAP: Record<string, string> = {
|
||||
Max: "max",
|
||||
"Prime Video": "prime",
|
||||
Netflix: "netflix",
|
||||
"Disney+": "disney",
|
||||
Hulu: "hulu",
|
||||
"Apple TV+": "appletv",
|
||||
"Paramount+": "paramount",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { t } from "i18next";
|
|||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useCopyToClipboard } from "react-use";
|
||||
|
||||
import { getNetworkContent } from "@/backend/metadata/traktApi";
|
||||
import { TMDBContentTypes } from "@/backend/metadata/types/tmdb";
|
||||
import { Icon, Icons } from "@/components/Icon";
|
||||
import { useLanguageStore } from "@/stores/language";
|
||||
|
|
@ -22,6 +23,9 @@ import { DetailsContentProps } from "./types";
|
|||
export function DetailsContent({ data, minimal = false }: DetailsContentProps) {
|
||||
const [imdbData, setImdbData] = useState<any>(null);
|
||||
const [rtData, setRtData] = useState<any>(null);
|
||||
const [providerData, setProviderData] = useState<string | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [, setIsLoadingImdb] = useState(false);
|
||||
const [showTrailer, setShowTrailer] = useState(false);
|
||||
const [selectedSeason, setSelectedSeason] = useState<number>(1);
|
||||
|
|
@ -62,6 +66,30 @@ export function DetailsContent({ data, minimal = false }: DetailsContentProps) {
|
|||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchNetworkData = async () => {
|
||||
if (!data.id) return;
|
||||
|
||||
try {
|
||||
const networkData = await getNetworkContent(data.id.toString());
|
||||
if (
|
||||
networkData &&
|
||||
networkData.platforms &&
|
||||
networkData.platforms.length > 0
|
||||
) {
|
||||
setProviderData(networkData.platforms[0]);
|
||||
} else {
|
||||
setProviderData(undefined);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch network data:", error);
|
||||
setProviderData(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
fetchNetworkData();
|
||||
}, [data.id]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchExternalData = async () => {
|
||||
if (!data.imdbId) return;
|
||||
|
|
@ -273,7 +301,12 @@ export function DetailsContent({ data, minimal = false }: DetailsContentProps) {
|
|||
|
||||
{/* Right Column - Details Info (1/3) */}
|
||||
<div className="md:col-span-1">
|
||||
<DetailsInfo data={data} imdbData={imdbData} rtData={rtData} />
|
||||
<DetailsInfo
|
||||
data={data}
|
||||
imdbData={imdbData}
|
||||
rtData={rtData}
|
||||
provider={providerData}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,12 @@ import { Trans } from "react-i18next";
|
|||
import { DetailsRatings } from "./DetailsRatings";
|
||||
import { DetailsInfoProps } from "./types";
|
||||
|
||||
export function DetailsInfo({ data, imdbData, rtData }: DetailsInfoProps) {
|
||||
export function DetailsInfo({
|
||||
data,
|
||||
imdbData,
|
||||
rtData,
|
||||
provider,
|
||||
}: DetailsInfoProps) {
|
||||
const [isShiftPressed, setIsShiftPressed] = useState(false);
|
||||
const [showCopied, setShowCopied] = useState(false);
|
||||
|
||||
|
|
@ -122,6 +127,7 @@ export function DetailsInfo({ data, imdbData, rtData }: DetailsInfoProps) {
|
|||
imdbId={data.imdbId}
|
||||
voteAverage={data.voteAverage}
|
||||
voteCount={data.voteCount}
|
||||
provider={provider}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { t } from "i18next";
|
||||
|
||||
import { PROVIDER_TO_IMAGE_MAP } from "@/backend/metadata/traktApi";
|
||||
import { Icon, Icons } from "@/components/Icon";
|
||||
import { getRTIcon } from "@/utils/rottenTomatoesScraper";
|
||||
|
||||
|
|
@ -10,19 +11,44 @@ export function DetailsRatings({
|
|||
mediaId,
|
||||
mediaType,
|
||||
imdbId,
|
||||
provider,
|
||||
}: DetailsRatingsProps) {
|
||||
const getProviderImage = (providerName: string) => {
|
||||
const imageKey =
|
||||
PROVIDER_TO_IMAGE_MAP[providerName] ||
|
||||
providerName.toLowerCase().replace(/\s+/g, "");
|
||||
return `/platforms/${imageKey}.png`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
{/* External Links */}
|
||||
<div className="flex gap-3 mt-2">
|
||||
{provider && (
|
||||
<div
|
||||
className="w-8 h-8 flex items-center justify-center transition-transform hover:scale-110 animate-[scaleIn_0.6s_ease-out_forwards]"
|
||||
style={{
|
||||
animationDelay: "0ms",
|
||||
transform: "scale(0)",
|
||||
opacity: 0,
|
||||
}}
|
||||
title={provider}
|
||||
>
|
||||
<img
|
||||
src={getProviderImage(provider)}
|
||||
alt={provider}
|
||||
className="w-8 h-8 rounded-md"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{mediaId && (
|
||||
<a
|
||||
href={`https://www.themoviedb.org/${mediaType === "show" ? "tv" : "movie"}/${mediaId}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-8 h-8 rounded-full bg-[#0d253f] flex items-center justify-center transition-transform hover:scale-110 animate-[scaleIn_0.6s_ease-out_forwards]"
|
||||
className="w-8 h-8 rounded-md bg-[#0d253f] flex items-center justify-center transition-transform hover:scale-110 animate-[scaleIn_0.6s_ease-out_forwards]"
|
||||
style={{
|
||||
animationDelay: "0ms",
|
||||
animationDelay: "60ms",
|
||||
transform: "scale(0)",
|
||||
opacity: 0,
|
||||
}}
|
||||
|
|
@ -36,9 +62,9 @@ export function DetailsRatings({
|
|||
href={`https://www.imdb.com/title/${imdbId}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-8 h-8 rounded-full bg-yellow-500 flex items-center justify-center transition-transform hover:scale-110 animate-[scaleIn_0.6s_ease-out_forwards]"
|
||||
className="w-8 h-8 rounded-md bg-yellow-500 flex items-center justify-center transition-transform hover:scale-110 animate-[scaleIn_0.6s_ease-out_forwards]"
|
||||
style={{
|
||||
animationDelay: "60ms",
|
||||
animationDelay: "120ms",
|
||||
transform: "scale(0)",
|
||||
opacity: 0,
|
||||
}}
|
||||
|
|
@ -53,7 +79,7 @@ export function DetailsRatings({
|
|||
<div
|
||||
className="flex items-center gap-1 animate-[scaleIn_0.6s_ease-out_forwards]"
|
||||
style={{
|
||||
animationDelay: "120ms",
|
||||
animationDelay: "180ms",
|
||||
transform: "scale(0)",
|
||||
opacity: 0,
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ export interface DetailsInfoProps {
|
|||
data: DetailsContent;
|
||||
imdbData?: any;
|
||||
rtData?: any;
|
||||
provider?: string;
|
||||
}
|
||||
|
||||
export interface DetailsRatingsProps {
|
||||
|
|
@ -130,4 +131,5 @@ export interface DetailsRatingsProps {
|
|||
mediaId?: number;
|
||||
mediaType?: "movie" | "show";
|
||||
imdbId?: string;
|
||||
provider?: string;
|
||||
}
|
||||
|
|
|
|||