"use client"; import React, { useEffect } from "react"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { signOut, useSession } from "next-auth/react"; import { BarChart3, Building, ChevronDown, FlaskConical, Home, LogOut, MoreHorizontal, Puzzle, Settings, Users, UserCheck, TestTube, } from "lucide-react"; import { useSidebar } from "~/components/ui/sidebar"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "~/components/ui/dropdown-menu"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "~/components/ui/tooltip"; import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarRail, } from "~/components/ui/sidebar"; import { Avatar, AvatarImage, AvatarFallback } from "~/components/ui/avatar"; import { Logo } from "~/components/ui/logo"; import { useStudyManagement } from "~/hooks/useStudyManagement"; import { handleAuthError, isAuthError } from "~/lib/auth-error-handler"; import { api } from "~/trpc/react"; // Navigation items const navigationItems = [ { title: "Overview", url: "/dashboard", icon: Home, }, { title: "Studies", url: "/studies", icon: Building, }, { title: "Experiments", url: "/experiments", icon: FlaskConical, }, { title: "Participants", url: "/participants", icon: Users, }, { title: "Trials", url: "/trials", icon: TestTube, }, { title: "Plugins", url: "/plugins", icon: Puzzle, }, { title: "Analytics", url: "/analytics", icon: BarChart3, }, ]; const adminItems = [ { title: "Administration", url: "/admin", icon: UserCheck, }, ]; interface AppSidebarProps extends React.ComponentProps { userRole?: string; } export function AppSidebar({ userRole = "researcher", ...props }: AppSidebarProps) { const { data: session } = useSession(); const pathname = usePathname(); const isAdmin = userRole === "administrator"; const { state: sidebarState } = useSidebar(); const isCollapsed = sidebarState === "collapsed"; const { selectedStudyId, userStudies, selectStudy, refreshStudyData } = useStudyManagement(); // Debug API call const { data: debugData } = api.dashboard.debug.useQuery(undefined, { enabled: process.env.NODE_ENV === "development", staleTime: 1000 * 30, // 30 seconds }); type Study = { id: string; name: string; }; // Filter navigation items based on study selection const availableNavigationItems = navigationItems.filter((item) => { // These items are always available if (item.url === "/dashboard" || item.url === "/studies") { return true; } // These items require a selected study return selectedStudyId !== null; }); const handleSignOut = async () => { await signOut({ callbackUrl: "/" }); }; const handleStudySelect = async (studyId: string) => { try { await selectStudy(studyId); } catch (error) { console.error("Failed to select study:", error); // Handle auth errors first if (isAuthError(error)) { await handleAuthError(error, "Session expired while selecting study"); return; } // If study selection fails (e.g., study not found), clear the selection await selectStudy(null); } }; const selectedStudy = userStudies.find( (study: Study) => study.id === selectedStudyId, ); // Debug logging for study data React.useEffect(() => { console.log("Sidebar debug - User studies:", { count: userStudies.length, studies: userStudies.map((s) => ({ id: s.id, name: s.name })), selectedStudyId, selectedStudy: selectedStudy ? { id: selectedStudy.id, name: selectedStudy.name } : null, }); }, [userStudies, selectedStudyId, selectedStudy]); // If we have a selectedStudyId but can't find the study, clear the selection React.useEffect(() => { if (selectedStudyId && userStudies.length > 0 && !selectedStudy) { console.warn( "Selected study not found in user studies, clearing selection", ); void selectStudy(null); } }, [selectedStudyId, userStudies, selectedStudy, selectStudy]); // Auto-refresh studies list when component mounts to catch external changes useEffect(() => { const interval = setInterval(() => { void (async () => { try { await refreshStudyData(); } catch (error) { console.error("Failed to refresh study data:", error); if (isAuthError(error)) { void handleAuthError(error, "Session expired during data refresh"); } } })(); }, 30000); // Refresh every 30 seconds return () => clearInterval(interval); }, [refreshStudyData]); // Show debug info in development const showDebug = process.env.NODE_ENV === "development"; return ( {/* Study Selector */} Active Study {isCollapsed ? ( {selectedStudy?.name ?? "Select Study"} Studies {userStudies.map((study: Study) => ( handleStudySelect(study.id)} className="cursor-pointer" > {study.name} ))} {selectedStudyId && ( { await selectStudy(null); }} > Clear selection )} Create study {selectedStudy?.name ?? "Select Study"} ) : ( {selectedStudy?.name ?? "Select Study"} Studies {userStudies.map((study: Study) => ( handleStudySelect(study.id)} className="cursor-pointer" > {study.name} ))} {selectedStudyId && ( { await selectStudy(null); }} > Clear selection )} Create study )} {/* Main Navigation */} Research {availableNavigationItems.map((item) => { const isActive = pathname === item.url || (item.url !== "/dashboard" && pathname.startsWith(item.url)); const menuButton = ( {item.title} ); return ( {isCollapsed ? ( {menuButton} {item.title} ) : ( menuButton )} ); })} {/* Study-specific items hint */} {!selectedStudyId && !isCollapsed && (
Select a study to access experiments, participants, trials, and analytics.
)} {/* Admin Section */} {isAdmin && ( Administration {adminItems.map((item) => { const isActive = pathname.startsWith(item.url); const menuButton = ( {item.title} ); return ( {isCollapsed ? ( {menuButton} {item.title} ) : ( menuButton )} ); })} )}
{/* Debug Info */} {showDebug && ( Debug Info
Session: {session?.user?.email ?? "No session"}
Role: {userRole ?? "No role"}
Studies: {userStudies.length}
Selected: {selectedStudy?.name ?? "None"}
Auth: {session ? "✓" : "✗"}
{debugData && ( <>
DB User: {debugData.user?.email ?? "None"}
System Roles: {debugData.systemRoles.join(", ") || "None"}
Memberships: {debugData.studyMemberships.length}
All Studies: {debugData.allStudies.length}
Session ID: {debugData.session.userId.slice(0, 8)}...
)}
)} {isCollapsed ? ( {( session?.user?.name ?? session?.user?.email ?? "U" ) .charAt(0) .toUpperCase()}
{session?.user?.name ?? "User"} {session?.user?.email ?? ""}
{( session?.user?.name ?? session?.user?.email ?? "U" ) .charAt(0) .toUpperCase()}
{session?.user?.name ?? "User"} {session?.user?.email ?? ""}
Profile & Settings Sign out
{session?.user?.name ?? "User Menu"}
) : ( {(session?.user?.name ?? session?.user?.email ?? "U") .charAt(0) .toUpperCase()}
{session?.user?.name ?? "User"} {session?.user?.email ?? ""}
{(session?.user?.name ?? session?.user?.email ?? "U") .charAt(0) .toUpperCase()}
{session?.user?.name ?? "User"} {session?.user?.email ?? ""}
Profile & Settings Sign out
)}
); }