From 5a2dab637c4b2475f42d21e9880cd08c2566e1d9 Mon Sep 17 00:00:00 2001 From: jtricerolph Date: Fri, 3 Jul 2026 16:25:20 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20AuthGate=20stuck=20on=20Loading=20?= =?UTF-8?q?=E2=80=94=20verify=20returns=20flat=20user=20object?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The auth /verify endpoint returns { user_id, name, email, is_admin, caps } directly, not wrapped in a user key, so data.user was undefined and the gate never rendered. Also: caps come back as bare slugs when ?app= is passed, so the prefixed room-planner:cap checks always failed — replaced with the standard can(user, cap) helper honouring is_admin. Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/components/AuthGate.tsx | 2 +- frontend/src/components/Layout.tsx | 3 ++- frontend/src/components/RoomModal.tsx | 3 ++- frontend/src/pages/Settings.tsx | 3 ++- frontend/src/types.ts | 10 +++++++--- 5 files changed, 14 insertions(+), 7 deletions(-) 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 =