Files
Cauê Faleiros 5b86d6ef0d
All checks were successful
CI / Validate frontend and API (push) Successful in 35s
CI / Build and publish Docker images (push) Successful in 20s
feat: manage homepage banner carousel
2026-09-04 16:44:34 -03:00

25 lines
3.9 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { ChevronLeft, ChevronRight, Play, Sparkles } from 'lucide-react';
import { Course } from '../types';
import { getCourses, getHomeBanners, PublicHomeBanner } from '../services/db';
import { CourseCoverImage } from './CourseCoverImage';
interface HeroProps { onPlay?: (course: Course) => void; }
export const Hero: React.FC<HeroProps> = ({ onPlay }) => {
const [banners, setBanners] = useState<PublicHomeBanner[]>([]);
const [courses, setCourses] = useState<Course[]>([]);
const [index, setIndex] = useState(0);
useEffect(() => { Promise.all([getHomeBanners(), getCourses()]).then(([loadedBanners, loadedCourses]) => { setBanners(loadedBanners); setCourses(loadedCourses); }).catch(() => setBanners([])); }, []);
useEffect(() => { if (banners.length < 2) return; const timer = window.setInterval(() => setIndex((current) => (current + 1) % banners.length), 7000); return () => window.clearInterval(timer); }, [banners.length]);
const fallback = courses[0];
const banner = banners[index];
const course = banner?.courseId ? courses.find((item) => item.id === banner.courseId) : fallback;
if (!banner && !fallback) return null;
const title = banner ? (banner.kind === 'course' ? banner.courseTitle || banner.title : banner.title) : fallback.title;
const description = banner ? (banner.kind === 'course' ? banner.courseDescription || banner.description : banner.description) : fallback.description;
const image = banner ? (banner.kind === 'course' ? banner.courseImageUrl : banner.imageUrl) : fallback.thumbnail;
const canPlay = Boolean(course && (banner?.kind !== 'custom' || banner.courseId));
return <div className="relative flex h-[75vh] max-h-[800px] min-h-[560px] w-full flex-col justify-end overflow-hidden px-6 pb-12 pt-28 md:px-12"><div className="absolute inset-0">{image && <CourseCoverImage source={image} alt={title} className="h-full w-full object-cover" style={{ transform: `scale(${banner?.coverImageZoom || course?.coverImageZoom || 1})`, objectPosition: `${banner?.coverImagePositionX ?? course?.coverImagePositionX ?? 50}% ${banner?.coverImagePositionY ?? course?.coverImagePositionY ?? 50}%` }} />}<div className="absolute inset-0 bg-gradient-to-t from-black via-black/60 to-black/75" /><div className="absolute inset-0 bg-gradient-to-r from-black/95 via-black/60 to-transparent" /></div><div className="relative z-10 mx-auto w-full max-w-[1440px]"><div className="max-w-3xl"><div className="mb-3 flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-white/90"><span className="flex items-center gap-1 rounded-full bg-orange-500 px-3 py-1"><Sparkles className="h-3 w-3" /> Destaque</span>{banner?.kind === 'course' && <span>{course?.category}</span>}</div><h1 className="font-display text-4xl font-extrabold leading-tight tracking-tight text-white md:text-6xl">{title}</h1>{description && <p className="mt-4 max-w-2xl text-base leading-relaxed text-gray-200 md:text-lg">{description}</p>}{canPlay && <button onClick={() => onPlay?.(course!)} className="mt-7 inline-flex items-center gap-3 rounded-full bg-white px-8 py-3.5 text-sm font-bold text-black"><Play className="h-5 w-5 fill-current" /> Assistir curso</button>}</div></div>{banners.length > 1 && <div className="absolute bottom-7 right-6 z-20 flex items-center gap-3 md:right-12"><button onClick={() => setIndex((index - 1 + banners.length) % banners.length)} className="rounded-full bg-black/50 p-2 text-white"><ChevronLeft className="h-5 w-5" /></button><div className="flex gap-1.5">{banners.map((item, itemIndex) => <button key={item.id} onClick={() => setIndex(itemIndex)} className={`h-2 rounded-full transition-all ${itemIndex === index ? 'w-6 bg-orange-500' : 'w-2 bg-white/50'}`} aria-label={`Banner ${itemIndex + 1}`} />)}</div><button onClick={() => setIndex((index + 1) % banners.length)} className="rounded-full bg-black/50 p-2 text-white"><ChevronRight className="h-5 w-5" /></button></div>}</div>;
};