From 3339b281c7f5de24c0e20ca9bed8d6f8018d28e2 Mon Sep 17 00:00:00 2001 From: caadiq Date: Thu, 1 Jan 2026 17:20:36 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EA=B0=A4=EB=9F=AC=EB=A6=AC=20=EC=B5=9C?= =?UTF-8?q?=EC=A0=81=ED=99=94=20=EB=B0=8F=20=EB=9D=BC=EC=9A=B0=ED=84=B0=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 컨셉 포토 갤러리 페이지 추가 (AlbumGallery.jsx) - react-photo-album 라이브러리로 Justified 레이아웃 구현 - 썸네일/원본 이미지 분리 (thumb_400, original 폴더) - 라우터 변경: /discography → /album - URL 형식 변경: ID 기반 → 앨범명 기반 (/album/하얀 그리움) - 앨범명 기반 API 추가 (/api/albums/by-name/:name) - 브레드크럼 스타일 네비게이션 적용 - 라이트박스 슬라이드 애니메이션 추가 - 점 형태 인디케이터로 변경 --- backend/routes/albums.js | 98 ++++++++ frontend/package-lock.json | 28 ++- frontend/package.json | 3 +- frontend/src/App.jsx | 6 +- frontend/src/components/pc/Header.jsx | 2 +- frontend/src/pages/pc/AlbumDetail.jsx | 255 +++++++++++++++----- frontend/src/pages/pc/AlbumGallery.jsx | 316 +++++++++++++++++++++++++ frontend/src/pages/pc/Discography.jsx | 6 +- frontend/src/pages/pc/Home.jsx | 2 +- 9 files changed, 642 insertions(+), 74 deletions(-) create mode 100644 frontend/src/pages/pc/AlbumGallery.jsx diff --git a/backend/routes/albums.js b/backend/routes/albums.js index c5be16d..14ce709 100644 --- a/backend/routes/albums.js +++ b/backend/routes/albums.js @@ -27,6 +27,73 @@ router.get("/", async (req, res) => { } }); +// 앨범명(slug)으로 조회 +router.get("/by-name/:name", async (req, res) => { + try { + // URL 디코딩된 앨범명으로 조회 + const albumName = decodeURIComponent(req.params.name); + const [albums] = await pool.query("SELECT * FROM albums WHERE title = ?", [ + albumName, + ]); + + if (albums.length === 0) { + return res.status(404).json({ error: "앨범을 찾을 수 없습니다." }); + } + + const album = albums[0]; + + // 트랙 정보 조회 (가사 포함) + const [tracks] = await pool.query( + "SELECT * FROM tracks WHERE album_id = ? ORDER BY track_number", + [album.id] + ); + album.tracks = tracks; + + // 티저 이미지 조회 + const [teasers] = await pool.query( + "SELECT image_url FROM album_teasers WHERE album_id = ? ORDER BY sort_order", + [album.id] + ); + album.teasers = teasers.map((t) => t.image_url); + + // 컨셉 포토 조회 (멤버 정보 포함) + const [photos] = await pool.query( + `SELECT + p.id, p.photo_url, p.photo_type, p.concept_name, p.sort_order, + GROUP_CONCAT(m.name ORDER BY m.id SEPARATOR ', ') as members + FROM album_photos p + LEFT JOIN album_photo_members pm ON p.id = pm.photo_id + LEFT JOIN members m ON pm.member_id = m.id + WHERE p.album_id = ? + GROUP BY p.id + ORDER BY p.sort_order`, + [album.id] + ); + + // 컨셉별로 그룹화 + const conceptPhotos = {}; + for (const photo of photos) { + const concept = photo.concept_name || "Default"; + if (!conceptPhotos[concept]) { + conceptPhotos[concept] = []; + } + conceptPhotos[concept].push({ + id: photo.id, + url: photo.photo_url, + type: photo.photo_type, + members: photo.members, + sortOrder: photo.sort_order, + }); + } + album.conceptPhotos = conceptPhotos; + + res.json(album); + } catch (error) { + console.error("앨범 조회 오류:", error); + res.status(500).json({ error: "앨범 정보를 가져오는데 실패했습니다." }); + } +}); + // 특정 앨범 조회 (트랙 및 상세 정보 포함) router.get("/:id", async (req, res) => { try { @@ -54,6 +121,37 @@ router.get("/:id", async (req, res) => { ); album.teasers = teasers.map((t) => t.image_url); + // 컨셉 포토 조회 (멤버 정보 포함) + const [photos] = await pool.query( + `SELECT + p.id, p.photo_url, p.photo_type, p.concept_name, p.sort_order, + GROUP_CONCAT(m.name ORDER BY m.id SEPARATOR ', ') as members + FROM album_photos p + LEFT JOIN album_photo_members pm ON p.id = pm.photo_id + LEFT JOIN members m ON pm.member_id = m.id + WHERE p.album_id = ? + GROUP BY p.id + ORDER BY p.sort_order`, + [album.id] + ); + + // 컨셉별로 그룹화 + const conceptPhotos = {}; + for (const photo of photos) { + const concept = photo.concept_name || "Default"; + if (!conceptPhotos[concept]) { + conceptPhotos[concept] = []; + } + conceptPhotos[concept].push({ + id: photo.id, + url: photo.photo_url, + type: photo.photo_type, + members: photo.members, + sortOrder: photo.sort_order, + }); + } + album.conceptPhotos = conceptPhotos; + res.json(album); } catch (error) { console.error("앨범 조회 오류:", error); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 39f2f3c..f797e90 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13,6 +13,7 @@ "react": "^18.2.0", "react-device-detect": "^2.2.3", "react-dom": "^18.2.0", + "react-photo-album": "^3.4.0", "react-router-dom": "^6.22.3" }, "devDependencies": { @@ -1179,14 +1180,14 @@ "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.27", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -1462,7 +1463,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/debug": { @@ -2246,6 +2247,27 @@ "react": "^18.3.1" } }, + "node_modules/react-photo-album": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/react-photo-album/-/react-photo-album-3.4.0.tgz", + "integrity": "sha512-pPaCoxEfVDhowpqxECq4SOD5kzP1Uao8PEN11Jasxayv4cZjad3Fy8SlKt6wvtLnVJRtOjsQDU/ZnnUuberwMg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/igordanchenko" + }, + "peerDependencies": { + "@types/react": "^18 || ^19", + "react": "^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 21f9b27..4ea98f9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,6 +14,7 @@ "react": "^18.2.0", "react-device-detect": "^2.2.3", "react-dom": "^18.2.0", + "react-photo-album": "^3.4.0", "react-router-dom": "^6.22.3" }, "devDependencies": { @@ -25,4 +26,4 @@ "tailwindcss": "^3.4.18", "vite": "^5.4.1" } -} \ No newline at end of file +} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 977af7d..5c6368b 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -6,6 +6,7 @@ import PCHome from './pages/pc/Home'; import PCMembers from './pages/pc/Members'; import PCDiscography from './pages/pc/Discography'; import PCAlbumDetail from './pages/pc/AlbumDetail'; +import PCAlbumGallery from './pages/pc/AlbumGallery'; import PCSchedule from './pages/pc/Schedule'; // PC 레이아웃 @@ -19,8 +20,9 @@ function App() { } /> } /> - } /> - } /> + } /> + } /> + } /> } /> diff --git a/frontend/src/components/pc/Header.jsx b/frontend/src/components/pc/Header.jsx index 4fb60bd..ad45fb7 100644 --- a/frontend/src/components/pc/Header.jsx +++ b/frontend/src/components/pc/Header.jsx @@ -13,7 +13,7 @@ function Header() { const navItems = [ { path: '/', label: '홈' }, { path: '/members', label: '멤버' }, - { path: '/discography', label: '앨범' }, + { path: '/album', label: '앨범' }, { path: '/schedule', label: '스케줄' }, ]; diff --git a/frontend/src/pages/pc/AlbumDetail.jsx b/frontend/src/pages/pc/AlbumDetail.jsx index e1c5d25..6b626e0 100644 --- a/frontend/src/pages/pc/AlbumDetail.jsx +++ b/frontend/src/pages/pc/AlbumDetail.jsx @@ -1,18 +1,104 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { motion, AnimatePresence } from 'framer-motion'; -import { ArrowLeft, Calendar, Music2, Play, Clock, X, ChevronLeft, ChevronRight } from 'lucide-react'; +import { Calendar, Music2, Clock, X, ChevronLeft, ChevronRight, Download } from 'lucide-react'; function AlbumDetail() { - const { id } = useParams(); + const { name } = useParams(); const navigate = useNavigate(); const [album, setAlbum] = useState(null); const [loading, setLoading] = useState(true); const [lightbox, setLightbox] = useState({ open: false, images: [], index: 0 }); const [slideDirection, setSlideDirection] = useState(0); + const [imageLoaded, setImageLoaded] = useState(false); + + // 라이트박스 네비게이션 함수 + const goToPrev = useCallback(() => { + if (lightbox.images.length <= 1) return; + setImageLoaded(false); + setSlideDirection(-1); + setLightbox(prev => ({ + ...prev, + index: (prev.index - 1 + prev.images.length) % prev.images.length + })); + }, [lightbox.images.length]); + + const goToNext = useCallback(() => { + if (lightbox.images.length <= 1) return; + setImageLoaded(false); + setSlideDirection(1); + setLightbox(prev => ({ + ...prev, + index: (prev.index + 1) % prev.images.length + })); + }, [lightbox.images.length]); + + const closeLightbox = useCallback(() => { + setLightbox(prev => ({ ...prev, open: false })); + }, []); + + // 이미지 다운로드 함수 + const downloadImage = useCallback(async () => { + const imageUrl = lightbox.images[lightbox.index]; + if (!imageUrl) return; + + try { + const response = await fetch(imageUrl); + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = `fromis9_photo_${String(lightbox.index + 1).padStart(2, '0')}.webp`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + window.URL.revokeObjectURL(url); + } catch (error) { + console.error('다운로드 오류:', error); + } + }, [lightbox.images, lightbox.index]); + + // 키보드 이벤트 핸들러 + useEffect(() => { + if (!lightbox.open) return; + + const handleKeyDown = (e) => { + switch (e.key) { + case 'ArrowLeft': + goToPrev(); + break; + case 'ArrowRight': + goToNext(); + break; + case 'Escape': + closeLightbox(); + break; + default: + break; + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [lightbox.open, goToPrev, goToNext, closeLightbox]); + + // 이미지 프리로딩 (이전/다음 이미지) + useEffect(() => { + if (!lightbox.open || lightbox.images.length <= 1) return; + + const preloadImages = []; + const prevIdx = (lightbox.index - 1 + lightbox.images.length) % lightbox.images.length; + const nextIdx = (lightbox.index + 1) % lightbox.images.length; + + [prevIdx, nextIdx].forEach(idx => { + const img = new Image(); + img.src = lightbox.images[idx]; + preloadImages.push(img); + }); + }, [lightbox.open, lightbox.index, lightbox.images]); useEffect(() => { - fetch(`/api/albums/${id}`) + fetch(`/api/albums/by-name/${name}`) .then(res => res.json()) .then(data => { setAlbum(data); @@ -22,7 +108,20 @@ function AlbumDetail() { console.error('앨범 데이터 로드 오류:', error); setLoading(false); }); - }, [id]); + }, [name]); + + // URL을 썸네일/원본 버전으로 변환하는 헬퍼 + const getThumbUrl = (url) => { + const parts = url.split('/'); + const filename = parts.pop(); + return [...parts, 'thumb_400', filename].join('/'); + }; + + const getOriginalUrl = (url) => { + const parts = url.split('/'); + const filename = parts.pop(); + return [...parts, 'original', filename].join('/'); + }; // 날짜 포맷팅 const formatDate = (dateStr) => { @@ -48,7 +147,7 @@ function AlbumDetail() { // 뒤로가기 const handleBack = () => { - navigate('/discography'); + navigate('/album'); }; if (loading) { @@ -80,16 +179,17 @@ function AlbumDetail() { className="py-16" >
- {/* 뒤로가기 버튼 */} - - - 앨범으로 돌아가기 - + {/* 브레드크럼 네비게이션 */} +
+ + / + {album?.title} +
{/* 앨범 정보 헤더 */}
@@ -148,11 +248,11 @@ function AlbumDetail() { {album.teasers.map((teaser, index) => (
setLightbox({ open: true, images: album.teasers, index })} + onClick={() => setLightbox({ open: true, images: album.teasers.map(t => getOriginalUrl(t)), index })} className="w-24 h-24 bg-gray-200 rounded-lg overflow-hidden cursor-pointer transition-all duration-200 hover:scale-110 hover:shadow-xl hover:z-10" > {`Teaser @@ -229,36 +329,50 @@ function AlbumDetail() {
{/* 컨셉 포토 섹션 */} + {album.conceptPhotos && Object.keys(album.conceptPhotos).length > 0 && ( -
-

컨셉 포토

- -
- {/* 4장 정사각형 그리드 - 실제 이미지는 object-cover로 크롭 */} -
- {[1, 2, 3, 4].map((num) => ( -
- {/* 실제 이미지가 들어갈 자리 - object-cover로 중앙 크롭 */} - {/* */} -
- {/* 더미 플레이스홀더 */} -
- {num} + {(() => { + // 모든 컨셉 포토를 하나의 배열로 합치고 처음 4개만 표시 + const allPhotos = Object.values(album.conceptPhotos).flat(); + const previewPhotos = allPhotos.slice(0, 4); + const totalCount = allPhotos.length; + + return ( + <> +
+

컨셉 포토

+
-
- ))} -
+
+ {previewPhotos.map((photo, idx) => ( +
setLightbox({ open: true, images: [getOriginalUrl(photo.url)], index: 0 })} + className="aspect-square bg-gray-200 rounded-xl overflow-hidden cursor-pointer transition-all duration-200 hover:scale-105 hover:shadow-xl hover:z-10" + > + {`컨셉 +
+ ))} +
+ + ); + })()} + )}
@@ -272,61 +386,75 @@ function AlbumDetail() { transition={{ duration: 0.2 }} className="fixed inset-0 bg-black/90 z-50 flex items-center justify-center" > - {/* 닫기 버튼 */} - + {/* 상단 버튼들 */} +
+ {/* 다운로드 버튼 */} + + {/* 닫기 버튼 */} + +
{/* 이전 버튼 */} {lightbox.images.length > 1 && ( )} + {/* 로딩 스피너 */} + {!imageLoaded && ( +
+
+
+ )} + {/* 이미지 */} e.stopPropagation()} - initial={{ opacity: 0, x: slideDirection * 100 }} - animate={{ opacity: 1, x: 0 }} + onLoad={() => setImageLoaded(true)} + initial={{ x: slideDirection * 100 }} + animate={{ x: 0 }} transition={{ duration: 0.25, ease: 'easeOut' }} /> {/* 다음 버튼 */} {lightbox.images.length > 1 && ( )} - {/* 인디케이터 */} + {/* 인디케이터 - 이미지 2개 이상일 때만 표시 */} + {lightbox.images.length > 1 && (
{lightbox.images.map((_, i) => (
+ )} )} diff --git a/frontend/src/pages/pc/AlbumGallery.jsx b/frontend/src/pages/pc/AlbumGallery.jsx new file mode 100644 index 0000000..9db000e --- /dev/null +++ b/frontend/src/pages/pc/AlbumGallery.jsx @@ -0,0 +1,316 @@ +import { useState, useEffect, useCallback, useMemo } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { motion, AnimatePresence } from 'framer-motion'; +import { X, ChevronLeft, ChevronRight, Download } from 'lucide-react'; +import { RowsPhotoAlbum } from 'react-photo-album'; +import 'react-photo-album/rows.css'; + +function AlbumGallery() { + const { name } = useParams(); + const navigate = useNavigate(); + const [album, setAlbum] = useState(null); + const [photos, setPhotos] = useState([]); + const [loading, setLoading] = useState(true); + const [lightbox, setLightbox] = useState({ open: false, index: 0 }); + const [imageLoaded, setImageLoaded] = useState(false); + const [slideDirection, setSlideDirection] = useState(0); + + // URL을 썸네일/원본 버전으로 변환하는 헬퍼 + const getThumbUrl = (url) => { + // https://s3.../photo/01.webp → https://s3.../photo/thumb_400/01.webp + const parts = url.split('/'); + const filename = parts.pop(); + return [...parts, 'thumb_400', filename].join('/'); + }; + + const getOriginalUrl = (url) => { + const parts = url.split('/'); + const filename = parts.pop(); + return [...parts, 'original', filename].join('/'); + }; + + // 이미지 dimensions 로드 + const loadImageDimensions = (url) => { + return new Promise((resolve) => { + const img = new Image(); + img.onload = () => resolve({ width: img.naturalWidth, height: img.naturalHeight }); + img.onerror = () => resolve({ width: 3, height: 4 }); // 기본 3:4 비율 + img.src = url; + }); + }; + + useEffect(() => { + fetch(`/api/albums/by-name/${name}`) + .then(res => res.json()) + .then(async data => { + setAlbum(data); + const allPhotos = []; + + if (data.conceptPhotos && typeof data.conceptPhotos === 'object') { + Object.entries(data.conceptPhotos).forEach(([concept, photos]) => { + photos.forEach(p => allPhotos.push({ + thumbUrl: getThumbUrl(p.url), + originalUrl: getOriginalUrl(p.url), + title: concept, + members: p.members ? p.members.split(', ') : [] + })); + }); + } + + // 모든 이미지 dimensions 로드 + const photosWithDimensions = await Promise.all( + allPhotos.map(async (photo) => { + const dims = await loadImageDimensions(photo.thumbUrl); + return { ...photo, width: dims.width, height: dims.height }; + }) + ); + + setPhotos(photosWithDimensions); + setLoading(false); + }) + .catch(error => { + console.error('앨범 데이터 로드 오류:', error); + setLoading(false); + }); + }, [id]); + + // 라이트박스 열기 + const openLightbox = (index) => { + setImageLoaded(false); + setLightbox({ open: true, index }); + }; + + // 라이트박스 닫기 + const closeLightbox = useCallback(() => { + setLightbox(prev => ({ ...prev, open: false })); + }, []); + + // 이전/다음 이미지 + const goToPrev = useCallback(() => { + if (photos.length <= 1) return; + setImageLoaded(false); + setSlideDirection(-1); + setLightbox(prev => ({ + ...prev, + index: (prev.index - 1 + photos.length) % photos.length + })); + }, [photos.length]); + + const goToNext = useCallback(() => { + if (photos.length <= 1) return; + setImageLoaded(false); + setSlideDirection(1); + setLightbox(prev => ({ + ...prev, + index: (prev.index + 1) % photos.length + })); + }, [photos.length]); + + // 다운로드 + const downloadImage = useCallback(async () => { + const photo = photos[lightbox.index]; + if (!photo) return; + + try { + const response = await fetch(photo.originalUrl); + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = `fromis9_${album?.title || 'photo'}_${String(lightbox.index + 1).padStart(2, '0')}.webp`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + window.URL.revokeObjectURL(url); + } catch (error) { + console.error('다운로드 오류:', error); + } + }, [photos, lightbox.index, album?.title]); + + // 키보드 이벤트 + useEffect(() => { + if (!lightbox.open) return; + + const handleKeyDown = (e) => { + switch (e.key) { + case 'ArrowLeft': goToPrev(); break; + case 'ArrowRight': goToNext(); break; + case 'Escape': closeLightbox(); break; + default: break; + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [lightbox.open, goToPrev, goToNext, closeLightbox]); + + // 프리로딩 + useEffect(() => { + if (!lightbox.open || photos.length <= 1) return; + + const prevIdx = (lightbox.index - 1 + photos.length) % photos.length; + const nextIdx = (lightbox.index + 1) % photos.length; + + [prevIdx, nextIdx].forEach(idx => { + const img = new Image(); + img.src = photos[idx].originalUrl; + }); + }, [lightbox.open, lightbox.index, photos]); + + if (loading) { + return ( + +
+
+ ); + } + + return ( + <> + +
+ {/* 브레드크럼 스타일 헤더 */} +
+
+ + / + + / + 컨셉 포토 +
+

컨셉 포토

+

{photos.length}장의 사진

+
+ + {/* Justified 갤러리 - react-photo-album */} + ({ + src: photo.thumbUrl, + width: photo.width || 300, + height: photo.height || 400, + key: idx.toString() + }))} + targetRowHeight={300} + spacing={8} + onClick={({ index }) => openLightbox(index)} + componentsProps={{ + container: { style: { cursor: 'pointer' } }, + image: { + loading: 'lazy', + style: { borderRadius: '8px', transition: 'transform 0.3s' } + } + }} + /> +
+
+ + {/* 라이트박스 */} + + {lightbox.open && ( + + {/* 상단 버튼들 */} +
+ + +
+ + {/* 카운터 */} +
+ {lightbox.index + 1} / {photos.length} +
+ + {/* 이전 버튼 */} + {photos.length > 1 && ( + + )} + + {/* 로딩 스피너 */} + {!imageLoaded && ( +
+
+
+ )} + + {/* 이미지 */} + setImageLoaded(true)} + initial={{ x: slideDirection * 100 }} + animate={{ x: 0 }} + transition={{ duration: 0.25, ease: 'easeOut' }} + /> + + {/* 다음 버튼 */} + {photos.length > 1 && ( + + )} + + {/* 하단 점 인디케이터 */} +
+ {photos.map((_, i) => ( +
+
+ )} +
+ + ); +} + +export default AlbumGallery; diff --git a/frontend/src/pages/pc/Discography.jsx b/frontend/src/pages/pc/Discography.jsx index cf2204e..26cb314 100644 --- a/frontend/src/pages/pc/Discography.jsx +++ b/frontend/src/pages/pc/Discography.jsx @@ -44,8 +44,8 @@ function Discography() { }; // 앨범 클릭 핸들러 - const handleAlbumClick = (albumId) => { - navigate(`/discography/${albumId}`); + const handleAlbumClick = (albumTitle) => { + navigate(`/album/${encodeURIComponent(albumTitle)}`); }; if (loading) { @@ -112,7 +112,7 @@ function Discography() { animate={{ opacity: 1, y: 0 }} transition={{ delay: index * 0.1 }} className="group bg-white rounded-2xl overflow-hidden shadow-lg hover:shadow-2xl transition-all duration-300 cursor-pointer" - onClick={() => handleAlbumClick(album.id)} + onClick={() => handleAlbumClick(album.title)} > {/* 앨범 커버 */}
5명의 멤버를 만나보세요