fix: AuthGate stuck on Loading — verify returns flat user object
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 <noreply@anthropic.com>
This commit is contained in:
parent
2993dfa216
commit
5a2dab637c
5 changed files with 14 additions and 7 deletions
|
|
@ -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))
|
||||
}, [])
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="app-shell">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<AppConfig | null>(null)
|
||||
const [taskTypes, setTaskTypes] = useState<Array<{ id: string; name: string }>>([])
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue