This commit is contained in:
2025-12-17 21:50:10 +07:00
parent 3920a9bf8c
commit f371178518
4 changed files with 166 additions and 0 deletions

View File

@@ -21,6 +21,7 @@ import { ProfilePage } from '@/pages/ProfilePage'
import { UserProfilePage } from '@/pages/UserProfilePage'
import { NotFoundPage } from '@/pages/NotFoundPage'
import { TeapotPage } from '@/pages/TeapotPage'
import { ServerErrorPage } from '@/pages/ServerErrorPage'
// Protected route wrapper
function ProtectedRoute({ children }: { children: React.ReactNode }) {
@@ -154,6 +155,10 @@ function App() {
<Route path="teapot" element={<TeapotPage />} />
<Route path="tea" element={<TeapotPage />} />
{/* Server error page */}
<Route path="500" element={<ServerErrorPage />} />
<Route path="error" element={<ServerErrorPage />} />
{/* 404 - must be last */}
<Route path="*" element={<NotFoundPage />} />
</Route>

View File

@@ -22,11 +22,28 @@ client.interceptors.request.use((config) => {
client.interceptors.response.use(
(response) => response,
(error: AxiosError<{ detail: string }>) => {
// Unauthorized - redirect to login
if (error.response?.status === 401) {
localStorage.removeItem('token')
localStorage.removeItem('user')
window.location.href = '/login'
}
// Server error or network error - redirect to 500 page
if (
error.response?.status === 500 ||
error.response?.status === 502 ||
error.response?.status === 503 ||
error.response?.status === 504 ||
error.code === 'ERR_NETWORK' ||
error.code === 'ECONNABORTED'
) {
// Only redirect if not already on error page
if (!window.location.pathname.startsWith('/500') && !window.location.pathname.startsWith('/error')) {
window.location.href = '/500'
}
}
return Promise.reject(error)
}
)

View File

@@ -0,0 +1,143 @@
import { Link } from 'react-router-dom'
import { NeonButton } from '@/components/ui'
import { Home, Sparkles, RefreshCw, ServerCrash, Flame, Zap } from 'lucide-react'
export function ServerErrorPage() {
const handleRefresh = () => {
window.location.href = '/'
}
return (
<div className="min-h-[70vh] flex flex-col items-center justify-center text-center px-4">
{/* Background effects */}
<div className="fixed inset-0 overflow-hidden pointer-events-none">
<div className="absolute top-1/4 -left-32 w-96 h-96 bg-red-500/5 rounded-full blur-[100px]" />
<div className="absolute bottom-1/4 -right-32 w-96 h-96 bg-orange-500/5 rounded-full blur-[100px]" />
</div>
{/* Server icon */}
<div className="relative mb-8">
{/* Smoke/fire effect */}
<div className="absolute -top-6 left-1/2 -translate-x-1/2 flex gap-3">
<Flame className="w-6 h-6 text-orange-500/60 animate-flicker" style={{ animationDelay: '0s' }} />
<Flame className="w-5 h-5 text-red-500/50 animate-flicker" style={{ animationDelay: '0.2s' }} />
<Flame className="w-6 h-6 text-orange-500/60 animate-flicker" style={{ animationDelay: '0.4s' }} />
</div>
{/* Server with error */}
<div className="relative">
<div className="w-32 h-32 rounded-2xl bg-dark-700/80 border-2 border-red-500/30 flex items-center justify-center shadow-[0_0_30px_rgba(239,68,68,0.2)]">
<ServerCrash className="w-16 h-16 text-red-400" />
</div>
{/* Error indicator */}
<div className="absolute -bottom-2 -right-2 w-10 h-10 rounded-xl bg-red-500/20 border border-red-500/40 flex items-center justify-center animate-pulse">
<Zap className="w-5 h-5 text-red-400" />
</div>
{/* Sparks */}
<div className="absolute top-2 -left-3 w-2 h-2 rounded-full bg-yellow-400 animate-spark" style={{ animationDelay: '0s' }} />
<div className="absolute top-6 -right-2 w-1.5 h-1.5 rounded-full bg-orange-400 animate-spark" style={{ animationDelay: '0.3s' }} />
<div className="absolute bottom-4 -left-2 w-1.5 h-1.5 rounded-full bg-red-400 animate-spark" style={{ animationDelay: '0.6s' }} />
</div>
{/* Glow effect */}
<div className="absolute inset-0 bg-red-500/20 rounded-full blur-3xl -z-10" />
</div>
{/* 500 text */}
<div className="relative mb-4">
<h1 className="text-8xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-red-400 via-orange-400 to-yellow-400">
500
</h1>
<div className="absolute inset-0 text-8xl font-bold text-red-500/20 blur-xl">
500
</div>
</div>
<h2 className="text-2xl font-bold text-white mb-3">
Ошибка сервера
</h2>
<p className="text-gray-400 mb-2 max-w-md">
Что-то пошло не так на нашей стороне.
</p>
<p className="text-gray-500 text-sm mb-8 max-w-md">
Мы уже работаем над решением проблемы. Попробуйте обновить страницу.
</p>
{/* Status info */}
<div className="glass rounded-xl p-4 mb-8 max-w-md border border-red-500/20">
<div className="flex items-center gap-2 text-red-400 mb-2">
<ServerCrash className="w-4 h-4" />
<span className="text-sm font-semibold">Internal Server Error</span>
</div>
<p className="text-gray-400 text-sm">
Сервер временно недоступен или перегружен. Обычно это быстро исправляется.
</p>
</div>
{/* Buttons */}
<div className="flex gap-4">
<NeonButton
size="lg"
icon={<RefreshCw className="w-5 h-5" />}
onClick={handleRefresh}
>
Обновить
</NeonButton>
<Link to="/">
<NeonButton size="lg" variant="secondary" icon={<Home className="w-5 h-5" />}>
На главную
</NeonButton>
</Link>
</div>
{/* Decorative sparkles */}
<div className="absolute top-1/4 left-1/4 opacity-20">
<Sparkles className="w-6 h-6 text-red-400 animate-pulse" />
</div>
<div className="absolute bottom-1/3 right-1/4 opacity-20">
<Sparkles className="w-4 h-4 text-orange-400 animate-pulse" style={{ animationDelay: '1s' }} />
</div>
{/* Custom animations */}
<style>{`
@keyframes flicker {
0%, 100% {
transform: translateY(0) scale(1);
opacity: 0.6;
}
25% {
transform: translateY(-3px) scale(1.1);
opacity: 0.8;
}
50% {
transform: translateY(-1px) scale(0.9);
opacity: 0.5;
}
75% {
transform: translateY(-4px) scale(1.05);
opacity: 0.7;
}
}
.animate-flicker {
animation: flicker 0.8s ease-in-out infinite;
}
@keyframes spark {
0%, 100% {
opacity: 0;
transform: scale(0);
}
50% {
opacity: 1;
transform: scale(1);
}
}
.animate-spark {
animation: spark 1.5s ease-in-out infinite;
}
`}</style>
</div>
)
}

View File

@@ -11,3 +11,4 @@ export { ProfilePage } from './ProfilePage'
export { UserProfilePage } from './UserProfilePage'
export { NotFoundPage } from './NotFoundPage'
export { TeapotPage } from './TeapotPage'
export { ServerErrorPage } from './ServerErrorPage'