feat: 모바일 일정 상세 페이지 및 에러 UI 추가
- MobileScheduleDetail 컴포넌트 생성 - 404 에러 페이지 UI (애니메이션 포함) - /schedule/:id 라우트 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
6275f0f92a
commit
f51a3f754a
2 changed files with 121 additions and 0 deletions
|
|
@ -25,6 +25,7 @@ import MobileAlbumDetail from './pages/mobile/public/AlbumDetail';
|
||||||
import MobileAlbumGallery from './pages/mobile/public/AlbumGallery';
|
import MobileAlbumGallery from './pages/mobile/public/AlbumGallery';
|
||||||
import MobileTrackDetail from './pages/mobile/public/TrackDetail';
|
import MobileTrackDetail from './pages/mobile/public/TrackDetail';
|
||||||
import MobileSchedule from './pages/mobile/public/Schedule';
|
import MobileSchedule from './pages/mobile/public/Schedule';
|
||||||
|
import MobileScheduleDetail from './pages/mobile/public/ScheduleDetail';
|
||||||
import MobileNotFound from './pages/mobile/public/NotFound';
|
import MobileNotFound from './pages/mobile/public/NotFound';
|
||||||
|
|
||||||
// 관리자 페이지
|
// 관리자 페이지
|
||||||
|
|
@ -104,6 +105,7 @@ function App() {
|
||||||
<Route path="/album/:name/gallery" element={<MobileLayout pageTitle="앨범"><MobileAlbumGallery /></MobileLayout>} />
|
<Route path="/album/:name/gallery" element={<MobileLayout pageTitle="앨범"><MobileAlbumGallery /></MobileLayout>} />
|
||||||
<Route path="/album/:name/track/:trackTitle" element={<MobileLayout pageTitle="앨범"><MobileTrackDetail /></MobileLayout>} />
|
<Route path="/album/:name/track/:trackTitle" element={<MobileLayout pageTitle="앨범"><MobileTrackDetail /></MobileLayout>} />
|
||||||
<Route path="/schedule" element={<MobileLayout useCustomLayout><MobileSchedule /></MobileLayout>} />
|
<Route path="/schedule" element={<MobileLayout useCustomLayout><MobileSchedule /></MobileLayout>} />
|
||||||
|
<Route path="/schedule/:id" element={<MobileScheduleDetail />} />
|
||||||
<Route path="*" element={<MobileNotFound />} />
|
<Route path="*" element={<MobileNotFound />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</MobileView>
|
</MobileView>
|
||||||
|
|
|
||||||
119
frontend/src/pages/mobile/public/ScheduleDetail.jsx
Normal file
119
frontend/src/pages/mobile/public/ScheduleDetail.jsx
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
import { useParams, Link } from 'react-router-dom';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { Calendar, ChevronLeft } from 'lucide-react';
|
||||||
|
import { getSchedule } from '../../../api/public/schedules';
|
||||||
|
|
||||||
|
function MobileScheduleDetail() {
|
||||||
|
const { id } = useParams();
|
||||||
|
|
||||||
|
const { data: schedule, isLoading, error } = useQuery({
|
||||||
|
queryKey: ['schedule', id],
|
||||||
|
queryFn: () => getSchedule(id),
|
||||||
|
retry: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
||||||
|
<div className="w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error || !schedule) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-gray-50 to-gray-100 px-6">
|
||||||
|
<div className="text-center">
|
||||||
|
{/* 아이콘 */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, scale: 0.5 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
transition={{ duration: 0.5, type: "spring", stiffness: 100 }}
|
||||||
|
className="mb-6"
|
||||||
|
>
|
||||||
|
<div className="w-24 h-24 mx-auto bg-gradient-to-br from-primary/10 to-primary/5 rounded-2xl flex items-center justify-center">
|
||||||
|
<Calendar size={48} className="text-primary/40" />
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* 메시지 */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.2, duration: 0.5 }}
|
||||||
|
className="mb-6"
|
||||||
|
>
|
||||||
|
<h2 className="text-xl font-bold text-gray-800 mb-2">
|
||||||
|
일정을 찾을 수 없습니다
|
||||||
|
</h2>
|
||||||
|
<p className="text-gray-500 text-sm leading-relaxed">
|
||||||
|
요청하신 일정이 존재하지 않거나
|
||||||
|
<br />
|
||||||
|
삭제되었을 수 있습니다.
|
||||||
|
</p>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* 장식 요소 */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay: 0.4, duration: 0.5 }}
|
||||||
|
className="flex justify-center gap-1.5 mb-8"
|
||||||
|
>
|
||||||
|
{[...Array(5)].map((_, i) => (
|
||||||
|
<motion.div
|
||||||
|
key={i}
|
||||||
|
className="w-1.5 h-1.5 rounded-full bg-primary"
|
||||||
|
animate={{
|
||||||
|
y: [0, -6, 0],
|
||||||
|
opacity: [0.3, 1, 0.3],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 1.2,
|
||||||
|
repeat: Infinity,
|
||||||
|
delay: i * 0.15,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* 버튼들 */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.5, duration: 0.5 }}
|
||||||
|
className="flex flex-col gap-3"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={() => window.history.back()}
|
||||||
|
className="flex items-center justify-center gap-2 w-full px-6 py-3 border-2 border-primary text-primary rounded-full font-medium active:bg-primary active:text-white transition-colors"
|
||||||
|
>
|
||||||
|
<ChevronLeft size={18} />
|
||||||
|
이전 페이지
|
||||||
|
</button>
|
||||||
|
<Link
|
||||||
|
to="/schedule"
|
||||||
|
className="flex items-center justify-center gap-2 w-full px-6 py-3 bg-gradient-to-r from-primary to-primary-dark text-white rounded-full font-medium active:opacity-90 transition-opacity"
|
||||||
|
>
|
||||||
|
<Calendar size={18} />
|
||||||
|
일정 목록
|
||||||
|
</Link>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 일정 상세 페이지 구현
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 p-4">
|
||||||
|
<div className="text-center py-8 text-gray-500">
|
||||||
|
일정 상세 페이지 준비 중
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MobileScheduleDetail;
|
||||||
Loading…
Add table
Reference in a new issue