41 lines
1.1 KiB
JavaScript
41 lines
1.1 KiB
JavaScript
import { Navigate, useLocation } from 'react-router-dom'
|
||
import { useAuth } from './AuthContext'
|
||
import Spinner from '../components/Spinner'
|
||
|
||
/**
|
||
* Route guard.
|
||
*
|
||
* Blocking on `loading` is deliberate: rendering the shell before /users/me
|
||
* resolves would paint the full 23-item nav and then remove items a moment
|
||
* later, which reads as a bug rather than as security.
|
||
*/
|
||
export default function RequireAuth({ children, permission }) {
|
||
const { status, can } = useAuth()
|
||
const location = useLocation()
|
||
|
||
if (status === 'anonymous') {
|
||
return <Navigate to="/auth/login" replace state={{ from: location }} />
|
||
}
|
||
if (status === 'error') {
|
||
return <Navigate to="/auth/login?expired=1" replace />
|
||
}
|
||
if (status === 'loading') {
|
||
return (
|
||
<div className="route-loading">
|
||
<Spinner label="Loading your workspace" />
|
||
</div>
|
||
)
|
||
}
|
||
if (permission && !can(permission)) return <Forbidden />
|
||
return children
|
||
}
|
||
|
||
export function Forbidden() {
|
||
return (
|
||
<div className="empty-state">
|
||
<h3>You don’t have access to this page</h3>
|
||
<p>Ask an administrator to grant your role the required permission.</p>
|
||
</div>
|
||
)
|
||
}
|