How to Build a Complete User Management Page in Mantis React Admin Template (MUI + CRUD Tutorial)How to Build a Complete User Management Page in Mantis React Admin Template (MUI + CRUD Tutorial)

Hello guys, how are you? Welcome back to my blog therichpost.com. Today in this post I will tell you How to Build a Complete User Management Page in Mantis React Admin Template (MUI + CRUD Tutorial).

Live Demo

For react js new comers, please check the below links:

  1. Reactjs Tutorials
  2. Nextjs
  3. Bootstrap 5
  4. React Free Ecommerce Templates
  5. React Free Admins

Why Mantis React is Perfect for Custom Admin Pages

The Mantis free template already includes:

  • Responsive layout
  • Sidebar navigation
  • MUI-based components
  • Hooks for custom pages
  • Pre-configured theme + typography

This makes extending the template extremely fast. We only need to create:

  • One page component
  • One route
  • One sidebar link

Everything else integrates perfectly.

Here is the GitHub link from we will download free version : React Admin Mantis free


Step 1 — Create the User Management Page

Create a new file src/pages/UserManagement.jsx and add below code inside it:

import React, { useEffect, useState } from 'react';
import {
  Box,
  Typography,
  TextField,
  Button,
  Paper,
  Table,
  TableHead,
  TableBody,
  TableCell,
  TableRow,
  TableContainer,
  TablePagination,
  Dialog,
  DialogTitle,
  DialogContent,
  DialogActions,
  MenuItem,
  Select,
  FormControl,
  InputLabel,
  IconButton,
  Stack,
  Avatar,
  SvgIcon,
} from '@mui/material';

/**
 * Inline SVG icons implemented with MUI SvgIcon to avoid external icon packages.
 * You can replace these with your project's asset icons if you prefer.
 */
function PencilIcon(props) {
  return (
    <SvgIcon {...props} viewBox="0 0 24 24">
      <path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1.003 1.003 0 0 0 0-1.42l-2.34-2.34a1.003 1.003 0 0 0-1.42 0l-1.83 1.83 3.75 3.75 1.84-1.82z" />
    </SvgIcon>
  );
}

function TrashIcon(props) {
  return (
    <SvgIcon {...props} viewBox="0 0 24 24">
      <path d="M6 19a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" />
    </SvgIcon>
  );
}

function UsersIcon(props) {
  return (
    <SvgIcon {...props} viewBox="0 0 24 24">
      <path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5s-3 1.34-3 3 1.34 3 3 3zM6 11c1.66 0 3-1.34 3-3S7.66 5 6 5 3 6.34 3 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5C13 14.17 8.33 13 6 13zm10 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-6-3.5z" />
    </SvgIcon>
  );
}

const STORAGE_KEY = 'mantis_users_v1';

function loadFromStorage() {
  const raw = localStorage.getItem(STORAGE_KEY);
  if (!raw) {
    const seed = [
      { id: 1, name: 'Alice Johnson', email: 'alice@example.com', role: 'admin', status: 'active' },
      { id: 2, name: 'Bob Smith', email: 'bob@example.com', role: 'user', status: 'inactive' },
      { id: 3, name: 'Cecilia Brown', email: 'cecilia@example.com', role: 'manager', status: 'active' },
    ];
    localStorage.setItem(STORAGE_KEY, JSON.stringify(seed));
    return seed;
  }
  try {
    return JSON.parse(raw);
  } catch (e) {
    console.error('Malformed users in storage', e);
    return [];
  }
}

function saveToStorage(list) {
  localStorage.setItem(STORAGE_KEY, JSON.stringify(list));
}

