From 093ca6635af506846fe43b8880c1a96a3225b542 Mon Sep 17 00:00:00 2001 From: gahusb Date: Sat, 25 Apr 2026 12:13:14 +0900 Subject: [PATCH] =?UTF-8?q?feat(travel):=20=EC=82=AC=EC=A7=84=20=EA=B7=B8?= =?UTF-8?q?=EB=A6=AC=EB=93=9C=20=EC=95=88=EC=A0=95=ED=99=94=20+=20?= =?UTF-8?q?=EC=95=A8=EB=B2=94=20=EC=BB=A4=EB=B2=84=20=EC=A7=80=EC=A0=95=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20+=20=EB=8F=99=EA=B8=B0=ED=99=94=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MasonryGrid: CSS columns → CSS Grid로 전환 (스크롤 시 정렬 위치 변동 방지) - HeroLightbox: "커버로 지정" 버튼 추가 (PUT /api/travel/albums/{album}/cover 호출) - Travel: 동기화 토스트에 신규 폴더 발견 수 표시 Co-Authored-By: Claude Opus 4.6 --- src/pages/travel/HeroLightbox.css | 35 +++++++++++++++++++++++++++++++ src/pages/travel/HeroLightbox.jsx | 35 +++++++++++++++++++++++++++++++ src/pages/travel/MasonryGrid.css | 23 ++++++++++---------- src/pages/travel/Travel.jsx | 2 +- 4 files changed, 83 insertions(+), 12 deletions(-) diff --git a/src/pages/travel/HeroLightbox.css b/src/pages/travel/HeroLightbox.css index 5e23e6e..245391f 100644 --- a/src/pages/travel/HeroLightbox.css +++ b/src/pages/travel/HeroLightbox.css @@ -59,6 +59,41 @@ font-weight: 600; } +/* ── Cover button ── */ +.hero-lb__cover-btn { + padding: 6px 14px; + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.15); + background: rgba(255, 255, 255, 0.08); + color: var(--tv-muted, rgba(232, 221, 208, 0.45)); + font-family: var(--tv-mono, 'Space Mono', 'Courier New', monospace); + font-size: 11px; + letter-spacing: 0.04em; + cursor: pointer; + transition: background 0.2s, color 0.2s, border-color 0.2s; + white-space: nowrap; + flex-shrink: 0; +} +.hero-lb__cover-btn:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.16); + color: var(--tv-text, #e8ddd0); + border-color: rgba(255, 255, 255, 0.3); +} +.hero-lb__cover-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} +.hero-lb__cover-btn--done { + border-color: rgba(200, 144, 94, 0.5); + color: #c8905e; + background: rgba(200, 144, 94, 0.12); +} +.hero-lb__cover-btn--error { + border-color: rgba(220, 80, 80, 0.5); + color: #dc5050; + background: rgba(220, 80, 80, 0.12); +} + .hero-lb__close { width: 40px; height: 40px; diff --git a/src/pages/travel/HeroLightbox.jsx b/src/pages/travel/HeroLightbox.jsx index 4844a10..89349e2 100644 --- a/src/pages/travel/HeroLightbox.jsx +++ b/src/pages/travel/HeroLightbox.jsx @@ -51,6 +51,8 @@ export default function HeroLightbox({ const pendingAdvanceRef = useRef(false); const stripRef = useRef(null); const prevOverflowRef = useRef(''); + const [coverStatus, setCoverStatus] = useState(null); // 'saving' | 'done' | 'error' + const coverTimerRef = useRef(null); const accent = useMemo(() => getRegionAccent(regionId), [regionId]); const reduced = useMemo(() => prefersReduced(), []); const animMs = reduced ? 0 : ANIM_MS; @@ -146,6 +148,28 @@ export default function HeroLightbox({ delta: 30, }); + /* — Set as album cover — */ + const handleSetCover = useCallback(async () => { + const p = photos[selectedIndex]; + if (!p || !albumName || coverStatus === 'saving') return; + const filename = p.file || p.filename || p.name || ''; + if (!filename) return; + setCoverStatus('saving'); + try { + const res = await fetch(`/api/travel/albums/${encodeURIComponent(albumName)}/cover`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ filename }), + }); + if (!res.ok) throw new Error(`${res.status}`); + setCoverStatus('done'); + } catch { + setCoverStatus('error'); + } + if (coverTimerRef.current) clearTimeout(coverTimerRef.current); + coverTimerRef.current = setTimeout(() => setCoverStatus(null), 2000); + }, [selectedIndex, photos, albumName, coverStatus]); + /* — Current photo — */ const photo = photos[selectedIndex]; if (!photo) return null; @@ -183,6 +207,17 @@ export default function HeroLightbox({ {' / '} {photos.length} +