From 237f9166f4d51ce921e8f28cbadf76e77c7e5671 Mon Sep 17 00:00:00 2001 From: pradeepkumar Date: Fri, 20 Mar 2026 17:04:59 +0530 Subject: [PATCH] feat: Implement admin user management page with create/delete functionality, add a super-admin-only sidebar link, and expose `isSuperAdmin` in AuthContext. --- src/app/dashboard/admin-users/page.tsx | 211 +++++++++++++++++++++++++ src/components/Sidebar.tsx | 19 +++ src/context/AuthContext.tsx | 11 +- 3 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 src/app/dashboard/admin-users/page.tsx diff --git a/src/app/dashboard/admin-users/page.tsx b/src/app/dashboard/admin-users/page.tsx new file mode 100644 index 0000000..c2683fb --- /dev/null +++ b/src/app/dashboard/admin-users/page.tsx @@ -0,0 +1,211 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useAuth } from '@/context/AuthContext'; +import { useRouter } from 'next/navigation'; +import api, { ApiResponse, getErrorMessage } from '@/services/api'; + +interface AdminUser { + id: string; + email: string; + role: string; + status: string; + createdAt: string; + lastLoginAt: string | null; +} + +export default function AdminUsersPage() { + const { user, isSuperAdmin } = useAuth(); + const router = useRouter(); + const [admins, setAdmins] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const [showCreate, setShowCreate] = useState(false); + const [newEmail, setNewEmail] = useState(''); + const [newPassword, setNewPassword] = useState(''); + const [creating, setCreating] = useState(false); + const [deletingId, setDeletingId] = useState(null); + + // Redirect non-super-admins + useEffect(() => { + if (!isSuperAdmin) { + router.push('/dashboard'); + } + }, [isSuperAdmin, router]); + + const fetchAdmins = async () => { + try { + setLoading(true); + const response = await api.get>('/users/admin/admins'); + setAdmins(response.data.data); + } catch (err) { + setError(getErrorMessage(err)); + } finally { + setLoading(false); + } + }; + + useEffect(() => { if (isSuperAdmin) fetchAdmins(); }, [isSuperAdmin]); + + const handleCreate = async (e: React.FormEvent) => { + e.preventDefault(); + if (!newEmail.trim() || !newPassword.trim()) return; + setCreating(true); + setError(''); + try { + await api.put('/users/admin/admins', { email: newEmail.trim(), password: newPassword }); + setShowCreate(false); + setNewEmail(''); + setNewPassword(''); + fetchAdmins(); + } catch (err) { + setError(getErrorMessage(err)); + } finally { + setCreating(false); + } + }; + + const handleDelete = async (id: string) => { + if (!confirm('Are you sure you want to delete this admin?')) return; + setDeletingId(id); + try { + await api.delete(`/users/admin/admins/${id}`); + setAdmins(prev => prev.filter(a => a.id !== id)); + } catch (err) { + setError(getErrorMessage(err)); + } finally { + setDeletingId(null); + } + }; + + if (!isSuperAdmin) return null; + + return ( +
+
+

Admin Users

+ +
+ + {error && ( +
+

{error}

+
+ )} + + {loading ? ( +
+
+
+ ) : ( +
+ + + + + + + + + + + + {admins.map(admin => ( + + + + + + + + ))} + +
EmailRoleStatusCreatedActions
{admin.email} + + {admin.role === 'SUPER_ADMIN' ? 'Super Admin' : 'Admin'} + + + + {admin.status} + + + {new Date(admin.createdAt).toLocaleDateString()} + + {admin.role === 'ADMIN' && admin.id !== user?.id ? ( + + ) : ( + + )} +
+
+ )} + + {/* Create Admin Modal */} + {showCreate && ( +
+
+
+

Create Admin User

+
+
+
+
+ + setNewEmail(e.target.value)} + required + className="w-full px-3 py-2 border border-gray-300 rounded-lg text-gray-900 bg-white placeholder:text-gray-500" + placeholder="admin@example.com" + /> +
+
+ + setNewPassword(e.target.value)} + required + minLength={8} + className="w-full px-3 py-2 border border-gray-300 rounded-lg text-gray-900 bg-white placeholder:text-gray-500" + placeholder="Min 8 characters" + /> +
+
+
+ + +
+
+
+
+ )} +
+ ); +} diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 79fd8ed..4b5f7f2 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -5,6 +5,7 @@ import { usePathname } from 'next/navigation'; import { useState, useEffect, useCallback } from 'react'; import { supportChatService } from '@/services/support-chat.service'; import { adminSocketService } from '@/services/socket.service'; +import { useAuth } from '@/context/AuthContext'; const menuItems = [ { @@ -137,6 +138,7 @@ const menuItems = [ export default function Sidebar() { const pathname = usePathname(); + const { isSuperAdmin } = useAuth(); const [unreadCount, setUnreadCount] = useState(0); const fetchUnreadCount = useCallback(async () => { @@ -229,6 +231,23 @@ export default function Sidebar() { ); })} + {isSuperAdmin && ( +
  • + + + + + Admin Users + +
  • + )} diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx index 01733a4..f9f64ea 100644 --- a/src/context/AuthContext.tsx +++ b/src/context/AuthContext.tsx @@ -8,6 +8,7 @@ interface AuthContextType { user: AuthUser | null; isLoading: boolean; isAuthenticated: boolean; + isSuperAdmin: boolean; login: (email: string, password: string) => Promise; logout: () => Promise; error: string | null; @@ -25,7 +26,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { try { const storedUser = authService.getStoredUser(); - if (storedUser && storedUser.role === 'ADMIN') { + if (storedUser && (storedUser.role === 'ADMIN' || storedUser.role === 'SUPER_ADMIN')) { setUser(storedUser); } else if (storedUser) { // Not admin, clear storage @@ -47,6 +48,13 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { setError(null); try { const response = await authService.login({ email, password }); + + // Block non-admin users from admin panel + if (response.user.role !== 'ADMIN' && response.user.role !== 'SUPER_ADMIN') { + authService.clearAuth(); + throw new Error('Access denied. Only admin users can login here.'); + } + setUser(response.user); router.push('/dashboard'); } catch (err) { @@ -71,6 +79,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { user, isLoading, isAuthenticated: !!user, + isSuperAdmin: user?.role === 'SUPER_ADMIN', login, logout, error,