fix admin exit not redirecting, my posts crash, add error boundary

This commit is contained in:
2026-03-21 20:19:06 +02:00
parent acc71217a8
commit 5a2d26f62f
2 changed files with 32 additions and 10 deletions

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useRef } from 'react' import { useState, useEffect, useRef, Component, type ReactNode, type ErrorInfo } from 'react'
import { BrowserRouter, Routes, Route, useLocation, useNavigate, Link } from 'react-router-dom' import { BrowserRouter, Routes, Route, useLocation, useNavigate, Link } from 'react-router-dom'
import { IconShieldCheck, IconArrowRight, IconX, IconSearch, IconChevronUp, IconMessageCircle, IconBug, IconBulb, IconCommand, IconArrowNarrowRight } from '@tabler/icons-react' import { IconShieldCheck, IconArrowRight, IconX, IconSearch, IconChevronUp, IconMessageCircle, IconBug, IconBulb, IconCommand, IconArrowNarrowRight } from '@tabler/icons-react'
import { AuthProvider, useAuthState } from './hooks/useAuth' import { AuthProvider, useAuthState } from './hooks/useAuth'
@@ -45,6 +45,26 @@ import ProfilePage from './pages/ProfilePage'
import RecoverPage from './pages/RecoverPage' import RecoverPage from './pages/RecoverPage'
import EmbedBoard from './pages/EmbedBoard' import EmbedBoard from './pages/EmbedBoard'
import { api } from './lib/api' import { api } from './lib/api'
class ErrorBoundary extends Component<{ children: ReactNode }, { error: Error | null }> {
state = { error: null as Error | null }
static getDerivedStateFromError(error: Error) { return { error } }
componentDidCatch(error: Error, info: ErrorInfo) { console.error('React error:', error, info) }
render() {
if (this.state.error) {
return (
<div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'var(--bg)', padding: 24 }}>
<div style={{ textAlign: 'center', maxWidth: 400 }}>
<h1 style={{ fontFamily: 'var(--font-heading)', fontSize: '1.5rem', color: 'var(--text)', marginBottom: 8 }}>Something went wrong</h1>
<p style={{ color: 'var(--text-secondary)', fontSize: '0.875rem', marginBottom: 16 }}>{this.state.error.message}</p>
<button onClick={() => { this.setState({ error: null }); window.location.href = '/' }} className="btn btn-primary">Go home</button>
</div>
</div>
)
}
return this.props.children
}
}
import BoardIcon from './components/BoardIcon' import BoardIcon from './components/BoardIcon'
import StatusBadge from './components/StatusBadge' import StatusBadge from './components/StatusBadge'
import Avatar from './components/Avatar' import Avatar from './components/Avatar'
@@ -348,16 +368,16 @@ function SearchPage() {
} }
function RequireAdmin({ children }: { children: React.ReactNode }) { function RequireAdmin({ children }: { children: React.ReactNode }) {
const [ok, setOk] = useState<boolean | null>(null) const admin = useAdmin()
const nav = useNavigate() const nav = useNavigate()
useEffect(() => { useEffect(() => {
api.get('/admin/boards') if (!admin.loading && !admin.isAdmin) {
.then(() => setOk(true)) nav('/', { replace: true })
.catch(() => nav('/admin/login', { replace: true })) }
}, [nav]) }, [admin.loading, admin.isAdmin, nav])
if (!ok) return null if (admin.loading || !admin.isAdmin) return null
return <>{children}</> return <>{children}</>
} }
@@ -407,7 +427,7 @@ function AdminBanner() {
Admin panel <IconArrowRight size={11} stroke={2.5} /> Admin panel <IconArrowRight size={11} stroke={2.5} />
</Link> </Link>
<button <button
onClick={admin.exitAdminMode} onClick={() => { admin.exitAdminMode(); window.location.href = '/' }}
className="inline-flex items-center gap-1 px-2.5 py-1 rounded" className="inline-flex items-center gap-1 px-2.5 py-1 rounded"
style={{ style={{
color: 'var(--text-tertiary)', color: 'var(--text-tertiary)',
@@ -545,6 +565,7 @@ export default function App() {
}, [auth.user?.isPasskeyUser]) }, [auth.user?.isPasskeyUser])
return ( return (
<ErrorBoundary>
<BrandingProvider> <BrandingProvider>
<ToastProvider> <ToastProvider>
<ConfirmProvider> <ConfirmProvider>
@@ -565,5 +586,6 @@ export default function App() {
</ConfirmProvider> </ConfirmProvider>
</ToastProvider> </ToastProvider>
</BrandingProvider> </BrandingProvider>
</ErrorBoundary>
) )
} }

View File

@@ -41,8 +41,8 @@ export default function MySubmissions() {
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
useEffect(() => { useEffect(() => {
api.get<Post[]>('/me/posts') api.get<{ posts: Post[] }>('/me/posts')
.then(setPosts) .then((r) => setPosts(r.posts ?? []))
.catch(() => {}) .catch(() => {})
.finally(() => setLoading(false)) .finally(() => setLoading(false))
}, []) }, [])