fromis_9/frontend/src/pages/mobile/public/AlbumDetail.jsx
caadiq e994aa08ca refactor: API 및 페이지 폴더 구조 정리 (2/3)
- api/schedules, albums, members → api/public/로 이동
- pages/pc/*.jsx → pages/pc/public/로 이동
- pages/mobile/*.jsx → pages/mobile/public/로 이동
- App.jsx 라우터 경로 수정
- 모든 public 페이지의 import 경로 수정
2026-01-09 22:00:14 +09:00

142 lines
6 KiB
JavaScript

import { motion } from 'framer-motion';
import { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { ArrowLeft, Play } from 'lucide-react';
// 모바일 앨범 상세 페이지
function MobileAlbumDetail() {
const { name } = useParams();
const navigate = useNavigate();
const [album, setAlbum] = useState(null);
const [tracks, setTracks] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
// 앨범 정보 로드
fetch('/api/albums')
.then(res => res.json())
.then(data => {
const found = data.find(a => a.folder_name === name);
if (found) {
setAlbum(found);
// 트랙 정보 로드
fetch(`/api/albums/${found.id}/tracks`)
.then(res => res.json())
.then(setTracks)
.catch(console.error);
}
setLoading(false);
})
.catch(console.error);
}, [name]);
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin" />
</div>
);
}
if (!album) {
return (
<div className="text-center py-12">
<p className="text-gray-500">앨범을 찾을 없습니다</p>
</div>
);
}
return (
<div className="pb-6">
{/* 헤더 */}
<div className="sticky top-14 z-40 bg-white/80 backdrop-blur-sm px-4 py-3 flex items-center gap-3 border-b">
<button onClick={() => navigate(-1)} className="p-1">
<ArrowLeft size={24} />
</button>
<span className="font-semibold truncate">{album.title}</span>
</div>
{/* 앨범 정보 */}
<div className="px-4 py-6">
<div className="flex gap-4">
<div className="w-32 h-32 rounded-2xl overflow-hidden bg-gray-200 shadow-lg flex-shrink-0">
{album.cover_medium_url && (
<img
src={album.cover_medium_url}
alt={album.title}
className="w-full h-full object-cover"
/>
)}
</div>
<div className="flex-1">
<h1 className="text-xl font-bold">{album.title}</h1>
<p className="text-gray-500 text-sm mt-1">{album.album_type}</p>
<p className="text-gray-400 text-sm">{album.release_date}</p>
<button
onClick={() => navigate(`/album/${name}/gallery`)}
className="mt-4 px-4 py-2 bg-primary text-white rounded-full text-sm font-medium"
>
갤러리 보기
</button>
</div>
</div>
</div>
{/* 트랙 리스트 */}
{tracks.length > 0 && (
<div className="px-4">
<h2 className="text-lg font-bold mb-3">수록곡</h2>
<div className="bg-white rounded-2xl overflow-hidden shadow-sm">
{tracks.map((track, index) => (
<motion.div
key={track.id}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: index * 0.05 }}
className="flex items-center gap-3 p-4 border-b border-gray-100 last:border-none"
>
<span className="w-6 text-center text-gray-400 text-sm">
{track.track_number}
</span>
<div className="flex-1 min-w-0">
<p className={`font-medium text-sm truncate ${track.is_title_track ? 'text-primary' : ''}`}>
{track.title}
{track.is_title_track && (
<span className="ml-2 text-xs bg-primary/10 text-primary px-2 py-0.5 rounded-full">
타이틀
</span>
)}
</p>
<p className="text-xs text-gray-400 truncate">{track.duration}</p>
</div>
{track.music_video_url && (
<a
href={track.music_video_url}
target="_blank"
rel="noopener noreferrer"
className="p-2 text-red-500"
>
<Play size={18} fill="currentColor" />
</a>
)}
</motion.div>
))}
</div>
</div>
)}
{/* 설명 */}
{album.description && (
<div className="px-4 mt-6">
<h2 className="text-lg font-bold mb-3">소개</h2>
<p className="text-gray-600 text-sm leading-relaxed bg-white p-4 rounded-2xl">
{album.description}
</p>
</div>
)}
</div>
);
}
export default MobileAlbumDetail;