From d98e9287d36832fe95628546c881affa214068c6 Mon Sep 17 00:00:00 2001 From: AutonetSellCar Deploy Date: Wed, 18 Feb 2026 08:58:57 +0900 Subject: [PATCH] fix: Show skeleton instead of sample images during banner loading Sample car images (Tesla, Sportage etc.) now only appear as fallback when the API fails to respond within 3 seconds. During normal loading, a pulse-animated skeleton is shown instead. Co-Authored-By: Claude Opus 4.6 --- frontend/src/app/page.tsx | 23 +++++++++++-- frontend/src/components/FilmStripSlider.tsx | 38 +++++++++++++++++++-- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index d5693b8..6d54c6a 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -12,8 +12,18 @@ export default function Home() { const { t, language } = useTranslation(); const [banners, setBanners] = useState([]); const [bannerSettings, setBannerSettings] = useState(); + const [bannerLoaded, setBannerLoaded] = useState(false); + const [showFallback, setShowFallback] = useState(false); useEffect(() => { + setBannerLoaded(false); + setShowFallback(false); + + // 3초 후에도 로딩 안 되면 샘플 배너 표시 + const fallbackTimer = setTimeout(() => { + setShowFallback(true); + }, 3000); + const loadBanners = async () => { try { const [bannersData, settingsData] = await Promise.all([ @@ -24,10 +34,15 @@ export default function Home() { setBannerSettings(settingsData); } catch (error) { console.error('Failed to load banners:', error); - // 에러 시 샘플 배너 사용 (FilmStripSlider 내부에서 처리) + setShowFallback(true); + } finally { + setBannerLoaded(true); + clearTimeout(fallbackTimer); } }; loadBanners(); + + return () => clearTimeout(fallbackTimer); }, [language]); return ( @@ -44,7 +59,11 @@ export default function Home() { {/* Film Strip Slider */} - +
diff --git a/frontend/src/components/FilmStripSlider.tsx b/frontend/src/components/FilmStripSlider.tsx index 1d6632f..32d332c 100644 --- a/frontend/src/components/FilmStripSlider.tsx +++ b/frontend/src/components/FilmStripSlider.tsx @@ -10,6 +10,7 @@ import { useLanguageStore, Language, translateCarName } from '@/lib/i18n'; interface FilmStripSliderProps { banners: HeroBanner[]; settings?: HeroBannerSettings; + loading?: boolean; } // 기본 설정 @@ -124,9 +125,11 @@ const sampleBanners: HeroBanner[] = [ }, ]; -export default function FilmStripSlider({ banners, settings }: FilmStripSliderProps) { +export default function FilmStripSlider({ banners, settings, loading }: FilmStripSliderProps) { const effectiveSettings = settings || defaultSettings; - const effectiveBanners = banners.length > 0 ? banners : sampleBanners; + // 로딩 중이면 빈 배열 사용 → 스켈레톤 표시 + // 로딩 완료 후 데이터 없으면 sampleBanners (3초 이상 미응답 시에만 도달) + const effectiveBanners = loading ? [] : (banners.length > 0 ? banners : sampleBanners); // 무한 루프를 위해 배너를 3배로 복제 const duplicatedBanners = [...effectiveBanners, ...effectiveBanners, ...effectiveBanners]; @@ -285,6 +288,37 @@ export default function FilmStripSlider({ banners, settings }: FilmStripSliderPr } }; + // 로딩 중이면 스켈레톤 표시 + if (loading || effectiveBanners.length === 0) { + return ( +
+
+ {Array.from({ length: 30 }).map((_, i) => ( +
+ ))} +
+
+
+ {Array.from({ length: 5 }).map((_, i) => ( +
+ ))} +
+
+
+ {Array.from({ length: 30 }).map((_, i) => ( +
+ ))} +
+
+
+
+ ); + } + return (
{/* Film strip effect - top perforation (hidden on very small screens) */}