1818 * (`/environments/:envId/:package/*`), the package-scoped {@link AppSidebar}
1919 * takes over instead. The two sidebars are mutually exclusive and share the
2020 * same `SidebarProvider` in `routes/__root.tsx`.
21+ *
22+ * Organization switching is now handled in the TopBar, so this sidebar only
23+ * focuses on functional navigation.
2124 */
2225
2326import { useMemo } from 'react' ;
2427import { Link , useLocation } from '@tanstack/react-router' ;
2528import {
26- Building2 ,
27- Check ,
28- ChevronsUpDown ,
29- Plus ,
3029 Boxes ,
3130 Globe ,
3231 Package as PackageIcon ,
@@ -36,126 +35,16 @@ import {
3635import {
3736 Sidebar ,
3837 SidebarContent ,
38+ SidebarFooter ,
3939 SidebarGroup ,
4040 SidebarGroupContent ,
41- SidebarHeader ,
4241 SidebarMenu ,
4342 SidebarMenuButton ,
4443 SidebarMenuItem ,
4544 SidebarSeparator ,
45+ SidebarTrigger ,
4646} from '@/components/ui/sidebar' ;
47- import {
48- DropdownMenu ,
49- DropdownMenuContent ,
50- DropdownMenuItem ,
51- DropdownMenuLabel ,
52- DropdownMenuSeparator ,
53- DropdownMenuTrigger ,
54- } from '@/components/ui/dropdown-menu' ;
55- import { useOrganizations , useSession } from '@/hooks/useSession' ;
56- import { toast } from '@/hooks/use-toast' ;
57-
58- /** Header: active organization + switcher. */
59- function OrgHeader ( ) {
60- const { organizations, loading, reload } = useOrganizations ( ) ;
61- const { session, setActiveOrganization } = useSession ( ) ;
62- const activeId = session ?. activeOrganizationId ?? undefined ;
63- const active = useMemo (
64- ( ) => organizations . find ( ( o ) => o . id === activeId ) ?? null ,
65- [ organizations , activeId ] ,
66- ) ;
67-
68- const handleSelect = async ( id : string ) => {
69- if ( id === activeId ) return ;
70- try {
71- await setActiveOrganization ( id ) ;
72- await reload ( ) ;
73- toast ( { title : 'Organization switched' } ) ;
74- } catch ( err ) {
75- toast ( {
76- title : 'Failed to switch organization' ,
77- description : ( err as Error ) . message ,
78- variant : 'destructive' ,
79- } ) ;
80- }
81- } ;
82-
83- return (
84- < SidebarHeader className = "border-b" >
85- < SidebarMenu >
86- < SidebarMenuItem >
87- < DropdownMenu >
88- < DropdownMenuTrigger asChild >
89- < SidebarMenuButton
90- size = "lg"
91- className = "data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
92- >
93- < div className = "flex aspect-square size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground" >
94- < Building2 className = "size-4" />
95- </ div >
96- < div className = "grid flex-1 text-left text-sm leading-tight" >
97- < span className = "truncate font-semibold" >
98- { active ?. name ?? ( loading ? 'Loading…' : 'Select organization' ) }
99- </ span >
100- { active ?. slug && (
101- < span className = "truncate text-xs text-muted-foreground" >
102- { active . slug }
103- </ span >
104- ) }
105- </ div >
106- < ChevronsUpDown className = "ml-auto size-4 opacity-60" />
107- </ SidebarMenuButton >
108- </ DropdownMenuTrigger >
109- < DropdownMenuContent
110- className = "w-[--radix-dropdown-menu-trigger-width] min-w-56"
111- align = "start"
112- side = "bottom"
113- sideOffset = { 4 }
114- >
115- < DropdownMenuLabel className = "text-[10px] uppercase tracking-wider text-muted-foreground" >
116- Organizations
117- </ DropdownMenuLabel >
118- { organizations . map ( ( org ) => (
119- < DropdownMenuItem
120- key = { org . id }
121- onSelect = { ( e ) => {
122- e . preventDefault ( ) ;
123- handleSelect ( org . id ) ;
124- } }
125- className = "gap-2"
126- >
127- < div className = "min-w-0 flex-1" >
128- < div className = "truncate font-medium" > { org . name } </ div >
129- { org . slug && (
130- < code className = "font-mono text-[11px] text-muted-foreground" >
131- { org . slug }
132- </ code >
133- ) }
134- </ div >
135- { org . id === activeId && (
136- < Check className = "size-3.5 text-primary" />
137- ) }
138- </ DropdownMenuItem >
139- ) ) }
140- < DropdownMenuSeparator />
141- < DropdownMenuItem asChild >
142- < Link to = "/orgs/new" className = "gap-2" >
143- < Plus className = "size-3.5" />
144- New organization…
145- </ Link >
146- </ DropdownMenuItem >
147- < DropdownMenuItem asChild >
148- < Link to = "/orgs" className = "gap-2 text-muted-foreground" >
149- Manage organizations
150- </ Link >
151- </ DropdownMenuItem >
152- </ DropdownMenuContent >
153- </ DropdownMenu >
154- </ SidebarMenuItem >
155- </ SidebarMenu >
156- </ SidebarHeader >
157- ) ;
158- }
47+ import { useSession } from '@/hooks/useSession' ;
15948
16049/**
16150 * Extract the `:envId` segment from the current pathname when the user is
@@ -184,7 +73,6 @@ export function GlobalSidebar() {
18473
18574 return (
18675 < Sidebar collapsible = "icon" >
187- < OrgHeader />
18876 < SidebarContent >
18977 < SidebarGroup >
19078 < SidebarGroupContent >
@@ -271,6 +159,13 @@ export function GlobalSidebar() {
271159 </ SidebarGroupContent >
272160 </ SidebarGroup >
273161 </ SidebarContent >
162+ < SidebarFooter >
163+ < SidebarMenu >
164+ < SidebarMenuItem >
165+ < SidebarTrigger />
166+ </ SidebarMenuItem >
167+ </ SidebarMenu >
168+ </ SidebarFooter >
274169 </ Sidebar >
275170 ) ;
276171}
0 commit comments