2026-01-07 10:10:12 +09:00
|
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
|
|
|
import { useState, useEffect } from 'react';
|
|
|
|
|
import { Instagram } from 'lucide-react';
|
2026-01-10 00:02:42 +09:00
|
|
|
import { getMembers } from '../../../api/public/members';
|
2026-01-07 10:10:12 +09:00
|
|
|
|
|
|
|
|
// 모바일 멤버 페이지
|
|
|
|
|
function MobileMembers() {
|
|
|
|
|
const [members, setMembers] = useState([]);
|
|
|
|
|
const [formerMembers, setFormerMembers] = useState([]);
|
|
|
|
|
const [selectedMember, setSelectedMember] = useState(null);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-01-10 00:02:42 +09:00
|
|
|
getMembers()
|
2026-01-07 10:10:12 +09:00
|
|
|
.then(data => {
|
|
|
|
|
setMembers(data.filter(m => !m.is_former));
|
|
|
|
|
setFormerMembers(data.filter(m => m.is_former));
|
|
|
|
|
})
|
|
|
|
|
.catch(console.error);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
// 멤버 카드 렌더링 함수
|
2026-01-10 00:09:13 +09:00
|
|
|
const renderMemberCard = (member, index, isFormer = false) => (
|
2026-01-07 10:10:12 +09:00
|
|
|
<motion.div
|
|
|
|
|
key={member.id}
|
|
|
|
|
onClick={() => setSelectedMember(member)}
|
|
|
|
|
className="text-center cursor-pointer"
|
2026-01-10 00:09:13 +09:00
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
transition={{ delay: index * 0.05, duration: 0.3 }}
|
2026-01-07 10:10:12 +09:00
|
|
|
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 (
|
2026-01-09 09:26:51 +09:00
|
|
|
<div className="px-4 py-4">
|
2026-01-07 10:10:12 +09:00
|
|
|
{/* 현재 멤버 */}
|
|
|
|
|
<div className="grid grid-cols-3 gap-3">
|
2026-01-10 00:09:13 +09:00
|
|
|
{members.map((member, index) => renderMemberCard(member, index))}
|
2026-01-07 10:10:12 +09:00
|
|
|
</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">
|
2026-01-10 00:09:13 +09:00
|
|
|
{formerMembers.map((member, index) => renderMemberCard(member, index, true))}
|
2026-01-07 10:10:12 +09:00
|
|
|
</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;
|