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 =