50 lines
1.5 KiB
TypeScript
50 lines
1.5 KiB
TypeScript
import type { ReactNode } from 'react'
|
|
import { Link, Navigate, useLocation } from 'react-router-dom'
|
|
import { useAuth } from './AuthContext'
|
|
import type { AuthUser } from '../lib/types'
|
|
|
|
type RequiredRole = 'admin' | 'view' | 'management'
|
|
|
|
export function ManagementGate({
|
|
children,
|
|
role = 'management',
|
|
}: {
|
|
children: ReactNode
|
|
role?: RequiredRole
|
|
}) {
|
|
const { loading, user } = useAuth()
|
|
const location = useLocation()
|
|
|
|
if (loading) {
|
|
return <p className="text-slate-500 dark:text-slate-400">Checking sign-in…</p>
|
|
}
|
|
if (!user) {
|
|
return <Navigate to="/login" replace state={{ from: location.pathname + location.search }} />
|
|
}
|
|
if (!hasRole(user, role)) {
|
|
return <AccessDenied role={role} />
|
|
}
|
|
return <>{children}</>
|
|
}
|
|
|
|
function hasRole(user: AuthUser, role: RequiredRole): boolean {
|
|
if (role === 'admin') return user.canAdmin
|
|
if (role === 'view') return user.canView
|
|
return user.canAdmin || user.canView
|
|
}
|
|
|
|
function AccessDenied({ role }: { role: RequiredRole }) {
|
|
const roleName = role === 'management' ? 'management' : `${role} role`
|
|
return (
|
|
<div className="card mx-auto max-w-xl space-y-3 text-center">
|
|
<h1 className="text-xl font-bold">Access denied</h1>
|
|
<p className="text-slate-500 dark:text-slate-400">
|
|
Your account does not have the {roleName} permission required for this page.
|
|
</p>
|
|
<Link to="/" className="btn-primary mx-auto w-fit">
|
|
Back to forms
|
|
</Link>
|
|
</div>
|
|
)
|
|
}
|