diff --git a/frontend/src/pages/mobile/public/AlbumDetail.jsx b/frontend/src/pages/mobile/public/AlbumDetail.jsx index 74d9d34..0f88b29 100644 --- a/frontend/src/pages/mobile/public/AlbumDetail.jsx +++ b/frontend/src/pages/mobile/public/AlbumDetail.jsx @@ -1,6 +1,7 @@ import { motion, AnimatePresence } from 'framer-motion'; -import { useState, useEffect, useCallback, useRef } from 'react'; +import { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; +import { useQuery } from '@tanstack/react-query'; import { Play, Calendar, Music2, Clock, X, Download, ChevronDown, ChevronUp, FileText, ChevronRight } from 'lucide-react'; import { Swiper, SwiperSlide } from 'swiper/react'; import { Virtual } from 'swiper/modules'; @@ -9,17 +10,21 @@ import { getAlbumByName } from '../../../api/public/albums'; import { formatDate } from '../../../utils/date'; import LightboxIndicator from '../../../components/common/LightboxIndicator'; -// 모바일 앨범 상세 페이지 function MobileAlbumDetail() { 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, showNav: true }); const [showAllTracks, setShowAllTracks] = useState(false); const [showDescriptionModal, setShowDescriptionModal] = useState(false); const swiperRef = useRef(null); + // useQuery로 앨범 데이터 로드 + const { data: album, isLoading: loading } = useQuery({ + queryKey: ['album', name], + queryFn: () => getAlbumByName(name), + enabled: !!name, + }); + // 라이트박스 열기 - 히스토리 추가 const openLightbox = useCallback((images, index, options = {}) => { setLightbox({ open: true, images, index, showNav: options.showNav !== false, teasers: options.teasers }); @@ -85,18 +90,6 @@ function MobileAlbumDetail() { return () => { document.body.style.overflow = ''; }; }, [lightbox.open, showDescriptionModal]); - useEffect(() => { - getAlbumByName(name) - .then(data => { - setAlbum(data); - setLoading(false); - }) - .catch(error => { - console.error('앨범 로드 오류:', error); - setLoading(false); - }); - }, [name]); - // 총 재생 시간 계산 const getTotalDuration = () => { if (!album?.tracks) return ''; diff --git a/frontend/src/pages/mobile/public/AlbumGallery.jsx b/frontend/src/pages/mobile/public/AlbumGallery.jsx index 964d700..8d19b7c 100644 --- a/frontend/src/pages/mobile/public/AlbumGallery.jsx +++ b/frontend/src/pages/mobile/public/AlbumGallery.jsx @@ -1,5 +1,6 @@ -import { useState, useEffect, useCallback, useRef } from 'react'; +import { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; +import { useQuery } from '@tanstack/react-query'; import { X, Download, ChevronRight, Info, Users, Tag } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { Swiper, SwiperSlide } from 'swiper/react'; @@ -12,34 +13,29 @@ import LightboxIndicator from '../../../components/common/LightboxIndicator'; function MobileAlbumGallery() { const { name } = useParams(); const navigate = useNavigate(); - const [album, setAlbum] = useState(null); - const [photos, setPhotos] = useState([]); - const [loading, setLoading] = useState(true); const [selectedIndex, setSelectedIndex] = useState(null); const [showInfo, setShowInfo] = useState(false); const swiperRef = useRef(null); - useEffect(() => { - getAlbumByName(name) - .then(data => { - setAlbum(data); - const allPhotos = []; - if (data.conceptPhotos && typeof data.conceptPhotos === 'object') { - Object.entries(data.conceptPhotos).forEach(([concept, photos]) => { - photos.forEach(p => allPhotos.push({ - ...p, - concept: concept !== 'Default' ? concept : null - })); - }); - } - setPhotos(allPhotos); - setLoading(false); - }) - .catch(error => { - console.error('앨범 데이터 로드 오류:', error); - setLoading(false); - }); - }, [name]); + // useQuery로 앨범 데이터 로드 + const { data: album, isLoading: loading } = useQuery({ + queryKey: ['album', name], + queryFn: () => getAlbumByName(name), + enabled: !!name, + }); + + // 앨범 데이터에서 사진 목록 추출 + const photos = useMemo(() => { + if (!album?.conceptPhotos) return []; + const allPhotos = []; + Object.entries(album.conceptPhotos).forEach(([concept, conceptPhotos]) => { + conceptPhotos.forEach(p => allPhotos.push({ + ...p, + concept: concept !== 'Default' ? concept : null + })); + }); + return allPhotos; + }, [album]); // 라이트박스 열기 - 히스토리 추가 const openLightbox = useCallback((index) => { diff --git a/frontend/src/pages/pc/public/AlbumDetail.jsx b/frontend/src/pages/pc/public/AlbumDetail.jsx index c0957d9..16d04fd 100644 --- a/frontend/src/pages/pc/public/AlbumDetail.jsx +++ b/frontend/src/pages/pc/public/AlbumDetail.jsx @@ -1,5 +1,6 @@ import { useState, useEffect, useCallback, memo } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; +import { useQuery } from '@tanstack/react-query'; import { motion, AnimatePresence } from 'framer-motion'; import { Calendar, Music2, Clock, X, ChevronLeft, ChevronRight, Download, MoreVertical, FileText } from 'lucide-react'; import { getAlbumByName } from '../../../api/public/albums'; @@ -9,15 +10,20 @@ import LightboxIndicator from '../../../components/common/LightboxIndicator'; function AlbumDetail() { 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 [preloadedImages] = useState(() => new Set()); // 프리로드된 이미지 URL 추적 + const [preloadedImages] = useState(() => new Set()); const [showDescriptionModal, setShowDescriptionModal] = useState(false); const [showMenu, setShowMenu] = useState(false); + // useQuery로 앨범 데이터 로드 + const { data: album, isLoading: loading } = useQuery({ + queryKey: ['album', name], + queryFn: () => getAlbumByName(name), + enabled: !!name, + }); + // 라이트박스 네비게이션 함수 const goToPrev = useCallback(() => { if (lightbox.images.length <= 1) return; @@ -125,18 +131,6 @@ function AlbumDetail() { }); }, [lightbox.open, lightbox.index, lightbox.images, preloadedImages]); - useEffect(() => { - getAlbumByName(name) - .then(data => { - setAlbum(data); - setLoading(false); - }) - .catch(error => { - console.error('앨범 데이터 로드 오류:', error); - setLoading(false); - }); - }, [name]); - // URL 헬퍼 함수는 더 이상 필요 없음 - API에서 직접 제공 // 총 재생 시간 계산 diff --git a/frontend/src/pages/pc/public/AlbumGallery.jsx b/frontend/src/pages/pc/public/AlbumGallery.jsx index f101f8f..df8a42a 100644 --- a/frontend/src/pages/pc/public/AlbumGallery.jsx +++ b/frontend/src/pages/pc/public/AlbumGallery.jsx @@ -1,5 +1,6 @@ -import { useState, useEffect, useCallback, memo } from 'react'; +import { useState, useEffect, useCallback, memo, useMemo } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; +import { useQuery } from '@tanstack/react-query'; import { motion, AnimatePresence } from 'framer-motion'; import { X, ChevronLeft, ChevronRight, Download } from 'lucide-react'; import { RowsPhotoAlbum } from 'react-photo-album'; @@ -40,42 +41,34 @@ const galleryStyles = ` 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); - const [preloadedImages] = useState(() => new Set()); // 프리로드된 이미지 URL 추적 + const [preloadedImages] = useState(() => new Set()); - useEffect(() => { - getAlbumByName(name) - .then(data => { - setAlbum(data); - const allPhotos = []; - - if (data.conceptPhotos && typeof data.conceptPhotos === 'object') { - Object.entries(data.conceptPhotos).forEach(([concept, photos]) => { - photos.forEach(p => allPhotos.push({ - // API에서 직접 제공하는 URL 및 크기 정보 사용 - mediumUrl: p.medium_url, - originalUrl: p.original_url, - width: p.width || 800, - height: p.height || 1200, - title: concept, - members: p.members ? p.members.split(', ') : [] - })); - }); - } - - setPhotos(allPhotos); - setLoading(false); - }) - .catch(error => { - console.error('앨범 데이터 로드 오류:', error); - setLoading(false); - }); - }, [name]); + // useQuery로 앨범 데이터 로드 + const { data: album, isLoading: loading } = useQuery({ + queryKey: ['album', name], + queryFn: () => getAlbumByName(name), + enabled: !!name, + }); + + // 앨범 데이터에서 사진 목록 추출 + const photos = useMemo(() => { + if (!album?.conceptPhotos) return []; + const allPhotos = []; + Object.entries(album.conceptPhotos).forEach(([concept, conceptPhotos]) => { + conceptPhotos.forEach(p => allPhotos.push({ + mediumUrl: p.medium_url, + originalUrl: p.original_url, + width: p.width || 800, + height: p.height || 1200, + title: concept, + members: p.members ? p.members.split(', ') : [] + })); + }); + return allPhotos; + }, [album]); // 라이트박스 열기 const openLightbox = (index) => {