diff --git a/frontend-temp/src/App.jsx b/frontend-temp/src/App.jsx index 289aff4..d6d2e07 100644 --- a/frontend-temp/src/App.jsx +++ b/frontend-temp/src/App.jsx @@ -13,6 +13,7 @@ import { Layout as MobileLayout } from '@/components/mobile'; // 페이지 import { PCHome, MobileHome } from '@/pages/home'; +import { PCMembers, MobileMembers } from '@/pages/members'; /** * PC 환경에서 body에 클래스 추가하는 래퍼 @@ -48,8 +49,8 @@ function App() { } /> - {/* 추가 페이지는 Phase 8-11에서 구현 */} - {/* } /> */} + } /> + {/* 추가 페이지는 Phase 9-11에서 구현 */} {/* } /> */} {/* } /> */} {/* } /> */} @@ -72,8 +73,15 @@ function App() { } /> - {/* 추가 페이지는 Phase 8-11에서 구현 */} - {/* } /> */} + + + + } + /> + {/* 추가 페이지는 Phase 9-11에서 구현 */} {/* } /> */} {/* } /> */} diff --git a/frontend-temp/src/pages/index.js b/frontend-temp/src/pages/index.js index 31dd15b..9f87a08 100644 --- a/frontend-temp/src/pages/index.js +++ b/frontend-temp/src/pages/index.js @@ -1,2 +1,5 @@ // 홈 페이지 export * from './home'; + +// 멤버 페이지 +export * from './members'; diff --git a/frontend-temp/src/pages/members/index.js b/frontend-temp/src/pages/members/index.js new file mode 100644 index 0000000..c04e64c --- /dev/null +++ b/frontend-temp/src/pages/members/index.js @@ -0,0 +1,2 @@ +export { default as PCMembers } from './pc/Members'; +export { default as MobileMembers } from './mobile/Members'; diff --git a/frontend-temp/src/pages/members/mobile/Members.jsx b/frontend-temp/src/pages/members/mobile/Members.jsx new file mode 100644 index 0000000..c3070c2 --- /dev/null +++ b/frontend-temp/src/pages/members/mobile/Members.jsx @@ -0,0 +1,238 @@ +import { motion } from 'framer-motion'; +import { useState, useMemo, useRef, useEffect } from 'react'; +import { Instagram, Calendar } from 'lucide-react'; +import { Swiper, SwiperSlide } from 'swiper/react'; +import 'swiper/css'; +import { useMembers } from '@/hooks'; + +/** + * Mobile 멤버 페이지 - 카드 스와이프 스타일 + */ +function MobileMembers() { + const [currentIndex, setCurrentIndex] = useState(0); + const swiperRef = useRef(null); + const indicatorRef = useRef(null); + + // 멤버 데이터 로드 + const { data: allMembers = [] } = useMembers(); + + // 현재/전 멤버 정렬 (현재 멤버 먼저, 전 멤버 나중) + const members = useMemo(() => { + return [...allMembers].sort((a, b) => { + if (a.is_former !== b.is_former) { + return a.is_former ? 1 : -1; + } + return 0; + }); + }, [allMembers]); + + // 나이 계산 + const calculateAge = (birthDate) => { + if (!birthDate) return null; + const birth = new Date(birthDate); + const today = new Date(); + let age = today.getFullYear() - birth.getFullYear(); + const monthDiff = today.getMonth() - birth.getMonth(); + if ( + monthDiff < 0 || + (monthDiff === 0 && today.getDate() < birth.getDate()) + ) { + age--; + } + return age; + }; + + // 인디케이터 자동 스크롤 + useEffect(() => { + if (indicatorRef.current && members.length > 0) { + const container = indicatorRef.current; + const itemWidth = 64; // 52px 썸네일 + 12px 간격 + const containerWidth = container.offsetWidth; + const paddingLeft = 16; // px-4 + const targetScroll = + paddingLeft + currentIndex * itemWidth + 26 - containerWidth / 2; + + container.scrollTo({ + left: Math.max(0, targetScroll), + behavior: 'smooth', + }); + } + }, [currentIndex, members.length]); + + // 인디케이터 클릭 핸들러 + const handleIndicatorClick = (index) => { + if (swiperRef.current) { + swiperRef.current.slideTo(index); + } + }; + + if (members.length === 0) { + return ( +
+

