@@ -36,6 +36,7 @@ type AdminUser = {
3636 lastActive : string | null ;
3737 avatar : string | null ;
3838 avatarUrl : string | null ;
39+ provider ?: string ;
3940} ;
4041
4142// type for supabase user
@@ -49,12 +50,20 @@ type SupabaseUser = {
4950 banned ?: boolean ;
5051 created_at : string ;
5152 last_sign_in_at : string | null ;
53+ app_metadata ?: {
54+ provider ?: string ;
55+ } ;
56+ identities ?: {
57+ provider : string ;
58+ } [ ] ;
5259} ;
5360
5461export default function UsersPage ( ) {
5562 const [ users , setUsers ] = useState < AdminUser [ ] > ( [ ] )
5663 const [ searchTerm , setSearchTerm ] = useState ( "" )
5764 const [ statusFilter , setStatusFilter ] = useState ( "all" )
65+ const [ viewUser , setViewUser ] = useState < AdminUser | null > ( null )
66+ const [ isProfileDialogOpen , setIsProfileDialogOpen ] = useState ( false )
5867
5968 useEffect ( ( ) => {
6069 fetch ( "/api/admin-users" )
@@ -63,6 +72,13 @@ export default function UsersPage() {
6372 if ( data . users ) {
6473 setUsers ( data . users . map ( ( user : SupabaseUser ) => {
6574 const meta = user . user_metadata || { } ;
75+ // Try to get provider from app_metadata or identities
76+ let provider = undefined ;
77+ if ( user . app_metadata && user . app_metadata . provider ) {
78+ provider = user . app_metadata . provider ;
79+ } else if ( user . identities && user . identities . length > 0 ) {
80+ provider = user . identities [ 0 ] . provider ;
81+ }
6682 return {
6783 id : user . id ,
6884 name : meta . full_name || user . email ,
@@ -72,6 +88,7 @@ export default function UsersPage() {
7288 lastActive : user . last_sign_in_at ,
7389 avatar : user . email [ 0 ] ?. toUpperCase ( ) || "" ,
7490 avatarUrl : meta . avatar_url || null ,
91+ provider,
7592 } ;
7693 } ) ) ;
7794 }
@@ -328,7 +345,11 @@ export default function UsersPage() {
328345 </ TableCell >
329346 < TableCell > { getStatusBadge ( user . status ) } </ TableCell >
330347 < TableCell className = "hidden md:table-cell text-xs" > { new Date ( user . joinDate ) . toLocaleDateString ( ) } </ TableCell >
331- < TableCell className = "hidden lg:table-cell text-xs" > { new Date ( user . lastActive || "" ) . toLocaleDateString ( ) } </ TableCell >
348+ < TableCell className = "hidden lg:table-cell text-xs" > {
349+ user . lastActive && ! isNaN ( new Date ( user . lastActive ) . getTime ( ) )
350+ ? new Date ( user . lastActive ) . toLocaleDateString ( )
351+ : "—"
352+ } </ TableCell >
332353 < TableCell className = "text-right" >
333354 < DropdownMenu >
334355 < DropdownMenuTrigger asChild >
@@ -338,7 +359,10 @@ export default function UsersPage() {
338359 </ DropdownMenuTrigger >
339360 < DropdownMenuContent align = "end" className = "w-48" >
340361 < DropdownMenuLabel className = "text-xs" > Actions</ DropdownMenuLabel >
341- < DropdownMenuItem className = "text-xs" >
362+ < DropdownMenuItem className = "text-xs" onClick = { ( ) => {
363+ setViewUser ( user ) ;
364+ setIsProfileDialogOpen ( true ) ;
365+ } } >
342366 < Eye className = "mr-2 h-4 w-4" />
343367 View Profile
344368 </ DropdownMenuItem >
@@ -373,6 +397,56 @@ export default function UsersPage() {
373397 ) }
374398 </ CardContent >
375399 </ Card >
400+
401+ { /* User Profile Dialog */ }
402+ < Dialog open = { isProfileDialogOpen } onOpenChange = { setIsProfileDialogOpen } >
403+ < DialogContent className = "sm:max-w-lg" >
404+ < DialogHeader >
405+ < DialogTitle > User Profile</ DialogTitle >
406+ < DialogDescription > Details for the selected user.</ DialogDescription >
407+ </ DialogHeader >
408+ { viewUser && (
409+ < div className = "space-y-4 py-2" >
410+ < div className = "flex items-center gap-3" >
411+ < div className = "w-12 h-12 bg-gradient-to-br from-primary to-purple-600 rounded-full flex items-center justify-center text-white text-lg font-semibold" >
412+ { viewUser . avatar }
413+ </ div >
414+ < div >
415+ < div className = "font-bold text-lg text-zinc-900 dark:text-zinc-100" > { viewUser . name } </ div >
416+ < div className = "text-xs text-muted-foreground" > { viewUser . email } </ div >
417+ </ div >
418+ </ div >
419+ < div className = "grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm" >
420+ < div >
421+ < Label className = "text-xs text-muted-foreground" > UID</ Label >
422+ < div className = "break-all font-mono" > { viewUser . id } </ div >
423+ </ div >
424+ < div >
425+ < Label className = "text-xs text-muted-foreground" > Status</ Label >
426+ < div > { getStatusBadge ( viewUser . status ) } </ div >
427+ </ div >
428+ < div >
429+ < Label className = "text-xs text-muted-foreground" > Provider</ Label >
430+ < div > { viewUser . provider ? viewUser . provider . charAt ( 0 ) . toUpperCase ( ) + viewUser . provider . slice ( 1 ) : "—" } </ div >
431+ </ div >
432+ < div >
433+ < Label className = "text-xs text-muted-foreground" > Created at</ Label >
434+ < div > { new Date ( viewUser . joinDate ) . toLocaleString ( ) } </ div >
435+ </ div >
436+ < div >
437+ < Label className = "text-xs text-muted-foreground" > Last sign in at</ Label >
438+ < div > { viewUser . lastActive && ! isNaN ( new Date ( viewUser . lastActive ) . getTime ( ) ) ? new Date ( viewUser . lastActive ) . toLocaleString ( ) : "—" } </ div >
439+ </ div >
440+ < div className = "sm:col-span-2" >
441+ < Label className = "text-xs text-muted-foreground" > Email</ Label >
442+ < div > { viewUser . email } </ div >
443+ </ div >
444+ { /* Add more fields as needed, e.g. Providers, Phone, etc. */ }
445+ </ div >
446+ </ div >
447+ ) }
448+ </ DialogContent >
449+ </ Dialog >
376450 </ div >
377451 )
378452}
0 commit comments