From 0ccc66833d1550542df9df54fe3bbcfd13287849 Mon Sep 17 00:00:00 2001 From: Matt Ciaccio Date: Mon, 27 Apr 2026 23:44:04 +0200 Subject: [PATCH] fix(ui): admin settings loading-loop, real user name, expanded admin nav SettingsFormCard - Parent components pass `FIELDS.slice(...)` inline, so the prop reference changes on every render. The fetch callback's useCallback re-created itself, useEffect re-fired, and loading flicker meant the form never rendered. Capture fields in a ref so the callback is stable. Sidebar - Show real user name + avatar initial from session/profile, replacing the hardcoded "User Name" / "U" placeholder. - Default the admin-section to expanded so its items are reachable on first page load (was collapsed behind a chevron). Dashboard layout - Pass {name, email} from the session/profile through to . Co-Authored-By: Claude Opus 4.7 (1M context) --- src/app/(dashboard)/layout.tsx | 9 ++++++++- src/components/admin/shared/settings-form-card.tsx | 13 ++++++++++--- src/components/layout/sidebar.tsx | 12 ++++++++---- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/app/(dashboard)/layout.tsx b/src/app/(dashboard)/layout.tsx index e7ec176..6b2e4ef 100644 --- a/src/app/(dashboard)/layout.tsx +++ b/src/app/(dashboard)/layout.tsx @@ -38,7 +38,14 @@ export default async function DashboardLayout({ children }: { children: React.Re
- +
{children}
diff --git a/src/components/admin/shared/settings-form-card.tsx b/src/components/admin/shared/settings-form-card.tsx index b8fe171..f4b899a 100644 --- a/src/components/admin/shared/settings-form-card.tsx +++ b/src/components/admin/shared/settings-form-card.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useCallback, useEffect, useState, type ReactNode } from 'react'; +import { useCallback, useEffect, useRef, useState, type ReactNode } from 'react'; import { Loader2 } from 'lucide-react'; import { toast } from 'sonner'; @@ -67,12 +67,19 @@ export function SettingsFormCard({ title, description, fields, extra }: Settings const [values, setValues] = useState>({}); const [originals, setOriginals] = useState>({}); + // Parent components often pass `FIELDS.slice(0, 5)` directly, so the prop + // reference changes on every render. Capture it in a ref so the fetch + // callback can read the current list without being re-created and looping + // through useEffect forever. + const fieldsRef = useRef(fields); + fieldsRef.current = fields; + const fetchValues = useCallback(async () => { setLoading(true); try { const res = await apiFetch('/api/v1/admin/settings'); const next: Record = {}; - for (const field of fields) { + for (const field of fieldsRef.current) { const port = res.data.portSettings.find((s) => s.key === field.key); const global = res.data.globalSettings.find((s) => s.key === field.key); next[field.key] = port?.value ?? global?.value ?? field.defaultValue; @@ -82,7 +89,7 @@ export function SettingsFormCard({ title, description, fields, extra }: Settings } finally { setLoading(false); } - }, [fields]); + }, []); useEffect(() => { void fetchValues(); diff --git a/src/components/layout/sidebar.tsx b/src/components/layout/sidebar.tsx index b7e63e4..15ffec6 100644 --- a/src/components/layout/sidebar.tsx +++ b/src/components/layout/sidebar.tsx @@ -40,6 +40,7 @@ import type { Role } from '@/lib/db/schema/users'; interface SidebarProps { portRoles: (UserPortRole & { port: { id: string; slug: string; name: string }; role: Role })[]; isSuperAdmin?: boolean; + user?: { name: string; email: string }; } interface NavItem { @@ -178,6 +179,7 @@ function SidebarContent({ hasAdminAccess, hasMarinaAccess, hasResidentialAccess, + user, }: { collapsed: boolean; portSlug: string | undefined; @@ -185,9 +187,10 @@ function SidebarContent({ hasAdminAccess: boolean; hasMarinaAccess: boolean; hasResidentialAccess: boolean; + user?: SidebarProps['user']; }) { const pathname = usePathname(); - const [adminExpanded, setAdminExpanded] = useState(false); + const [adminExpanded, setAdminExpanded] = useState(true); const sections = buildNavSections(portSlug); function isActive(href: string, exact?: boolean): boolean { @@ -285,11 +288,11 @@ function SidebarContent({ - U + {(user?.name ?? 'U').slice(0, 1).toUpperCase()}
-

User Name

+

{user?.name ?? 'User'}

s.sidebarCollapsed); const toggleSidebar = useUIStore((s) => s.toggleSidebar); const currentPortSlug = useUIStore((s) => s.currentPortSlug); @@ -341,6 +344,7 @@ export function Sidebar({ portRoles, isSuperAdmin = false }: SidebarProps) { hasAdminAccess={hasAdminAccess} hasMarinaAccess={hasMarinaAccess} hasResidentialAccess={hasResidentialAccess} + user={user} /> {/* Collapse toggle */}