멤버 정보가 없습니다

+
+ ); + } + + return ( +
+ {/* 상단 썸네일 인디케이터 */} + +
+ {members.map((member, index) => { + const isSelected = index === currentIndex; + const isFormer = member.is_former; + + return ( + + ); + })} +
+
+ + {/* 메인 카드 영역 */} + + { + swiperRef.current = swiper; + }} + onSlideChange={(swiper) => setCurrentIndex(swiper.activeIndex)} + slidesPerView={1.12} + centeredSlides={true} + spaceBetween={0} + className="h-full !overflow-visible [&>.swiper-wrapper]:!overflow-visible" + style={{ padding: '8px 0' }} + > + {members.map((member) => { + const isFormer = member.is_former; + const age = calculateAge(member.birth_date); + + return ( + + {({ isActive }) => ( +
+ {/* 배경 이미지 */} + {member.image_url ? ( + {member.name} + ) : ( +
+ )} + + {/* 하단 그라데이션 오버레이 */} +
+ + {/* 전 멤버 라벨 */} + {isFormer && ( +
+ + 전 멤버 + +
+ )} + + {/* 멤버 정보 */} +
+ {/* 이름 */} +

+ {member.name} +

+ + {/* 생일 정보 */} + {member.birth_date && ( +
+ + + {member.birth_date + ?.slice(0, 10) + .replaceAll('-', '.')} + + {age && ( + + {age}세 + + )} +
+ )} + + {/* 인스타그램 버튼 */} + {!isFormer && member.instagram && ( + + + + Instagram + + + )} +
+
+ )} + + ); + })} + + +
+ ); +} + +export default MobileMembers; diff --git a/frontend-temp/src/pages/members/pc/Members.jsx b/frontend-temp/src/pages/members/pc/Members.jsx new file mode 100644 index 0000000..99dd162 --- /dev/null +++ b/frontend-temp/src/pages/members/pc/Members.jsx @@ -0,0 +1,152 @@ +import { motion } from 'framer-motion'; +import { Instagram, Calendar } from 'lucide-react'; +import { useMembers } from '@/hooks'; +import { Loading } from '@/components/common'; +import { formatDate } from '@/utils'; + +/** + * PC 멤버 페이지 + */ +function Members() { + const { data: members = [], isLoading: loading } = useMembers(); + + if (loading) { + return ( +
+ +
+ ); + } + + return ( +
+
+ {/* 헤더 */} +
+ + 멤버 + + + 프로미스나인의 멤버를 소개합니다 + +
+ + {/* 현재 멤버 그리드 */} +
+ {members + .filter((m) => !m.is_former) + .map((member, index) => ( + +
+ {/* 이미지 */} +
+ {member.name} +
+ + {/* 정보 */} +
+

{member.name}

+ +
+ + {formatDate(member.birth_date, 'YYYY.MM.DD')} +
+ + {/* 인스타그램 링크 */} + {member.instagram && ( + + + Instagram + + )} +
+ + {/* 호버 효과 - 컬러 바 */} +
+
+ + ))} +
+ + {/* 전 멤버 섹션 */} + {members.filter((m) => m.is_former).length > 0 && ( + +

전 멤버

+
+ {members + .filter((m) => m.is_former) + .map((member, index) => ( + +
+ {/* 이미지 - grayscale */} +
+ {member.name} +
+ + {/* 정보 */} +
+

+ {member.name} +

+ +
+ + + {formatDate(member.birth_date, 'YYYY.MM.DD')} + +
+
+ + {/* 호버 효과 - 컬러 바 */} +
+
+ + ))} +
+
+ )} +
+
+ ); +} + +export default Members;