Refactor: Redesign admin panel

This commit is contained in:
gpt-engineer-app[bot]
2025-10-04 18:58:39 +00:00
parent 095907b3a5
commit 35f380bfb4
6 changed files with 250 additions and 252 deletions

View File

@@ -179,28 +179,30 @@ export function UserRoleManager() {
</div>;
}
const filteredRoles = userRoles.filter(role => role.profiles?.username?.toLowerCase().includes(searchTerm.toLowerCase()) || role.profiles?.display_name?.toLowerCase().includes(searchTerm.toLowerCase()) || role.role.toLowerCase().includes(searchTerm.toLowerCase()));
return <div className="space-y-6">
return <div className="space-y-4">
{/* Add new role */}
<Card>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Card className="border-border/50">
<CardHeader className="pb-3">
<CardTitle className="text-base">Grant Role</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<Label htmlFor="user-search">Search Users</Label>
<Label htmlFor="user-search" className="text-sm">Search Users</Label>
<div className="relative">
<Search className="absolute left-3 top-3 w-4 h-4 text-muted-foreground" />
<Input id="user-search" placeholder="Search by username or display name..." value={newUserSearch} onChange={e => setNewUserSearch(e.target.value)} className="pl-10" />
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<Input id="user-search" placeholder="Search by username..." value={newUserSearch} onChange={e => setNewUserSearch(e.target.value)} className="pl-10 h-9 mt-1" />
</div>
{searchResults.length > 0 && <div className="mt-2 border rounded-lg bg-background">
{searchResults.map(profile => <div key={profile.user_id} className="p-3 hover:bg-muted/50 cursor-pointer border-b last:border-b-0" onClick={() => {
{searchResults.length > 0 && <div className="mt-2 border border-border/50 rounded-lg bg-background max-h-[200px] overflow-y-auto">
{searchResults.map(profile => <div key={profile.user_id} className="p-2.5 hover:bg-muted/50 cursor-pointer border-b border-border/50 last:border-b-0 text-sm" onClick={() => {
setNewUserSearch(profile.display_name || profile.username);
setSearchResults([profile]);
}}>
<div className="font-medium">
{profile.display_name || profile.username}
</div>
{profile.display_name && <div className="text-sm text-muted-foreground">
{profile.display_name && <div className="text-xs text-muted-foreground">
@{profile.username}
</div>}
</div>)}
@@ -208,10 +210,10 @@ export function UserRoleManager() {
</div>
<div>
<Label htmlFor="role-select">Role</Label>
<Label htmlFor="role-select" className="text-sm">Role</Label>
<Select value={newRole} onValueChange={setNewRole}>
<SelectTrigger>
<SelectValue placeholder="Select a role" />
<SelectTrigger className="h-9 mt-1">
<SelectValue placeholder="Select role" />
</SelectTrigger>
<SelectContent>
<SelectItem value="moderator">Moderator</SelectItem>
@@ -226,7 +228,7 @@ export function UserRoleManager() {
if (selectedUser && newRole) {
grantRole(selectedUser.user_id, newRole as 'admin' | 'moderator' | 'user');
}
}} disabled={!newRole || !searchResults.find(p => (p.display_name || p.username) === newUserSearch) || actionLoading === 'grant'} className="w-full md:w-auto">
}} disabled={!newRole || !searchResults.find(p => (p.display_name || p.username) === newUserSearch) || actionLoading === 'grant'} size="sm" className="w-full md:w-auto">
{actionLoading === 'grant' ? 'Granting...' : 'Grant Role'}
</Button>
</CardContent>
@@ -234,39 +236,39 @@ export function UserRoleManager() {
{/* Search existing roles */}
<div>
<Label htmlFor="role-search">Search Existing Roles</Label>
<Label htmlFor="role-search" className="text-sm">Search Existing Roles</Label>
<div className="relative">
<Search className="absolute left-3 top-3 w-4 h-4 text-muted-foreground" />
<Input id="role-search" placeholder="Search users with roles..." value={searchTerm} onChange={e => setSearchTerm(e.target.value)} className="pl-10" />
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<Input id="role-search" placeholder="Search users with roles..." value={searchTerm} onChange={e => setSearchTerm(e.target.value)} className="pl-10 h-9 mt-1" />
</div>
</div>
{/* User roles list */}
<div className="space-y-3">
{filteredRoles.length === 0 ? <div className="text-center py-8">
<Shield className="w-12 h-12 text-muted-foreground mx-auto mb-4" />
<h3 className="text-lg font-semibold mb-2">No roles found</h3>
<p className="text-muted-foreground">
{searchTerm ? 'No users match your search criteria.' : 'No user roles have been granted yet.'}
<div className="space-y-2">
{filteredRoles.length === 0 ? <div className="text-center py-12">
<Shield className="w-12 h-12 text-muted-foreground mx-auto mb-3" />
<h3 className="text-base font-semibold mb-1">No roles found</h3>
<p className="text-sm text-muted-foreground">
{searchTerm ? 'No users match your search.' : 'No user roles granted yet.'}
</p>
</div> : filteredRoles.map(userRole => <Card key={userRole.id}>
<CardContent className="flex items-center justify-between p-4">
<div className="flex items-center gap-3">
<div>
<div className="font-medium">
</div> : filteredRoles.map(userRole => <Card key={userRole.id} className="border-border/50">
<CardContent className="flex items-center justify-between p-3">
<div className="flex items-center gap-3 min-w-0 flex-1">
<div className="min-w-0">
<div className="font-medium text-sm truncate">
{userRole.profiles?.display_name || userRole.profiles?.username}
</div>
{userRole.profiles?.display_name && <div className="text-sm text-muted-foreground">
{userRole.profiles?.display_name && <div className="text-xs text-muted-foreground">
@{userRole.profiles.username}
</div>}
</div>
<Badge variant={userRole.role === 'admin' ? 'default' : 'secondary'}>
<Badge variant={userRole.role === 'admin' ? 'default' : 'secondary'} className="text-xs h-5">
{userRole.role}
</Badge>
</div>
{/* Only show revoke button if current user can manage this role */}
{(isSuperuser() || isAdmin() && !['admin', 'superuser'].includes(userRole.role)) && <Button variant="outline" size="sm" onClick={() => revokeRole(userRole.id)} disabled={actionLoading === userRole.id}>
{(isSuperuser() || isAdmin() && !['admin', 'superuser'].includes(userRole.role)) && <Button variant="outline" size="sm" onClick={() => revokeRole(userRole.id)} disabled={actionLoading === userRole.id} className="h-8 w-8 p-0 flex-shrink-0">
<X className="w-4 h-4" />
</Button>}
</CardContent>