From 837408423eff1405f1ceef67dbfb9bc55206cfd8 Mon Sep 17 00:00:00 2001 From: gahusb Date: Sun, 18 Jan 2026 15:34:05 +0900 Subject: [PATCH] =?UTF-8?q?=EC=97=AC=ED=96=89=20=ED=83=AD=20=EA=B5=AC?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80=20=20-=20CORS=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=EB=A1=9C=20Proxy=20=EC=84=9C=EB=B2=84=20=EA=B5=AC?= =?UTF-8?q?=EC=B6=95=ED=95=98=EC=97=AC=20api=20=ED=82=A4=EB=A1=9C=20JSON?= =?UTF-8?q?=20=EB=AA=A9=EB=A1=9D=20=EB=B6=88=EB=9F=AC=EC=98=B4=20=20-=20?= =?UTF-8?q?=EC=97=AC=ED=96=89=20=ED=83=AD=20UI=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/travel/Travel.css | 61 ++++++++++++++++ src/pages/travel/Travel.jsx | 138 ++++++++++++++++++++++++++++++++---- 2 files changed, 184 insertions(+), 15 deletions(-) diff --git a/src/pages/travel/Travel.css b/src/pages/travel/Travel.css index edd8ec6..8e350ee 100644 --- a/src/pages/travel/Travel.css +++ b/src/pages/travel/Travel.css @@ -52,6 +52,62 @@ gap: 18px; } +.travel-albums { + display: grid; + gap: 24px; +} + +.travel-album { + border: 1px solid var(--line); + border-radius: 24px; + padding: 20px; + background: rgba(9, 10, 16, 0.5); + display: grid; + gap: 18px; +} + +.travel-album__head { + display: flex; + justify-content: space-between; + align-items: center; + gap: 16px; + flex-wrap: wrap; +} + +.travel-album__eyebrow { + margin: 0 0 6px; + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.22em; + color: var(--accent); +} + +.travel-album__meta { + margin: 6px 0 0; + color: var(--muted); + font-size: 13px; +} + +.travel-album__cover { + width: 120px; + height: 120px; + border-radius: 16px; + object-fit: cover; + border: 1px solid rgba(255, 255, 255, 0.12); +} + +.travel-state { + color: var(--muted); +} + +.travel-error { + color: #f9b6b1; + border: 1px solid rgba(249, 182, 177, 0.4); + border-radius: 14px; + padding: 12px; + background: rgba(249, 182, 177, 0.1); +} + .travel-card { position: relative; border-radius: 20px; @@ -106,4 +162,9 @@ .travel-card.is-wide { grid-column: span 1; } + + .travel-album__cover { + width: 100%; + height: 160px; + } } diff --git a/src/pages/travel/Travel.jsx b/src/pages/travel/Travel.jsx index 4cd950e..ca30d05 100644 --- a/src/pages/travel/Travel.jsx +++ b/src/pages/travel/Travel.jsx @@ -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 (
@@ -21,20 +98,51 @@ const Travel = () => {
-
- {travelGallery.map((photo, index) => ( -
- {photo.title} -
-

{photo.title}

-

- {photo.location} · {photo.month} -

+
+ {loading ?

앨범을 불러오는 중...

: null} + {error ?

{error}

: null} + {!loading && !error && albums.length === 0 ? ( +

표시할 앨범이 없습니다.

+ ) : null} + + {albums.map((album) => ( +
+
+
+

Album

+

{album.name}

+

+ {album.count} photos +

+
+ {album.cover ? ( + {`${album.name} + ) : null}
-
+
+ {album.photos.map((photo, index) => { + const label = photo.title || getPhotoLabel(photo.src); + return ( +
+ {label} +
+

{label}

+
+
+ ); + })} +
+ ))}