feat(frontend): PC/모바일 404 페이지 추가
- PC/모바일 각각 사이트 스타일에 맞는 404 페이지 생성 - framer-motion 애니메이션 적용 - PC Layout 수정으로 푸터 하단 고정 - 모바일은 100dvh로 스크롤 없이 전체 화면 사용 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
25decd238b
commit
94fc7b4c7f
4 changed files with 188 additions and 2 deletions
|
|
@ -13,6 +13,7 @@ import PCAlbumDetail from './pages/pc/public/AlbumDetail';
|
|||
import PCAlbumGallery from './pages/pc/public/AlbumGallery';
|
||||
import PCTrackDetail from './pages/pc/public/TrackDetail';
|
||||
import PCSchedule from './pages/pc/public/Schedule';
|
||||
import PCNotFound from './pages/pc/public/NotFound';
|
||||
|
||||
// 모바일 페이지
|
||||
import MobileHome from './pages/mobile/public/Home';
|
||||
|
|
@ -22,6 +23,7 @@ import MobileAlbumDetail from './pages/mobile/public/AlbumDetail';
|
|||
import MobileAlbumGallery from './pages/mobile/public/AlbumGallery';
|
||||
import MobileTrackDetail from './pages/mobile/public/TrackDetail';
|
||||
import MobileSchedule from './pages/mobile/public/Schedule';
|
||||
import MobileNotFound from './pages/mobile/public/NotFound';
|
||||
|
||||
// 관리자 페이지
|
||||
import AdminLogin from './pages/pc/admin/AdminLogin';
|
||||
|
|
@ -82,6 +84,7 @@ function App() {
|
|||
<Route path="/album/:name/gallery" element={<PCAlbumGallery />} />
|
||||
<Route path="/album/:name/track/:trackTitle" element={<PCTrackDetail />} />
|
||||
<Route path="/schedule" element={<PCSchedule />} />
|
||||
<Route path="*" element={<PCNotFound />} />
|
||||
</Routes>
|
||||
</PCLayout>
|
||||
} />
|
||||
|
|
@ -97,6 +100,7 @@ function App() {
|
|||
<Route path="/album/:name/gallery" element={<MobileLayout pageTitle="앨범"><MobileAlbumGallery /></MobileLayout>} />
|
||||
<Route path="/album/:name/track/:trackTitle" element={<MobileLayout pageTitle="앨범"><MobileTrackDetail /></MobileLayout>} />
|
||||
<Route path="/schedule" element={<MobileLayout useCustomLayout><MobileSchedule /></MobileLayout>} />
|
||||
<Route path="*" element={<MobileNotFound />} />
|
||||
</Routes>
|
||||
</MobileView>
|
||||
</BrowserRouter>
|
||||
|
|
|
|||
|
|
@ -18,8 +18,10 @@ function Layout({ children }) {
|
|||
return (
|
||||
<div className="h-screen overflow-hidden flex flex-col">
|
||||
<Header />
|
||||
<main className={`flex-1 min-h-0 ${isSchedulePage ? 'overflow-hidden' : 'overflow-y-auto'}`}>
|
||||
{children}
|
||||
<main className={`flex-1 min-h-0 flex flex-col ${isSchedulePage ? 'overflow-hidden' : 'overflow-y-auto'}`}>
|
||||
<div className="flex-1 flex flex-col">
|
||||
{children}
|
||||
</div>
|
||||
{!hideFooter && <Footer />}
|
||||
</main>
|
||||
</div>
|
||||
|
|
|
|||
90
frontend/src/pages/mobile/public/NotFound.jsx
Normal file
90
frontend/src/pages/mobile/public/NotFound.jsx
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import { motion } from "framer-motion";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Home, ArrowLeft } from "lucide-react";
|
||||
|
||||
function MobileNotFound() {
|
||||
return (
|
||||
<div className="h-[100dvh] flex items-center justify-center bg-gradient-to-br from-gray-50 to-gray-100 px-6">
|
||||
<div className="text-center">
|
||||
{/* 404 숫자 */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.5 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.5, type: "spring", stiffness: 100 }}
|
||||
className="mb-6"
|
||||
>
|
||||
<h1 className="text-[120px] font-bold leading-none bg-gradient-to-br from-primary to-primary-dark bg-clip-text text-transparent select-none">
|
||||
404
|
||||
</h1>
|
||||
</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-sm text-gray-500 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-2 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"
|
||||
>
|
||||
<Link
|
||||
to="/"
|
||||
className="flex items-center justify-center gap-2 px-6 py-3.5 bg-gradient-to-r from-primary to-primary-dark text-white rounded-full font-medium shadow-lg shadow-primary/20 active:scale-95 transition-transform"
|
||||
>
|
||||
<Home size={18} />
|
||||
홈으로 가기
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => window.history.back()}
|
||||
className="flex items-center justify-center gap-2 px-6 py-3.5 border-2 border-primary text-primary rounded-full font-medium active:bg-primary/5 transition-colors"
|
||||
>
|
||||
<ArrowLeft size={18} />
|
||||
이전 페이지
|
||||
</button>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MobileNotFound;
|
||||
90
frontend/src/pages/pc/public/NotFound.jsx
Normal file
90
frontend/src/pages/pc/public/NotFound.jsx
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import { motion } from "framer-motion";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Home, ArrowLeft } from "lucide-react";
|
||||
|
||||
function NotFound() {
|
||||
return (
|
||||
<div className="flex-1 flex items-center justify-center bg-gradient-to-br from-gray-50 to-gray-100">
|
||||
<div className="text-center px-6">
|
||||
{/* 404 숫자 */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.5 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.5, type: "spring", stiffness: 100 }}
|
||||
className="mb-8"
|
||||
>
|
||||
<h1 className="text-[180px] font-bold leading-none bg-gradient-to-br from-primary to-primary-dark bg-clip-text text-transparent select-none">
|
||||
404
|
||||
</h1>
|
||||
</motion.div>
|
||||
|
||||
{/* 메시지 */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2, duration: 0.5 }}
|
||||
className="mb-8"
|
||||
>
|
||||
<h2 className="text-2xl font-bold text-gray-800 mb-3">
|
||||
페이지를 찾을 수 없습니다
|
||||
</h2>
|
||||
<p className="text-gray-500 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-2 mb-10"
|
||||
>
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
className="w-2 h-2 rounded-full bg-primary"
|
||||
animate={{
|
||||
y: [0, -8, 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 justify-center gap-4"
|
||||
>
|
||||
<button
|
||||
onClick={() => window.history.back()}
|
||||
className="flex items-center gap-2 px-6 py-3 border-2 border-primary text-primary rounded-full font-medium hover:bg-primary hover:text-white transition-colors duration-200"
|
||||
>
|
||||
<ArrowLeft size={18} />
|
||||
이전 페이지
|
||||
</button>
|
||||
<Link
|
||||
to="/"
|
||||
className="flex items-center gap-2 px-6 py-3 bg-gradient-to-r from-primary to-primary-dark text-white rounded-full font-medium hover:shadow-lg hover:shadow-primary/30 transition-all duration-200"
|
||||
>
|
||||
<Home size={18} />
|
||||
홈으로 가기
|
||||
</Link>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default NotFound;
|
||||
Loading…
Add table
Reference in a new issue