diff --git a/backend/routes/albums.js b/backend/routes/albums.js index f3929dc..29c56c2 100644 --- a/backend/routes/albums.js +++ b/backend/routes/albums.js @@ -81,13 +81,14 @@ router.get("/", async (req, res) => { } }); -// 앨범명으로 조회 +// 앨범 folder_name으로 조회 router.get("/by-name/:name", async (req, res) => { try { - const albumName = decodeURIComponent(req.params.name); - const [albums] = await pool.query("SELECT * FROM albums WHERE title = ?", [ - albumName, - ]); + const folderName = decodeURIComponent(req.params.name); + const [albums] = await pool.query( + "SELECT * FROM albums WHERE folder_name = ?", + [folderName] + ); if (albums.length === 0) { return res.status(404).json({ error: "앨범을 찾을 수 없습니다." }); diff --git a/frontend/src/pages/mobile/public/AlbumDetail.jsx b/frontend/src/pages/mobile/public/AlbumDetail.jsx index b9adc57..f1a944f 100644 --- a/frontend/src/pages/mobile/public/AlbumDetail.jsx +++ b/frontend/src/pages/mobile/public/AlbumDetail.jsx @@ -1,7 +1,7 @@ import { motion, AnimatePresence } from 'framer-motion'; import { useState, useEffect, useCallback } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; -import { ArrowLeft, Play, Calendar, Music2, Clock, X, ChevronLeft, ChevronRight, Download } from 'lucide-react'; +import { Play, Calendar, Music2, Clock, X, ChevronLeft, ChevronRight, Download, ChevronDown, ChevronUp, FileText } from 'lucide-react'; import { getAlbumByName } from '../../../api/public/albums'; import { formatDate } from '../../../utils/date'; @@ -11,8 +11,10 @@ function MobileAlbumDetail() { const navigate = useNavigate(); const [album, setAlbum] = useState(null); const [loading, setLoading] = useState(true); - const [lightbox, setLightbox] = useState({ open: false, images: [], index: 0 }); + const [lightbox, setLightbox] = useState({ open: false, images: [], index: 0, showNav: true }); const [imageLoaded, setImageLoaded] = useState(false); + const [showAllTracks, setShowAllTracks] = useState(false); + const [showDescriptionModal, setShowDescriptionModal] = useState(false); // 라이트박스 네비게이션 const goToPrev = useCallback(() => { @@ -60,13 +62,13 @@ function MobileAlbumDetail() { // 라이트박스 body 스크롤 방지 useEffect(() => { - if (lightbox.open) { + if (lightbox.open || showDescriptionModal) { document.body.style.overflow = 'hidden'; } else { document.body.style.overflow = ''; } return () => { document.body.style.overflow = ''; }; - }, [lightbox.open]); + }, [lightbox.open, showDescriptionModal]); useEffect(() => { getAlbumByName(name) @@ -113,68 +115,85 @@ function MobileAlbumDetail() { // 모든 컨셉 포토를 하나의 배열로 const allPhotos = album.conceptPhotos ? Object.values(album.conceptPhotos).flat() : []; + const displayTracks = showAllTracks ? album.tracks : album.tracks?.slice(0, 5); return ( <>
- {/* 헤더 */} -
- - {album.title} -
- - {/* 앨범 정보 섹션 */} -
-
- {/* 앨범 커버 */} - setLightbox({ - open: true, - images: [album.cover_original_url || album.cover_medium_url], - index: 0 - })} - > - {album.cover_medium_url && ( + {/* 앨범 히어로 섹션 - 커버 이미지 배경 */} +
+ {/* 배경 블러 이미지 */} +
+ +
+
+ + {/* 콘텐츠 */} +
+
+ {/* 앨범 커버 */} + setLightbox({ + open: true, + images: [album.cover_original_url || album.cover_medium_url], + index: 0, + showNav: false + })} + > {album.title} - )} - + - {/* 앨범 정보 */} - - - {album.album_type} - -

{album.title}

- - {/* 메타 정보 */} -
-
- - {formatDate(album.release_date, 'YYYY.MM.DD')} + {/* 앨범 정보 */} + + + {album.album_type} + +

{album.title}

