"use client"; import { IconBookmarkFilled, IconCheck, IconCircleCheckFilled, IconDeviceTv, IconLoader, IconMovie, IconPlayerPlayFilled, IconPlus, IconStarFilled, } from "@tabler/icons-react"; import { type MotionStyle, type MotionValue, motion } from "motion/react"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useEffect, useState, useTransition } from "react"; import { toast } from "sonner"; import { useProgress } from "@/components/navigation-progress"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; import { useTiltEffect } from "@/hooks/use-tilt-effect"; import { resolveTitle } from "@/lib/actions/titles"; import { quickAddToWatchlist } from "@/lib/actions/watchlist"; type TitleStatus = "watchlist" | "in_progress" | "completed"; interface TiltStyles { imageStyle: MotionStyle; glareBackground: MotionValue; glareOpacity: MotionValue; } interface CardInnerProps { title: string; type: string; posterPath: string | null; releaseDate?: string | null; voteAverage?: number | null; userStatus?: TitleStatus | null; episodeProgress?: { watched: number; total: number } | null; tiltStyles?: TiltStyles; } export interface TitleCardProps extends CardInnerProps { id?: string; tmdbId: number; } type QuickAddState = "idle" | "loading" | "added"; const statusConfig = { watchlist: { icon: IconBookmarkFilled, label: "On Watchlist", badgeClass: "bg-status-watching/90 text-white", }, in_progress: { icon: IconPlayerPlayFilled, label: "Watching", badgeClass: "bg-status-watching/90 text-white", }, completed: { icon: IconCircleCheckFilled, label: "Completed", badgeClass: "bg-status-completed/90 text-white", }, } as const; function QuickAddButton({ tmdbId, type, userStatus, }: { tmdbId: number; type: "movie" | "tv"; userStatus?: TitleStatus | null; }) { const [state, setState] = useState( userStatus ? "added" : "idle", ); const [addedStatus, setAddedStatus] = useState( userStatus ?? null, ); // Sync local state when prop changes (e.g. after navigation or SWR revalidation) useEffect(() => { if (userStatus) { setState("added"); setAddedStatus(userStatus); } }, [userStatus]); const config = addedStatus ? statusConfig[addedStatus] : null; async function handleClick(e: React.MouseEvent) { e.preventDefault(); e.stopPropagation(); if (state === "loading" || state === "added") return; setState("loading"); try { await quickAddToWatchlist(tmdbId, type); setState("added"); setAddedStatus("watchlist"); } catch { setState("idle"); } } if (state === "added" && config) { const StatusIcon = config.icon; return ( } > {config.label} ); } return ( } > {state === "idle" && } {state === "loading" && } {state === "added" && } {state === "added" ? "Added to Watchlist" : "Add to Watchlist"} ); } function ProgressBar({ watched, total }: { watched: number; total: number }) { const pct = total > 0 ? (watched / total) * 100 : 0; return ( } >
{watched}/{total} episodes ); } function CardInner({ title, type, posterPath, releaseDate, voteAverage, userStatus, episodeProgress, tiltStyles, }: CardInnerProps) { const year = releaseDate?.slice(0, 4); const TypeIcon = type === "movie" ? IconMovie : IconDeviceTv; const ringClass = userStatus ? "ring-primary/25 shadow-sm shadow-primary/5" : "ring-white/[0.06]"; return (
{posterPath ? ( {title} ) : (

{title}

)}
{tiltStyles && ( )}
{userStatus && ( } > {statusConfig[userStatus].label} )}

{title}

{year && {year}} {voteAverage != null && voteAverage > 0 && ( {voteAverage.toFixed(1)} )}
{episodeProgress && episodeProgress.watched > 0 && ( )}
); } export function TitleCard({ id, tmdbId, type, title, posterPath, releaseDate, voteAverage, userStatus, episodeProgress, }: TitleCardProps) { const tilt = useTiltEffect(); const router = useRouter(); const progress = useProgress(); const [isPending, startTransition] = useTransition(); const cardContent = ( ); return (
{id ? ( {cardContent} ) : ( )}
); }