11"use client" ;
22
33import { useRouter } from "@bprogress/next/app" ;
4- import { ChevronDown , Clock } from "lucide-react" ;
4+ import { ChevronDown , Clock , Plus } from "lucide-react" ;
55import Link from "next/link" ;
66import { forwardRef , useEffect , useImperativeHandle , useMemo , useRef , useState } from "react" ;
77
88import type { Auth0Organization , Auth0OrgName } from "@/app/services/auth0/types" ;
9-
9+ import { CreateOrganizationModal } from "@/components/auth/CreateOrganizationModal" ;
1010import { Button } from "@/components/ui/button" ;
1111import { SearchableDropdown , type SearchableDropdownRef } from "@/components/ui/SearchableDropdown" ;
1212import { WrapWithKeyboardShortcut } from "@/components/ui/WrapWithKeyboardShortcut" ;
@@ -28,8 +28,9 @@ const OrgSwitcherClientInternal = forwardRef<
2828 organizations : Auth0Organization [ ] ;
2929 currentOrgName ?: Auth0OrgName ;
3030 isFernAdmin : boolean ;
31+ accessToken : string ;
3132 }
32- > ( ( { organizations, currentOrgName, isFernAdmin } , ref ) => {
33+ > ( ( { organizations, currentOrgName, isFernAdmin, accessToken } , ref ) => {
3334 const dropdownRef = useRef < SearchableDropdownRef > ( null ) ;
3435
3536 useImperativeHandle ( ref , ( ) => ( {
@@ -41,6 +42,7 @@ const OrgSwitcherClientInternal = forwardRef<
4142 const [ localOrgName , setLocalOrgName ] = useState ( currentOrgName ) ;
4243 const [ searchTerm , setSearchTerm ] = useState ( "" ) ;
4344 const [ recentOrgNames , setRecentOrgNames ] = useState < Auth0OrgName [ ] > ( [ ] ) ;
45+ const [ showOrgModal , setShowOrgModal ] = useState ( false ) ;
4446
4547 useEffect ( ( ) => {
4648 setLocalOrgName ( orgName ) ;
@@ -119,84 +121,113 @@ const OrgSwitcherClientInternal = forwardRef<
119121
120122 const currentOrg = organizations . find ( ( org ) => org . name === localOrgName ) ;
121123
124+ const shouldShowSearch = organizations . length > 10 ;
125+
122126 return (
123- < SearchableDropdown
124- ref = { dropdownRef }
125- items = { filteredOrganizationsWithAdmin }
126- searchTerm = { searchTerm }
127- onSearchChange = { setSearchTerm }
128- onSelect = { ( org ) => onSelectOrg ( org . name ) }
129- searchPlaceholder = "Search organizations..."
130- emptyMessage = "No organizations found"
131- getItemKey = { ( org ) => org . id }
132- shouldShowSearch = { organizations . length > 10 }
133- renderItem = { ( organization , onSelectFromDropdown , isHighlighted ) => {
134- // Check if this is the admin option
135- const isAdminOption = "__isAdminOption" in organization && organization . __isAdminOption ;
136-
137- if ( isAdminOption ) {
127+ < >
128+ < SearchableDropdown
129+ ref = { dropdownRef }
130+ items = { filteredOrganizationsWithAdmin }
131+ searchTerm = { searchTerm }
132+ onSearchChange = { setSearchTerm }
133+ onSelect = { ( org ) => onSelectOrg ( org . name ) }
134+ searchPlaceholder = "Search organizations..."
135+ emptyMessage = "No organizations found"
136+ getItemKey = { ( org ) => org . id }
137+ shouldShowSearch = { shouldShowSearch }
138+ searchRightContent = {
139+ < Button
140+ size = "sm"
141+ variant = "ghost"
142+ className = "h-[38px] w-[38px] shrink-0 p-0"
143+ onClick = { ( ) => setShowOrgModal ( true ) }
144+ >
145+ < Plus className = "h-4 w-4" />
146+ < span className = "sr-only" > Create organization</ span >
147+ </ Button >
148+ }
149+ headerContent = {
150+ ! shouldShowSearch ? (
151+ < Button
152+ size = "sm"
153+ variant = "ghost"
154+ className = "flex w-full items-center justify-start gap-2 px-2"
155+ onClick = { ( ) => setShowOrgModal ( true ) }
156+ >
157+ < Plus className = "h-4 w-4" />
158+ < span > Create new org</ span >
159+ </ Button >
160+ ) : undefined
161+ }
162+ renderItem = { ( organization , onSelectFromDropdown , isHighlighted ) => {
163+ // Check if this is the admin option
164+ const isAdminOption = "__isAdminOption" in organization && organization . __isAdminOption ;
165+
166+ if ( isAdminOption ) {
167+ return (
168+ < button
169+ type = "button"
170+ className = { cn (
171+ "flex w-full cursor-pointer items-center justify-between px-3 rounded py-1.5 text-left text-sm focus:outline-none flex-wrap text-muted-foreground" ,
172+ isHighlighted ? "bg-gray-300" : "hover:bg-gray-300"
173+ ) }
174+ onClick = { ( ) => {
175+ onSelectOrg ( organization . name ) ;
176+ onSelectFromDropdown ( ) ;
177+ } }
178+ >
179+ < div className = "flex flex-1 items-center gap-2" >
180+ Go to < code className = "text-wrap max-w-[170px]" > { organization . name } </ code >
181+ </ div >
182+ < div className = "flex-shrink-0 justify-end" > →</ div >
183+ </ button >
184+ ) ;
185+ }
186+
187+ // Regular organization
188+ const isRecent = recentOrgNames . includes ( organization . name ) ;
189+ const isCurrent = organization . name === localOrgName ;
138190 return (
139- < button
140- type = "button"
191+ < Link
141192 className = { cn (
142- "flex w-full cursor-pointer items-center justify-between px-3 rounded py-1.5 text-left text-sm focus:outline-none flex-wrap text-muted-foreground " ,
143- isHighlighted ? "bg-gray-300" : "hover:bg-gray-300"
193+ "flex w-full cursor-pointer items-center justify-between px-3 rounded py-1.5 text-left text-sm focus:outline-none" ,
194+ searchTerm . length > 0 && isHighlighted ? "bg-gray-300" : "hover:bg-gray-300"
144195 ) }
196+ href = { getPathnameForOrg ( organization . name ) }
197+ onMouseOver = { ( ) => {
198+ onHoverOrg ( organization . name ) ;
199+ } }
145200 onClick = { ( ) => {
146- onSelectOrg ( organization . name ) ;
201+ if ( isCurrent ) {
202+ return ;
203+ }
204+ onClickOrg ( organization . name ) ;
147205 onSelectFromDropdown ( ) ;
148206 } }
149207 >
150- < div className = "flex flex-1 items-center gap-2" >
151- Go to < code className = "text-wrap max-w-[170px]" > { organization . name } </ code >
208+ < div className = "flex items-center gap-2" >
209+ < OrgLogo organization = { organization } />
210+ { getOrgDisplayName ( organization ) }
152211 </ div >
153- < div className = "flex-shrink-0 justify-end" > → </ div >
154- </ button >
212+ { isRecent && < Clock className = "h-4 w-4 text-gray-600" /> }
213+ </ Link >
155214 ) ;
156- }
157-
158- // Regular organization
159- const isRecent = recentOrgNames . includes ( organization . name ) ;
160- const isCurrent = organization . name === localOrgName ;
161- return (
162- < Link
163- className = { cn (
164- "flex w-full cursor-pointer items-center justify-between px-3 rounded py-1.5 text-left text-sm focus:outline-none" ,
165- searchTerm . length > 0 && isHighlighted ? "bg-gray-300" : "hover:bg-gray-300"
166- ) }
167- href = { getPathnameForOrg ( organization . name ) }
168- onMouseOver = { ( ) => {
169- onHoverOrg ( organization . name ) ;
170- } }
171- onClick = { ( ) => {
172- if ( isCurrent ) {
173- return ;
174- }
175- onClickOrg ( organization . name ) ;
176- onSelectFromDropdown ( ) ;
177- } }
178- >
179- < div className = "flex items-center gap-2" >
180- < OrgLogo organization = { organization } />
181- { getOrgDisplayName ( organization ) }
182- </ div >
183- { isRecent && < Clock className = "h-4 w-4 text-gray-600" /> }
184- </ Link >
185- ) ;
186- } }
187- >
188- < Button
189- variant = "outline"
190- className = "shrink-0 justify-between !pl-2 md:min-w-[200px]"
191- disabled = { organizations . length === 0 }
215+ } }
192216 >
193- < div className = "flex items-center gap-2" >
194- { currentOrg && < OrgLogo organization = { currentOrg } /> }
195- { currentOrg ? getOrgDisplayName ( currentOrg ) : "Select Organization" }
196- </ div >
197- < ChevronDown className = "h-4 w-4 opacity-50" />
198- </ Button >
199- </ SearchableDropdown >
217+ < Button
218+ variant = "outline"
219+ className = "shrink-0 justify-between !pl-2 md:min-w-[200px]"
220+ disabled = { organizations . length === 0 }
221+ >
222+ < div className = "flex items-center gap-2" >
223+ { currentOrg && < OrgLogo organization = { currentOrg } /> }
224+ { currentOrg ? getOrgDisplayName ( currentOrg ) : "Select Organization" }
225+ </ div >
226+ < ChevronDown className = "h-4 w-4 opacity-50" />
227+ </ Button >
228+ </ SearchableDropdown >
229+ < CreateOrganizationModal accessToken = { accessToken } open = { showOrgModal } onOpenChange = { setShowOrgModal } />
230+ </ >
200231 ) ;
201232} ) ;
202233
@@ -205,11 +236,13 @@ OrgSwitcherClientInternal.displayName = "OrgSwitcherClientInternal";
205236export const OrgSwitcherClient = ( {
206237 organizations : initialOrganizations ,
207238 currentOrgName,
208- isFernAdmin
239+ isFernAdmin,
240+ accessToken
209241} : {
210242 organizations : Auth0Organization [ ] ;
211243 currentOrgName ?: Auth0OrgName ;
212244 isFernAdmin : boolean ;
245+ accessToken : string ;
213246} ) => {
214247 const orgSwitcherRef = useRef < OrgSwitcherClientRef > ( null ) ;
215248
@@ -228,6 +261,7 @@ export const OrgSwitcherClient = ({
228261 organizations = { organizations }
229262 currentOrgName = { currentOrgName }
230263 isFernAdmin = { isFernAdmin }
264+ accessToken = { accessToken }
231265 />
232266 </ WrapWithKeyboardShortcut >
233267 ) ;
0 commit comments