Files
Vault/app/admin/users/admin-client.tsx
T
twotalesanimation 81ad7e4ea9 Initial commit
2026-06-11 10:46:09 +02:00

185 lines
5.1 KiB
TypeScript

'use client';
import React, { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { SidebarProvider, SidebarInset } from '@/components/ui/sidebar';
import { AppSidebar } from '@/components/app-sidebar';
import { SiteHeader } from '@/components/site-header';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { formatDistanceToNow } from 'date-fns';
import { Eye } from 'lucide-react';
type User = {
id: string;
email: string;
name: string | null;
image: string | null;
role: string;
createdAt: Date;
enrollments: Array<{
course: {
id: string;
title: string;
};
}>;
lastActivity: Date | null;
};
function UsersAdminClientUI() {
const [users, setUsers] = useState<User[]>([]);
const [isLoading, setIsLoading] = useState(true);
const router = useRouter();
useEffect(() => {
const fetchUsers = async () => {
setIsLoading(true);
try {
const res = await fetch('/api/admin/users');
if (res.ok) {
const data = await res.json();
setUsers(data);
}
} catch (err) {
console.error('Failed to fetch users:', err);
} finally {
setIsLoading(false);
}
};
fetchUsers();
}, []);
const getRoleBadgeVariant = (role: string) => {
switch (role) {
case 'superadmin':
return 'default';
case 'admin':
return 'secondary';
default:
return 'outline';
}
};
if (isLoading) {
return (
<div className="p-4 text-sm text-muted-foreground">Loading users...</div>
);
}
return (
<div className="flex flex-col gap-6 py-4 md:gap-6 md:py-6 px-4 lg:px-16">
<Card>
<CardHeader>
<CardTitle>Total: {users.length} users</CardTitle>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Email</TableHead>
<TableHead>Name</TableHead>
<TableHead>Role</TableHead>
<TableHead>Courses</TableHead>
<TableHead>Last Activity</TableHead>
<TableHead>Joined</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.length === 0 ? (
<TableRow>
<TableCell colSpan={7} className="text-center py-8 text-muted-foreground">
No users found
</TableCell>
</TableRow>
) : (
users.map((user) => (
<TableRow key={user.id}>
<TableCell className="font-medium">{user.email}</TableCell>
<TableCell>{user.name || '-'}</TableCell>
<TableCell>
<Badge variant={getRoleBadgeVariant(user.role)}>
{user.role}
</Badge>
</TableCell>
<TableCell>
{user.enrollments.length > 0 ? (
<div className="flex gap-1 flex-wrap">
{user.enrollments.map((enrollment, idx) => (
<Badge key={idx} variant="outline" className="text-xs">
{enrollment.course.title}
</Badge>
))}
</div>
) : (
<span className="text-muted-foreground text-sm">-</span>
)}
</TableCell>
<TableCell>
{user.lastActivity ? (
formatDistanceToNow(new Date(user.lastActivity), { addSuffix: true })
) : (
<span className="text-muted-foreground text-sm">No activity</span>
)}
</TableCell>
<TableCell>
{formatDistanceToNow(new Date(user.createdAt), { addSuffix: true })}
</TableCell>
<TableCell className="text-right">
<Button
variant="ghost"
size="sm"
onClick={() => router.push(`/admin/users/${user.id}`)}
>
<Eye className="w-4 h-4" />
View
</Button>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</CardContent>
</Card>
</div>
);
}
export default function sersAdminClient() {
return (
<SidebarProvider
style={
{
'--sidebar-width': 'calc(var(--spacing) * 72)',
'--header-height': 'calc(var(--spacing) * 12)',
} as React.CSSProperties
}
>
<AppSidebar variant="inset" />
<SidebarInset>
<SiteHeader />
<div className="flex flex-1 flex-col">
<div className="@container/main flex flex-1 flex-col gap-2">
<UsersAdminClientUI />
</div>
</div>
</SidebarInset>
</SidebarProvider>
);
}