Skip to content

Admin Package

The Admin package provides a complete admin panel with Livewire modals, HLCRF layouts, form components, global search, and an extensible menu system.

Installation

bash
composer require host-uk/core-admin

Quick Start

php
<?php

namespace Mod\Blog;

use Core\Events\AdminPanelBooting;
use Core\Front\Admin\Contracts\AdminMenuProvider;
use Core\Front\Admin\Support\MenuItemBuilder;

class Boot
{
    public static array $listens = [
        AdminPanelBooting::class => 'onAdminPanel',
    ];

    public function onAdminPanel(AdminPanelBooting $event): void
    {
        // Register admin menu
        $event->menu(new BlogMenuProvider());

        // Register routes
        $event->routes(fn () => require __DIR__.'/Routes/admin.php');
    }
}

Key Features

User Interface

Search & Discovery

Components

Features

Components Overview

Form Components

blade
<x-admin::input name="title" label="Title" required />
<x-admin::textarea name="content" label="Content" rows="10" />
<x-admin::select name="status" label="Status" :options="$statuses" />
<x-admin::checkbox name="published" label="Published" />
<x-admin::toggle name="featured" label="Featured" />
<x-admin::button type="submit">Save</x-admin::button>

Learn more about Forms →

Layout Components

blade
<x-hlcrf::layout>
    <x-hlcrf::header>
        <h1>Dashboard</h1>
    </x-hlcrf::header>

    <x-hlcrf::content>
        <x-admin::card-grid>
            <x-admin::stat-card title="Posts" :value="$postCount" />
            <x-admin::stat-card title="Users" :value="$userCount" />
        </x-admin::card-grid>
    </x-hlcrf::content>

    <x-hlcrf::right>
        <x-admin::activity-feed :limit="10" />
    </x-hlcrf::right>
</x-hlcrf::layout>

Learn more about HLCRF Layouts →

Admin Routes

php
// Routes/admin.php
use Mod\Blog\View\Modal\Admin\PostEditor;
use Mod\Blog\View\Modal\Admin\PostsList;

Route::middleware(['web', 'auth', 'admin'])->prefix('admin')->group(function () {
    // Livewire modal routes
    Route::get('/posts', PostsList::class)->name('admin.blog.posts');
    Route::get('/posts/create', PostEditor::class)->name('admin.blog.posts.create');
    Route::get('/posts/{post}/edit', PostEditor::class)->name('admin.blog.posts.edit');
});

Livewire Modals

Create full-page modals for admin interfaces:

php
<?php

namespace Mod\Blog\View\Modal\Admin;

use Livewire\Component;

class PostEditor extends Component
{
    public ?Post $post = null;
    public string $title = '';
    public string $content = '';

    protected array $rules = [
        'title' => 'required|max:255',
        'content' => 'required',
    ];

    public function mount(?Post $post = null): void
    {
        $this->post = $post;
        $this->title = $post?->title ?? '';
        $this->content = $post?->content ?? '';
    }

    public function save(): void
    {
        $validated = $this->validate();

        if ($this->post) {
            $this->post->update($validated);
        } else {
            Post::create($validated);
        }

        $this->dispatch('post-saved');
        $this->redirect(route('admin.blog.posts'));
    }

    public function render()
    {
        return view('blog::admin.post-editor');
    }
}

Learn more about Livewire Modals →

Register searchable resources:

php
<?php

namespace Mod\Blog\Search;

use Core\Admin\Search\Contracts\SearchProvider;
use Core\Admin\Search\SearchResult;

class PostSearchProvider implements SearchProvider
{
    public function search(string $query): array
    {
        return Post::where('title', 'like', "%{$query}%")
            ->limit(5)
            ->get()
            ->map(fn (Post $post) => new SearchResult(
                title: $post->title,
                description: $post->excerpt,
                url: route('admin.blog.posts.edit', $post),
                icon: 'document-text',
                category: 'Blog Posts'
            ))
            ->toArray();
    }

    public function getCategory(): string
    {
        return 'Blog';
    }
}

Register in your Boot.php:

php
public function onAdminPanel(AdminPanelBooting $event): void
{
    $event->search(new PostSearchProvider());
}

Learn more about Search →

Configuration

php
// config/admin.php
return [
    'middleware' => ['web', 'auth', 'admin'],
    'prefix' => 'admin',

    'menu' => [
        'auto_discover' => true,
        'cache_enabled' => true,
    ],

    'search' => [
        'enabled' => true,
        'min_length' => 2,
        'limit' => 10,
    ],

    'honeypot' => [
        'enabled' => true,
        'field_name' => env('HONEYPOT_FIELD', 'website'),
    ],
];

Middleware

The admin panel uses these middleware by default:

  • web - Web routes, sessions, CSRF
  • auth - Require authentication
  • admin - Check user is admin (gates/policies)

Best Practices

1. Use Livewire Modals for Forms

php
// ✅ Good - Livewire modal
Route::get('/posts/create', PostEditor::class);

// ❌ Bad - Traditional controller
Route::get('/posts/create', [PostController::class, 'create']);

2. Use Form Components

blade
{{-- ✅ Good - consistent styling --}}
<x-admin::input name="title" label="Title" required />

{{-- ❌ Bad - custom HTML --}}
<input type="text" name="title" class="form-input">

3. Register Search Providers

php
// ✅ Good - searchable resources
$event->search(new PostSearchProvider());
$event->search(new CategorySearchProvider());

4. Use HLCRF for Layouts

blade
{{-- ✅ Good - composable layout --}}
<x-hlcrf::layout>
    <x-hlcrf::header>Header</x-hlcrf::header>
    <x-hlcrf::content>Content</x-hlcrf::content>
</x-hlcrf::layout>

Testing

php
<?php

namespace Tests\Feature\Admin;

use Tests\TestCase;
use Mod\Tenant\Models\User;

class PostEditorTest extends TestCase
{
    public function test_admin_can_create_post(): void
    {
        $admin = User::factory()->admin()->create();

        $this->actingAs($admin)
            ->livewire(PostEditor::class)
            ->set('title', 'Test Post')
            ->set('content', 'Test content')
            ->call('save')
            ->assertRedirect(route('admin.blog.posts'));

        $this->assertDatabaseHas('posts', [
            'title' => 'Test Post',
        ]);
    }
}

Learn More

Released under the EUPL-1.2 License.