Creating an Admin Panel in React: Manage Users, Roles, and Permissions

by Vincy. Last modified on October 8th, 2025.

Building an admin panel in React provides an efficient interface to manage your application entities.

In this React example, we are creating an admin panel to manage users, roles, and user privileges that have access to features.

It gives a modern web application with React’s component-based architecture connected to backend APIs.

It has a secure user authentication process to log in to the React admin console.

In this React tutorial, you will have a production-ready React admin panel equipped with backend support for user management, role assignment, and permission control.

Admin login authentication

react admin template login page

The first step is to login before landing in the React admin panel.

It is a basic login template that promts the user email and password to verify their authenticity.

The login form submit action calls the handleSubmit hook to send the login request to the PHP.

Once logged in, the state is managed in the client’s local storage. It will be used as a reference to perform automatic navigation based on the logged-in state.

This JSX file has the login template and the required state variables and hooks needed for managing the user login.

Login.js

import React, { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import axios from 'axios'
import './Login.css'
function Login() {
  const [userId, setUserId] = useState('')
  const [password, setPassword] = useState('')
  const [errorMessage, setErrorMessage] = useState('')
  const navigate = useNavigate()
  useEffect(() => {
    const userId = localStorage.getItem('user_id') // check the same key you store after login
    if (userId) {
      navigate('/users') // redirect if already logged in
    }
  }, [navigate])
  const handleSubmit = async (event) => {
    event.preventDefault()
    if (!userId || !password) {
      setErrorMessage('Please enter both User ID and Password.')
      return
    }
    setErrorMessage('')
    try {
      const res = await axios.post('http://localhost/react-admin-template-api/login-action.php', {
        email: userId, // send email
        password: password, // plain password entered
      })
      const data = res.data
      console.log('Login response:', data)
      if (data.success) {
        // Login successful
        localStorage.setItem('user_id', data.user_id) // store user ID for session
        localStorage.setItem('user_type', data.user_type) // optional
        navigate('/users')
      } else {
        setErrorMessage(data.error || 'Invalid User ID or Password.')
      }
    } catch (err) {
      console.error(err)
      setErrorMessage('Server error. Please try again.')
    }
  }
  return (
    <div className="login-page-container">
      <div className="adminlogin-container">
        <h2>User Login</h2>
        <form onSubmit={handleSubmit} className="login-form">
          {errorMessage && <p className="error-message">{errorMessage}</p>}
          <div className="form-group">
            <label htmlFor="userId">Email ID:</label>
            <input
              type="text"
              id="userId"
              name="userId"
              placeholder="Enter your Email ID"
              value={userId}
              onChange={(e) => setUserId(e.target.value)}
              required
            />
          </div>
          <div className="form-group">
            <label htmlFor="password">Password:</label>
            <input
              type="password"
              id="password"
              name="password"
              placeholder="Enter your Password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              required
            />
          </div>
          <button type="submit" className="login-button">
            Log In
          </button>
        </form>
      </div>
    </div>
  )
}
export default Login

Most of you are familiar with this PHP script created for processing the login request.

If you want to see how to manage login with PHP session, read the linked tutorial.

login-action.php

<?php
header("Access-Control-Allow-Origin:  http://localhost:3000");
header("Access-Control-Allow-Methods: POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
include "db.php";
$raw = file_get_contents("php://input");
$data = json_decode($raw, true);
// Validate input
if (!$data || !isset($data['email'], $data['password'])) {
    echo json_encode(['success' => false, 'error' => 'Invalid input']);
    exit;
}
$email = trim($data['email']);
$password = trim($data['password']);
// Fetch user info by email
$stmt = $conn->prepare("SELECT id, first_name, last_name, password, user_type FROM users WHERE email = ?");
$stmt->bind_param("s", $email);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($id, $firstName, $lastName, $hashedPassword, $userType);
if ($stmt->num_rows === 0) {
    echo json_encode(['success' => false, 'error' => 'User not found']);
    $stmt->close();
    $conn->close();
    exit;
}
$stmt->fetch();
// Verify hashed password
if (password_verify($password, $hashedPassword)) {
    echo json_encode([
        'success' => true,
        'user_id' => $id,
        'first_name' => $firstName,
        'last_name' => $lastName,
        'user_type' => $userType
    ]);
} else {
    echo json_encode(['success' => false, 'error' => 'Invalid password']);
}
$stmt->close();
$conn->close();

Admin dashboard charts and navigations

After login, the landing page will show an admin dashboard. It is the right place to showcase all your application highlights, statistics and quick navigation and all.

This React admin dashboard shows the registered users’ overall count and current month count in a card-like interface.

It displays a line chart for the number of users registered for every month.

React Admin Console Dashboard

The below script returns the React widget to show the registered user count. The count is requested via axios and set to the React state variable.

views/widgets/WidgetsCard.js

import React, { useEffect, useRef, useState } from 'react'
import axios from 'axios'
import { CRow, CCol, CWidgetStatsA } from '@coreui/react'
import { getStyle } from '@coreui/utils'
import { CChartLine } from '@coreui/react-chartjs'
import { useNavigate } from 'react-router-dom'

const WidgetsCard = (props) => {
  const widgetChartRef1 = useRef(null)
  const [loggedInUserRole, setLoggedInUserRole] = useState('')
  const [totalUsers, setTotalUsers] = useState(0)
  const [monthUsers, setMonthUsers] = useState(0)
  const navigate = useNavigate()

  useEffect(() => {
    const loggedInUserId = localStorage.getItem('user_id') || 0
    if (!loggedInUserId) return
    axios
      .get('http://localhost/react-admin-template-api/get-role-by-user.php?user_id='+loggedInUserId)
      .then((res) => {
        if (res.data.user_type) {
          setLoggedInUserRole(res.data.user_type)
        }
      })
      .catch(() => console.error('Error fetching role'))

      // Fetch total users
    axios
    .get('http://localhost/react-admin-template-api/get-total-user-count.php')
    .then((res) => {
      if (res.data.total_users) {
        setTotalUsers(res.data.total_users)
      }
    })
    .catch(() => console.error('Error fetching total users'))

  // Fetch this month’s users
  axios
    .get('http://localhost/react-admin-template-api/get-month-user-count.php')
    .then((res) => {
      if (res.data.month_users) {
        setMonthUsers(res.data.month_users)
      }
    })
    .catch(() => console.error('Error fetching month users'))
  }, [])

  const handleUserClick = () => navigate('/users')
  const handlePermissionClick = () => navigate('/permission')

  return (
    <CRow className={props.className} xs={{ gutter: 4 }}>
      <CCol sm={6} xl={8} xxl={6}>
        <CWidgetStatsA
          color="primary"
          value={
            <>
              {totalUsers} 
              <span className="fs-6 fw-normal">
                 ({monthUsers} this month)
              </span>
            </>
          }
          title="Users"
          onClick={handleUserClick}
          chart={
            <CChartLine
              ref={widgetChartRef1}
              className="mt-3 mx-3"
              style={{ height: '70px' }}
              data={{
                labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'],
                datasets: [
                  {
                    label: 'Users',
                    backgroundColor: 'transparent',
                    borderColor: 'rgba(255,255,255,.55)',
                    pointBackgroundColor: getStyle('--cui-primary'),
                    data: [65, 59, 84, 84, 51, 55, 40],
                  },
                ],
              }}
              options={{
                plugins: { legend: { display: false } },
                maintainAspectRatio: false,
                scales: { x: { display: false }, y: { display: false } },
                elements: { line: { borderWidth: 1, tension: 0.4 }, point: { radius: 4 } },
              }}
            />
          }
        />
      </CCol>
      {['SuperAdmin'].includes(loggedInUserRole) && (
        <CCol sm={6} xl={8} xxl={6}>
          <CWidgetStatsA
            color="warning"
            value={
              <>
                Set Privileges
                <span className="fs-6 fw-normal">
                  (Super Admin only)
                </span>
              </>
            }
            title="Permission"
            onClick={handlePermissionClick}
            chart={
              <CChartLine
                className="mt-3"
                style={{ height: '70px' }}
                data={{
                  labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'],
                  datasets: [
                    {
                      label: 'Permission',
                      backgroundColor: 'rgba(255,255,255,.2)',
                      borderColor: 'rgba(255,255,255,.55)',
                      data: [78, 81, 80, 45, 34, 12, 40],
                      fill: true,
                    },
                  ],
                }}
                options={{
                  plugins: { legend: { display: false } },
                  maintainAspectRatio: false,
                  scales: { x: { display: false }, y: { display: false } },
                  elements: { line: { borderWidth: 2, tension: 0.4 }, point: { radius: 0 } },
                }}
              />
            }
          />
        </CCol>
      )}
    </CRow>
  )
}

export default WidgetsCard

The line chart showing the monthly signup statistics uses the React-ChartJS library.

views/dashboard/MainChart.js

import React, { useEffect, useRef, useState } from 'react'
import { CChartLine } from '@coreui/react-chartjs'
import { getStyle } from '@coreui/utils'
import axios from 'axios'

const MainChart = ({ newSignup = 0 }) => {
  const chartRef = useRef(null)
  const [userData, setUserData] = useState(new Array(12).fill(0))

  useEffect(() => {
    axios
      .get('http://localhost/react-admin-template-api/fetch-registered-by-month.php')
      .then((res) => {
        const data = new Array(12).fill(0)
        res.data.forEach((item) => {
          const monthIndex = new Date(item.month + '-01').getMonth()
          data[monthIndex] = Number(item.total)
        })
        setUserData(data)
        console.log('User data for chart:', data)
      })
      .catch((err) => console.error(err))
  }, [])

  // Update chart dynamically when newSignup changes
  useEffect(() => {
    if (newSignup > 0) {
      const currentMonthIndex = new Date().getMonth()
      setUserData((prevData) => {
        const updatedData = [...prevData]
        updatedData[currentMonthIndex] += newSignup
        return updatedData
      })
    }
  }, [newSignup])

  useEffect(() => {
    const handleColorSchemeChange = () => {
      if (chartRef.current) {
        setTimeout(() => {
          const chart = chartRef.current
          chart.options.scales.x.grid.color = getStyle('--cui-border-color-translucent')
          chart.options.scales.x.ticks.color = getStyle('--cui-body-color')
          chart.options.scales.y.grid.color = getStyle('--cui-border-color-translucent')
          if (chart.options.plugins.legend) {
            chart.options.plugins.legend.labels.color = getStyle('--cui-body-color')
          }
          chart.options.scales.y.ticks.color = getStyle('--cui-body-color')
          chart.update()
        }, 50)
      }
    }

    document.documentElement.addEventListener('ColorSchemeChange', handleColorSchemeChange)
    return () => {
      document.documentElement.removeEventListener('ColorSchemeChange', handleColorSchemeChange)
    }
  }, [chartRef])

  const maxDataValue = Math.max(...userData)
  const yAxisMax = maxDataValue > 0 ? Math.ceil(maxDataValue * 1.2) : 100

  return (
    <CChartLine
      ref={chartRef}
      style={{ height: '300px', marginTop: '20px' }}
      data={{
        labels: [
          'Jan',
          'Feb',
          'Mar',
          'Apr',
          'May',
          'Jun',
          'Jul',
          'Aug',
          'Sep',
          'Oct',
          'Nov',
          'Dec',
        ],
        datasets: [
          {
            label: 'Monthly Signups',
            backgroundColor: `rgba(${getStyle('--cui-info-rgb')}, .1)`,
            borderColor: getStyle('--cui-info'),
            pointHoverBackgroundColor: getStyle('--cui-info'),
            borderWidth: 2,
            data: userData,
            fill: true,
          },
        ],
      }}
      options={{
        maintainAspectRatio: false,
        plugins: {
          legend: {
            display: true,
            position: 'top',
            align: 'end',
          },
        },
        scales: {
          x: {
            grid: { color: getStyle('--cui-border-color-translucent'), drawOnChartArea: false },
            ticks: { color: getStyle('--cui-body-color') },
          },
          y: {
            beginAtZero: true,
            grid: { color: getStyle('--cui-border-color-translucent') },
            max: yAxisMax,
            ticks: {
              color: getStyle('--cui-body-color'),
              maxTicksLimit: 5,
              stepSize: Math.ceil(yAxisMax / 5),
            },
          },
        },
        elements: { line: { tension: 0.4 }, point: { radius: 0, hitRadius: 10, hoverRadius: 4 } },
      }}
    />
  )
}

export default MainChart

Role-based user management via admin panel

In this React admin template, the user list is shown in a tabular form. This example provides to list the paginated results in a React template.

This tabular view is with the component-based structure. The components have the JSX to group each part of the user-list template.

It groups the cells into a row component. and groups the row component into a table. The dynamic data is loaded into these components.

react admin manage users

UsersTable.js

import React, { useState, useEffect } from 'react'
import {
  CTable,
  CTableBody,
  CTableDataCell,
  CTableHead,
  CTableHeaderCell,
  CTableRow,
  CButton,
  CPagination,
  CPaginationItem,
} from '@coreui/react'
import CIcon from '@coreui/icons-react'
import { cilWarning } from '@coreui/icons'
import UserAddForm from './UserAddForm'
import { useNavigate } from 'react-router-dom'
import axios from 'axios'
const USERS_PER_PAGE = 10
const Users = () => {
  const [showForm, setShowForm] = useState(false)
  const [loggedInUserRole, setLoggedInUserRole] = useState('')
  const [warning, setWarning] = useState('')
  const [users, setUsers] = useState([])
  const [currentPage, setCurrentPage] = useState(1)
  const navigate = useNavigate()
  useEffect(() => {
    const loggedInUserId = localStorage.getItem('user_id') || 0
    fetch(`http://localhost/react-admin-template-api/get-role-by-user.php?user_id=${loggedInUserId}`)
      .then((res) => res.json())
      .then((data) => setLoggedInUserRole(data.user_type))
      .catch((err) => console.error(err))
  }, [])
  useEffect(() => {
    axios
      .get('http://localhost/react-admin-template-api/get-user.php')
      .then((res) => setUsers(res.data))
      .catch((err) => console.error(err))
  }, [])
  const pageCount = Math.ceil(users.length / USERS_PER_PAGE)
  const startIndex = (currentPage - 1) * USERS_PER_PAGE
  const endIndex = startIndex + USERS_PER_PAGE
  const currentUsers = users.slice(startIndex, endIndex)
  const handleAddUser = () => {
    if (loggedInUserRole === 'Admin' || loggedInUserRole === 'SuperAdmin') {
      navigate('/users/add')
    } else {
      setWarning(' Access Denied ')
      setTimeout(() => setWarning(''), 3000)
    }
  }
  const handleUserSubmit = () => {
    setShowForm(false)
  }
  const handleCancelForm = () => setShowForm(false)
  // ** Pagination numbers with ellipsis **
  const getPageNumbers = () => {
    const pages = []
    if (pageCount <= 5) {
      for (let i = 1; i <= pageCount; i++) pages.push(i)
    } else {
      if (currentPage <= 3) {
        pages.push(1, 2, 3, '...', pageCount)
      } else if (currentPage >= pageCount - 2) {
        pages.push(1, '...', pageCount - 2, pageCount - 1, pageCount)
      } else {
        pages.push(1, '...', currentPage - 1, currentPage, currentPage + 1, '...', pageCount)
      }
    }
    return pages
  }
  return (
    <>
      {showForm ? (
        <UserAddForm onAddUser={handleUserSubmit} onCancel={handleCancelForm} />
      ) : (
        <>
          <div className="mb-3 d-flex justify-content-end align-items-center gap-2">
            {warning && (
              <div className="alert alert-danger d-flex align-items-center mb-0 py-1 px-2 fw-bold">
                <CIcon icon={cilWarning} className="me-2" />
                {warning}
              </div>
            )}
            <CButton color="primary" onClick={handleAddUser}>
              + Add User
            </CButton>
          </div>
          <CTable align="middle" className="mb-0 border" hover responsive>
            <CTableHead>
              <CTableRow>
                <CTableHeaderCell>User</CTableHeaderCell>
                <CTableHeaderCell>Email</CTableHeaderCell>
                <CTableHeaderCell>Country</CTableHeaderCell>
                <CTableHeaderCell>Payment Method</CTableHeaderCell>
                <CTableHeaderCell>User Type</CTableHeaderCell>
              </CTableRow>
            </CTableHead>
            <CTableBody>
              {currentUsers.map((user, index) => (
                <CTableRow key={startIndex + index}>
                  <CTableDataCell>
                    {user.first_name} {user.last_name}
                  </CTableDataCell>
                  <CTableDataCell>{user.email}</CTableDataCell>
                  <CTableDataCell>{user.country}</CTableDataCell>
                  <CTableDataCell>{user.payment_method}</CTableDataCell>
                  <CTableDataCell>{user.user_type}</CTableDataCell>
                </CTableRow>
              ))}
            </CTableBody>
          </CTable>
          {/* CoreUI Pagination with ellipsis */}
          {users.length > USERS_PER_PAGE && (
            <CPagination align="left" className="mt-3">
              <CPaginationItem
                aria-label="Previous"
                disabled={currentPage === 1}
                onClick={() => setCurrentPage(currentPage - 1)}
              >
                &laquo;
              </CPaginationItem>
              {getPageNumbers().map((page, index) =>
                page === '...' ? (
                  <CPaginationItem key={index} disabled>
                    ...
                  </CPaginationItem>
                ) : (
                  <CPaginationItem
                    key={page}
                    active={page === currentPage}
                    onClick={() => setCurrentPage(page)}
                  >
                    {page}
                  </CPaginationItem>
                ),
              )}
              <CPaginationItem
                aria-label="Next"
                disabled={currentPage === pageCount}
                onClick={() => setCurrentPage(currentPage + 1)}
              >
                &raquo;
              </CPaginationItem>
            </CPagination>
          )}
        </>
      )}
    </>
  )
}
export default Users

This PHP file is part of the backend API created for this example. It returns the user data in a JSON format. It returns the data in descending order.

get-user.php

<?php
header("Access-Control-Allow-Origin:  http://localhost:3000");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
include "db.php";
// If it's a GET request → fetch all users
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    $result = $conn->query("SELECT * FROM users ORDER BY create_at DESC");
    $users = [];
    while ($row = $result->fetch_assoc()) {
        $users[] = $row;
    }
    $conn->close();
    echo json_encode($users);
}

Create a new user into the database

If you want to enter a new entry to the database, you should have write permission. This form is used to add a new user to the database.

It has the React validation, function to apply minimum client-side validation on submit.

After React validation the entered data is passed to the PHP. In the backend, it verifies the users’ uniqueness based on the email ID submitted via the form.

This admin panel form shows a toast message to acknowledge the request of the end user.

In a previous tutorial, we saw how to build form using React Hook Form. You can replace this form with React Hook Form to minimize the custom code.

react admin add user

UserAddForm.js

import React, { useState, useMemo } from 'react'
import {
  CForm,
  CFormInput,
  CFormLabel,
  CButton,
  CCard,
  CCardBody,
  CCol,
  CRow,
  CContainer,
} from '@coreui/react'
import { ToastContainer, toast } from 'react-toastify'
import 'react-toastify/dist/ReactToastify.css'
import axios from 'axios'
import './UserAddForm.css'
import { useNavigate } from 'react-router-dom'
import Select from 'react-select'
import countryList from 'react-select-country-list'
function UserAddForm() {
  const [formData, setFormData] = useState({
    firstName: '',
    lastName: '',
    email: '',
    password: '',
    paymentMethod: '',
    country: '',
    userType: '',
  })
  const navigate = useNavigate()
  const options = useMemo(() => countryList().getData(), [])
  const roleOptions = [
    { value: 'Admin', label: 'Admin' },
    { value: 'Viewer', label: 'Viewer' },
    { value: 'User', label: 'User' },
  ]
  const handleChange = (e) => {
    const { name, value } = e.target
    setFormData({ ...formData, [name]: value })
  }
  const handleCountryChange = (selectedOption) => {
    setFormData({ ...formData, country: selectedOption.value })
  }
  const handleRoleChange = (selectedOption) => {
    setFormData({ ...formData, userType: selectedOption.value })
  }
  const validateForm = () => {
    const errors = []
    if (!formData.firstName.trim()) errors.push('First name is required.')
    if (!formData.lastName.trim()) errors.push('Last name is required.')
    if (!formData.email.trim()) errors.push('Email is required.')
    if (!formData.password.trim()) errors.push('Password is required.')
    if (!formData.paymentMethod.trim()) errors.push('Payment method is required.')
    if (!formData.country.trim()) errors.push('Country is required.')
    if (!formData.userType.trim()) errors.push('User type is required.')
    return errors
  }
  const handleSubmit = (e) => {
    e.preventDefault()
    const errors = validateForm()
    if (errors.length > 0) {
      toast.error(errors[0])
      return
    }
    axios
      .post('http://localhost/react-admin-template-api/add-user-action.php', formData, {
        headers: { 'Content-Type': 'application/json' },
      })
      .then((res) => {
        console.log('Response from server:', res.data)
        if (res.data.success) {
          toast.success(res.data.message)
          setFormData({
            firstName: '',
            lastName: '',
            email: '',
            password: '',
            paymentMethod: '',
            country: '',
            userType: '',
          })
          navigate('/users')
        } else {
          toast.error(res.data.error || 'Something went wrong.')
        }
      })
      .catch(() => {
        toast.error('Server error. Please try again.')
      })
  }
  return (
    <CContainer className="d-flex justify-content-center align-items-center min-vh-100">
      <CRow className="w-100">
        <CCol md={8} lg={6} className="mx-auto">
          <CCard className="shadow-lg">
            <CCardBody className="p-3">
              <div className="contact-header text-center">
                <img src="/ic-form-add.png" alt="contact" className="contact-icon mb-2 mt-3" />
                <h1 className="contact-title">Add New User</h1>
              </div>
              <hr className="custom-hr" />
              <CForm onSubmit={handleSubmit}>
                <CRow className="mb-3">
                  <CFormLabel>User:</CFormLabel>
                  <CCol>
                    <CFormInput
                      type="text"
                      name="firstName"
                      value={formData.firstName}
                      onChange={handleChange}
                      placeholder="First Name"
                      style={{ backgroundColor: '#fbe17d' }}
                    />
                  </CCol>
                  <CCol>
                    <CFormInput
                      type="text"
                      name="lastName"
                      value={formData.lastName}
                      onChange={handleChange}
                      placeholder="Last Name"
                      style={{ backgroundColor: '#fbe17d' }}
                    />
                  </CCol>
                </CRow>
                <div className="mb-3">
                  <CFormLabel>Email:</CFormLabel>
                  <CFormInput
                    type="email"
                    name="email"
                    value={formData.email}
                    onChange={handleChange}
                    placeholder="Enter email"
                    style={{ backgroundColor: '#fbe17d' }}
                  />
                </div>
                <div className="mb-3">
                  <CFormLabel>Password:</CFormLabel>
                  <CFormInput
                    type="password"
                    name="password"
                    value={formData.password}
                    onChange={handleChange}
                    placeholder="Enter password"
                    style={{ backgroundColor: '#fbe17d' }}
                  />
                </div>
                <div className="mb-3">
                  <CFormLabel>Payment Method:</CFormLabel>
                  <CFormInput
                    type="text"
                    name="paymentMethod"
                    value={formData.paymentMethod}
                    onChange={handleChange}
                    placeholder="e.g. Visa, MasterCard, PayPal"
                    style={{ backgroundColor: '#fbe17d' }}
                  />
                </div>
                <div className="mb-3">
                  <CFormLabel>Country:</CFormLabel>
                  <Select
                    options={options}
                    value={options.find((option) => option.value === formData.country)}
                    onChange={handleCountryChange}
                    styles={{
                      control: (provided) => ({
                        ...provided,
                        backgroundColor: '#fbe17d',
                      }),
                    }}
                  />
                </div>
                <div className="mb-3">
                  <CFormLabel>User Type:</CFormLabel>
                  <Select
                    options={roleOptions}
                    value={roleOptions.find((role) => role.value === formData.userType)}
                    onChange={handleRoleChange}
                    styles={{
                      control: (provided) => ({
                        ...provided,
                        backgroundColor: '#fbe17d',
                      }),
                    }}
                  />
                </div>
                <CButton type="submit" color="dark" className="w-100">
                  Submit
                </CButton>
              </CForm>
            </CCardBody>
          </CCard>
        </CCol>
      </CRow>
      <ToastContainer position="top-center" autoClose={3000} toastClassName="text-center small" />
    </CContainer>
  )
}
export default UserAddForm

PHP endpoint to process the React add form submit action

This PHP script verifies if the entered email is already registered. If not, it will permit the signup request. If you want to verify the user availability for the username field, see the link.

add-user-action.php

<?php
header("Access-Control-Allow-Origin:  http://localhost:3000");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
include "db.php";
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $data = json_decode(file_get_contents("php://input"), true);    
    $firstName = $data['firstName'] ?? '';
    $lastName = $data['lastName'] ?? '';
    $email = $data['email'] ?? '';
    $password = isset($data['password']) ? password_hash($data['password'], PASSWORD_BCRYPT) : '';
    $paymentMethod = $data['paymentMethod'] ?? '';
    $country = $data['country'] ?? '';
    $userType = $data['userType'] ?? '';
    // Check if email already exists
    $checkStmt = $conn->prepare("SELECT id FROM users WHERE email = ?");
    $checkStmt->bind_param("s", $email);
    $checkStmt->execute();
    $checkStmt->store_result();
    if ($checkStmt->num_rows > 0) {
        echo json_encode(['success' => false, 'error' => 'Email already exists']);
        $checkStmt->close();
        $conn->close();
        exit;
    }
    $checkStmt->close();
    $stmt = $conn->prepare("INSERT INTO users (first_name, last_name, email, password, payment_method, country, user_type) VALUES (?, ?, ?, ?, ?, ?, ?)");
    $stmt->bind_param("sssssss", $firstName, $lastName, $email, $password, $paymentMethod, $country, $userType);    
    if (!$stmt->execute()) {
        echo json_encode(['success' => false, 'error' => $stmt->error]);
        $stmt->close();
        $conn->close();
        exit;
    }
    $stmt->close();
    $conn->close();  
    echo json_encode(['success' => true, 'message' => 'User inserted successfully']);
    exit;
}

