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

128 lines
5.6 KiB
React
Raw Normal View History

import { motion, AnimatePresence } from 'framer-motion';
import { useState, useEffect } from 'react';
import { Instagram } from 'lucide-react';
import { getMembers } from '../../../api/public/members';
// 모바일 멤버 페이지
function MobileMembers() {
const [members, setMembers] = useState([]);
const [formerMembers, setFormerMembers] = useState([]);
const [selectedMember, setSelectedMember] = useState(null);
useEffect(() => {
getMembers()
.then(data => {
setMembers(data.filter(m => !m.is_former));
setFormerMembers(data.filter(m => m.is_former));
})
.catch(console.error);
}, []);
// 멤버 카드 렌더링 함수
const renderMemberCard = (member, index, isFormer = false) => (
<motion.div
key={member.id}
onClick={() => setSelectedMember(member)}
className="text-center cursor-pointer"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.05, duration: 0.3 }}
whileTap={{ scale: 0.95 }}
>
<div className={`aspect-square rounded-2xl overflow-hidden bg-gray-200 mb-2 shadow-sm ${isFormer ? 'grayscale' : ''}`}>
{member.image_url && (
<img
src={member.image_url}
alt={member.name}
className="w-full h-full object-cover"
/>
)}
</div>
<p className={`font-medium text-sm ${isFormer ? 'text-gray-400' : ''}`}>{member.name}</p>
<p className="text-xs text-gray-400">{member.position || ''}</p>
</motion.div>
);
return (
<div className="px-4 py-4">
{/* 현재 멤버 */}
<div className="grid grid-cols-3 gap-3">
{members.map((member, index) => renderMemberCard(member, index))}
</div>
{/* 전 멤버 */}
{formerMembers.length > 0 && (
<>
<div className="mt-8 mb-4">
<h2 className="text-lg font-bold text-gray-400"> 멤버</h2>
</div>
<div className="grid grid-cols-3 gap-3">
{formerMembers.map((member, index) => renderMemberCard(member, index, true))}
</div>
</>
)}
{/* 멤버 상세 모달 */}
<AnimatePresence>
{selectedMember && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-50 flex items-end"
onClick={() => setSelectedMember(null)}
>
<motion.div
initial={{ y: '100%' }}
animate={{ y: 0 }}
exit={{ y: '100%' }}
transition={{ type: 'spring', damping: 25, stiffness: 300 }}
className="bg-white w-full rounded-t-3xl p-6 pb-24"
onClick={e => e.stopPropagation()}
>
<div className="flex gap-4">
<div className={`w-24 h-24 rounded-2xl overflow-hidden bg-gray-200 flex-shrink-0 ${selectedMember.is_former ? 'grayscale' : ''}`}>
{selectedMember.image_url && (
<img
src={selectedMember.image_url}
alt={selectedMember.name}
className="w-full h-full object-cover"
/>
)}
</div>
<div>
<h2 className="text-xl font-bold">{selectedMember.name}</h2>
<p className="text-gray-500 text-sm">{selectedMember.position}</p>
<p className="text-gray-400 text-sm mt-1">
{selectedMember.birth_date?.slice(0, 10).replaceAll('-', '.')}
</p>
{/* 전 멤버가 아닌 경우에만 인스타그램 표시 */}
{!selectedMember.is_former && selectedMember.instagram && (
<a
href={selectedMember.instagram}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 mt-2 text-pink-500"
>
<Instagram size={16} />
<span className="text-sm">Instagram</span>
</a>
)}
</div>
</div>
<button
onClick={() => setSelectedMember(null)}
className="w-full mt-6 py-3 bg-gray-100 rounded-xl font-medium"
>
닫기
</button>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}
export default MobileMembers;