Spaces:
Sleeping
Sleeping
| import { useEffect, useState, useRef } from 'react'; | |
| import MovieSection from './MovieSection.jsx'; | |
| import CombinedRegionSection from './CombinedRegionSection.jsx'; | |
| const SECTION_CONFIGS = [ | |
| { type: 'newest', title: 'Phim mới cập nhật', href: '/danh-sach/phim-moi-cap-nhat' }, | |
| { type: 'series', title: 'Phim bộ đặc sắc', href: '/danh-sach/phim-bo' }, | |
| { type: 'single', title: 'Phim Điện Ảnh Mới Coóng', href: '/danh-sach/phim-le' }, | |
| { category: 'phim-chieu-rap', title: 'Mãn Nhãn với Phim Chiếu Rạp', href: '/the-loai/phim-chieu-rap' }, | |
| { type: 'cartoon', title: 'Thế giới hoạt hình', href: '/danh-sach/hoat-hinh' }, | |
| { country: 'trung-quoc', title: 'Phim Trung Quốc mới', href: '/quoc-gia/trung-quoc' }, | |
| { country: 'han-quoc', title: 'Phim Hàn Quốc mới', href: '/quoc-gia/han-quoc' }, | |
| { country: 'au-my', title: 'Phim Âu-Mỹ mới', href: '/quoc-gia/au-my' } | |
| ]; | |
| export default function HomeSectionsLoader() { | |
| const [loadedSections, setLoadedSections] = useState([]); | |
| const [loading, setLoading] = useState(false); | |
| const [currentIndex, setCurrentIndex] = useState(0); | |
| const loaderRef = useRef(null); | |
| const fetchSectionData = async (config) => { | |
| try { | |
| const params = new URLSearchParams(); | |
| if (config.type) params.append('type', config.type); | |
| if (config.category) params.append('category', config.category); | |
| if (config.country) params.append('country', config.country); | |
| const res = await fetch(`/api/movies?${params.toString()}`); | |
| if (!res.ok) throw new Error('Failed to fetch movies'); | |
| const data = await res.json(); | |
| return data.items || []; | |
| } catch (e) { | |
| console.error(`[HomeSectionsLoader] Error loading ${config.title}:`, e); | |
| return []; | |
| } | |
| }; | |
| // Tải danh mục tiếp theo khi cuộn xuống | |
| const loadNextSection = async () => { | |
| if (loading || currentIndex >= SECTION_CONFIGS.length) return; | |
| setLoading(true); | |
| const config = SECTION_CONFIGS[currentIndex]; | |
| const movies = await fetchSectionData(config); | |
| if (movies.length > 0) { | |
| setLoadedSections(prev => { | |
| if (prev.some(s => s.title === config.title)) return prev; | |
| return [...prev, { ...config, movies }]; | |
| }); | |
| } | |
| setCurrentIndex(prev => prev + 1); | |
| setLoading(false); | |
| }; | |
| // Lần đầu tải 2 Section đầu tiên để lấp đầy khung nhìn ngay lập tức | |
| useEffect(() => { | |
| let active = true; | |
| const initLoad = async () => { | |
| setLoading(true); | |
| // Load Section 1 | |
| const config1 = SECTION_CONFIGS[0]; | |
| const movies1 = await fetchSectionData(config1); | |
| if (active && movies1.length > 0) { | |
| setLoadedSections([{ ...config1, movies: movies1 }]); | |
| } | |
| // Load Section 2 | |
| const config2 = SECTION_CONFIGS[1]; | |
| const movies2 = await fetchSectionData(config2); | |
| if (active && movies2.length > 0) { | |
| setLoadedSections(prev => [...prev, { ...config2, movies: movies2 }]); | |
| } | |
| if (active) { | |
| setCurrentIndex(2); | |
| setLoading(false); | |
| } | |
| }; | |
| initLoad(); | |
| return () => { | |
| active = false; | |
| }; | |
| }, []); | |
| // Lắng nghe Intersection Observer để kích hoạt tải Section tiếp theo khi cuộn xuống gần cuối | |
| useEffect(() => { | |
| if (!loaderRef.current || currentIndex >= SECTION_CONFIGS.length || loading) return; | |
| const observer = new IntersectionObserver((entries) => { | |
| const target = entries[0]; | |
| if (target.isIntersecting && !loading) { | |
| loadNextSection(); | |
| } | |
| }, { | |
| rootMargin: '350px', // Bắt đầu tải ngầm trước khi cuộn tới 350px để tạo trải nghiệm bất tận mượt mà | |
| threshold: 0.1 | |
| }); | |
| observer.observe(loaderRef.current); | |
| return () => observer.disconnect(); | |
| }, [currentIndex, loading]); | |
| const normalSections = loadedSections.filter(sec => !['trung-quoc', 'han-quoc', 'au-my'].includes(sec.country)); | |
| const regionSections = loadedSections.filter(sec => ['trung-quoc', 'han-quoc', 'au-my'].includes(sec.country)); | |
| return ( | |
| <div className="space-y-4"> | |
| {normalSections.map((sec, i) => ( | |
| <MovieSection | |
| key={`${sec.type || sec.category || sec.country}-${i}`} | |
| title={sec.title} | |
| movies={sec.movies} | |
| href={sec.href} | |
| /> | |
| ))} | |
| {regionSections.length > 0 && ( | |
| <CombinedRegionSection sections={regionSections} /> | |
| )} | |
| {/* Điểm kích hoạt cuộn tải Section */} | |
| {currentIndex < SECTION_CONFIGS.length && ( | |
| <div ref={loaderRef} className="flex flex-col items-center justify-center py-12"> | |
| {loading && ( | |
| <> | |
| <div className="relative flex h-16 w-16 items-center justify-center"> | |
| {/* Vòng quay hiệu ứng Neon cao cấp */} | |
| <div className="absolute inset-0 animate-spin rounded-full border-4 border-t-primary border-r-accent border-b-transparent border-l-transparent"></div> | |
| <div className="h-6 w-6 animate-pulse rounded-full bg-primary/80 blur-[2px]"></div> | |
| </div> | |
| <span className="mt-4 text-xs font-black uppercase tracking-widest text-gray-500 animate-pulse"> | |
| Đang tải thêm danh mục phim... | |
| </span> | |
| </> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |