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 { t, language } = useTranslation();
|
||||||
const [banners, setBanners] = useState<HeroBanner[]>([]);
|
const [banners, setBanners] = useState<HeroBanner[]>([]);
|
||||||
const [bannerSettings, setBannerSettings] = useState<HeroBannerSettings | undefined>();
|
const [bannerSettings, setBannerSettings] = useState<HeroBannerSettings | undefined>();
|
||||||
|
const [bannerLoaded, setBannerLoaded] = useState(false);
|
||||||
|
const [showFallback, setShowFallback] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setBannerLoaded(false);
|
||||||
|
setShowFallback(false);
|
||||||
|
|
||||||
|
// 3초 후에도 로딩 안 되면 샘플 배너 표시
|
||||||
|
const fallbackTimer = setTimeout(() => {
|
||||||
|
setShowFallback(true);
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
const loadBanners = async () => {
|
const loadBanners = async () => {
|
||||||
try {
|
try {
|
||||||
const [bannersData, settingsData] = await Promise.all([
|
const [bannersData, settingsData] = await Promise.all([
|
||||||
@@ -24,10 +34,15 @@ export default function Home() {
|
|||||||
setBannerSettings(settingsData);
|
setBannerSettings(settingsData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load banners:', error);
|
console.error('Failed to load banners:', error);
|
||||||
// 에러 시 샘플 배너 사용 (FilmStripSlider 내부에서 처리)
|
setShowFallback(true);
|
||||||
|
} finally {
|
||||||
|
setBannerLoaded(true);
|
||||||
|
clearTimeout(fallbackTimer);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
loadBanners();
|
loadBanners();
|
||||||
|
|
||||||
|
return () => clearTimeout(fallbackTimer);
|
||||||
}, [language]);
|
}, [language]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -44,7 +59,11 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Film Strip Slider */}
|
{/* 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="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">
|
<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 {
|
interface FilmStripSliderProps {
|
||||||
banners: HeroBanner[];
|
banners: HeroBanner[];
|
||||||
settings?: HeroBannerSettings;
|
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 effectiveSettings = settings || defaultSettings;
|
||||||
const effectiveBanners = banners.length > 0 ? banners : sampleBanners;
|
// 로딩 중이면 빈 배열 사용 → 스켈레톤 표시
|
||||||
|
// 로딩 완료 후 데이터 없으면 sampleBanners (3초 이상 미응답 시에만 도달)
|
||||||
|
const effectiveBanners = loading ? [] : (banners.length > 0 ? banners : sampleBanners);
|
||||||
|
|
||||||
// 무한 루프를 위해 배너를 3배로 복제
|
// 무한 루프를 위해 배너를 3배로 복제
|
||||||
const duplicatedBanners = [...effectiveBanners, ...effectiveBanners, ...effectiveBanners];
|
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 (
|
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="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) */}
|
{/* Film strip effect - top perforation (hidden on very small screens) */}
|
||||||
|
|||||||
Reference in New Issue
Block a user