export default function UserManagement() {
  const [users, setUsers] = useState([]);
  const [query, setQuery] = useState('');
  const [page, setPage] = useState(0);
  const [rowsPerPage, setRowsPerPage] = useState(8);

  // Dialog/form
  const [open, setOpen] = useState(false);
  const [editingUser, setEditingUser] = useState(null);
  const [form, setForm] = useState({ name: '', email: '', role: 'user', status: 'active' });

  useEffect(() => {
    setUsers(loadFromStorage());
  }, []);

  const handleOpenAdd = () => {
    setEditingUser(null);
    setForm({ name: '', email: '', role: 'user', status: 'active' });
    setOpen(true);
  };

  const handleOpenEdit = (u) => {
    setEditingUser(u);
    setForm({ name: u.name, email: u.email, role: u.role, status: u.status });
    setOpen(true);
  };

  const handleClose = () => setOpen(false);

  const handleDelete = (id) => {
    if (!window.confirm('Delete this user?')) return;
    const updated = users.filter((u) => u.id !== id);
    setUsers(updated);
    saveToStorage(updated);
    const maxPage = Math.max(0, Math.ceil(updated.length / rowsPerPage) - 1);
    setPage((p) => Math.min(p, maxPage));
  };

  const handleSubmit = (e) => {
    e?.preventDefault();
    if (!form.name.trim() || !form.email.trim()) {
      alert('Name and email are required.');
      return;
    }

    if (editingUser) {
      const updated = users.map((u) => (u.id === editingUser.id ? { ...u, ...form } : u));
      setUsers(updated);
      saveToStorage(updated);
    } else {
      const nextId = users.length ? Math.max(...users.map((u) => u.id)) + 1 : 1;
      const newUser = { id: nextId, ...form };
      const updated = [newUser, ...users];
      setUsers(updated);
      saveToStorage(updated);
      setPage(0);
    }

    setOpen(false);
  };

  // filtering
  const filtered = users.filter((u) => {
    const q = query.toLowerCase();
    return (
      u.name.toLowerCase().includes(q) ||
      u.email.toLowerCase().includes(q) ||
      u.role.toLowerCase().includes(q) ||
      u.status.toLowerCase().includes(q)
    );
  });

  const paginated = filtered.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
  const emptyRows = Math.max(0, (1 + page) * rowsPerPage - filtered.length);

  return (
    <Box sx={{ p: 3 }}>
      <Stack direction="row" justifyContent="space-between" alignItems="center" mb={2}>
        <Stack direction="row" alignItems="center" spacing={2}>
          <Avatar sx={{ bgcolor: 'primary.main' }}>
            <UsersIcon fontSize="small" />
          </Avatar>
          <Typography variant="h5">User Management</Typography>
        </Stack>

        <Stack direction="row" spacing={2} alignItems="center">
          <TextField
            size="small"
            placeholder="Search by name, email or role"
            value={query}
            onChange={(e) => {
              setQuery(e.target.value);
              setPage(0);
            }}
            sx={{ width: 320 }}
          />
          <Button variant="contained" onClick={handleOpenAdd}>
            Add User
          </Button>
        </Stack>
      </Stack>

      <Paper elevation={1}>
        <TableContainer>
          <Table>
            <TableHead>
              <TableRow>
                <TableCell>Name</TableCell>
                <TableCell>Email</TableCell>
                <TableCell>Role</TableCell>
                <TableCell>Status</TableCell>
                <TableCell align="right">Actions</TableCell>
              </TableRow>
            </TableHead>

            <TableBody>
              {paginated.length === 0 && (
                <TableRow>
                  <TableCell colSpan={5} align="center">
                    No users found.
                  </TableCell>
                </TableRow>
              )}

              {paginated.map((u) => (
                <TableRow key={u.id} hover>
                  <TableCell>
                    <Stack direction="row" spacing={2} alignItems="center">
                      <Avatar>{u.name.charAt(0)}</Avatar>
                      <Box>
                        <Typography variant="subtitle2">{u.name}</Typography>
                        <Typography variant="caption" color="text.secondary">
                          {u.email}
                        </Typography>
                      </Box>
                    </Stack>
                  </TableCell>

                  <TableCell>{u.email}</TableCell>
                  <TableCell>{u.role}</TableCell>
                  <TableCell>{u.status}</TableCell>
                  <TableCell align="right">
                    <IconButton size="small" onClick={() => handleOpenEdit(u)} aria-label="edit">
                      <PencilIcon fontSize="small" />
                    </IconButton>
                    <IconButton size="small" onClick={() => handleDelete(u.id)} aria-label="delete">
                      <TrashIcon fontSize="small" />
                    </IconButton>
                  </TableCell>
                </TableRow>
              ))}

              {emptyRows > 0 && (
                <TableRow style={{ height: 53 * emptyRows }}>
                  <TableCell colSpan={5} />
                </TableRow>
              )}
            </TableBody>
          </Table>
        </TableContainer>

        <TablePagination
          component="div"
          count={filtered.length}
          page={page}
          onPageChange={(e, newPage) => setPage(newPage)}
          rowsPerPage={rowsPerPage}
          onRowsPerPageChange={(e) => {
            setRowsPerPage(parseInt(e.target.value, 10));
            setPage(0);
          }}
          rowsPerPageOptions={[5, 8, 10, 25]}
        />
      </Paper>

      {/* Dialog */}
      <Dialog open={open} onClose={handleClose} fullWidth maxWidth="sm">
        <DialogTitle>{editingUser ? 'Edit User' : 'Add User'}</DialogTitle>
        <DialogContent>
          <Box component="form" onSubmit={handleSubmit} sx={{ mt: 1, display: 'grid', gap: 2 }}>
            <TextField label="Full name" fullWidth value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} />
            <TextField label="Email" fullWidth value={form.email} onChange={(e) => setForm({ ...form, email: e.target.value })} />

            <Stack direction="row" spacing={2}>
              <FormControl fullWidth>
                <InputLabel id="role-label">Role</InputLabel>
                <Select labelId="role-label" value={form.role} label="Role" onChange={(e) => setForm({ ...form, role: e.target.value })}>
                  <MenuItem value="user">User</MenuItem>
                  <MenuItem value="admin">Admin</MenuItem>
                  <MenuItem value="manager">Manager</MenuItem>
                </Select>
              </FormControl>

              <FormControl fullWidth>
                <InputLabel id="status-label">Status</InputLabel>
                <Select labelId="status-label" value={form.status} label="Status" onChange={(e) => setForm({ ...form, status: e.target.value })}>
                  <MenuItem value="active">Active</MenuItem>
                  <MenuItem value="inactive">Inactive</MenuItem>
                  <MenuItem value="pending">Pending</MenuItem>
                </Select>
              </FormControl>
            </Stack>
          </Box>
        </DialogContent>

        <DialogActions>
          <Button onClick={handleClose}>Cancel</Button>
          <Button onClick={handleSubmit} variant="contained">
            Save
          </Button>
        </DialogActions>
      </Dialog>
    </Box>
  );
}

