import { createFileRoute, Link } from "@tanstack/react-router" import { useState } from "react" import { useTranslation } from "react-i18next" import { Bookmark, BookmarkCheck, Star, User } from "lucide-react" import { BackButton } from "@/components/back-button" import { CommunityReviews } from "@/components/community-reviews" import { ReviewDetailSheet } from "@/components/review-detail-sheet" import { ViewingHistory } from "@/components/viewing-history" import { RatingHistogram } from "@/components/rating-histogram" import { HorizontalStrip } from "@/components/horizontal-strip" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Skeleton } from "@/components/ui/skeleton" import { posterUrl, tmdbProfileUrl } from "@/lib/api/client" import { useMovie, useMovieHistory, useMovieProfile } from "@/features/movies" import { useDocumentTitle } from "@/hooks/use-document-title" import { useWatchlistStatus, useAddToWatchlist, useRemoveFromWatchlist, } from "@/features/watchlist" import type { CastMemberDto, CrewMemberDto, SocialReviewDto } from "@/features/movies" export const Route = createFileRoute("/_app/movies/$id")({ component: MovieDetailPage, }) function MovieDetailPage() { const { t } = useTranslation() const { id } = Route.useParams() const { data, isPending } = useMovie(id) const { data: profile } = useMovieProfile(id) const { data: history } = useMovieHistory(id) useDocumentTitle(data?.movie.title) const [detailReview, setDetailReview] = useState(null) if (isPending) return if (!data) return null const { movie, stats, reviews } = data const hasStats = profile && (profile.budget_usd != null || profile.revenue_usd != null || profile.vote_average != null) return (
{(profile?.overview ?? movie.overview) && (

{profile?.overview ?? movie.overview}

)} {hasStats && (
{profile.budget_usd != null && (

${(profile.budget_usd / 1e6).toFixed(0)}M

{t("movie.budget")}

)} {profile.revenue_usd != null && (

${(profile.revenue_usd / 1e6).toFixed(0)}M

{t("movie.revenue")}

)} {profile.vote_average != null && (

{profile.vote_average.toFixed(1)}

{t("movie.tmdb")}

)}
)} {stats.rating_histogram.length > 0 && (

{t("movie.ratingDistribution")}

)} {profile && profile.cast.length > 0 && (

{t("movie.cast")}

)} {profile && profile.crew.length > 0 && (

{t("movie.crew")}

)} {profile && profile.keywords.length > 0 && (

{t("movie.keywords")}

{profile.keywords.map((k) => ( {k.name} ))}
)} {history && } {detailReview && ( !open && setDetailReview(null)} movie={movie} review={{ id: "", rating: detailReview.rating, comment: detailReview.comment, watched_at: detailReview.watched_at, watch_medium: detailReview.watch_medium }} userName={detailReview.user_display} /> )}
) } function HeroSection({ movie, stats, movieId, tagline, }: { movie: { title: string; release_year: number; director?: string; poster_path?: string; genres: string[]; runtime_minutes?: number } stats: { total_count: number; avg_rating?: number; federated_count: number } movieId: string tagline?: string }) { const { t } = useTranslation() const { data: watchlistData } = useWatchlistStatus(movieId) const addWatchlist = useAddToWatchlist() const removeWatchlist = useRemoveFromWatchlist() const onWatchlist = watchlistData?.on_watchlist ?? false return (
{movie.poster_path && ( )}

{movie.title}

{movie.release_year} {movie.director && ` · ${movie.director}`} {movie.runtime_minutes && ` · ${movie.runtime_minutes}m`}

{tagline &&

{tagline}

} {movie.genres.length > 0 && (
{movie.genres.map((g) => ( {g} ))}
)}
{stats.avg_rating != null && ( {stats.avg_rating.toFixed(1)} )} {t("common.reviews", { count: stats.total_count })}
) } function PersonStrip({ items, type }: { items: (CastMemberDto | CrewMemberDto)[]; type: "cast" | "crew" }) { return ( {items.map((person, i) => { const subtitle = type === "cast" ? (person as CastMemberDto).character : (person as CrewMemberDto).job return (
{person.profile_path ? ( ) : (
)}

{person.name}

{subtitle}

) })}
) } function DetailSkeleton() { return (
{[1, 2, 3].map((i) => ( ))}
) }