'use client' import { useEffect, useRef, useState } from 'react' import type { Movie } from '@/types' import TagSelector from '@/components/tags/TagSelector' import VideoPlayerModal from '@/components/mixed/VideoPlayerModal' interface Props { movie: Movie libraryId: string onClose: () => void onPrev?: () => void onNext?: () => void onTagsChanged?: () => void onDeleted: (movieId: string) => void onMetadataRefreshed?: () => void } export default function MovieDetailModal({ movie, libraryId, onClose, onPrev, onNext, onTagsChanged, onDeleted, onMetadataRefreshed }: Props) { const overlayRef = useRef(null) const menuRef = useRef(null) const [playing, setPlaying] = useState(false) const [menuOpen, setMenuOpen] = useState(false) const [confirming, setConfirming] = useState(false) const [deleting, setDeleting] = useState(false) const [refreshing, setRefreshing] = useState(false) useEffect(() => { const handleKey = (e: KeyboardEvent) => { if (e.key === 'Escape') { if (menuOpen) { setMenuOpen(false); return } if (confirming) { setConfirming(false); return } onClose() } } document.addEventListener('keydown', handleKey) document.body.style.overflow = 'hidden' return () => { document.removeEventListener('keydown', handleKey) document.body.style.overflow = '' } }, [onClose, menuOpen, confirming]) // Close menu on outside click useEffect(() => { if (!menuOpen) return const handler = (e: MouseEvent) => { if (menuRef.current && !menuRef.current.contains(e.target as Node)) { setMenuOpen(false) } } document.addEventListener('mousedown', handler) return () => document.removeEventListener('mousedown', handler) }, [menuOpen]) const handleOverlayClick = (e: React.MouseEvent) => { if (e.target === overlayRef.current) onClose() } const videoUrl = `/api/file?libraryId=${encodeURIComponent(libraryId)}&path=${encodeURIComponent(movie.videoPath)}` const handleConfirmDelete = () => { setDeleting(true) fetch(`/api/movies?libraryId=${encodeURIComponent(libraryId)}&movieId=${encodeURIComponent(movie.id)}`, { method: 'DELETE', }) .then(() => onDeleted(movie.id)) .catch(() => setDeleting(false)) } const handleRefreshMetadata = () => { setRefreshing(true) setMenuOpen(false) const itemKey = `${libraryId}:movie:${movie.id}` fetch( `/api/nfo-refresh?libraryId=${encodeURIComponent(libraryId)}&itemType=movie&itemKey=${encodeURIComponent(itemKey)}`, { method: 'POST' } ) .then(() => onMetadataRefreshed?.()) .finally(() => setRefreshing(false)) } if (playing) { return ( setPlaying(false)} onPrev={onPrev} onNext={onNext} context="movies" /> ) } const heroUrl = movie.backdropUrl ?? movie.posterUrl return (
{/* Close button */} {/* Prev / Next buttons on the detail card */} {onPrev && ( )} {onNext && ( )} {/* Hero image */}
{heroUrl ? ( // eslint-disable-next-line @next/next/no-img-element {movie.title} ) : (
🎬
)}
{/* Info */}
{/* Title row with kebab menu */}

{movie.title}

{movie.year && ( {movie.year} )} {/* Kebab menu */}
{menuOpen && (
)}
{/* Meta row */} {(movie.rating !== null || movie.runtime !== null || movie.genres.length > 0) && (
{movie.rating !== null && ( ★ {movie.rating.toFixed(1)} )} {movie.runtime !== null && ( {movie.runtime} min )} {movie.genres.map((g) => ( {g} ))}
)} {movie.plot && (

{movie.plot}

)} {/* Confirmation banner */} {confirming && (

Permanently delete this movie and all its files?

)} {/* Play button */} {/* Tags */}

Tags

) }