Develop Laravel e-commerce website step by step

Step 1: Create the Laravel Project

Create the Laravel Project: Open your terminal and run the following command:

composer create-project laravel/laravel ecommerce-admin
cd ecommerce-admin

Open Project in VS Code

code .

Install Dependencies for Jetstream:

composer require laravel/jetstream

Install Jetstream with Inertia (since it works seamlessly with Bootstrap 5):

php artisan jetstream:install livewire

Install npm dependencies and build assets:

npm install
npm run dev

Run the Migrations:

php artisan migrate

Step 2: Configure Multi-User Authentication (Admin & Manager Roles)

Update the User model to support roles: Open the app/Models/User.php file and add the following code to manage roles:

public function hasRole($role)
{
    return $this->role === $role;
}

Add the role field to the users table: Update the migration for the users table. Open database/migrations/xxxx_xx_xx_create_users_table.php:

public function up()
{
    Schema::create('users', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->string('email')->unique();
        $table->timestamp('email_verified_at')->nullable();
        $table->string('password');
        $table->string('role')->default('manager'); // Default role is 'manager'
        $table->rememberToken();
        $table->timestamps();
    });
}

Run the Migration:

php artisan migrate

Assign Roles to Users: Seed admin and manager users. Open database/seeders/DatabaseSeeder.php and add:

use App\Models\User;
use Illuminate\Support\Facades\Hash;

public function run()
{
    User::create([
        'name' => 'Admin User',
        'email' => 'admin@example.com',
        'password' => Hash::make('password123'),
        'role' => 'admin',
    ]);

    User::create([
        'name' => 'Manager User',
        'email' => 'manager@example.com',
        'password' => Hash::make('password123'),
        'role' => 'manager',
    ]);
}

Run the Seeder:

php artisan db:seed

Step 3: Implement Middleware for Role-Based Access Control

Create a Custom Middleware:
Run the following command to create the middleware:

php artisan make:middleware RoleMiddleware

Update the Middleware: Open the newly created middleware at app/Http/Middleware/RoleMiddleware.php:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class RoleMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string  $role
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function handle(Request $request, Closure $next, string $role): Response
    {
        // Ensure the user is authenticated
        if (!$request->user()) {
            return redirect()->route('login')->with('error', 'Please log in to access this page.');
        }

        // Ensure the user has the required role
        if ($request->user()->role !== $role) {
            abort(403, 'Unauthorized action.');
        }

        return $next($request);
    }
}

Explanation
Role Checking:
The middleware checks if the authenticated user has the specified role. If not, it returns a 403 Unauthorized response.

Handling Missing Users:
It ensures the user is authenticated before checking the role by using $request->user(). If the user is not logged in or doesn’t have the required role, the request is aborted.

Register the Middleware in bootstrap/app.php
Open bootstrap/app.php and register the middleware alias inside the middleware configuration.