Set and manage permissions management

Another feature of this React admin example is operating with the role and its permission-based access privileges.

When inserting a new user, the form will have the option to select the available role.

This screen will provide an interface to give read or write permission to the user roles.

react admin set permission

The below JSX script will return the template for this UI. It lists the 4 roles for the UI to select and save the permission.

On submitting the selected permission, the code below sends the role-permission mapping data to the server.

Permission.js

import React, { useState, useEffect, useMemo } from 'react'
import {
  CTable,
  CTableHead,
  CTableRow,
  CTableHeaderCell,
  CTableBody,
  CTableDataCell,
  CFormCheck,
  CButton,
  CAlert,
} from '@coreui/react'
import axios from 'axios'
const Permission = () => {
  const [loggedInUserRole, setLoggedInUserRole] = useState('guest')
  const [permissions, setPermissions] = useState({})
  const roles = useMemo(
    () => [
      { id: 1, role: 'Admin' },
      { id: 2, role: 'SuperAdmin' },
      { id: 3, role: 'Viewer' },
      { id: 4, role: 'User' },
    ],
    [],
  )
  useEffect(() => {
    const loggedInUserId = localStorage.getItem('user_id') || 0
    axios
      .get('http://localhost/react-admin-template-api/get-role-by-user.php?user_id='+loggedInUserId)
      .then((res) => {
        if (res.data.user_type) {
          setLoggedInUserRole(res.data.user_type)
        }
      })
      .catch(() => console.error('Error fetching user role'))
  }, [])
  useEffect(() => {
    const loggedInUserId = localStorage.getItem('user_id') || 0
    axios
      .get('http://localhost/react-admin-template-api/get-role-permission.php?user_id='+loggedInUserId)
      .then((res) => {
        if (res.data.success) {
          setPermissions(res.data.permissions)
        }
      })
      .catch(() => console.error('Error fetching permissions'))
  }, []) //[roles]
  const handleCheckboxChange = (roleId, type) => {
    setPermissions((prev) => ({
      ...prev,
      [roleId]: {
        ...prev[roleId],
        [type]: prev[roleId]?.[type] === 1 ? 0 : 1,
      },
    }))
  }
  const handleSave = () => {
    const loggedInUserId = localStorage.getItem('user_id')
    axios
      .post('http://localhost/react-admin-template-api/set-role-permission.php', { user_id: loggedInUserId, permissions })
      .then((res) => alert(res.data.message || 'Permissions saved!'))
      .catch(() => alert('Error saving permissions!'))
  }
  if (!['SuperAdmin'].includes(loggedInUserRole)) {
    return (
      <CAlert color="danger" className="text-center fw-bold shadow mb-3">
        Access denied. Admin only.
      </CAlert>
    )
  }
  return (
    <>
      <CTable align="middle" className="mb-0 border" hover responsive striped>
        <CTableHead className="bg-secondary text-white">
          <CTableRow>
            <CTableHeaderCell style={{ width: '16%' }}>Role</CTableHeaderCell>
            <CTableHeaderCell>Permissions</CTableHeaderCell>
          </CTableRow>
        </CTableHead>
        <CTableBody>
          {roles.map((r) => (
            <CTableRow key={r.id}>
              <CTableDataCell style={{ width: '16%' }}>{r.role}</CTableDataCell>
              <CTableDataCell>
                <div className="d-flex">
                  <CFormCheck
                    label="Read"
                    checked={permissions[r.id]?.read === 1}
                    disabled={loggedInUserRole !== 'SuperAdmin'}
                    onChange={() => handleCheckboxChange(r.id, 'read')}
                    className="me-4"
                  />
                  <CFormCheck
                    label="Write"
                    checked={permissions[r.id]?.write === 1}
                    disabled={loggedInUserRole !== 'SuperAdmin' && loggedInUserRole !== 'Admin'}
                    onChange={() => handleCheckboxChange(r.id, 'write')}
                  />
                </div>
              </CTableDataCell>
            </CTableRow>
          ))}
        </CTableBody>
      </CTable>
      <div className="mt-3 text-start">
        <CButton color="dark" onClick={handleSave}>
          Save Permissions
        </CButton>
      </div>
    </>
  )
}
export default Permission