Step 2 — Add Routing and for that add below code inside src/routes/MainRoutes.jsx:

...
const UserManagement = Loadable(lazy(() => import('pages/UserManagement') ));
...
[
...
{
      path: 'users',
      element: <UserManagement />
    }
]

Step 3 — Add Sidebar Navigation for that add below code inside menu-items/support.jsx file:

// assets
import { ChromeOutlined, QuestionOutlined, UserOutlined } from '@ant-design/icons';

// icons
const icons = {
  ChromeOutlined,
  QuestionOutlined,
  UserOutlined
};
...
[
...
{
      id: 'users-page',
      title: 'Users Page',
      type: 'item',
      url: '/users',
      icon: icons.UserOutlined
    },

Final Result: A Fully Functional User Management System

When finished, you’ll have:

  • A clean, modern API-ready user management module
  • Full CRUD operations
  • Scalable MUI architecture
  • Sidebar integration
  • Reusable components
  • 100% native to the Mantis template

This is exactly the type of page used in real-world SaaS dashboards and admin panels.


Conclusion

The Mantis Free React Admin Template is powerful, flexible, and easy to extend.
By creating your own User Management page, you take full control of the admin panel and open the door to building:

  • Role-Based Access Control
  • Permission systems
  • Authentication dashboards
  • Customer/Subscriber management
  • Product or order management

If you want the next part — integrating this with an API backend (Node / Express / MongoDB or Supabase) — I can generate the full code.

Just say “Write Part 2 Backend API”.

Ajay

Thanks

By therichpost

Hello to all. Welcome to therichpost.com. Myself Ajay Malhotra and I am freelance full stack developer. I love coding. I know WordPress, Core php, Angularjs, Angular 19, MedusaJs, Next.js, Bootstrap 5, Nodejs, Laravel, Codeigniter, Shopify, Squarespace, jQuery, Google Map Api, Vuejs, Reactjs, Big commerce etc.