+ + {/* 메타 정보 */} +
+
+ + {formatDate(album.release_date, 'YYYY.MM.DD')} +
+
+ + {album.tracks?.length || 0}곡 +
+
+ + {getTotalDuration()} +
-
- - {album.tracks?.length || 0}곡 -
-
- - {getTotalDuration()} -
-
- + + {/* 앨범 소개 버튼 */} + {album.description && ( + + )} + +
@@ -183,10 +202,10 @@ function MobileAlbumDetail() { -

티저 포토

-
+

티저 포토

+
{album.teasers.map((teaser, index) => (
t.original_url), index, - teasers: album.teasers + teasers: album.teasers, + showNav: true })} - className="w-20 h-20 flex-shrink-0 bg-gray-200 rounded-xl overflow-hidden relative" + className="w-24 h-24 flex-shrink-0 bg-gray-100 rounded-2xl overflow-hidden relative shadow-sm" > {teaser.media_type === 'video' ? ( <> @@ -206,8 +226,8 @@ function MobileAlbumDetail() { muted />
-
- +
+
@@ -229,50 +249,44 @@ function MobileAlbumDetail() { -

수록곡

-
- {album.tracks.map((track, index) => ( +

수록곡

+
+ {displayTracks?.map((track) => (
-
- - {String(track.track_number).padStart(2, '0')} - -
-
-
-

- {track.title} -

- {track.is_title_track === 1 && ( - - TITLE - - )} -
+ + {String(track.track_number).padStart(2, '0')} + +
+

+ {track.title} +

+ {track.is_title_track === 1 && ( + + TITLE + + )}
{track.duration || '-'} - {track.music_video_url && ( - - - - )}
))}
+ {/* 더보기/접기 버튼 */} + {album.tracks.length > 5 && ( + + )} )} @@ -282,27 +296,20 @@ function MobileAlbumDetail() { initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.1 }} - className="px-4" + className="px-4 py-4" > -
-

컨셉 포토

- -
+

컨셉 포토

{allPhotos.slice(0, 6).map((photo, idx) => (
setLightbox({ open: true, - images: allPhotos.map(p => p.original_url), - index: idx + images: [photo.original_url], + index: 0, + showNav: false })} - className="aspect-square bg-gray-200 rounded-xl overflow-hidden" + className="aspect-square bg-gray-100 rounded-xl overflow-hidden shadow-sm" > ))}
- - )} - - {/* 설명 */} - {album.description && ( - -

소개

-

- {album.description} -

+ {/* 전체보기 버튼 - 모바일 스타일 */} +
)}
+ {/* 앨범 소개 다이얼로그 */} + + {showDescriptionModal && album?.description && ( + setShowDescriptionModal(false)} + > + { + if (info.offset.y > 200 || info.velocity.y > 500) { + setShowDescriptionModal(false); + } + }} + className="bg-white rounded-t-3xl w-full max-h-[80vh] overflow-hidden" + onClick={(e) => e.stopPropagation()} + > + {/* 드래그 핸들 */} +
+
+
+ {/* 헤더 */} +
+

앨범 소개

+ +
+ {/* 내용 */} +
+

+ {album.description} +

+
+ + + )} + + {/* 라이트박스 */} {lightbox.open && ( @@ -338,7 +390,7 @@ function MobileAlbumDetail() { initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} - className="fixed inset-0 bg-black z-50 flex items-center justify-center" + className="fixed inset-0 bg-black z-[60] flex items-center justify-center" onClick={closeLightbox} > {/* 상단 버튼 */} @@ -360,8 +412,8 @@ function MobileAlbumDetail() {
- {/* 이전 버튼 */} - {lightbox.images.length > 1 && ( + {/* 이전 버튼 - showNav가 true일 때만 */} + {lightbox.showNav && lightbox.images.length > 1 && ( )} - {/* 인디케이터 */} - {lightbox.images.length > 1 && ( -
- {lightbox.images.map((_, i) => ( -
- ))} + {/* 인디케이터 - showNav가 true일 때만 */} + {lightbox.showNav && lightbox.images.length > 1 && ( +
+
+ + {lightbox.index + 1} / {lightbox.images.length} + +
)}