import { Metadata } from 'next'; import { db } from '@/lib/db'; import { auth } from '@/lib/auth'; import { redirect } from 'next/navigation'; import { getCachedBunnyStorageStats, getCachedUserBunnyStorage, getCachedUserMediaStorage } from '@/lib/admin-stats'; import { Film, HardDrive } from 'lucide-react'; import Link from 'next/link'; import { Button } from '@/components/ui/button'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { format } from 'date-fns'; export const metadata: Metadata = { title: 'Manage Users | Admin', }; type SortBy = | 'user' | 'joinedDate' | 'workspacesOwned' | 'invitedMembers' | 'projectsOwned' | 'totalComments' | 'bunnyUpload' | 'mediaStorage'; type SortDirection = 'asc' | 'desc'; const SORTABLE_COLUMNS: SortBy[] = [ 'user', 'joinedDate', 'workspacesOwned', 'invitedMembers', 'projectsOwned', 'totalComments', 'bunnyUpload', 'mediaStorage', ]; function formatBytes(bytes: number, decimals = 2) { if (bytes < 0) return 'Error Fetching'; if (!+bytes) return '0 Bytes'; const k = 1000; const dm = decimals < 0 ? 0 : decimals; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`; } function isSortBy(value: string | undefined): value is SortBy { return !!value && SORTABLE_COLUMNS.includes(value as SortBy); } function getDefaultSortDirection(sortBy: SortBy): SortDirection { return sortBy === 'user' ? 'asc' : 'desc'; } function getSortIndicator(column: SortBy, activeSortBy: SortBy, activeSortDirection: SortDirection): string { if (column !== activeSortBy) return '↕'; return activeSortDirection === 'asc' ? '↑' : '↓'; } export default async function AdminUsersPage({ searchParams }: { searchParams: Promise<{ page?: string; sortBy?: string; sortDirection?: string }> }) { const session = await auth(); if (!session?.user?.isAdmin) { redirect('/'); } const resolvedSearchParams = await searchParams; const requestedPage = Number(resolvedSearchParams?.page) || 1; const pageSize = 20; const sortBy: SortBy = isSortBy(resolvedSearchParams?.sortBy) ? resolvedSearchParams.sortBy : 'joinedDate'; const sortDirection: SortDirection = resolvedSearchParams?.sortDirection === 'asc' || resolvedSearchParams?.sortDirection === 'desc' ? resolvedSearchParams.sortDirection : getDefaultSortDirection(sortBy); const [users, userStorage, userBunnyStorage, bunnyStorageStats] = await Promise.all([ db.user.findMany({ orderBy: { createdAt: 'desc' }, select: { id: true, name: true, email: true, createdAt: true, ownedWorkspaces: { select: { _count: { select: { members: true, } } } }, _count: { select: { ownedWorkspaces: true, projects: true, comments: true, } } } }), getCachedUserMediaStorage(), getCachedUserBunnyStorage(), getCachedBunnyStorageStats(), ]); const usersWithMetrics = users.map((user) => ({ ...user, invitedMembersCount: user.ownedWorkspaces.reduce( (total, workspace) => total + workspace._count.members, 0 ), bunnyUploadBytes: userBunnyStorage[user.id] || 0, mediaStorageBytes: userStorage[user.id]?.total || 0, })); const sortedUsers = [...usersWithMetrics].sort((a, b) => { let comparison = 0; if (sortBy === 'user') { const aLabel = (a.name || a.email || 'Anonymous').toLowerCase(); const bLabel = (b.name || b.email || 'Anonymous').toLowerCase(); comparison = aLabel.localeCompare(bLabel); } else if (sortBy === 'joinedDate') { comparison = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(); } else if (sortBy === 'workspacesOwned') { comparison = a._count.ownedWorkspaces - b._count.ownedWorkspaces; } else if (sortBy === 'invitedMembers') { comparison = a.invitedMembersCount - b.invitedMembersCount; } else if (sortBy === 'projectsOwned') { comparison = a._count.projects - b._count.projects; } else if (sortBy === 'totalComments') { comparison = a._count.comments - b._count.comments; } else if (sortBy === 'bunnyUpload') { comparison = a.bunnyUploadBytes - b.bunnyUploadBytes; } else if (sortBy === 'mediaStorage') { comparison = a.mediaStorageBytes - b.mediaStorageBytes; } if (comparison === 0) { comparison = new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(); } return sortDirection === 'asc' ? comparison : -comparison; }); const totalUsers = sortedUsers.length; const totalPages = Math.max(1, Math.ceil(totalUsers / pageSize)); const page = Math.min(Math.max(1, requestedPage), totalPages); const skip = (page - 1) * pageSize; const paginatedUsers = sortedUsers.slice(skip, skip + pageSize); const buildUsersPageHref = ( targetPage: number, targetSortBy: SortBy = sortBy, targetSortDirection: SortDirection = sortDirection ): string => { const params = new URLSearchParams({ page: String(targetPage), sortBy: targetSortBy, sortDirection: targetSortDirection, }); return `/admin/users?${params.toString()}`; }; const buildSortHref = (column: SortBy): string => { const nextDirection: SortDirection = column === sortBy ? sortDirection === 'asc' ? 'desc' : 'asc' : getDefaultSortDirection(column); return buildUsersPageHref(1, column, nextDirection); }; return (

Users

Bunny Stream Storage
{formatBytes(bunnyStorageStats.totalBytes)}
Cloudflare R2 Media Storage
{formatBytes(Object.values(userStorage).reduce((sum, item) => sum + item.total, 0))}
All Users A comprehensive list of all {totalUsers} users registered on the platform.
User {getSortIndicator('user', sortBy, sortDirection)} Joined Date {getSortIndicator('joinedDate', sortBy, sortDirection)} Workspaces Owned {getSortIndicator('workspacesOwned', sortBy, sortDirection)} Invited Members {getSortIndicator('invitedMembers', sortBy, sortDirection)} Projects Owned {getSortIndicator('projectsOwned', sortBy, sortDirection)} Total Comments {getSortIndicator('totalComments', sortBy, sortDirection)} Bunny Upload {getSortIndicator('bunnyUpload', sortBy, sortDirection)} Media Storage {getSortIndicator('mediaStorage', sortBy, sortDirection)} {paginatedUsers.length === 0 ? ( No users found. ) : ( paginatedUsers.map((user) => (
{user.name || 'Anonymous'} {user.email}
{format(new Date(user.createdAt), 'MMM dd, yyyy')} {user._count.ownedWorkspaces} {user.invitedMembersCount} {user._count.projects} {user._count.comments} {formatBytes(user.bunnyUploadBytes)}
{formatBytes(user.mediaStorageBytes)} {(userStorage[user.id]?.voice > 0 || userStorage[user.id]?.image > 0) && ( {userStorage[user.id]?.voice > 0 && 🎤 {formatBytes(userStorage[user.id]?.voice)}} {userStorage[user.id]?.voice > 0 && userStorage[user.id]?.image > 0 && } {userStorage[user.id]?.image > 0 && 🖼️ {formatBytes(userStorage[user.id]?.image)}} )}
)) )}
{/* Pagination */} {totalPages > 1 && (
Page {page} of {totalPages}
)}
); }