get-role-permission.php

<?php
header("Access-Control-Allow-Origin: http://localhost:3000");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
header("Content-Type: application/json");
include "db.php";
$user_id = isset($_GET['user_id']) ? intval($_GET['user_id']) : 0;
$res = $conn->query("SELECT user_type FROM users WHERE id = $user_id LIMIT 1");
$currentRole = strtolower($res->fetch_assoc()['user_type'] ?? 'guest');
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    $result = $conn->query("SELECT role_id, `read`, `write` FROM tbl_user_permission");
    $permissions = [];
    while ($row = $result->fetch_assoc()) {
        $permissions[$row['role_id']] = [
            "read" => (int)$row['read'],
            "write" => (int)$row['write']
        ];
    }
    echo json_encode(["success" => true, "permissions" => $permissions, "role" => $currentRole]);
    exit;
}

set-role-permission.php

<?php
header("Access-Control-Allow-Origin: http://localhost:3000");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
header("Content-Type: application/json");
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    include "db.php";
    $raw = file_get_contents("php://input");
    $data = json_decode($raw, true);
    $user_id = isset($data['user_id']) ? intval($data['user_id']) : 0;
    $res = $conn->query("SELECT user_type FROM users WHERE id = $user_id LIMIT 1");
    $currentRole = strtolower($res->fetch_assoc()['user_type'] ?? 'guest');
        $raw = file_get_contents("php://input");
        $data = json_decode($raw, true);
        if (isset($data['permissions'])) {
            foreach ($data['permissions'] as $role_id => $perm) {
                if ($currentRole === 'SuperAdmin') {
                    $read = isset($perm['read']) ? (int)$perm['read'] : 1;
                    $write = isset($perm['write']) ? (int)$perm['write'] : 1;
                } else {
                    continue; // other roles cannot update
                }
                $sql = "INSERT INTO tbl_user_permission (role_id, `read`, `write`) 
                    VALUES ('$role_id', '$read', '$write')
                    ON DUPLICATE KEY UPDATE 
                        `read` = VALUES(`read`), 
                        `write` = VALUES(`write`)";
                $conn->query($sql);
            }
            echo json_encode(["success" => true, "message" => "Permissions updated successfully"]);
            exit;
        }
        echo json_encode(["success" => false, "message" => "No permissions data received"]);
}

