feat(l1): usePermissions extensions for l1_tech + coverage flag

Adds 'l1_tech' to the AccountRole union, includes can_cover_l1 on the User
type, and exposes isL1Tech / canCoverL1 / canUseL1Surface /
canUseEngineerSurface from usePermissions. Existing isEngineer/isOwner/
etc. flags unchanged in semantics.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-28 13:54:52 -04:00
parent 465b8ff880
commit 4586010b87
3 changed files with 30 additions and 10 deletions

View File

@@ -32,9 +32,10 @@ export function ProtectedRoute({ requiredRole, children }: ProtectedRouteProps)
if (requiredRole) {
const ROLE_HIERARCHY: Record<EffectiveRole, number> = {
super_admin: 4,
owner: 3,
engineer: 2,
super_admin: 5,
owner: 4,
engineer: 3,
l1_tech: 2,
viewer: 1,
}
if (ROLE_HIERARCHY[effectiveRole] < ROLE_HIERARCHY[requiredRole]) {

View File

@@ -1,19 +1,20 @@
/**
* Centralized permissions hook for ResolutionFlow.
*
* Role hierarchy: super_admin > owner > engineer > viewer
* Role hierarchy: super_admin > owner > engineer > l1_tech > viewer
*
* Mirrors backend logic in backend/app/core/permissions.py
*/
import { useAuthStore } from '@/store/authStore'
import type { User } from '@/types'
export type EffectiveRole = 'super_admin' | 'owner' | 'engineer' | 'viewer'
export type EffectiveRole = 'super_admin' | 'owner' | 'engineer' | 'l1_tech' | 'viewer'
const ROLE_HIERARCHY: Record<EffectiveRole, number> = {
super_admin: 4,
owner: 3,
engineer: 2,
super_admin: 5,
owner: 4,
engineer: 3,
l1_tech: 2,
viewer: 1,
}
@@ -21,7 +22,9 @@ function getEffectiveRole(user: User | null): EffectiveRole {
if (!user) return 'viewer'
if (user.is_super_admin) return 'super_admin'
if (user.account_role === 'owner') return 'owner'
return user.role as EffectiveRole
if (user.account_role === 'engineer') return 'engineer'
if (user.account_role === 'l1_tech') return 'l1_tech'
return 'viewer'
}
function hasMinimumRole(user: User | null, minimum: EffectiveRole): boolean {
@@ -39,8 +42,23 @@ export function usePermissions() {
isSuperAdmin: effectiveRole === 'super_admin',
isAccountOwner: effectiveRole === 'owner' || effectiveRole === 'super_admin',
isEngineer: hasMinimumRole(user, 'engineer'),
isL1Tech: effectiveRole === 'l1_tech',
isViewer: effectiveRole === 'viewer',
// L1 workspace permissions
canCoverL1: (
Boolean(user?.can_cover_l1) ||
effectiveRole === 'owner' ||
effectiveRole === 'super_admin'
),
canUseL1Surface: (
effectiveRole === 'l1_tech' ||
effectiveRole === 'owner' ||
effectiveRole === 'super_admin' ||
(user?.account_role === 'engineer' && Boolean(user?.can_cover_l1))
),
canUseEngineerSurface: hasMinimumRole(user, 'engineer'),
// Content creation permissions
canCreateTrees: hasMinimumRole(user, 'engineer'),
canCreateSteps: hasMinimumRole(user, 'engineer'),

View File

@@ -9,7 +9,8 @@ export interface User {
is_active: boolean
must_change_password: boolean
account_id: string | null
account_role: 'owner' | 'engineer' | 'viewer' | null
account_role: 'owner' | 'engineer' | 'l1_tech' | 'viewer' | null
can_cover_l1: boolean
team_id: string | null
created_at: string
last_login: string | null