<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Foundation\Configuration\Exceptions;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->web(append: [
            \Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class,
        ]);

        // Register custom middleware alias
        $middleware->alias([
            'role' => \App\Http\Middleware\RoleMiddleware::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();

Create the necessary controllers

php artisan make:controller AdminController
php artisan make:controller ManagerController

Define Controller Methods

AdminController.php:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class AdminController extends Controller
{
    public function dashboard()
    {
        return view('admin.dashboard');
    }

    public function orders()
    {
        return view('admin.orders');
    }
}

ManagerController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ManagerController extends Controller
{
    public function orders()
    {
        return view('manager.orders');
    }
}

Update Routes in web.php

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\AdminController;
use App\Http\Controllers\ManagerController;

Route::get('/', function () {
    return view('welcome');
});

// Middleware group for authenticated users
Route::middleware([
    'auth:sanctum',
    config('jetstream.auth_session'),
    'verified',
])->group(function () {
    // General dashboard for all authenticated users
    Route::get('/dashboard', function () {
        return view('dashboard');
    })->name('dashboard');

    // Admin-specific routes
    Route::middleware('role:admin')->group(function () {
        Route::get('/admin/dashboard', [AdminController::class, 'dashboard'])->name('admin.dashboard');
        Route::get('/admin/orders', [AdminController::class, 'orders'])->name('admin.orders');
    });

    // Manager-specific routes
    Route::middleware('role:manager')->group(function () {
        Route::get('/manager/orders', [ManagerController::class, 'orders'])->name('manager.orders');
    });
});

Create Views for Admin and Manager

Create the views in resources/views/admin/ and resources/views/manager/

resources/views/admin/dashboard.blade.php

<h1>Admin Dashboard</h1>
<p>Welcome to the admin panel!</p>

resources/views/admin/orders.blade.php

<h1>Manage Orders (admin)</h1>
<p>Here are all the orders for the admin to manage.</p>

resources/views/manager/orders.blade.php

<h1>Manager Orders (Manager)</h1>
<p>Here are the orders for the manager to handle.</p>

Create Product Category CRUD operation with AJAX, Laravel, and Bootstrap 5, along with SweetAlert for alerts.

Create Migration and Model

php artisan make:model Category -m

Migration File for Categories

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateCategoriesTable extends Migration
{
    public function up()
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->id();
            $table->string('name')->unique();
            $table->text('description')->nullable();
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('categories');
    }
}

Run the Migration

php artisan migrate

Set fillable to Category model

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Category extends Model
{
    use HasFactory;

    protected $fillable = ['name', 'description'];

}

Create Controller for Categories

php artisan make:controller CategoryController --resource

Controller Logic: CategoryController.php

<?php
 
namespace App\Http\Controllers;
 
use App\Models\Category;
use Illuminate\Http\Request;
use Response;
 
class CategoryController extends Controller
{
    public function index()
    {
        if (request()->ajax()) {
            $categories = Category::all();
            return response()->json($categories);
        }
        return view('admin.categories.index');
    }
 
    public function show($id)
    {
        $category = Category::findOrFail($id);
        return response()->json($category);
    }

    public function store(Request $request)
    {
        $validated = $request->validate([
            'name' => 'required|unique:categories|max:255',
            'description' => 'nullable|string',
        ]);

        Category::create($validated);

        return response()->json(['success' => 'Category added successfully.']);
    }
 
    public function update(Request $request, $id)
    {
        $validated = $request->validate([
            'name' => 'required|max:255|unique:categories,name,' . $id,
            'description' => 'nullable|string',
        ]);

        $category = Category::findOrFail($id);
        $category->update($validated);

        return response()->json(['success' => 'Category updated successfully.']);
    }
 
    public function destroy($id)
    {
        Category::findOrFail($id)->delete();
        return response()->json(['success' => 'Category deleted successfully.']);
    }
}

Define Routes

use App\Http\Controllers\CategoryController;

Route::middleware(['auth:sanctum', 'role:admin'])->group(function () {
    Route::resource('categories', CategoryController::class)->except(['create', 'edit']);
});

Sample Layout

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@yield('title', 'E-commerce Admin')</title>

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">

    @yield('styles')
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container-fluid">
            <a class="navbar-brand" href="#">Admin Panel</a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" 
                data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" 
                aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                    <li class="nav-item">
                        <a class="nav-link active" aria-current="page" href="{{ route('dashboard') }}">Dashboard</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{{ route('categories.index') }}">Categories</a>
                    </li>
                </ul>
                <ul class="navbar-nav ms-auto">
                    <li class="nav-item">
                        <a class="nav-link" href="#" onclick="event.preventDefault(); 
                            document.getElementById('logout-form').submit();">Logout</a>
                    </li>
                </ul>
                <form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
                    @csrf
                </form>
            </div>
        </div>
    </nav>

    <div class="container mt-4">
        @yield('content')
    </div>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

    <!-- Bootstrap JS and dependencies -->
    <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>

    @yield('scripts')
</body>
</html>

Create Views and AJAX Logic

Create the Blade template for managing categories at resources/views/admin/categories/index.blade.php

index.blade.php

@extends('layouts.ecomapp')
@section('title', 'Manage Categories')

 
@section('content')
<div class="container">
    <h2 class="mt-4">Manage Categories</h2>
    <button id="addCategory" class="btn btn-primary mb-3">Add Category</button>
    <table class="table table-bordered" id="categoriesTable">
        <thead>
            <tr>
                <th>Name</th>
                <th>Description</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody></tbody>
    </table>
</div>
 
<!-- Modal -->
<div class="modal fade" id="categoryModal" tabindex="-1" aria-hidden="true">
    <div class="modal-dialog">
        <div class="modal-content">
            <form id="categoryForm">
                <div class="modal-header">
                    <h5 class="modal-title" id="modalTitle"></h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    <input type="hidden" id="categoryId">
                    <div class="mb-3">
                        <label for="name" class="form-label">Name</label>
                        <input type="text" class="form-control" id="name" required>
                    </div>
                    <div class="mb-3">
                        <label for="description" class="form-label">Description</label>
                        <textarea class="form-control" id="description"></textarea>
                    </div>
                </div>
                <div class="modal-footer">
                    <button type="submit" class="btn btn-primary">Save</button>
                </div>
            </form>
        </div>
    </div>
</div>
@endsection
 
@section('scripts')
<script src="//cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
$(document).ready(function () {
    fetchCategories();
 
    function fetchCategories() {
        $.ajax({
            url: "{{ route('categories.index') }}",
            method: 'GET',
            success: function (data) {
                let rows = '';
                data.forEach(category => {
                    rows += `<tr>
                        <td>${category.name}</td>
                        <td>${category.description}</td>
                        <td>
                            <button class="btn btn-sm btn-warning edit" data-id="${category.id}">Edit</button>
                            <button class="btn btn-sm btn-danger delete" data-id="${category.id}">Delete</button>
                        </td>
                    </tr>`;
                });
                $('#categoriesTable tbody').html(rows);
            }
        });
    }
 
    $('#addCategory').click(function () {
        $('#categoryForm')[0].reset(); // Clear all form inputs
        $('#categoryId').val(''); // Ensure the hidden ID field is empty
        $('#modalTitle').text('Add Category');
        $('#categoryModal').modal('show');
    });

 
    $('#categoryForm').submit(function (e) {
        e.preventDefault();

        const id = $('#categoryId').val().trim(); // Trim to avoid whitespaces
        const url = id ? `/categories/${id}` : "{{ route('categories.store') }}"; // Determine URL
        const method = id ? 'PUT' : 'POST'; // Determine HTTP method

        $.ajax({
            url: url,
            method: 'POST', // Always use POST and emulate PUT if needed
            data: {
                _method: method, // Emulate PUT if updating
                name: $('#name').val(),
                description: $('#description').val(),
                _token: "{{ csrf_token() }}"
            },
            success: function (response) {
                $('#categoryModal').modal('hide');
                Swal.fire('Success', response.success, 'success');
                fetchCategories(); // Refresh the categories list
            },
            error: function (xhr) {
                console.error(xhr.responseText); // Log error for debugging
                Swal.fire('Error', 'Something went wrong!', 'error');
            }
        });
    });


 
    $(document).on('click', '.edit', function () {
        const id = $(this).data('id');
        $.get(`/categories/${id}`, function (category) {
            $('#categoryId').val(category.id);
            $('#name').val(category.name);
            $('#description').val(category.description);
            $('#modalTitle').text('Edit Category');
            $('#categoryModal').modal('show');
        });
    });
 
    $(document).on('click', '.delete', function () {
        const id = $(this).data('id');
        Swal.fire({
            title: 'Are you sure?',
            text: "You won't be able to revert this!",
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'Yes, delete it!'
        }).then((result) => {
            if (result.isConfirmed) {
                $.ajax({
                    url: `/categories/${id}`,
                    method: 'DELETE',
                    data: {
                        _token: "{{ csrf_token() }}"
                    },
                    success: function (response) {
                        Swal.fire('Deleted!', response.success, 'success');
                        fetchCategories();
                    }
                });
            }
        });
    });
});
</script>
@endsection

Subcategory Management

Create Migration and Model for Subcategory

php artisan make:model Subcategory -m

Migration: create_subcategories_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateSubcategoriesTable extends Migration
{
    public function up()
    {
        Schema::create('subcategories', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->text('description')->nullable();
            $table->foreignId('category_id')->constrained('categories')->onDelete('cascade');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('subcategories');
    }
}

Run the migration:

php artisan migrate

Define Models

Subcategory.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Subcategory extends Model
{
    use HasFactory;

    protected $fillable = ['name', 'description', 'category_id'];

    public function category()
    {
        return $this->belongsTo(Category::class);
    }
}

Category.php

Add a relation in your Category model:

public function subcategories()
{
    return $this->hasMany(Subcategory::class);
}

Create Controller for Subcategory

php artisan make:controller SubcategoryController --resource

Update SubcategoryController.php:

<?php
 
namespace App\Http\Controllers;
 
use App\Models\Category;
use App\Models\Subcategory;
use Illuminate\Http\Request;
 
class SubcategoryController extends Controller
{
    public function index($categoryId)
    {
        $category = Category::findOrFail($categoryId);
        $subcategories = $category->subcategories;
 
        return view('admin.subcategories.index', compact('category', 'subcategories'));
    }
    
    public function show($id)
    {
        $subcategory = Subcategory::findOrFail($id);
        return response()->json($subcategory);
    }

 
    public function store(Request $request, $categoryId)
    {
        $validated = $request->validate([
            'name' => 'required|max:255',
            'description' => 'nullable|string',
        ]);
 
        Subcategory::create(array_merge($validated, ['category_id' => $categoryId]));
 
        return redirect()->route('subcategories.index', $categoryId)
                         ->with('success', 'Subcategory added successfully.');
    }
 
    public function update(Request $request, $id)
    {
        $validated = $request->validate([
            'name' => 'required|max:255',
            'description' => 'nullable|string',
        ]);

        $subcategory = Subcategory::findOrFail($id);
        $subcategory->update($validated);

        return response()->json(['success' => 'Subcategory updated successfully.']);
    }

 
    public function destroy($id)
    {
        Subcategory::findOrFail($id)->delete();
        return redirect()->back()->with('success', 'Subcategory deleted successfully.');
    }
}

Define Routes

Route::middleware(['auth:sanctum', 'role:admin'])->group(function () {
    Route::resource('categories', CategoryController::class)->except(['create', 'edit']);

   Route::get('categories/{category}/subcategories', [SubcategoryController::class, 'index'])->name('subcategories.index');
        Route::post('categories/{category}/subcategories', [SubcategoryController::class, 'store'])->name('subcategories.store');
        Route::put('subcategories/{subcategory}', [SubcategoryController::class, 'update'])->name('subcategories.update');
        Route::delete('subcategories/{subcategory}', [SubcategoryController::class, 'destroy'])->name('subcategories.destroy');
        Route::get('subcategories/{subcategory}', [SubcategoryController::class, 'show'])->name('subcategories.show');
});

Update the Category view to add a button

<a href="/categories/${category.id}/subcategories" class="btn btn-sm btn-info">
      Manage Subcategories
</a>

Create Views for Subcategories

Create the index view for subcategories at resources/views/admin/subcategories/index.blade.php:

@extends('layouts.ecomapp')

@section('title', 'Manage Subcategories')

@section('content')
<div class="container">
    <h2 class="mt-4">Manage Subcategories for {{ $category->name }}</h2>
    <button id="addSubcategory" class="btn btn-primary mb-3">Add Subcategory</button>

    <table class="table table-bordered" id="subcategoriesTable">
        <thead>
            <tr>
                <th>Name</th>
                <th>Description</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
            @foreach($subcategories as $subcategory)
            <tr>
                <td>{{ $subcategory->name }}</td>
                <td>{{ $subcategory->description }}</td>
                <td>
                    <button class="btn btn-warning btn-sm edit" data-id="{{ $subcategory->id }}">Edit</button>
                    <form action="{{ route('subcategories.destroy', $subcategory->id) }}" method="POST" class="d-inline">
                        @csrf
                        @method('DELETE')
                        <button type="submit" class="btn btn-danger btn-sm">Delete</button>
                    </form>
                </td>
            </tr>
            @endforeach
        </tbody>
    </table>
</div>

<!-- Add/Edit Subcategory Modal -->
<div class="modal fade" id="subcategoryModal" tabindex="-1" aria-hidden="true">
    <div class="modal-dialog">
        <div class="modal-content">
            <form id="subcategoryForm">
                <div class="modal-header">
                    <h5 class="modal-title" id="modalTitle"></h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    <input type="hidden" id="subcategoryId">
                    <div class="mb-3">
                        <label for="name" class="form-label">Name</label>
                        <input type="text" class="form-control" id="name" required>
                    </div>
                    <div class="mb-3">
                        <label for="description" class="form-label">Description</label>
                        <textarea class="form-control" id="description"></textarea>
                    </div>
                </div>
                <div class="modal-footer">
                    <button type="submit" class="btn btn-primary">Save</button>
                </div>
            </form>
        </div>
    </div>
</div>
@endsection

@section('scripts')
<script src="//cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
    $(document).on('click', '#addSubcategory', function () {
        $('#subcategoryForm')[0].reset(); // Clear the form
        $('#subcategoryId').val(''); // Clear hidden ID field
        $('#modalTitle').text('Add Subcategory');
        $('#subcategoryModal').modal('show');
    });

    $('#subcategoryForm').submit(function (e) {
        e.preventDefault();
        const id = $('#subcategoryId').val();
        const url = id ? `/subcategories/${id}` : "{{ route('subcategories.store', $category->id) }}";
        const method = id ? 'PUT' : 'POST';

        $.ajax({
            url: url,
            method: 'POST', // Always use POST and emulate PUT if needed
            data: {
                _method: method, // Emulate the correct HTTP method
                name: $('#name').val(),
                description: $('#description').val(),
                _token: "{{ csrf_token() }}"
            },
            success: function (response) {
                $('#subcategoryModal').modal('hide'); // Hide modal on success
                location.reload(); // Reload the page to show the new data
            },
            error: function (xhr) {
                console.error(xhr.responseText); // Log errors for debugging
                Swal.fire('Error', 'Failed to save the subcategory!', 'error');
            }
        });
    });

    $(document).on('click', '.edit', function () {
        const id = $(this).data('id');
        $.ajax({
            url: `/subcategories/${id}`, // Fetch the subcategory details
            method: 'GET',
            success: function (subcategory) {
                // Populate the modal with the subcategory data
                $('#subcategoryId').val(subcategory.id);
                $('#name').val(subcategory.name);
                $('#description').val(subcategory.description);
                $('#modalTitle').text('Edit Subcategory');
                $('#subcategoryModal').modal('show');
            },
            error: function (xhr) {
                console.error(xhr.responseText);
                Swal.fire('Error', 'Failed to load subcategory details!', 'error');
            }
        });
    });

    $(document).on('submit', 'form[action*="subcategories/"]:not(#subcategoryForm)', function (e) {
        e.preventDefault();
        const form = $(this);
        Swal.fire({
            title: 'Are you sure?',
            text: "You won't be able to revert this!",
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'Yes, delete it!'
        }).then((result) => {
            if (result.isConfirmed) {
                $.ajax({
                    url: form.attr('action'),
                    method: 'POST', // Use POST to delete (with DELETE emulation)
                    data: {
                        _method: 'DELETE',
                        _token: "{{ csrf_token() }}"
                    },
                    success: function (response) {
                        Swal.fire('Deleted!', response.success, 'success');
                        location.reload(); // Reload to reflect changes
                    },
                    error: function (xhr) {
                        console.error(xhr.responseText);
                        Swal.fire('Error', 'Failed to delete the subcategory!', 'error');
                    }
                });
            }
        });
    });
</script>
@endsection

Setting up Admin Template for Application

Download the Template using this link
Download Now

Similar Posts

Leave a Reply

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