dptxa-proxy / src /components /HeroBanner.jsx
TXAVLOG
Deploy DPTXA to Hugging Face Spaces
4bea261
Raw
History Blame Contribute Delete
9.52 kB
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>
);
}