Spaces:
Sleeping
Sleeping
| import { useState, useEffect, useCallback } from 'react'; | |
| import { Play, Info, ChevronLeft, ChevronRight } from 'lucide-react'; | |
| import { formatImageUrl } from '../lib/image'; | |
| export default function HeroBanner({ movies = [] }) { | |
| const [active, setActive] = useState(0); | |
| const [progress, setProgress] = useState(0); | |
| const [favorites, setFavorites] = useState([]); | |
| useEffect(() => { | |
| fetch('/api/user/favorite') | |
| .then(res => res.json()) | |
| .then(data => { | |
| if (data && data.favorites) { | |
| setFavorites(data.favorites.map(f => f.slug)); | |
| } | |
| }) | |
| .catch(() => {}); | |
| }, []); | |
| const handleFavoriteClick = async (m) => { | |
| try { | |
| const res = await fetch('/api/user/favorite', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| movieSlug: m.slug, | |
| movieName: m.name, | |
| movieThumb: m.thumb_url || m.poster_url | |
| }) | |
| }); | |
| const data = await res.json(); | |
| if (res.ok) { | |
| if (window.txatoast) { | |
| window.txatoast.success(data.message); | |
| } | |
| if (data.action === 'added') { | |
| setFavorites(prev => [...prev, m.slug]); | |
| } else { | |
| setFavorites(prev => prev.filter(slug => slug !== m.slug)); | |
| } | |
| } else { | |
| if (window.txatoast) { | |
| window.txatoast.error(data.error || 'Vui lòng đăng nhập!'); | |
| } | |
| } | |
| } catch (e) { | |
| if (window.txatoast) { | |
| window.txatoast.error('Không thể thực hiện lúc này!'); | |
| } | |
| } | |
| }; | |
| const heroMovies = movies.slice(0, 5); | |
| const next = useCallback(() => { | |
| setActive((prev) => (prev + 1) % heroMovies.length); | |
| setProgress(0); | |
| }, [heroMovies.length]); | |
| const prev = useCallback(() => { | |
| setActive((prev) => (prev - 1 + heroMovies.length) % heroMovies.length); | |
| setProgress(0); | |
| }, [heroMovies.length]); | |
| useEffect(() => { | |
| if (heroMovies.length <= 1) return; | |
| const interval = 100; // 0.1s | |
| const step = 100 / (6000 / interval); // 6s per slide | |
| const timer = setInterval(() => { | |
| setProgress((p) => { | |
| if (p >= 100) { | |
| next(); | |
| return 0; | |
| } | |
| return p + step; | |
| }); | |
| }, interval); | |
| return () => clearInterval(timer); | |
| }, [heroMovies.length, next]); | |
| if (!heroMovies.length) return null; | |
| const movie = heroMovies[active]; | |
| const bg = formatImageUrl(movie.poster_url || movie.thumb_url); | |
| const year = movie.year || (movie.modified?.time ? new Date(movie.modified.time).getFullYear() : ''); | |
| return ( | |
| <div className="relative h-[600px] w-full overflow-hidden md:h-[750px] lg:h-[850px]"> | |
| {/* Background Slides with Stylized Text */} | |
| {heroMovies.map((m, i) => ( | |
| <div | |
| key={m._id} | |
| className={`absolute inset-0 transition-all duration-[2000ms] ease-out ${ | |
| i === active ? 'opacity-100 scale-100' : 'opacity-0 scale-110' | |
| }`} | |
| > | |
| {/* Main Background Image */} | |
| <div | |
| className="absolute inset-0 bg-cover bg-center md:bg-[center_right_20%]" | |
| style={{ backgroundImage: `url(${formatImageUrl(m.poster_url || m.thumb_url)})` }} | |
| /> | |
| {/* Stylized Background Text Layer (The "Thu Hút Mãnh Liệt" style in bg) */} | |
| <div className="absolute inset-0 flex items-center justify-center opacity-10 select-none pointer-events-none overflow-hidden"> | |
| <h1 className="text-[20vw] font-black uppercase whitespace-nowrap rotate-[-10deg] text-white"> | |
| {m.name} | |
| </h1> | |
| </div> | |
| </div> | |
| ))} | |
| {/* Gradients for cinematic feel */} | |
| <div className="absolute inset-0 bg-gradient-to-r from-[#0f0f0f] via-[#0f0f0f]/80 to-transparent z-10" /> | |
| <div className="absolute inset-0 bg-gradient-to-t from-[#0f0f0f] via-transparent to-transparent z-10" /> | |
| <div className="relative mx-auto flex h-full max-w-7xl flex-col justify-center px-6 z-20"> | |
| <div className="max-w-2xl transition-all duration-700 transform opacity-100 translate-y-0"> | |
| <h1 className="text-4xl font-black text-white md:text-6xl lg:text-7xl drop-shadow-[0_0_30px_rgba(255,255,255,0.2)]"> | |
| <a href={`/phim/${movie.slug}`} className="hover:text-primary transition-colors cursor-pointer"> | |
| {movie.name} | |
| </a> | |
| </h1> | |
| {movie.origin_name && ( | |
| <p className="mt-2 text-sm font-bold text-gray-400 md:text-base uppercase tracking-wider opacity-60 italic">{movie.origin_name}</p> | |
| )} | |
| {/* Badges Row */} | |
| <div className="mt-6 flex flex-wrap items-center gap-3"> | |
| <div className="flex items-center gap-1.5 rounded-lg bg-yellow-500/20 px-2.5 py-1 text-[10px] font-black text-yellow-500 ring-1 ring-yellow-500/30"> | |
| <i className="fas fa-star"></i> {movie.tmdb?.vote_average || (8.5 + (movie.slug.length % 15) / 10).toFixed(1)} | |
| </div> | |
| <span className="glass rounded-lg px-2.5 py-1 text-[10px] font-black text-gray-300 ring-1 ring-white/10 uppercase"> | |
| {movie.quality || 'Full HD'} | |
| </span> | |
| <span className="glass rounded-lg px-2.5 py-1 text-[10px] font-black text-gray-300 ring-1 ring-white/10"> | |
| {year} | |
| </span> | |
| {movie.tmdb?.season && ( | |
| <span className="glass rounded-lg px-2.5 py-1 text-[10px] font-black text-gray-300 ring-1 ring-white/10"> | |
| Phần {movie.tmdb.season} | |
| </span> | |
| )} | |
| <span className="glass rounded-lg px-2.5 py-1 text-[10px] font-black text-gray-300 ring-1 ring-white/10"> | |
| {movie.episode_current?.toLowerCase().includes('hoàn tất') ? 'Hoàn tất' : `Tập ${movie.episode_current?.match(/\d+/)?.[0] || '??'}`} | |
| {movie.episode_total && movie.episode_total !== '1' && ` / ${movie.episode_total}`} | |
| </span> | |
| </div> | |
| {/* Categories Row */} | |
| <div className="mt-4 flex flex-wrap gap-2"> | |
| {movie.category?.slice(0, 4).map(cat => ( | |
| <span key={cat.id} className="text-[11px] font-bold text-gray-500 hover:text-white transition-colors cursor-pointer"> | |
| {cat.name} | |
| </span> | |
| ))} | |
| </div> | |
| <p className="mt-8 line-clamp-3 max-w-xl text-sm leading-relaxed text-gray-400 md:text-base drop-shadow-md"> | |
| {movie.content?.replace(/<[^>]*>/g, '') || movie.description || ''} | |
| </p> | |
| <div className="mt-10 flex items-center gap-4"> | |
| <a | |
| href={`/xem/${movie.slug}`} | |
| className="group flex h-14 w-14 items-center justify-center rounded-full bg-yellow-500 text-black shadow-[0_0_30px_rgba(234,179,8,0.4)] transition-all hover:scale-110 active:scale-95" | |
| data-txatooltip="Xem phim ngay" | |
| > | |
| <i className="fas fa-play text-xl ml-1"></i> | |
| </a> | |
| <button | |
| className={`glass flex h-14 w-14 items-center justify-center rounded-full transition-all hover:bg-white/10 hover:ring-2 hover:ring-white/20 ${favorites.includes(movie.slug) ? 'text-primary' : 'text-white'}`} | |
| onClick={() => handleFavoriteClick(movie)} | |
| data-txatooltip={favorites.includes(movie.slug) ? "Bỏ yêu thích" : "Thêm vào yêu thích"} | |
| > | |
| <i className={`${favorites.includes(movie.slug) ? 'fas' : 'far'} fa-heart text-xl`}></i> | |
| </button> | |
| <a | |
| href={`/phim/${movie.slug}`} | |
| className="glass flex h-14 w-14 items-center justify-center rounded-full text-white transition-all hover:bg-white/10 hover:ring-2 hover:ring-white/20" | |
| data-txatooltip="Thông tin chi tiết" | |
| > | |
| <i className="fas fa-info text-xl"></i> | |
| </a> | |
| </div> | |
| </div> | |
| {/* Thumbnails Navigation (Right side row) */} | |
| <div className="absolute bottom-16 right-6 hidden lg:flex items-center gap-3"> | |
| {heroMovies.map((m, i) => ( | |
| <div | |
| key={m._id} | |
| onClick={() => { setActive(i); setProgress(0); }} | |
| className={`group relative h-16 w-24 cursor-pointer overflow-hidden rounded-xl border-2 transition-all duration-500 ${ | |
| i === active ? 'border-primary scale-110 shadow-[0_0_20px_rgba(229,9,20,0.5)]' : 'border-white/10 opacity-50 hover:opacity-100' | |
| }`} | |
| > | |
| <img src={formatImageUrl(m.thumb_url || m.poster_url)} className="h-full w-full object-cover" alt="nav" /> | |
| {i === active && ( | |
| <div className="absolute bottom-0 left-0 h-1 bg-primary transition-all duration-100" style={{ width: `${progress}%` }} /> | |
| )} | |
| </div> | |
| ))} | |
| </div> | |
| {/* Mobile Navigation */} | |
| <div className="absolute bottom-8 left-6 flex lg:hidden gap-2"> | |
| {heroMovies.map((_, i) => ( | |
| <div | |
| key={i} | |
| onClick={() => { setActive(i); setProgress(0); }} | |
| className={`h-1.5 rounded-full transition-all duration-500 ${i === active ? 'w-8 bg-primary' : 'w-2 bg-white/20'}`} | |
| /> | |
| ))} | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |