"use client"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import Image from "next/image"; import { SelectedBanner } from "@/app/components/SelectedBanner"; import Link from "next/link"; import useSWR from "swr"; type Banner = { id: string; title: string; imageUrl: string; linkUrl?: string | null }; type SubItem = { id: string; name: string; href: string }; const fetcher = (url: string) => fetch(url).then((r) => r.json()); export function HeroBanner({ subItems, activeSubId, hideSubOnMobile, showPartnerCats }: { subItems?: SubItem[]; activeSubId?: string; hideSubOnMobile?: boolean; showPartnerCats?: boolean }) { const usePartnerCats = ((!Array.isArray(subItems) || subItems.length === 0) && showPartnerCats !== false); // 파트너 카테고리 불러오기 (홈 배너 하단 블랙바에 표시) const { data: catData } = useSWR<{ categories: any[] }>(usePartnerCats ? "/api/partner-categories" : null, fetcher, { revalidateOnFocus: false, dedupingInterval: 5 * 60 * 1000 }); const categories = catData?.categories ?? []; const [selectedCatId, setSelectedCatId] = useState(""); useEffect(() => { if (!usePartnerCats) return; if (!selectedCatId && categories.length > 0) { let id = ""; try { id = (window as any).__partnerCategoryId || localStorage.getItem("selectedPartnerCategoryId") || ""; } catch {} if (!id) id = categories[0].id; setSelectedCatId(id); try { (window as any).__partnerCategoryId = id; localStorage.setItem("selectedPartnerCategoryId", id); window.dispatchEvent(new CustomEvent("partnerCategorySelect", { detail: { id } })); } catch {} } }, [usePartnerCats, categories, selectedCatId]); const onSelectCategory = useCallback((id: string) => { if (!usePartnerCats) return; setSelectedCatId(id); // 전역 이벤트로 선택 전달 try { (window as any).__partnerCategoryId = id; localStorage.setItem("selectedPartnerCategoryId", id); window.dispatchEvent(new CustomEvent("partnerCategorySelect", { detail: { id } })); } catch {} }, [usePartnerCats]); // SWR 캐시 사용: 페이지 전환 시 재요청/깜빡임 최소화 const { data } = useSWR<{ banners: Banner[] }>("/api/banners", fetcher, { revalidateOnFocus: false, dedupingInterval: 10 * 60 * 1000, // 10분 간 동일 요청 합치기 keepPreviousData: true, }); const banners = data?.banners ?? []; const [activeIndex, setActiveIndex] = useState(0); const [isHovered, setIsHovered] = useState(false); const [progress, setProgress] = useState(0); // 0..1 const rotationMs = 5000; const rafIdRef = useRef(null); const startedAtRef = useRef(0); const numSlides = banners.length; const canAutoPlay = numSlides > 1 && !isHovered; const startTicker = useCallback(() => { if (!canAutoPlay) return; startedAtRef.current = performance.now(); const tick = () => { const now = performance.now(); const elapsed = now - startedAtRef.current; const nextProgress = Math.min(1, elapsed / rotationMs); setProgress(nextProgress); if (nextProgress >= 1) { setActiveIndex((i) => (i + 1) % Math.max(1, numSlides)); startedAtRef.current = performance.now(); setProgress(0); } rafIdRef.current = window.requestAnimationFrame(tick); }; rafIdRef.current = window.requestAnimationFrame(tick); }, [canAutoPlay, numSlides, rotationMs]); const stopTicker = useCallback(() => { if (rafIdRef.current) { window.cancelAnimationFrame(rafIdRef.current); rafIdRef.current = null; } }, []); useEffect(() => { stopTicker(); setProgress(0); startTicker(); return () => stopTicker(); }, [startTicker, stopTicker, activeIndex, isHovered, numSlides]); const goTo = useCallback((index: number) => { if (numSlides === 0) return; const next = (index + numSlides) % numSlides; setActiveIndex(next); setProgress(0); startedAtRef.current = performance.now(); }, [numSlides]); const goPrev = useCallback(() => goTo(activeIndex - 1), [activeIndex, goTo]); const goNext = useCallback(() => goTo(activeIndex + 1), [activeIndex, goTo]); const translatePercent = useMemo(() => (numSlides > 0 ? -(activeIndex * 100) : 0), [activeIndex, numSlides]); if (numSlides === 0) { return (
{/* 분리된 하단 블랙 바: 높이 58px, 중앙 서브카테고리 (스켈레톤 상태에서도 동일 레이아웃 유지) */}
{usePartnerCats ? (
{categories.map((c: any) => ( ))}
) : ( Array.isArray(subItems) && subItems.length > 0 && (
{subItems.map((s) => ( {s.name} ))}
) )} {!usePartnerCats && (!Array.isArray(subItems) || subItems.length === 0) && (
암실소문
)}
); } return (
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} aria-roledescription="carousel" >
{banners.map((banner) => (
{banner.linkUrl ? ( {banner.title} ) : ( {banner.title} )} {/* Figma 스타일: 오버레이 제거 */}

{banner.title}

))}
{/* 좌우 내비게이션 버튼 */} {numSlides > 1 && (
)} {/* Pagination - Figma 스타일: 활성은 주황 바, 비활성은 회색 점 (배너 위에 오버랩) */} {numSlides > 1 && (
{banners.map((_, i) => (
)}
{/* 분리된 하단 블랙 바: 높이 58px, 중앙 서브카테고리 */}
{usePartnerCats ? (
{categories.map((c: any) => ( ))}
) : ( Array.isArray(subItems) && subItems.length > 0 && (
{subItems.map((s) => ( {s.name} ))}
) )} {!usePartnerCats && (!Array.isArray(subItems) || subItems.length === 0) && (
암실소문
)}
{/* Progress bar {numSlides > 1 && (
)} */}
); }