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} + + {coverStatus === 'saving' ? '저장 중…' + : coverStatus === 'done' ? '커버 지정됨' + : coverStatus === 'error' ? '실패' + : '커버로 지정'} + { {syncResult.error ? `동기화 실패: ${syncResult.error}` - : `+${syncResult.added} 추가 / ${syncResult.removed} 삭제 / 썸네일 ${syncResult.thumbs_generated}개 (${syncResult.duration_sec}s)` + : `+${syncResult.added} 추가 / ${syncResult.removed} 삭제 / 썸네일 ${syncResult.thumbs_generated}개${syncResult.discovered ? ` / 신규 폴더 ${syncResult.discovered}개 발견` : ''} (${syncResult.duration_sec}s)` } )}