Protected routes and access permissions

This is to stop users from accessing the protected URLs directly if they know the link. If the local login id session is empty, then the React ProtectedRoute component restricts accessing. Rather, it is redirecting back to the login page.

ProtectedRoute.js

import React from 'react'
import { Navigate } from 'react-router-dom'
const ProtectedRoute = ({ children }) => {
  const loginId = localStorage.getItem('user_id')
  if (!loginId) {
    return <Navigate to="/login" replace />
  }
  return children
}
export default ProtectedRoute

This PHP code returns the logged in user role to the React component. The current user’s role is saved to permit or restrict the user based on their privileges.

get-role-by-user.php

<?php
header("Access-Control-Allow-Origin: http://localhost:3000");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
include 'db.php';
$user_id = $_REQUEST['user_id']; 
$sql = "SELECT user_type FROM users WHERE id = $user_id LIMIT 1";
$result = $conn->query($sql);
if ($result && $result->num_rows > 0) {
    $row = $result->fetch_assoc();
    echo json_encode(['user_type' => $row['user_type']]);
}
$conn->close();

Steps to setup

  1. Download and extract the code into your React project directory.
  2. Run npm install to get node_modules.
  3. Move the /react-admin-template-api/ directory to your PHP environment.
  4. Replace the example path (http://localhost/react-admin-template-api/) with the your server domain root.
  5. Run npm start dev to start the server.

Conclusion

We have built a React admin panel project with primitive functionalities. We got an admin dashboard foundation to highlight application metrics. It manages users and roles with PHP backend support.

With ProtedtedRoute React component, the security is provided by ensuring user authenticity and privilege. By saving the in-user ID and role to the localStorage, it leads to protected access across the application.

References:

  1. Plotting data points using ChartJS line chart.
  2. Things to know about designing an admin panel.

Download

Vincy
Written by Vincy, a web developer with 15+ years of experience and a Masters degree in Computer Science. She specializes in building modern, lightweight websites using PHP, JavaScript, React, and related technologies. Phppot helps you in mastering web development through over a decade of publishing quality tutorials.

Leave a Reply

Your email address will not be published. Required fields are marked *

↑ Back to Top

Share this page