feat: NotFound (404) 페이지 추가
- PC/Mobile 404 페이지 구현 - App.jsx에 catch-all 라우트 추가 - pages/common/ 폴더 구조 생성 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
9d1f54c68a
commit
276ef509f0
5 changed files with 198 additions and 6 deletions
|
|
@ -324,8 +324,8 @@ function App() {
|
||||||
- [x] mobile/Birthday.jsx
|
- [x] mobile/Birthday.jsx
|
||||||
|
|
||||||
### 페이지 - Common (pages/common/)
|
### 페이지 - Common (pages/common/)
|
||||||
- [ ] pc/NotFound.jsx
|
- [x] pc/NotFound.jsx
|
||||||
- [ ] mobile/NotFound.jsx
|
- [x] mobile/NotFound.jsx
|
||||||
|
|
||||||
### 페이지 - Admin (pages/admin/) - PC 전용
|
### 페이지 - Admin (pages/admin/) - PC 전용
|
||||||
- [ ] Login.jsx
|
- [ ] Login.jsx
|
||||||
|
|
@ -436,7 +436,6 @@ import 'swiper/css';
|
||||||
### 미완료 작업
|
### 미완료 작업
|
||||||
|
|
||||||
#### 공개 영역
|
#### 공개 영역
|
||||||
- [ ] NotFound 페이지 (pc/mobile)
|
|
||||||
- [ ] useToast 훅
|
- [ ] useToast 훅
|
||||||
|
|
||||||
#### 관리자 영역 (별도 요청 시 진행)
|
#### 관리자 영역 (별도 요청 시 진행)
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import {
|
||||||
PCAlbumGallery,
|
PCAlbumGallery,
|
||||||
MobileAlbumGallery,
|
MobileAlbumGallery,
|
||||||
} from '@/pages/album';
|
} from '@/pages/album';
|
||||||
|
import { PCNotFound, MobileNotFound } from '@/pages/common';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PC 환경에서 body에 클래스 추가하는 래퍼
|
* PC 환경에서 body에 클래스 추가하는 래퍼
|
||||||
|
|
@ -75,8 +76,8 @@ function App() {
|
||||||
<Route path="/album/:name" element={<PCAlbumDetail />} />
|
<Route path="/album/:name" element={<PCAlbumDetail />} />
|
||||||
<Route path="/album/:name/track/:trackTitle" element={<PCTrackDetail />} />
|
<Route path="/album/:name/track/:trackTitle" element={<PCTrackDetail />} />
|
||||||
<Route path="/album/:name/gallery" element={<PCAlbumGallery />} />
|
<Route path="/album/:name/gallery" element={<PCAlbumGallery />} />
|
||||||
{/* 추가 페이지 */}
|
{/* 404 페이지 */}
|
||||||
{/* <Route path="*" element={<PCNotFound />} /> */}
|
<Route path="*" element={<PCNotFound />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</PCLayout>
|
</PCLayout>
|
||||||
}
|
}
|
||||||
|
|
@ -146,7 +147,8 @@ function App() {
|
||||||
</MobileLayout>
|
</MobileLayout>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{/* 추가 페이지 */}
|
{/* 404 페이지 */}
|
||||||
|
<Route path="*" element={<MobileNotFound />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</MobileView>
|
</MobileView>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
|
|
||||||
5
frontend-temp/src/pages/common/index.js
Normal file
5
frontend-temp/src/pages/common/index.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// PC
|
||||||
|
export { default as PCNotFound } from './pc/NotFound';
|
||||||
|
|
||||||
|
// Mobile
|
||||||
|
export { default as MobileNotFound } from './mobile/NotFound';
|
||||||
93
frontend-temp/src/pages/common/mobile/NotFound.jsx
Normal file
93
frontend-temp/src/pages/common/mobile/NotFound.jsx
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { Home, ArrowLeft } from 'lucide-react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mobile 404 페이지
|
||||||
|
*/
|
||||||
|
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;
|
||||||
93
frontend-temp/src/pages/common/pc/NotFound.jsx
Normal file
93
frontend-temp/src/pages/common/pc/NotFound.jsx
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { Home, ArrowLeft } from 'lucide-react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PC 404 페이지
|
||||||
|
*/
|
||||||
|
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