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.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 && (