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 <noreply@anthropic.com>
This commit is contained in:
@@ -12,8 +12,18 @@ export default function Home() {
|
||||
const { t, language } = useTranslation();
|
||||
const [banners, setBanners] = useState<HeroBanner[]>([]);
|
||||
const [bannerSettings, setBannerSettings] = useState<HeroBannerSettings | undefined>();
|
||||
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() {
|
||||
</div>
|
||||
|
||||
{/* Film Strip Slider */}
|
||||
<FilmStripSlider banners={banners} settings={bannerSettings} />
|
||||
<FilmStripSlider
|
||||
banners={banners}
|
||||
settings={bannerSettings}
|
||||
loading={!bannerLoaded && !showFallback}
|
||||
/>
|
||||
|
||||
<div className="container mx-auto px-4 py-4 sm:py-8">
|
||||
<div className="flex flex-col lg:flex-row items-center gap-4 lg:gap-6">
|
||||
|
||||
@@ -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 (
|
||||
<div className="relative w-full overflow-hidden bg-gradient-to-b from-gray-900 to-gray-800 py-4 sm:py-8">
|
||||
<div className="absolute top-0 left-0 right-0 h-3 sm:h-4 bg-gray-900 hidden sm:flex items-center justify-around">
|
||||
{Array.from({ length: 30 }).map((_, i) => (
|
||||
<div key={i} className="w-2 sm:w-3 h-1.5 sm:h-2 bg-gray-700 rounded-sm" />
|
||||
))}
|
||||
</div>
|
||||
<div className="relative overflow-hidden mx-auto" style={{ height: imageHeight + 20 }}>
|
||||
<div className="flex absolute" style={{ paddingLeft: gap, gap: gap }}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex-shrink-0 rounded-lg overflow-hidden bg-gray-700 animate-pulse"
|
||||
style={{ width: imageWidth, height: imageHeight }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute bottom-0 left-0 right-0 h-3 sm:h-4 bg-gray-900 hidden sm:flex items-center justify-around">
|
||||
{Array.from({ length: 30 }).map((_, i) => (
|
||||
<div key={i} className="w-2 sm:w-3 h-1.5 sm:h-2 bg-gray-700 rounded-sm" />
|
||||
))}
|
||||
</div>
|
||||
<div className="absolute top-0 sm:top-4 bottom-0 sm:bottom-4 left-0 w-8 sm:w-24 bg-gradient-to-r from-gray-900 to-transparent pointer-events-none z-10" />
|
||||
<div className="absolute top-0 sm:top-4 bottom-0 sm:bottom-4 right-0 w-8 sm:w-24 bg-gradient-to-l from-gray-900 to-transparent pointer-events-none z-10" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative w-full overflow-hidden bg-gradient-to-b from-gray-900 to-gray-800 py-4 sm:py-8">
|
||||
{/* Film strip effect - top perforation (hidden on very small screens) */}
|
||||
|
||||
Reference in New Issue
Block a user