Selaa lähdekoodia

disable role select in UI for self-change restrictions

- Non-superadmins see disabled select with tooltip 'Вы не можете сменить свою роль'
- Last superadmin sees disabled select with tooltip 'Нельзя сменить роль единственного суперадмина'
- Shows 'это вы' badge on own card
neyrogovnarik 1 kuukausi sitten
vanhempi
sitoutus
549f5c68ad
1 muutettua tiedostoa jossa 62 lisäystä ja 38 poistoa
  1. 62 38
      frontend/src/app/admin/users/page.tsx

+ 62 - 38
frontend/src/app/admin/users/page.tsx

@@ -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>
   )
 }