mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-05 23:44:22 +00:00
Load users in AdminDashboard
This commit is contained in:
@@ -11,47 +11,33 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
Box,
|
Box,
|
||||||
|
LoadingOverlay,
|
||||||
|
Alert,
|
||||||
} from '@mantine/core';
|
} 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 CreateUserModal = ({ opened, onClose, onCreateUser, loading }) => {
|
||||||
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 [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [displayName, setDisplayName] = useState('');
|
const [displayName, setDisplayName] = useState('');
|
||||||
const [role, setRole] = useState('viewer');
|
const [role, setRole] = useState('viewer');
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = async () => {
|
||||||
onCreateUser({ email, password, displayName, role });
|
const result = await onCreateUser({ email, password, displayName, role });
|
||||||
setEmail('');
|
if (result.success) {
|
||||||
setPassword('');
|
setEmail('');
|
||||||
setDisplayName('');
|
setPassword('');
|
||||||
setRole('viewer');
|
setDisplayName('');
|
||||||
onClose();
|
setRole('viewer');
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -92,7 +78,9 @@ const CreateUserModal = ({ opened, onClose, onCreateUser }) => {
|
|||||||
<Button variant="default" onClick={onClose}>
|
<Button variant="default" onClick={onClose}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleSubmit}>Create User</Button>
|
<Button onClick={handleSubmit} loading={loading}>
|
||||||
|
Create User
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Modal>
|
</Modal>
|
||||||
@@ -100,20 +88,31 @@ const CreateUserModal = ({ opened, onClose, onCreateUser }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const AdminDashboard = ({ opened, onClose }) => {
|
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 [createModalOpened, setCreateModalOpened] = useState(false);
|
||||||
|
const { user: currentUser } = useAuth();
|
||||||
|
|
||||||
const handleCreateUser = (newUser) => {
|
const handleCreateUser = async (userData) => {
|
||||||
// In production, make an API call here
|
return await create(userData);
|
||||||
setUsers([
|
|
||||||
...users,
|
|
||||||
{ ...newUser, id: users.length + 1, createdAt: new Date().toISOString() },
|
|
||||||
]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteUser = (userId) => {
|
const handleDeleteUser = async (userId) => {
|
||||||
// In production, make an API call here
|
if (userId === currentUser.id) {
|
||||||
setUsers(users.filter((user) => user.id !== userId));
|
notifications.show({
|
||||||
|
title: 'Error',
|
||||||
|
message: 'You cannot delete your own account',
|
||||||
|
color: 'red',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await deleteUser(userId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const rows = users.map((user) => (
|
const rows = users.map((user) => (
|
||||||
@@ -133,6 +132,7 @@ const AdminDashboard = ({ opened, onClose }) => {
|
|||||||
variant="subtle"
|
variant="subtle"
|
||||||
color="red"
|
color="red"
|
||||||
onClick={() => handleDeleteUser(user.id)}
|
onClick={() => handleDeleteUser(user.id)}
|
||||||
|
disabled={user.id === currentUser.id}
|
||||||
>
|
>
|
||||||
<IconTrash size={16} />
|
<IconTrash size={16} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
@@ -143,7 +143,20 @@ const AdminDashboard = ({ opened, onClose }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal opened={opened} onClose={onClose} size="xl" title="Admin Dashboard">
|
<Modal opened={opened} onClose={onClose} size="xl" title="Admin Dashboard">
|
||||||
<Box>
|
<Box pos="relative">
|
||||||
|
<LoadingOverlay visible={loading} />
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<Alert
|
||||||
|
icon={<IconAlertCircle size={16} />}
|
||||||
|
title="Error"
|
||||||
|
color="red"
|
||||||
|
mb="md"
|
||||||
|
>
|
||||||
|
{error}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
<Group justify="space-between" mb="md">
|
<Group justify="space-between" mb="md">
|
||||||
<Text size="xl" fw={700}>
|
<Text size="xl" fw={700}>
|
||||||
User Management
|
User Management
|
||||||
@@ -173,6 +186,7 @@ const AdminDashboard = ({ opened, onClose }) => {
|
|||||||
opened={createModalOpened}
|
opened={createModalOpened}
|
||||||
onClose={() => setCreateModalOpened(false)}
|
onClose={() => setCreateModalOpened(false)}
|
||||||
onCreateUser={handleCreateUser}
|
onCreateUser={handleCreateUser}
|
||||||
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
155
frontend/src/hooks/useAdmin.js
Normal file
155
frontend/src/hooks/useAdmin.js
Normal file
@@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
39
frontend/src/services/adminApi.js
Normal file
39
frontend/src/services/adminApi.js
Normal file
@@ -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();
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user