여행 탭 구성 추가
- CORS 문제로 Proxy 서버 구축하여 api 키로 JSON 목록 불러옴 - 여행 탭 UI 수정
This commit is contained in:
@@ -1,8 +1,85 @@
|
||||
import React from 'react';
|
||||
import { travelGallery } from '../../data/travel';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import './Travel.css';
|
||||
|
||||
const normalizePhotos = (items = []) =>
|
||||
items
|
||||
.map((item) => {
|
||||
if (typeof item === 'string') return { src: item, title: '' };
|
||||
if (!item) return null;
|
||||
return {
|
||||
src: item.url || item.path || item.src || '',
|
||||
title: item.title || item.name || '',
|
||||
};
|
||||
})
|
||||
.filter((item) => item && item.src);
|
||||
|
||||
const getPhotoLabel = (src) => {
|
||||
if (!src) return '';
|
||||
const parts = src.split('/');
|
||||
return parts[parts.length - 1];
|
||||
};
|
||||
|
||||
const Travel = () => {
|
||||
const [albums, setAlbums] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
const loadAlbums = async () => {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
|
||||
try {
|
||||
const albumRes = await fetch('/api/travel/albums');
|
||||
if (!albumRes.ok) {
|
||||
throw new Error(`앨범 목록 로딩 실패 (${albumRes.status})`);
|
||||
}
|
||||
const albumJson = await albumRes.json();
|
||||
const items = albumJson.items ?? [];
|
||||
|
||||
const hydrated = await Promise.all(
|
||||
items.map(async (item) => {
|
||||
const name = item.album || item.name || '';
|
||||
if (!name) return null;
|
||||
const photoRes = await fetch(
|
||||
`/api/travel/albums/${encodeURIComponent(name)}`
|
||||
);
|
||||
if (!photoRes.ok) {
|
||||
throw new Error(`앨범 로딩 실패: ${name}`);
|
||||
}
|
||||
const photoJson = await photoRes.json();
|
||||
const photos = normalizePhotos(photoJson.items ?? []);
|
||||
return {
|
||||
name,
|
||||
count: item.count ?? photos.length,
|
||||
cover: item.cover || photos[0]?.src || '',
|
||||
photos,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
if (!cancelled) {
|
||||
setAlbums(hydrated.filter(Boolean));
|
||||
}
|
||||
} catch (err) {
|
||||
if (!cancelled) {
|
||||
setError(err?.message ?? String(err));
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
loadAlbums();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="travel">
|
||||
<header className="travel-header">
|
||||
@@ -21,20 +98,51 @@ const Travel = () => {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section className="travel-grid">
|
||||
{travelGallery.map((photo, index) => (
|
||||
<article
|
||||
key={photo.id}
|
||||
className={`travel-card ${index % 3 === 0 ? 'is-wide' : ''}`}
|
||||
>
|
||||
<img src={photo.image} alt={photo.title} loading="lazy" />
|
||||
<div className="travel-card__overlay">
|
||||
<p className="travel-card__title">{photo.title}</p>
|
||||
<p className="travel-card__meta">
|
||||
{photo.location} · {photo.month}
|
||||
</p>
|
||||
<section className="travel-albums">
|
||||
{loading ? <p className="travel-state">앨범을 불러오는 중...</p> : null}
|
||||
{error ? <p className="travel-error">{error}</p> : null}
|
||||
{!loading && !error && albums.length === 0 ? (
|
||||
<p className="travel-state">표시할 앨범이 없습니다.</p>
|
||||
) : null}
|
||||
|
||||
{albums.map((album) => (
|
||||
<div key={album.name} className="travel-album">
|
||||
<div className="travel-album__head">
|
||||
<div>
|
||||
<p className="travel-album__eyebrow">Album</p>
|
||||
<h2>{album.name}</h2>
|
||||
<p className="travel-album__meta">
|
||||
{album.count} photos
|
||||
</p>
|
||||
</div>
|
||||
{album.cover ? (
|
||||
<img
|
||||
className="travel-album__cover"
|
||||
src={album.cover}
|
||||
alt={`${album.name} cover`}
|
||||
loading="lazy"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</article>
|
||||
<div className="travel-grid">
|
||||
{album.photos.map((photo, index) => {
|
||||
const label = photo.title || getPhotoLabel(photo.src);
|
||||
return (
|
||||
<article
|
||||
key={`${album.name}-${photo.src}`}
|
||||
className={`travel-card ${
|
||||
index % 6 === 0 ? 'is-wide' : ''
|
||||
}`}
|
||||
>
|
||||
<img src={photo.src} alt={label} loading="lazy" />
|
||||
<div className="travel-card__overlay">
|
||||
<p className="travel-card__title">{label}</p>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user