2026-01-01 00:26:04 +09:00
|
|
|
import { useState, useEffect } from 'react';
|
2025-12-31 21:51:23 +09:00
|
|
|
import { motion } from 'framer-motion';
|
|
|
|
|
import { Link } from 'react-router-dom';
|
2026-01-07 10:30:09 +09:00
|
|
|
import { Calendar, ArrowRight, Clock, Link2, Tag } from 'lucide-react';
|
2026-01-09 22:00:14 +09:00
|
|
|
import { getTodayKST } from '../../../utils/date';
|
2026-01-09 22:02:04 +09:00
|
|
|
import { getMembers } from '../../../api/public/members';
|
|
|
|
|
import { getUpcomingSchedules } from '../../../api/public/schedules';
|
2025-12-31 21:51:23 +09:00
|
|
|
|
|
|
|
|
function Home() {
|
2026-01-01 00:26:04 +09:00
|
|
|
const [members, setMembers] = useState([]);
|
2026-01-06 12:04:27 +09:00
|
|
|
const [upcomingSchedules, setUpcomingSchedules] = useState([]);
|
2026-01-01 00:26:04 +09:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-01-06 12:04:27 +09:00
|
|
|
// 멤버 데이터 로드
|
2026-01-09 22:02:04 +09:00
|
|
|
getMembers()
|
2026-01-01 00:26:04 +09:00
|
|
|
.then(data => setMembers(data))
|
|
|
|
|
.catch(error => console.error('멤버 데이터 로드 오류:', error));
|
2026-01-06 12:04:27 +09:00
|
|
|
|
|
|
|
|
// 다가오는 일정 로드 (오늘 이후 3개)
|
2026-01-09 22:02:04 +09:00
|
|
|
getUpcomingSchedules(3)
|
2026-01-06 12:04:27 +09:00
|
|
|
.then(data => setUpcomingSchedules(data))
|
|
|
|
|
.catch(error => console.error('일정 데이터 로드 오류:', error));
|
2026-01-01 00:26:04 +09:00
|
|
|
}, []);
|
|
|
|
|
|
2025-12-31 21:51:23 +09:00
|
|
|
return (
|
|
|
|
|
<div>
|
|
|
|
|
{/* 히어로 섹션 */}
|
|
|
|
|
<section className="relative h-[600px] bg-gradient-to-br from-primary to-primary-dark overflow-hidden">
|
|
|
|
|
<div className="absolute inset-0 bg-black/20" />
|
|
|
|
|
<div className="relative max-w-7xl mx-auto px-6 h-full flex items-center">
|
|
|
|
|
<motion.div
|
|
|
|
|
initial={{ opacity: 0, y: 30 }}
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
transition={{ duration: 0.8 }}
|
|
|
|
|
className="text-white"
|
|
|
|
|
>
|
|
|
|
|
<h1 className="text-6xl font-bold mb-4">fromis_9</h1>
|
|
|
|
|
<p className="text-2xl font-light mb-2">프로미스나인</p>
|
|
|
|
|
<p className="text-lg opacity-80 mb-8 leading-relaxed">
|
|
|
|
|
인사드리겠습니다. 둘, 셋!<br />
|
|
|
|
|
이제는 약속해 소중히 간직해,<br />
|
|
|
|
|
당신의 아이돌로 성장하겠습니다!
|
|
|
|
|
</p>
|
|
|
|
|
<Link
|
|
|
|
|
to="/members"
|
|
|
|
|
className="inline-flex items-center gap-2 bg-white text-primary px-6 py-3 rounded-full font-medium hover:bg-gray-100 transition-colors"
|
|
|
|
|
>
|
|
|
|
|
멤버 보기
|
|
|
|
|
<ArrowRight size={18} />
|
|
|
|
|
</Link>
|
|
|
|
|
</motion.div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 장식 */}
|
|
|
|
|
<div className="absolute right-0 bottom-0 w-1/2 h-full opacity-10">
|
|
|
|
|
<div className="absolute right-10 top-20 w-64 h-64 rounded-full bg-white/30" />
|
|
|
|
|
<div className="absolute right-40 bottom-20 w-48 h-48 rounded-full bg-white/20" />
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
2026-01-07 10:30:09 +09:00
|
|
|
{/* 그룹 통계 섹션 */}
|
|
|
|
|
<section className="py-16 bg-gray-50">
|
2025-12-31 21:51:23 +09:00
|
|
|
<div className="max-w-7xl mx-auto px-6">
|
2026-01-07 10:30:09 +09:00
|
|
|
<div className="grid grid-cols-4 gap-6">
|
|
|
|
|
<motion.div
|
|
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
transition={{ delay: 0.1 }}
|
|
|
|
|
className="bg-gradient-to-br from-primary to-primary-dark rounded-2xl p-6 text-white text-center"
|
2025-12-31 21:51:23 +09:00
|
|
|
>
|
2026-01-07 10:30:09 +09:00
|
|
|
<p className="text-3xl font-bold mb-1">2018.01.24</p>
|
|
|
|
|
<p className="text-white/70 text-sm">데뷔일</p>
|
|
|
|
|
</motion.div>
|
|
|
|
|
<motion.div
|
|
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
transition={{ delay: 0.2 }}
|
|
|
|
|
className="bg-gradient-to-br from-primary to-primary-dark rounded-2xl p-6 text-white text-center"
|
2025-12-31 21:51:23 +09:00
|
|
|
>
|
2026-01-07 10:30:09 +09:00
|
|
|
<p className="text-3xl font-bold mb-1">D+{(Math.floor((new Date() - new Date('2018-01-24')) / (1000 * 60 * 60 * 24)) + 1).toLocaleString()}</p>
|
|
|
|
|
<p className="text-white/70 text-sm">D+Day</p>
|
|
|
|
|
</motion.div>
|
|
|
|
|
<motion.div
|
|
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
transition={{ delay: 0.3 }}
|
|
|
|
|
className="bg-gradient-to-br from-primary to-primary-dark rounded-2xl p-6 text-white text-center"
|
2025-12-31 21:51:23 +09:00
|
|
|
>
|
2026-01-07 10:30:09 +09:00
|
|
|
<p className="text-3xl font-bold mb-1">5</p>
|
|
|
|
|
<p className="text-white/70 text-sm">멤버 수</p>
|
|
|
|
|
</motion.div>
|
|
|
|
|
<motion.div
|
|
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
transition={{ delay: 0.4 }}
|
|
|
|
|
className="bg-gradient-to-br from-primary to-primary-dark rounded-2xl p-6 text-white text-center"
|
|
|
|
|
>
|
|
|
|
|
<p className="text-3xl font-bold mb-1">flover</p>
|
|
|
|
|
<p className="text-white/70 text-sm">팬덤명</p>
|
|
|
|
|
</motion.div>
|
2025-12-31 21:51:23 +09:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
{/* 멤버 미리보기 */}
|
|
|
|
|
<section className="py-16 bg-gray-50">
|
|
|
|
|
<div className="max-w-7xl mx-auto px-6">
|
|
|
|
|
<div className="flex justify-between items-center mb-8">
|
|
|
|
|
<h2 className="text-3xl font-bold">멤버</h2>
|
|
|
|
|
<Link to="/members" className="text-primary hover:underline flex items-center gap-1">
|
|
|
|
|
전체보기 <ArrowRight size={16} />
|
|
|
|
|
</Link>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="grid grid-cols-5 gap-6">
|
2026-01-02 23:35:36 +09:00
|
|
|
{members.filter(m => !m.is_former).map((member, index) => (
|
2025-12-31 21:51:23 +09:00
|
|
|
<motion.div
|
|
|
|
|
key={member.id}
|
|
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
transition={{ delay: index * 0.1 }}
|
|
|
|
|
className="bg-white rounded-2xl overflow-hidden shadow-sm hover:shadow-lg transition-shadow"
|
|
|
|
|
>
|
|
|
|
|
<div className="aspect-square bg-gray-100">
|
|
|
|
|
<img
|
2026-01-01 00:26:04 +09:00
|
|
|
src={member.image_url}
|
2025-12-31 21:51:23 +09:00
|
|
|
alt={member.name}
|
|
|
|
|
className="w-full h-full object-cover"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="p-4 text-center">
|
|
|
|
|
<h3 className="font-bold text-lg">{member.name}</h3>
|
2026-01-01 00:26:04 +09:00
|
|
|
<p className="text-sm text-gray-500">{member.position?.split(',')[0]}</p>
|
2025-12-31 21:51:23 +09:00
|
|
|
</div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
2026-01-03 14:30:30 +09:00
|
|
|
{/* 일정 미리보기 */}
|
2026-01-07 10:30:09 +09:00
|
|
|
<section className="py-16 bg-gray-50">
|
2025-12-31 21:51:23 +09:00
|
|
|
<div className="max-w-7xl mx-auto px-6">
|
|
|
|
|
<div className="flex justify-between items-center mb-8">
|
2026-01-03 14:30:30 +09:00
|
|
|
<h2 className="text-3xl font-bold">다가오는 일정</h2>
|
2025-12-31 21:51:23 +09:00
|
|
|
<Link to="/schedule" className="text-primary hover:underline flex items-center gap-1">
|
|
|
|
|
전체보기 <ArrowRight size={16} />
|
|
|
|
|
</Link>
|
|
|
|
|
</div>
|
2026-01-06 12:04:27 +09:00
|
|
|
{upcomingSchedules.length === 0 ? (
|
|
|
|
|
<div className="text-center py-12 text-gray-400">
|
|
|
|
|
<Calendar size={48} className="mx-auto mb-4 opacity-30" />
|
|
|
|
|
<p>예정된 일정이 없습니다</p>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
{upcomingSchedules.map((schedule, index) => {
|
|
|
|
|
const scheduleDate = new Date(schedule.date);
|
|
|
|
|
const day = scheduleDate.getDate();
|
|
|
|
|
const weekdays = ['일', '월', '화', '수', '목', '금', '토'];
|
|
|
|
|
const weekday = weekdays[scheduleDate.getDay()];
|
|
|
|
|
|
|
|
|
|
// 멤버 처리
|
|
|
|
|
const memberList = schedule.member_names ? schedule.member_names.split(',') : [];
|
|
|
|
|
const displayMembers = memberList.length >= 5 ? ['프로미스나인'] : memberList;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<motion.div
|
|
|
|
|
key={schedule.id}
|
|
|
|
|
initial={{ opacity: 0 }}
|
|
|
|
|
animate={{ opacity: 1 }}
|
|
|
|
|
transition={{ delay: index * 0.05 }}
|
|
|
|
|
className="flex items-stretch bg-white rounded-2xl shadow-sm hover:shadow-md transition-shadow overflow-hidden"
|
|
|
|
|
>
|
2026-01-07 10:10:12 +09:00
|
|
|
{/* 날짜 영역 - primary 색상 고정 */}
|
2026-01-06 12:04:27 +09:00
|
|
|
<div className="w-20 flex flex-col items-center justify-center text-white py-5 bg-primary">
|
|
|
|
|
<span className="text-3xl font-bold">{day}</span>
|
|
|
|
|
<span className="text-sm font-medium opacity-80">{weekday}</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 내용 영역 */}
|
|
|
|
|
<div className="flex-1 p-5 flex flex-col justify-center">
|
|
|
|
|
<h3 className="font-bold text-lg text-gray-900 mb-2">{schedule.title}</h3>
|
|
|
|
|
|
|
|
|
|
<div className="flex flex-wrap items-center gap-3 text-sm text-gray-500">
|
|
|
|
|
{schedule.time && (
|
|
|
|
|
<div className="flex items-center gap-1">
|
2026-01-07 10:10:12 +09:00
|
|
|
<Clock size={14} className="text-primary opacity-60" />
|
2026-01-06 12:04:27 +09:00
|
|
|
<span>{schedule.time.slice(0, 5)}</span>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{schedule.category_name && (
|
2026-01-07 10:10:12 +09:00
|
|
|
<div className="flex items-center gap-1">
|
|
|
|
|
<Tag size={14} className="text-primary opacity-60" />
|
|
|
|
|
<span>{schedule.category_name}</span>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{schedule.source_name && (
|
|
|
|
|
<div className="flex items-center gap-1">
|
|
|
|
|
<Link2 size={14} className="text-primary opacity-60" />
|
|
|
|
|
<span>{schedule.source_name}</span>
|
2026-01-06 12:04:27 +09:00
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 멤버 태그 */}
|
|
|
|
|
{displayMembers.length > 0 && (
|
|
|
|
|
<div className="flex flex-wrap gap-1.5 mt-3">
|
|
|
|
|
{displayMembers.map((name, i) => (
|
|
|
|
|
<span
|
|
|
|
|
key={i}
|
|
|
|
|
className="px-2.5 py-1 bg-primary/10 text-primary text-xs font-medium rounded-full"
|
|
|
|
|
>
|
|
|
|
|
{name.trim()}
|
|
|
|
|
</span>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
2025-12-31 21:51:23 +09:00
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default Home;
|
2026-01-06 12:04:27 +09:00
|
|
|
|