fromis_9/frontend/src/pages/mobile/public/AlbumGallery.jsx

133 lines
5.3 KiB
React
Raw Normal View History

import { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { ArrowLeft, X, ChevronLeft, ChevronRight } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
// 모바일 앨범 갤러리 페이지
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);
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}/photos`)
.then(res => res.json())
.then(setPhotos)
.catch(console.error);
}
setLoading(false);
})
.catch(console.error);
}, [name]);
// 이미지 네비게이션
const goToImage = (delta) => {
const newIndex = selectedIndex + delta;
if (newIndex >= 0 && newIndex < photos.length) {
setSelectedIndex(newIndex);
}
};
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>
);
}
return (
<div className="pb-4">
{/* 헤더 */}
<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="grid grid-cols-3 gap-0.5 p-0.5">
{photos.map((photo, index) => (
<motion.div
key={photo.id}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: index * 0.02 }}
onClick={() => setSelectedIndex(index)}
className="aspect-square bg-gray-200 cursor-pointer"
>
<img
src={photo.thumb_url}
alt=""
className="w-full h-full object-cover"
loading="lazy"
/>
</motion.div>
))}
</div>
{/* 풀스크린 뷰어 */}
<AnimatePresence>
{selectedIndex !== null && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black z-50 flex flex-col"
>
{/* 뷰어 헤더 */}
<div className="flex items-center justify-between p-4 text-white">
<button onClick={() => setSelectedIndex(null)}>
<X size={24} />
</button>
<span className="text-sm">
{selectedIndex + 1} / {photos.length}
</span>
<div className="w-6" />
</div>
{/* 이미지 */}
<div className="flex-1 flex items-center justify-center relative">
<img
src={photos[selectedIndex]?.medium_url || photos[selectedIndex]?.original_url}
alt=""
className="max-w-full max-h-full object-contain"
/>
{/* 좌우 네비게이션 */}
{selectedIndex > 0 && (
<button
onClick={() => goToImage(-1)}
className="absolute left-2 p-2 text-white/80"
>
<ChevronLeft size={32} />
</button>
)}
{selectedIndex < photos.length - 1 && (
<button
onClick={() => goToImage(1)}
className="absolute right-2 p-2 text-white/80"
>
<ChevronRight size={32} />
</button>
)}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}
export default MobileAlbumGallery;