2026-01-30 10:09:12 +08:00
|
|
|
|
|
|
|
|
import React from 'react';
|
|
|
|
|
import { useRouteError, isRouteErrorResponse, useNavigate } from 'react-router-dom';
|
|
|
|
|
import { Icon } from '@iconify/react';
|
|
|
|
|
import './ErrorPage.css';
|
|
|
|
|
|
|
|
|
|
const ErrorPage: React.FC = () => {
|
2026-01-30 10:09:51 +08:00
|
|
|
const error = useRouteError();
|
|
|
|
|
const navigate = useNavigate();
|
2026-01-30 10:09:12 +08:00
|
|
|
|
2026-01-30 10:09:51 +08:00
|
|
|
let errorMessage = 'An unexpected error has occurred.';
|
|
|
|
|
let errorStatus = 'Error';
|
2026-01-30 10:09:12 +08:00
|
|
|
|
2026-01-30 10:09:51 +08:00
|
|
|
if (isRouteErrorResponse(error)) {
|
|
|
|
|
// Handle specific router errors (404, 401, etc.)
|
|
|
|
|
errorStatus = `${error.status}`;
|
|
|
|
|
errorMessage = error.statusText || error.data?.message || 'Page not found';
|
|
|
|
|
} else if (error instanceof Error) {
|
|
|
|
|
// Handle standard generic errors
|
|
|
|
|
errorMessage = error.message;
|
|
|
|
|
// Special handling for Minified React Error #310 or similar
|
|
|
|
|
if (errorMessage.includes('Minified React error')) {
|
|
|
|
|
errorMessage = 'Application encountered a rendering error. Please reload.';
|
|
|
|
|
}
|
|
|
|
|
} else if (typeof error === 'string') {
|
|
|
|
|
errorMessage = error;
|
2026-01-30 10:09:12 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-30 10:09:51 +08:00
|
|
|
const handleReload = () => {
|
|
|
|
|
window.location.reload();
|
|
|
|
|
};
|
2026-01-30 10:09:12 +08:00
|
|
|
|
2026-01-30 10:09:51 +08:00
|
|
|
const handleGoHome = () => {
|
|
|
|
|
navigate('/');
|
|
|
|
|
// Optional: reload specific parts or reset state if needed
|
|
|
|
|
// window.location.href = '/';
|
|
|
|
|
};
|
2026-01-30 10:09:12 +08:00
|
|
|
|
2026-01-30 10:15:36 +08:00
|
|
|
const styles = {
|
|
|
|
|
page: {
|
|
|
|
|
display: 'flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
minHeight: '100vh',
|
|
|
|
|
padding: '24px',
|
|
|
|
|
backgroundColor: '#f8fafc',
|
|
|
|
|
color: '#334155',
|
|
|
|
|
fontFamily: 'system-ui, -apple-system, sans-serif'
|
|
|
|
|
},
|
|
|
|
|
card: {
|
|
|
|
|
maxWidth: '480px',
|
|
|
|
|
width: '100%',
|
|
|
|
|
padding: '48px 32px',
|
|
|
|
|
textAlign: 'center' as const,
|
|
|
|
|
background: 'rgba(255, 255, 255, 0.9)',
|
|
|
|
|
backdropFilter: 'blur(12px)',
|
|
|
|
|
borderRadius: '24px',
|
|
|
|
|
boxShadow: '0 4px 24px rgba(0, 0, 0, 0.1)',
|
|
|
|
|
border: '1px solid rgba(255, 255, 255, 0.5)'
|
|
|
|
|
},
|
|
|
|
|
iconWrapper: {
|
|
|
|
|
display: 'inline-flex',
|
|
|
|
|
padding: '16px',
|
|
|
|
|
borderRadius: '50%',
|
|
|
|
|
backgroundColor: 'rgba(239, 68, 68, 0.1)',
|
|
|
|
|
color: '#ef4444',
|
|
|
|
|
marginBottom: '24px'
|
|
|
|
|
},
|
|
|
|
|
title: {
|
|
|
|
|
fontSize: '32px',
|
|
|
|
|
fontWeight: '700',
|
|
|
|
|
marginBottom: '12px',
|
|
|
|
|
margin: '0 0 12px 0',
|
|
|
|
|
color: '#1e293b'
|
|
|
|
|
},
|
|
|
|
|
message: {
|
|
|
|
|
color: '#64748b',
|
|
|
|
|
fontSize: '16px',
|
|
|
|
|
lineHeight: '1.6',
|
|
|
|
|
marginBottom: '32px'
|
|
|
|
|
},
|
|
|
|
|
actions: {
|
|
|
|
|
display: 'flex',
|
|
|
|
|
gap: '16px',
|
|
|
|
|
justifyContent: 'center'
|
|
|
|
|
},
|
|
|
|
|
button: {
|
|
|
|
|
display: 'flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
gap: '8px',
|
|
|
|
|
padding: '12px 24px',
|
|
|
|
|
borderRadius: '12px',
|
|
|
|
|
fontWeight: '600',
|
|
|
|
|
fontSize: '14px',
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
border: 'none',
|
|
|
|
|
transition: 'all 0.2s ease'
|
|
|
|
|
},
|
|
|
|
|
btnPrimary: {
|
|
|
|
|
backgroundColor: '#3b82f6',
|
|
|
|
|
color: 'white',
|
|
|
|
|
boxShadow: '0 4px 12px rgba(59, 130, 246, 0.3)'
|
|
|
|
|
},
|
|
|
|
|
btnSecondary: {
|
|
|
|
|
backgroundColor: '#f1f5f9',
|
|
|
|
|
color: '#334155'
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-30 10:09:51 +08:00
|
|
|
return (
|
2026-01-30 10:15:36 +08:00
|
|
|
<div className="error-page" style={styles.page}>
|
|
|
|
|
<div className="error-card glass-panel" style={styles.card}>
|
|
|
|
|
<div className="error-icon-wrapper" style={styles.iconWrapper}>
|
2026-01-30 10:09:51 +08:00
|
|
|
<Icon icon="solar:danger-triangle-bold-duotone" width="64" className="error-icon" />
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-01-30 10:15:36 +08:00
|
|
|
<h1 className="error-title" style={styles.title}>{errorStatus === 'Error' ? 'Oops!' : errorStatus}</h1>
|
|
|
|
|
<p className="error-message" style={styles.message}>{errorMessage}</p>
|
2026-01-30 10:09:12 +08:00
|
|
|
|
2026-01-30 10:15:36 +08:00
|
|
|
<div className="error-actions" style={styles.actions}>
|
|
|
|
|
<button onClick={handleReload} className="error-btn error-btn--primary" style={{ ...styles.button, ...styles.btnPrimary }}>
|
2026-01-30 10:09:51 +08:00
|
|
|
<Icon icon="solar:refresh-circle-bold-duotone" width="20" />
|
|
|
|
|
Reload Page
|
|
|
|
|
</button>
|
2026-01-30 10:15:36 +08:00
|
|
|
<button onClick={handleGoHome} className="error-btn error-btn--secondary" style={{ ...styles.button, ...styles.btnSecondary }}>
|
2026-01-30 10:09:51 +08:00
|
|
|
<Icon icon="solar:home-smile-bold-duotone" width="20" />
|
|
|
|
|
Go Home
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{import.meta.env.DEV && error instanceof Error && (
|
|
|
|
|
<div className="error-stack">
|
|
|
|
|
<pre>{error.stack}</pre>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2026-01-30 10:09:12 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default ErrorPage;
|