diff --git a/frontend/src/components/settings/admin/AdminDashboard.jsx b/frontend/src/components/settings/admin/AdminDashboard.jsx
index 74f468b..3345ce2 100644
--- a/frontend/src/components/settings/admin/AdminDashboard.jsx
+++ b/frontend/src/components/settings/admin/AdminDashboard.jsx
@@ -11,47 +11,33 @@ import {
Text,
ActionIcon,
Box,
+ LoadingOverlay,
+ Alert,
} from '@mantine/core';
-import { IconTrash, IconEdit, IconPlus } from '@tabler/icons-react';
+import {
+ IconTrash,
+ IconEdit,
+ IconPlus,
+ IconAlertCircle,
+} from '@tabler/icons-react';
+import { useAdmin } from '../../../hooks/useAdmin';
+import { useAuth } from '../../../contexts/AuthContext';
-// Dummy data - replace with actual API calls in production
-const DUMMY_USERS = [
- {
- id: 1,
- email: 'admin@example.com',
- displayName: 'Admin User',
- role: 'admin',
- createdAt: '2024-01-01',
- },
- {
- id: 2,
- email: 'editor@example.com',
- displayName: 'Editor User',
- role: 'editor',
- createdAt: '2024-01-02',
- },
- {
- id: 3,
- email: 'viewer@example.com',
- displayName: 'Viewer User',
- role: 'viewer',
- createdAt: '2024-01-03',
- },
-];
-
-const CreateUserModal = ({ opened, onClose, onCreateUser }) => {
+const CreateUserModal = ({ opened, onClose, onCreateUser, loading }) => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [displayName, setDisplayName] = useState('');
const [role, setRole] = useState('viewer');
- const handleSubmit = () => {
- onCreateUser({ email, password, displayName, role });
- setEmail('');
- setPassword('');
- setDisplayName('');
- setRole('viewer');
- onClose();
+ const handleSubmit = async () => {
+ const result = await onCreateUser({ email, password, displayName, role });
+ if (result.success) {
+ setEmail('');
+ setPassword('');
+ setDisplayName('');
+ setRole('viewer');
+ onClose();
+ }
};
return (
@@ -92,7 +78,9 @@ const CreateUserModal = ({ opened, onClose, onCreateUser }) => {
-
+
@@ -100,20 +88,31 @@ const CreateUserModal = ({ opened, onClose, onCreateUser }) => {
};
const AdminDashboard = ({ opened, onClose }) => {
- const [users, setUsers] = useState(DUMMY_USERS);
+ const {
+ data: users,
+ loading,
+ error,
+ create,
+ delete: deleteUser,
+ } = useAdmin('users');
const [createModalOpened, setCreateModalOpened] = useState(false);
+ const { user: currentUser } = useAuth();
- const handleCreateUser = (newUser) => {
- // In production, make an API call here
- setUsers([
- ...users,
- { ...newUser, id: users.length + 1, createdAt: new Date().toISOString() },
- ]);
+ const handleCreateUser = async (userData) => {
+ return await create(userData);
};
- const handleDeleteUser = (userId) => {
- // In production, make an API call here
- setUsers(users.filter((user) => user.id !== userId));
+ const handleDeleteUser = async (userId) => {
+ if (userId === currentUser.id) {
+ notifications.show({
+ title: 'Error',
+ message: 'You cannot delete your own account',
+ color: 'red',
+ });
+ return;
+ }
+
+ return await deleteUser(userId);
};
const rows = users.map((user) => (
@@ -133,6 +132,7 @@ const AdminDashboard = ({ opened, onClose }) => {
variant="subtle"
color="red"
onClick={() => handleDeleteUser(user.id)}
+ disabled={user.id === currentUser.id}
>
@@ -143,7 +143,20 @@ const AdminDashboard = ({ opened, onClose }) => {
return (
-
+
+
+
+ {error && (
+ }
+ title="Error"
+ color="red"
+ mb="md"
+ >
+ {error}
+
+ )}
+
User Management
@@ -173,6 +186,7 @@ const AdminDashboard = ({ opened, onClose }) => {
opened={createModalOpened}
onClose={() => setCreateModalOpened(false)}
onCreateUser={handleCreateUser}
+ loading={loading}
/>
diff --git a/frontend/src/hooks/useAdmin.js b/frontend/src/hooks/useAdmin.js
new file mode 100644
index 0000000..08d3cc4
--- /dev/null
+++ b/frontend/src/hooks/useAdmin.js
@@ -0,0 +1,155 @@
+import { useState, useCallback, useEffect } from 'react';
+import { notifications } from '@mantine/notifications';
+import * as adminApi from '../services/adminApi';
+
+export const useAdmin = (resource) => {
+ const [data, setData] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ const fetchData = useCallback(async () => {
+ setLoading(true);
+ try {
+ let result;
+ switch (resource) {
+ case 'users':
+ result = await adminApi.listUsers();
+ break;
+ case 'stats':
+ result = await adminApi.getSystemStats();
+ break;
+ default:
+ throw new Error(`Unknown resource type: ${resource}`);
+ }
+ setData(result);
+ setError(null);
+ } catch (err) {
+ console.error(`Failed to fetch ${resource}:`, err);
+ setError(err.message);
+ notifications.show({
+ title: 'Error',
+ message: `Failed to load ${resource}. Please try again.`,
+ color: 'red',
+ });
+ } finally {
+ setLoading(false);
+ }
+ }, [resource]);
+
+ const createItem = useCallback(
+ async (itemData) => {
+ try {
+ let newItem;
+ switch (resource) {
+ case 'users':
+ newItem = await adminApi.createUser(itemData);
+ break;
+ default:
+ throw new Error(`Create not supported for resource: ${resource}`);
+ }
+ setData((prevData) =>
+ Array.isArray(prevData) ? [...prevData, newItem] : prevData
+ );
+ notifications.show({
+ title: 'Success',
+ message: `${resource} created successfully`,
+ color: 'green',
+ });
+ return { success: true, data: newItem };
+ } catch (err) {
+ console.error(`Failed to create ${resource}:`, err);
+ notifications.show({
+ title: 'Error',
+ message: err.message || `Failed to create ${resource}`,
+ color: 'red',
+ });
+ return { success: false, error: err.message };
+ }
+ },
+ [resource]
+ );
+
+ const deleteItem = useCallback(
+ async (itemId) => {
+ try {
+ switch (resource) {
+ case 'users':
+ await adminApi.deleteUser(itemId);
+ break;
+ default:
+ throw new Error(`Delete not supported for resource: ${resource}`);
+ }
+ setData((prevData) =>
+ Array.isArray(prevData)
+ ? prevData.filter((item) => item.id !== itemId)
+ : prevData
+ );
+ notifications.show({
+ title: 'Success',
+ message: `${resource} deleted successfully`,
+ color: 'green',
+ });
+ return { success: true };
+ } catch (err) {
+ console.error(`Failed to delete ${resource}:`, err);
+ notifications.show({
+ title: 'Error',
+ message: err.message || `Failed to delete ${resource}`,
+ color: 'red',
+ });
+ return { success: false, error: err.message };
+ }
+ },
+ [resource]
+ );
+
+ const updateItem = useCallback(
+ async (itemId, itemData) => {
+ try {
+ let updatedItem;
+ switch (resource) {
+ case 'users':
+ updatedItem = await adminApi.updateUser(itemId, itemData);
+ break;
+ default:
+ throw new Error(`Update not supported for resource: ${resource}`);
+ }
+ setData((prevData) =>
+ Array.isArray(prevData)
+ ? prevData.map((item) => (item.id === itemId ? updatedItem : item))
+ : prevData
+ );
+ notifications.show({
+ title: 'Success',
+ message: `${resource} updated successfully`,
+ color: 'green',
+ });
+ return { success: true, data: updatedItem };
+ } catch (err) {
+ console.error(`Failed to update ${resource}:`, err);
+ notifications.show({
+ title: 'Error',
+ message: err.message || `Failed to update ${resource}`,
+ color: 'red',
+ });
+ return { success: false, error: err.message };
+ }
+ },
+ [resource]
+ );
+
+ // Fetch data on mount
+ useEffect(() => {
+ fetchData();
+ }, [fetchData]);
+
+ return {
+ data,
+ loading,
+ error,
+ refresh: fetchData,
+ create: createItem,
+ delete: deleteItem,
+ update: updateItem,
+ };
+};
diff --git a/frontend/src/services/adminApi.js b/frontend/src/services/adminApi.js
new file mode 100644
index 0000000..3c6c4a0
--- /dev/null
+++ b/frontend/src/services/adminApi.js
@@ -0,0 +1,39 @@
+import { apiCall } from './authApi';
+import { API_BASE_URL } from '../utils/constants';
+
+const ADMIN_BASE_URL = `${API_BASE_URL}/admin`;
+
+// User Management
+export const listUsers = async () => {
+ const response = await apiCall(`${ADMIN_BASE_URL}/users`);
+ return response.json();
+};
+
+export const createUser = async (userData) => {
+ const response = await apiCall(`${ADMIN_BASE_URL}/users`, {
+ method: 'POST',
+ body: JSON.stringify(userData),
+ });
+ return response.json();
+};
+
+export const deleteUser = async (userId) => {
+ const response = await apiCall(`${ADMIN_BASE_URL}/users/${userId}`, {
+ method: 'DELETE',
+ });
+ return response.json();
+};
+
+export const updateUser = async (userId, userData) => {
+ const response = await apiCall(`${ADMIN_BASE_URL}/users/${userId}`, {
+ method: 'PUT',
+ body: JSON.stringify(userData),
+ });
+ return response.json();
+};
+
+// System Statistics
+export const getSystemStats = async () => {
+ const response = await apiCall(`${ADMIN_BASE_URL}/stats`);
+ return response.json();
+};