'use client' import { useState, useCallback, useEffect } from 'react' import Link from 'next/link' import { useSearchParams, usePathname } from 'next/navigation' import { trpc } from '@/lib/trpc/client' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Skeleton } from '@/components/ui/skeleton' import { UserAvatar } from '@/components/shared/user-avatar' import { UserActions, UserMobileActions } from '@/components/admin/user-actions' import { Pagination } from '@/components/shared/pagination' import { Plus, Users, Search } from 'lucide-react' import { formatDate } from '@/lib/utils' type RoleValue = 'SUPER_ADMIN' | 'PROGRAM_ADMIN' | 'JURY_MEMBER' | 'MENTOR' | 'OBSERVER' type TabKey = 'all' | 'jury' | 'mentors' | 'observers' | 'admins' const TAB_ROLES: Record = { all: undefined, jury: ['JURY_MEMBER'], mentors: ['MENTOR'], observers: ['OBSERVER'], admins: ['SUPER_ADMIN', 'PROGRAM_ADMIN'], } const statusColors: Record = { ACTIVE: 'success', INVITED: 'secondary', SUSPENDED: 'destructive', } const roleColors: Record = { JURY_MEMBER: 'default', MENTOR: 'secondary', OBSERVER: 'outline', PROGRAM_ADMIN: 'default', SUPER_ADMIN: 'destructive' as 'default', } export function MembersContent() { const searchParams = useSearchParams() const pathname = usePathname() const tab = (searchParams.get('tab') as TabKey) || 'all' const search = searchParams.get('search') || '' const page = parseInt(searchParams.get('page') || '1', 10) const [searchInput, setSearchInput] = useState(search) // Debounced search useEffect(() => { const timer = setTimeout(() => { updateParams({ search: searchInput || null, page: '1' }) }, 300) return () => clearTimeout(timer) // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchInput]) const updateParams = useCallback( (updates: Record) => { const params = new URLSearchParams(searchParams.toString()) Object.entries(updates).forEach(([key, value]) => { if (value === null || value === '') { params.delete(key) } else { params.set(key, value) } }) window.history.replaceState(null, '', `${pathname}?${params.toString()}`) }, [searchParams, pathname] ) const roles = TAB_ROLES[tab] const { data, isLoading } = trpc.user.list.useQuery({ roles: roles, search: search || undefined, page, perPage: 20, }) const handleTabChange = (value: string) => { updateParams({ tab: value === 'all' ? null : value, page: '1' }) } return (
{/* Header */}

Members

Manage jury members, mentors, observers, and admins

{/* Tabs */}
All Jury Mentors Observers Admins {/* Search */}
setSearchInput(e.target.value)} className="pl-9" />
{/* Content */} {isLoading ? ( ) : data && data.users.length > 0 ? ( <> {/* Desktop table */} Member Role Expertise Assignments Status Last Login Actions {data.users.map((user) => (
).avatarUrl as string | undefined} size="sm" />

{user.name || 'Unnamed'}

{user.email}

{user.role.replace(/_/g, ' ')} {user.expertiseTags && user.expertiseTags.length > 0 ? (
{user.expertiseTags.slice(0, 2).map((tag) => ( {tag} ))} {user.expertiseTags.length > 2 && ( +{user.expertiseTags.length - 2} )}
) : ( - )}
{user.role === 'MENTOR' ? (

{user._count.mentorAssignments} mentored

) : (

{user._count.assignments} assigned

)}
{user.status} {user.lastLoginAt ? ( formatDate(user.lastLoginAt) ) : ( Never )}
))}
{/* Mobile cards */}
{data.users.map((user) => (
).avatarUrl as string | undefined} size="md" />
{user.name || 'Unnamed'} {user.email}
{user.status}
Role {user.role.replace(/_/g, ' ')}
Assignments {user.role === 'MENTOR' ? `${user._count.mentorAssignments} mentored` : `${user._count.assignments} assigned`}
{user.expertiseTags && user.expertiseTags.length > 0 && (
{user.expertiseTags.map((tag) => ( {tag} ))}
)}
))}
{/* Pagination */} updateParams({ page: String(newPage) })} /> ) : (

No members found

{search ? 'Try adjusting your search' : 'Invite members to get started'}

)}
) } function MembersSkeleton() { return (
{[...Array(5)].map((_, i) => (
))}
) }