@@ -8,6 +8,7 @@ import { cn } from '@/lib/core/utils/cn'
88import {
99 useAdminUsers ,
1010 useBanUser ,
11+ useImpersonateUser ,
1112 useSetUserRole ,
1213 useUnbanUser ,
1314} from '@/hooks/queries/admin-users'
@@ -28,13 +29,16 @@ export function Admin() {
2829 const setUserRole = useSetUserRole ( )
2930 const banUser = useBanUser ( )
3031 const unbanUser = useUnbanUser ( )
32+ const impersonateUser = useImpersonateUser ( )
3133
3234 const [ workflowId , setWorkflowId ] = useState ( '' )
3335 const [ usersOffset , setUsersOffset ] = useState ( 0 )
3436 const [ searchInput , setSearchInput ] = useState ( '' )
3537 const [ searchQuery , setSearchQuery ] = useState ( '' )
3638 const [ banUserId , setBanUserId ] = useState < string | null > ( null )
3739 const [ banReason , setBanReason ] = useState ( '' )
40+ const [ impersonatingUserId , setImpersonatingUserId ] = useState < string | null > ( null )
41+ const [ impersonationGuardError , setImpersonationGuardError ] = useState < string | null > ( null )
3842
3943 const {
4044 data : usersData ,
@@ -67,6 +71,29 @@ export function Admin() {
6771 )
6872 }
6973
74+ const handleImpersonate = ( userId : string ) => {
75+ setImpersonationGuardError ( null )
76+ if ( session ?. user ?. role !== 'admin' ) {
77+ setImpersonatingUserId ( null )
78+ setImpersonationGuardError ( 'Only admins can impersonate users.' )
79+ return
80+ }
81+
82+ setImpersonatingUserId ( userId )
83+ impersonateUser . reset ( )
84+ impersonateUser . mutate (
85+ { userId } ,
86+ {
87+ onError : ( ) => {
88+ setImpersonatingUserId ( null )
89+ } ,
90+ onSuccess : ( ) => {
91+ window . location . assign ( '/workspace' )
92+ } ,
93+ }
94+ )
95+ }
96+
7097 const pendingUserIds = useMemo ( ( ) => {
7198 const ids = new Set < string > ( )
7299 if ( setUserRole . isPending && ( setUserRole . variables as { userId ?: string } ) ?. userId )
@@ -75,6 +102,9 @@ export function Admin() {
75102 ids . add ( ( banUser . variables as { userId : string } ) . userId )
76103 if ( unbanUser . isPending && ( unbanUser . variables as { userId ?: string } ) ?. userId )
77104 ids . add ( ( unbanUser . variables as { userId : string } ) . userId )
105+ if ( impersonateUser . isPending && ( impersonateUser . variables as { userId ?: string } ) ?. userId )
106+ ids . add ( ( impersonateUser . variables as { userId : string } ) . userId )
107+ if ( impersonatingUserId ) ids . add ( impersonatingUserId )
78108 return ids
79109 } , [
80110 setUserRole . isPending ,
@@ -83,6 +113,9 @@ export function Admin() {
83113 banUser . variables ,
84114 unbanUser . isPending ,
85115 unbanUser . variables ,
116+ impersonateUser . isPending ,
117+ impersonateUser . variables ,
118+ impersonatingUserId ,
86119 ] )
87120 return (
88121 < div className = 'flex h-full flex-col gap-[24px]' >
@@ -152,9 +185,15 @@ export function Admin() {
152185 </ p >
153186 ) }
154187
155- { ( setUserRole . error || banUser . error || unbanUser . error ) && (
188+ { ( setUserRole . error ||
189+ banUser . error ||
190+ unbanUser . error ||
191+ impersonateUser . error ||
192+ impersonationGuardError ) && (
156193 < p className = 'text-[13px] text-[var(--text-error)]' >
157- { ( setUserRole . error || banUser . error || unbanUser . error ) ?. message ??
194+ { impersonationGuardError ||
195+ ( setUserRole . error || banUser . error || unbanUser . error || impersonateUser . error )
196+ ?. message ||
158197 'Action failed. Please try again.' }
159198 </ p >
160199 ) }
@@ -175,7 +214,7 @@ export function Admin() {
175214 < span className = 'flex-1' > Email</ span >
176215 < span className = 'w-[80px]' > Role</ span >
177216 < span className = 'w-[80px]' > Status</ span >
178- < span className = 'w-[180px ] text-right' > Actions</ span >
217+ < span className = 'w-[250px ] text-right' > Actions</ span >
179218 </ div >
180219
181220 { usersData . users . length === 0 && (
@@ -206,9 +245,22 @@ export function Admin() {
206245 < Badge variant = 'green' > Active</ Badge >
207246 ) }
208247 </ span >
209- < span className = 'flex w-[180px ] justify-end gap-[4px]' >
248+ < span className = 'flex w-[250px ] justify-end gap-[4px]' >
210249 { u . id !== session ?. user ?. id && (
211250 < >
251+ < Button
252+ variant = 'active'
253+ className = 'h-[28px] px-[8px] text-[12px]'
254+ onClick = { ( ) => handleImpersonate ( u . id ) }
255+ disabled = { pendingUserIds . has ( u . id ) }
256+ >
257+ { impersonatingUserId === u . id ||
258+ ( impersonateUser . isPending &&
259+ ( impersonateUser . variables as { userId ?: string } | undefined )
260+ ?. userId === u . id )
261+ ? 'Switching...'
262+ : 'Impersonate' }
263+ </ Button >
212264 < Button
213265 variant = 'active'
214266 className = 'h-[28px] px-[8px] text-[12px]'
0 commit comments