diff --git a/frontend/src/components/AuthGate.tsx b/frontend/src/components/AuthGate.tsx
index c6afac4..54b46c8 100644
--- a/frontend/src/components/AuthGate.tsx
+++ b/frontend/src/components/AuthGate.tsx
@@ -24,7 +24,7 @@ export default function AuthGate({ children }: { children: React.ReactNode }) {
if (!r.ok) throw new Error(`Auth check failed: ${r.status}`)
return r.json()
})
- .then(data => { if (data) setUser(data.user) })
+ .then(data => { if (data) setUser(data) })
.catch(err => setError(err.message))
}, [])
diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx
index 87e428b..a434111 100644
--- a/frontend/src/components/Layout.tsx
+++ b/frontend/src/components/Layout.tsx
@@ -1,12 +1,13 @@
import { NavLink } from 'react-router-dom'
import { BedDouble, Settings, LogOut } from 'lucide-react'
import { useAuth } from './AuthGate'
+import { can } from '../types'
const ICON_PROPS = { size: 16, strokeWidth: 1.75 }
export default function Layout({ children }: { children: React.ReactNode }) {
const { user } = useAuth()
- const hasCap = (cap: string) => user.caps.includes(`room-planner:${cap}`)
+ const hasCap = (cap: string) => can(user, cap)
return (
diff --git a/frontend/src/components/RoomModal.tsx b/frontend/src/components/RoomModal.tsx
index 0340511..cee8a2d 100644
--- a/frontend/src/components/RoomModal.tsx
+++ b/frontend/src/components/RoomModal.tsx
@@ -1,5 +1,6 @@
import { useState, useCallback } from 'react'
import { X, CheckSquare, Square, Package, ClipboardList } from 'lucide-react'
+import { can } from '../types'
import type { RoomData, TaskData, AppConfig, User } from '../types'
import { bookingStatusColor, isDirty } from '../lib/booking-flow'
import { detectTwin } from '../lib/twin-detect'
@@ -36,7 +37,7 @@ export default function RoomModal({ room, viewDate, config, user, onClose, onTas
const [statusLoading, setStatusLoading] = useState(false)
const [notesTab, setNotesTab] = useState<'booking' | 'departing'>('booking')
- const hasCap = (cap: string) => user.caps.includes(`room-planner:${cap}`)
+ const hasCap = (cap: string) => can(user, cap)
const todayTasks = room.tasks.filter(t => {
const d = t.task_when_date || t.task_period_from
diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx
index 52edd8d..9e91096 100644
--- a/frontend/src/pages/Settings.tsx
+++ b/frontend/src/pages/Settings.tsx
@@ -1,11 +1,12 @@
import { useState, useEffect } from 'react'
import { useAuth } from '../components/AuthGate'
+import { can } from '../types'
import type { AppConfig, TaskDisplayConfig } from '../types'
import * as api from '../api'
export default function Settings() {
const { user } = useAuth()
- const hasCap = (cap: string) => user.caps.includes(`room-planner:${cap}`)
+ const hasCap = (cap: string) => can(user, cap)
const [config, setConfig] = useState
(null)
const [taskTypes, setTaskTypes] = useState>([])
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
index 4f2ba03..1ad644b 100644
--- a/frontend/src/types.ts
+++ b/frontend/src/types.ts
@@ -126,11 +126,15 @@ export interface AppConfig {
}
export interface User {
- id: number
+ user_id: number
name: string
email: string
- apps: string[]
- caps: string[]
+ is_admin: boolean
+ caps: string[] // bare slugs — verify?app=room-planner strips the prefix
+}
+
+export function can(user: User, cap: string): boolean {
+ return user.is_admin || user.caps.includes(cap)
}
export type PlannerCap =