|
|
@@ -2,6 +2,7 @@
|
|
|
|
|
|
import { useState, useEffect, useCallback } from 'react'
|
|
|
import { api } from '@/lib/api'
|
|
|
+import { useAuth } from '@/hooks/useAuth'
|
|
|
import type { User } from '@/types'
|
|
|
|
|
|
const roleLabels: Record<string, string> = {
|
|
|
@@ -24,6 +25,7 @@ const statusLabels: Record<string, string> = {
|
|
|
* Загружает до 100 пользователей через API /admin/users.
|
|
|
*/
|
|
|
export default function AdminUsersPage() {
|
|
|
+ const { user: currentUser } = useAuth()
|
|
|
const [users, setUsers] = useState<User[]>([])
|
|
|
const [loading, setLoading] = useState(true)
|
|
|
const [roleFilter, setRoleFilter] = useState('')
|
|
|
@@ -74,45 +76,67 @@ export default function AdminUsersPage() {
|
|
|
))}
|
|
|
</div>
|
|
|
|
|
|
- <div className="space-y-2">
|
|
|
- {users.map((user) => (
|
|
|
- <div key={user.id} className="flex items-center justify-between rounded-lg border border-white/10 bg-white/5 p-4">
|
|
|
- <div className="flex-1">
|
|
|
- <div className="flex items-center gap-2">
|
|
|
- <span className="font-medium text-white">{user.name || user.email}</span>
|
|
|
- <span className={`rounded-full px-2 py-0.5 text-xs ${
|
|
|
- user.status === 'active' ? 'bg-green-500/20 text-green-400' :
|
|
|
- user.status === 'banned' ? 'bg-red-500/20 text-red-400' :
|
|
|
- 'bg-yellow-500/20 text-yellow-400'
|
|
|
- }`}>
|
|
|
- {statusLabels[user.status]}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- <p className="text-sm text-white/40">{user.email}</p>
|
|
|
- </div>
|
|
|
- <div className="flex items-center gap-2">
|
|
|
- <select value={user.role}
|
|
|
- onChange={(e) => updateUser(user.id, { role: e.target.value })}
|
|
|
- className="rounded-lg border border-white/10 bg-white/5 px-2 py-1.5 text-sm text-white outline-none"
|
|
|
- >
|
|
|
- {Object.entries(roleLabels).map(([k, v]) => (
|
|
|
- <option key={k} value={k} className="bg-[#1e293b]">{v}</option>
|
|
|
- ))}
|
|
|
- </select>
|
|
|
- <button onClick={() => updateUser(user.id, {
|
|
|
- status: user.status === 'banned' ? 'active' : 'banned'
|
|
|
- })}
|
|
|
- className={`rounded-lg px-3 py-1.5 text-sm transition ${
|
|
|
- user.status === 'banned' ? 'bg-green-600/80 text-white hover:bg-green-500' :
|
|
|
- 'bg-red-600/80 text-white hover:bg-red-500'
|
|
|
- }`}
|
|
|
- >
|
|
|
- {user.status === 'banned' ? 'Разблокировать' : 'Заблокировать'}
|
|
|
- </button>
|
|
|
- </div>
|
|
|
+ {(() => {
|
|
|
+ const superadminCount = users.filter((u) => u.role === 'superadmin').length
|
|
|
+ return (
|
|
|
+ <div className="space-y-2">
|
|
|
+ {users.map((user) => {
|
|
|
+ const isSelf = user.id === currentUser?.id
|
|
|
+ const isLastSuperadmin = user.role === 'superadmin' && superadminCount <= 1
|
|
|
+ const roleDisabled = isSelf && (currentUser?.role !== 'superadmin' || isLastSuperadmin)
|
|
|
+ const roleTitle = isSelf
|
|
|
+ ? currentUser?.role !== 'superadmin'
|
|
|
+ ? 'Вы не можете сменить свою роль'
|
|
|
+ : 'Нельзя сменить роль единственного суперадмина'
|
|
|
+ : undefined
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div key={user.id} className="flex items-center justify-between rounded-lg border border-white/10 bg-white/5 p-4">
|
|
|
+ <div className="flex-1">
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
+ <span className="font-medium text-white">{user.name || user.email}</span>
|
|
|
+ {isSelf && <span className="rounded bg-green-600/30 px-1.5 py-0.5 text-xs text-green-300">это вы</span>}
|
|
|
+ <span className={`rounded-full px-2 py-0.5 text-xs ${
|
|
|
+ user.status === 'active' ? 'bg-green-500/20 text-green-400' :
|
|
|
+ user.status === 'banned' ? 'bg-red-500/20 text-red-400' :
|
|
|
+ 'bg-yellow-500/20 text-yellow-400'
|
|
|
+ }`}>
|
|
|
+ {statusLabels[user.status]}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <p className="text-sm text-white/40">{user.email}</p>
|
|
|
+ </div>
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
+ <div className="relative" title={roleTitle}>
|
|
|
+ <select value={user.role}
|
|
|
+ disabled={roleDisabled}
|
|
|
+ onChange={(e) => updateUser(user.id, { role: e.target.value })}
|
|
|
+ className={`rounded-lg border border-white/10 px-2 py-1.5 text-sm text-white outline-none transition ${
|
|
|
+ roleDisabled ? 'cursor-not-allowed opacity-50' : 'bg-white/5'
|
|
|
+ }`}
|
|
|
+ >
|
|
|
+ {Object.entries(roleLabels).map(([k, v]) => (
|
|
|
+ <option key={k} value={k} className="bg-[#1e293b]">{v}</option>
|
|
|
+ ))}
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <button onClick={() => updateUser(user.id, {
|
|
|
+ status: user.status === 'banned' ? 'active' : 'banned'
|
|
|
+ })}
|
|
|
+ className={`rounded-lg px-3 py-1.5 text-sm transition ${
|
|
|
+ user.status === 'banned' ? 'bg-green-600/80 text-white hover:bg-green-500' :
|
|
|
+ 'bg-red-600/80 text-white hover:bg-red-500'
|
|
|
+ }`}
|
|
|
+ >
|
|
|
+ {user.status === 'banned' ? 'Разблокировать' : 'Заблокировать'}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ })}
|
|
|
</div>
|
|
|
- ))}
|
|
|
- </div>
|
|
|
+ )
|
|
|
+ })()}
|
|
|
</div>
|
|
|
)
|
|
|
}
|