From 27286e23dbfe1352f127d287ab5ef569562b8441 Mon Sep 17 00:00:00 2001 From: Vadym Samoilenko Date: Fri, 1 May 2026 17:45:49 +0100 Subject: [PATCH] fix(sidebar): show org settings link for platform admins without memberships MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Platform admins query GET /organizations (not memberships) so currentOrgId was always null — hiding the Settings nav link. Now falls back to the first org from useOrganizations() for admins, gated with enabled:isPlatformAdmin to avoid 403 for non-admin roles. Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/components/Layout/Sidebar.tsx | 76 +++++++++++++++------- frontend/src/hooks/useClients.ts | 3 +- 2 files changed, 54 insertions(+), 25 deletions(-) diff --git a/frontend/src/components/Layout/Sidebar.tsx b/frontend/src/components/Layout/Sidebar.tsx index de8ecd7..ab03591 100644 --- a/frontend/src/components/Layout/Sidebar.tsx +++ b/frontend/src/components/Layout/Sidebar.tsx @@ -1,6 +1,6 @@ import { Link, useLocation, useParams } from 'react-router-dom'; import { useAuthStore } from '../../lib/auth'; -import { useMyMemberships } from '../../hooks/useClients'; +import { useMyMemberships, useOrganizations } from '../../hooks/useClients'; import { useJobs, useBriefs, useMyQCQueueCount } from '../../hooks/useJob'; interface SidebarItem { @@ -24,6 +24,7 @@ export function Sidebar({ onMobileClose }: SidebarProps) { const isQCRole = ['linguist', 'reviewer', 'production', 'admin'].includes(user?.role || ''); const isPMOrAdmin = ['project_manager', 'admin'].includes(user?.role || ''); const isAdminOrProduction = ['production', 'admin'].includes(user?.role || ''); + const isPlatformAdmin = user?.role === 'admin'; const myQCCount = useMyQCQueueCount(isQCRole); const { data: finalData } = useJobs( @@ -36,18 +37,20 @@ export function Sidebar({ onMobileClose }: SidebarProps) { ); const { data: allBriefs = [] } = useBriefs(); + const { data: allOrgs = [] } = useOrganizations({ enabled: isPlatformAdmin }); const qcBadge = isQCRole ? myQCCount : 0; const finalBadge = isPMOrAdmin ? (finalData?.total || 0) : 0; const failuresBadge = isAdminOrProduction ? (failuresData?.total || 0) : 0; const briefsBadge = allBriefs.filter(b => b.status === 'submitted').length; - // Determine current org ID from route params or first membership. + // Determine current org ID from route params, first membership, or first org (admin fallback). // The route param :orgSlug actually carries the organization _id (hex string), // not the human-readable slug — the backend queries memberships by organization_id. const currentOrgId = params.orgSlug || - (memberships.length === 1 ? memberships[0].organization_id : null); + (memberships.length > 0 ? memberships[0].organization_id : null) || + (isPlatformAdmin && allOrgs.length > 0 ? allOrgs[0].id : null); const sidebarItems: SidebarItem[] = [ { @@ -152,29 +155,54 @@ export function Sidebar({ onMobileClose }: SidebarProps) { - {/* Org Switcher — shown when user has memberships */} - {memberships.length > 0 && ( + {/* Org Switcher — shown when user has memberships or is platform admin with orgs */} + {(memberships.length > 0 || (isPlatformAdmin && allOrgs.length > 0)) && (
- {memberships.length === 1 ? ( -
- {memberships[0].organization_name} - · {memberships[0].role_in_org} -
+ {memberships.length > 0 ? ( + memberships.length === 1 ? ( +
+ {memberships[0].organization_name} + · {memberships[0].role_in_org} +
+ ) : ( + + ) ) : ( - + /* Platform admin with no personal memberships — show org switcher from full org list */ + allOrgs.length === 1 ? ( +
+ {allOrgs[0].name} + · admin +
+ ) : ( + + ) )}
)} diff --git a/frontend/src/hooks/useClients.ts b/frontend/src/hooks/useClients.ts index 5b3bb30..c378410 100644 --- a/frontend/src/hooks/useClients.ts +++ b/frontend/src/hooks/useClients.ts @@ -223,10 +223,11 @@ export function useArchiveProject(clientId: string) { // ── Organizations (SaaS) ────────────────────────────────────────────────────── -export function useOrganizations() { +export function useOrganizations(options?: { enabled?: boolean }) { return useQuery({ queryKey: ['organizations'], queryFn: () => apiClient.listOrganizations(), + enabled: options?.enabled !== false, }); }