11import {
2- useAllListMyDelegatedChildAccountsQuery ,
32 useChildAccountsInfiniteQuery ,
3+ useMyDelegatedChildAccountsQuery ,
44} from '@linode/queries' ;
5- import { Drawer , LinkButton , Notice , Typography } from '@linode/ui' ;
5+ import {
6+ Button ,
7+ Drawer ,
8+ LinkButton ,
9+ Notice ,
10+ Stack ,
11+ Typography ,
12+ useTheme ,
13+ } from '@linode/ui' ;
614import React , { useMemo , useState } from 'react' ;
715
16+ import ErrorStateCloud from 'src/assets/icons/error-state-cloud.svg' ;
817import { DebouncedSearchTextField } from 'src/components/DebouncedSearchTextField' ;
918import { useParentChildAuthentication } from 'src/features/Account/SwitchAccounts/useParentChildAuthentication' ;
1019import { useSwitchToParentAccount } from 'src/features/Account/SwitchAccounts/useSwitchToParentAccount' ;
@@ -14,6 +23,7 @@ import { sendSwitchToParentAccountEvent } from 'src/utilities/analytics/customEv
1423import { getStorage , storage } from 'src/utilities/storage' ;
1524
1625import { ChildAccountList } from './SwitchAccounts/ChildAccountList' ;
26+ import { ChildAccountsTable } from './SwitchAccounts/ChildAccountsTable' ;
1727import { updateParentTokenInLocalStorage } from './SwitchAccounts/utils' ;
1828
1929import type { APIError , Filter , UserType } from '@linode/api-v4' ;
@@ -34,10 +44,13 @@ interface HandleSwitchToChildAccountProps {
3444
3545export const SwitchAccountDrawer = ( props : Props ) => {
3646 const { onClose, open, userType } = props ;
47+ const theme = useTheme ( ) ;
3748 const [ isParentTokenError , setIsParentTokenError ] = React . useState <
3849 APIError [ ]
3950 > ( [ ] ) ;
4051 const [ searchQuery , setSearchQuery ] = React . useState < string > ( '' ) ;
52+ const [ page , setPage ] = useState ( 1 ) ;
53+ const [ pageSize , setPageSize ] = useState ( 25 ) ;
4154 const { isIAMDelegationEnabled } = useIsIAMDelegationEnabled ( ) ;
4255 const isParentUserType = userType === 'parent' ;
4356 const isProxyUserType = userType === 'proxy' ;
@@ -94,20 +107,20 @@ export const SwitchAccountDrawer = (props: Props) => {
94107 ) ;
95108
96109 const {
97- data : allChildAccounts ,
98- error : allChildAccountsError ,
99- isLoading : allChildAccountsLoading ,
100- isRefetching : allChildAccountsIsRefetching ,
101- refetch : refetchAllChildAccounts ,
102- } = useAllListMyDelegatedChildAccountsQuery ( {
103- params : { } ,
110+ data : delegatedChildAccounts ,
111+ error : delegatedChildAccountsError ,
112+ isLoading : delegatedChildAccountsLoading ,
113+ isRefetching : delegatedChildAccountsIsRefetching ,
114+ refetch : refetchDelegatedChildAccounts ,
115+ } = useMyDelegatedChildAccountsQuery ( {
116+ params : {
117+ page,
118+ page_size : pageSize ,
119+ } ,
120+ filter,
104121 enabled : isIAMDelegationEnabled && isParentUserType ,
105122 } ) ;
106123
107- const refetchFn = isIAMDelegationEnabled
108- ? refetchAllChildAccounts
109- : refetchChildAccounts ;
110-
111124 const handleSwitchToChildAccount = React . useCallback (
112125 async ( {
113126 currentTokenWithBearer,
@@ -147,35 +160,57 @@ export const SwitchAccountDrawer = (props: Props) => {
147160 } ) ;
148161 onClose ( event ) ;
149162 location . reload ( ) ;
150- } catch ( error ) {
163+ } catch {
151164 // Error is handled by createTokenError.
152165 }
153166 } ,
154- [ createToken , updateCurrentToken , revokeToken ]
167+ [ createToken , isProxyUserType , updateCurrentToken , revokeToken ]
155168 ) ;
156169
157170 const [ isSwitchingChildAccounts , setIsSwitchingChildAccounts ] =
158171 useState < boolean > ( false ) ;
159172
173+ const isLoading =
174+ isInitialLoading ||
175+ isSubmitting ||
176+ isSwitchingChildAccounts ||
177+ isRefetching ||
178+ delegatedChildAccountsLoading ||
179+ delegatedChildAccountsIsRefetching ;
180+
181+ const refetchFn = isIAMDelegationEnabled
182+ ? refetchDelegatedChildAccounts
183+ : refetchChildAccounts ;
160184 const handleClose = ( ) => {
161185 setIsSwitchingChildAccounts ( false ) ;
186+ setSearchQuery ( '' ) ;
162187 onClose ( ) ;
163188 } ;
164189
165190 const childAccounts = useMemo ( ( ) => {
166191 if ( isIAMDelegationEnabled ) {
167- if ( searchQuery && allChildAccounts ) {
168- // Client-side filter: match company field with searchQuery (case-insensitive, contains)
169- const normalizedQuery = searchQuery . toLowerCase ( ) ;
170- return allChildAccounts . filter ( ( account ) =>
171- account . company ?. toLowerCase ( ) . includes ( normalizedQuery )
172- ) ;
173- }
174- return allChildAccounts ;
192+ return delegatedChildAccounts ?. data || [ ] ;
175193 }
176194 return data ?. pages . flatMap ( ( page ) => page . data ) ;
177- } , [ isIAMDelegationEnabled , searchQuery , allChildAccounts , data ] ) ;
195+ } , [ isIAMDelegationEnabled , delegatedChildAccounts , data ] ) ;
196+
197+ const handlePageChange = ( newPage : number ) => {
198+ setPage ( newPage ) ;
199+ } ;
178200
201+ const handlePageSizeChange = ( newPageSize : number ) => {
202+ setPageSize ( newPageSize ) ;
203+ setPage ( 1 ) ; // Reset to first page when page size changes
204+ } ;
205+
206+ const handleSearchQueryChange = ( query : string ) => {
207+ setSearchQuery ( query ) ;
208+ setPage ( 1 ) ; // Reset to first page when search query changes
209+ } ;
210+
211+ const hasError = isIAMDelegationEnabled
212+ ? delegatedChildAccountsError
213+ : childAccountInfiniteError ;
179214 return (
180215 < Drawer onClose = { handleClose } open = { open } title = "Switch Account" >
181216 { createTokenErrorReason && (
@@ -207,69 +242,95 @@ export const SwitchAccountDrawer = (props: Props) => {
207242 ) }
208243 .
209244 </ Typography >
210- { isIAMDelegationEnabled &&
211- allChildAccounts &&
212- allChildAccounts . length !== 0 && (
213- < >
214- < DebouncedSearchTextField
215- clearable
216- debounceTime = { 250 }
217- hideLabel
218- label = "Search"
219- onSearch = { setSearchQuery }
220- placeholder = "Search"
221- sx = { { marginBottom : 3 } }
222- value = { searchQuery }
223- />
224- { searchQuery && childAccounts && childAccounts . length === 0 && (
225- < Typography sx = { { fontStyle : 'italic' } } >
245+
246+ { hasError && (
247+ < Stack alignItems = "center" gap = { 1 } justifyContent = "center" >
248+ < ErrorStateCloud />
249+ < Typography > Unable to load data.</ Typography >
250+ < Typography >
251+ Try again or contact support if the issue persists.
252+ </ Typography >
253+ < Button
254+ buttonType = "primary"
255+ onClick = { ( ) => refetchFn ( ) }
256+ sx = { ( theme ) => ( {
257+ marginTop : theme . spacingFunction ( 16 ) ,
258+ } ) }
259+ >
260+ Try again
261+ </ Button >
262+ </ Stack >
263+ ) }
264+ { ! hasError && (
265+ < >
266+ < DebouncedSearchTextField
267+ clearable
268+ debounceTime = { 250 }
269+ hideLabel
270+ key = { `switch-search-${ searchQuery } ` }
271+ label = "Search"
272+ onSearch = { handleSearchQueryChange }
273+ placeholder = "Search"
274+ sx = { { marginBottom : theme . spacingFunction ( 12 ) } }
275+ value = { searchQuery }
276+ />
277+ { searchQuery &&
278+ childAccounts &&
279+ childAccounts . length === 0 &&
280+ ! isLoading && (
281+ < Typography
282+ sx = { {
283+ fontStyle : 'italic' ,
284+ marginTop : theme . spacingFunction ( 6 ) ,
285+ } }
286+ >
226287 No search results
227288 </ Typography >
228289 ) }
229- </ >
230- ) }
290+ </ >
291+ ) }
292+ { isIAMDelegationEnabled && (
293+ < ChildAccountsTable
294+ childAccounts = { childAccounts }
295+ currentTokenWithBearer = {
296+ isProxyOrDelegateUserType
297+ ? currentParentTokenWithBearer
298+ : currentTokenWithBearer
299+ }
300+ isLoading = { isLoading }
301+ isSwitchingChildAccounts = { isSwitchingChildAccounts }
302+ onClose = { onClose }
303+ onPageChange = { handlePageChange }
304+ onPageSizeChange = { handlePageSizeChange }
305+ onSwitchAccount = { handleSwitchToChildAccount }
306+ page = { page }
307+ pageSize = { pageSize }
308+ setIsSwitchingChildAccounts = { setIsSwitchingChildAccounts }
309+ totalResults = { delegatedChildAccounts ?. results || 0 }
310+ userType = { userType }
311+ />
312+ ) }
231313 { ! isIAMDelegationEnabled && (
232- < DebouncedSearchTextField
233- clearable
234- debounceTime = { 250 }
235- hideLabel
236- label = "Search"
237- onSearch = { setSearchQuery }
238- placeholder = "Search"
239- sx = { { marginBottom : 3 } }
240- value = { searchQuery }
314+ < ChildAccountList
315+ childAccounts = { childAccounts }
316+ currentTokenWithBearer = {
317+ isProxyOrDelegateUserType
318+ ? currentParentTokenWithBearer
319+ : currentTokenWithBearer
320+ }
321+ fetchNextPage = { fetchNextPage }
322+ filter = { filter }
323+ hasNextPage = { hasNextPage }
324+ isFetchingNextPage = { isFetchingNextPage }
325+ isLoading = { isLoading }
326+ isSwitchingChildAccounts = { isSwitchingChildAccounts }
327+ onClose = { onClose }
328+ onSwitchAccount = { handleSwitchToChildAccount }
329+ refetchFn = { refetchFn }
330+ setIsSwitchingChildAccounts = { setIsSwitchingChildAccounts }
331+ userType = { userType }
241332 />
242333 ) }
243- < ChildAccountList
244- childAccounts = { childAccounts }
245- currentTokenWithBearer = {
246- isProxyOrDelegateUserType
247- ? currentParentTokenWithBearer
248- : currentTokenWithBearer
249- }
250- errors = { {
251- childAccountInfiniteError,
252- allChildAccountsError,
253- } }
254- fetchNextPage = { fetchNextPage }
255- filter = { filter }
256- hasNextPage = { hasNextPage }
257- isFetchingNextPage = { isFetchingNextPage }
258- isLoading = {
259- isInitialLoading ||
260- isSubmitting ||
261- isSwitchingChildAccounts ||
262- isRefetching ||
263- allChildAccountsLoading ||
264- allChildAccountsIsRefetching
265- }
266- isSwitchingChildAccounts = { isSwitchingChildAccounts }
267- onClose = { onClose }
268- onSwitchAccount = { handleSwitchToChildAccount }
269- refetchFn = { refetchFn }
270- setIsSwitchingChildAccounts = { setIsSwitchingChildAccounts }
271- userType = { userType }
272- />
273334 </ Drawer >
274335 ) ;
275336} ;
0 commit comments