From e5d403655ea040d3a395291cf82c5a33e9cd2a41 Mon Sep 17 00:00:00 2001 From: caadiq Date: Mon, 12 Jan 2026 18:48:00 +0900 Subject: [PATCH] =?UTF-8?q?=EB=AA=A8=EB=B0=94=EC=9D=BC:=20=EA=B3=A1=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=ED=99=94=EB=A9=B4=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?(TrackDetail=20=ED=8E=98=EC=9D=B4=EC=A7=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.jsx | 2 + .../src/pages/mobile/public/AlbumDetail.jsx | 3 +- .../src/pages/mobile/public/TrackDetail.jsx | 299 ++++++++++++++++++ 3 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 frontend/src/pages/mobile/public/TrackDetail.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 80eea25..3809265 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -20,6 +20,7 @@ import MobileMembers from './pages/mobile/public/Members'; import MobileAlbum from './pages/mobile/public/Album'; import MobileAlbumDetail from './pages/mobile/public/AlbumDetail'; import MobileAlbumGallery from './pages/mobile/public/AlbumGallery'; +import MobileTrackDetail from './pages/mobile/public/TrackDetail'; import MobileSchedule from './pages/mobile/public/Schedule'; // 관리자 페이지 @@ -94,6 +95,7 @@ function App() { } /> } /> } /> + } /> } /> diff --git a/frontend/src/pages/mobile/public/AlbumDetail.jsx b/frontend/src/pages/mobile/public/AlbumDetail.jsx index 0f88b29..2493024 100644 --- a/frontend/src/pages/mobile/public/AlbumDetail.jsx +++ b/frontend/src/pages/mobile/public/AlbumDetail.jsx @@ -261,7 +261,8 @@ function MobileAlbumDetail() { {displayTracks?.map((track) => (
navigate(`/album/${encodeURIComponent(album.title)}/track/${encodeURIComponent(track.title)}`)} + className="flex items-center gap-3 py-2.5 px-3 rounded-xl hover:bg-gray-50 active:bg-gray-100 transition-colors cursor-pointer" > {String(track.track_number).padStart(2, '0')} diff --git a/frontend/src/pages/mobile/public/TrackDetail.jsx b/frontend/src/pages/mobile/public/TrackDetail.jsx new file mode 100644 index 0000000..8ec4082 --- /dev/null +++ b/frontend/src/pages/mobile/public/TrackDetail.jsx @@ -0,0 +1,299 @@ +import { useMemo } from 'react'; +import { useParams, useNavigate, Link } from 'react-router-dom'; +import { useQuery } from '@tanstack/react-query'; +import { motion } from 'framer-motion'; +import { Clock, User, Music, Mic2, ChevronLeft, ChevronRight } from 'lucide-react'; +import { getTrack } from '../../../api/public/albums'; + +// 유튜브 URL에서 비디오 ID 추출 +const getYoutubeVideoId = (url) => { + if (!url) return null; + const patterns = [ + /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/, + ]; + for (const pattern of patterns) { + const match = url.match(pattern); + if (match) return match[1]; + } + return null; +}; + +// 쉼표 기준 줄바꿈 처리 +const formatCredit = (text) => { + if (!text) return null; + return text.split(',').map((item, index) => ( + {item.trim()} + )); +}; + +// 모바일 곡 상세 페이지 +function TrackDetail() { + const { name: albumName, trackTitle } = useParams(); + const navigate = useNavigate(); + + // useQuery로 트랙 데이터 로드 + const { data: track, isLoading: loading, error } = useQuery({ + queryKey: ['track', albumName, trackTitle], + queryFn: () => getTrack(albumName, trackTitle), + enabled: !!albumName && !!trackTitle, + }); + + const youtubeVideoId = useMemo(() => getYoutubeVideoId(track?.music_video_url), [track?.music_video_url]); + + if (loading) { + return ( +
+
+
+ ); + } + + if (error || !track) { + return ( +
+

트랙을 찾을 수 없습니다.

+
+ ); + } + + return ( + + {/* 헤더 - 뒤로가기 */} +
+ +
+

{track.album?.title}

+

{track.title}

+
+
+ + {/* 트랙 정보 헤더 */} +
+
+ {/* 앨범 커버 */} + + {track.album?.title} + + + {/* 트랙 정보 */} +
+
+ {track.is_title_track === 1 && ( + + TITLE + + )} + + Track {String(track.track_number).padStart(2, '0')} + +
+ +

{track.title}

+ +

+ {track.album?.album_type} · {track.album?.title} +

+ + {track.duration && ( +
+ + {track.duration} +
+ )} +
+
+
+ + {/* 뮤직비디오 섹션 */} + {youtubeVideoId && ( + +

+
+ 뮤직비디오 +

+
+