import { createFileRoute, Link } from "@tanstack/react-router" import { useTranslation } from "react-i18next" import { Bookmark, BookmarkCheck, Globe, Star, TrendingUp, User, Users } from "lucide-react" import { BackButton } from "@/components/back-button" import { StarDisplay } from "@/components/star-display" import { RatingHistogram } from "@/components/rating-histogram" import { EmptyState } from "@/components/empty-state" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" import { Skeleton } from "@/components/ui/skeleton" import { posterUrl, tmdbProfileUrl } from "@/lib/api/client" import { timeAgo, shortDate } from "@/lib/date" import { useMovie, useMovieHistory, useMovieProfile } from "@/hooks/use-movies" import { useDocumentTitle } from "@/hooks/use-document-title" import { useWatchlistStatus, useAddToWatchlist, useRemoveFromWatchlist, } from "@/hooks/use-watchlist" import type { CastMemberDto, CrewMemberDto } from "@/lib/api/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) if (isPending) return if (!data) return null const { movie, stats, reviews } = data useDocumentTitle(movie.title) 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} ))}
)}

{t("movie.community")}

{!reviews.items.length ? ( ) : (
{reviews.items.map((r, i) => (
{r.user_display} {r.is_federated && } {timeAgo(r.watched_at)}
{r.comment && (

{r.comment}

)}
))}
)}
{history && history.viewings.length > 0 && (

{t("movie.yourHistory")}

{history.trend && (
{t("movie.trend", { trend: history.trend })}
)} {history.viewings.map((v) => (

{shortDate(v.watched_at)}

{v.comment && (

{v.comment}

)}
))}
)}
) } 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) => ( ))}
) }