mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 11:11:12 -05:00
Refactor: Redesign admin panel
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user