From b9be6a3d04443178f01f6e305fdd5e70cdb3614d Mon Sep 17 00:00:00 2001 From: Yurij Finiv Date: Sat, 10 Oct 2026 21:34:01 +0300 Subject: [PATCH 1/2] Add admin-scoped error pages matching Lazy Admin UI --- resources/views/errors/page.blade.php | 70 +++++++++++++++++++++++++++ src/Exceptions/AdminErrorPages.php | 67 +++++++++++++++++++++++++ src/LazyAdminServiceProvider.php | 8 +++ tests/Feature/AdminErrorPagesTest.php | 40 +++++++++++++++ 4 files changed, 185 insertions(+) create mode 100644 resources/views/errors/page.blade.php create mode 100644 src/Exceptions/AdminErrorPages.php create mode 100644 tests/Feature/AdminErrorPagesTest.php diff --git a/resources/views/errors/page.blade.php b/resources/views/errors/page.blade.php new file mode 100644 index 0000000..12de456 --- /dev/null +++ b/resources/views/errors/page.blade.php @@ -0,0 +1,70 @@ +@php + $pages = [ + 401 => ['title' => 'Authentication required', 'text' => 'Sign in to continue to the admin area.', 'icon' => 'lock'], + 403 => ['title' => 'Access denied', 'text' => 'You do not have permission to view this page.', 'icon' => 'shield'], + 404 => ['title' => 'Page not found', 'text' => 'This admin page may have moved, or the address may be incorrect.', 'icon' => 'search'], + 419 => ['title' => 'Session expired', 'text' => 'Your session has expired. Refresh the page and try again.', 'icon' => 'clock'], + 429 => ['title' => 'Too many requests', 'text' => 'You have made too many requests. Please try again shortly.', 'icon' => 'clock'], + 500 => ['title' => 'Something went wrong', 'text' => 'An unexpected error occurred. Please try again later.', 'icon' => 'alert'], + 503 => ['title' => 'Temporarily unavailable', 'text' => 'The admin area is temporarily unavailable. Please try again soon.', 'icon' => 'alert'], + ]; + $page = $pages[$status] ?? $pages[500]; +@endphp + + + + + + + {{ $status }} · {{ $page['title'] }} · {{ config('app.name', 'Lazy Admin') }} + + + +
{{ config('app.name', 'Lazy Admin') }}/ Admin
+
+ +
{{ $status }}
+

{{ $page['title'] }}

+

{{ $page['text'] }}

+
+ Back to dashboard + +
+
+
+ + + diff --git a/src/Exceptions/AdminErrorPages.php b/src/Exceptions/AdminErrorPages.php new file mode 100644 index 0000000..1a766a8 --- /dev/null +++ b/src/Exceptions/AdminErrorPages.php @@ -0,0 +1,67 @@ +expectsJson() || $request->is('livewire/*') || ! $this->isAdminRequest($request)) { + return null; + } + + if (! $exception instanceof HttpExceptionInterface) { + return null; + } + + $status = $exception->getStatusCode(); + + if (! in_array($status, [401, 403, 404, 419, 429, 500, 503], true)) { + return null; + } + + return response()->view('lazy::errors.page', [ + 'status' => $status, + 'homeUrl' => url('/'.trim((string) config('lazy.admin.route.prefix', 'admin'), '/')), + ], $status, $exception->getHeaders()); + } + + private function isAdminRequest(Request $request): bool + { + $domain = config('lazy.admin.route.domain'); + + if (is_string($domain) && $domain !== '' && $request->getHost() !== $domain) { + return false; + } + + $routeName = $request->route()?->getName(); + $namePrefix = trim((string) config('lazy.admin.route.name', 'admin.'), '.'); + + if (is_string($routeName) && $namePrefix !== '' && + ($routeName === $namePrefix || str_starts_with($routeName, $namePrefix.'.'))) { + return true; + } + + $prefix = trim((string) config('lazy.admin.route.prefix', 'admin'), '/'); + + if ($prefix === '') { + return false; + } + + $segments = explode('/', trim($request->decodedPath(), '/')); + $prefixSegments = explode('/', $prefix); + + if (array_slice($segments, 0, count($prefixSegments)) === $prefixSegments) { + return true; + } + + // Permit a locale before the admin prefix on a missing route. + return count($segments) > count($prefixSegments) + && preg_match('/^[a-z]{2}(?:-[A-Za-z]{2})?$/', $segments[0]) === 1 + && array_slice($segments, 1, count($prefixSegments)) === $prefixSegments; + } +} diff --git a/src/LazyAdminServiceProvider.php b/src/LazyAdminServiceProvider.php index 034f50f..3ab94b4 100644 --- a/src/LazyAdminServiceProvider.php +++ b/src/LazyAdminServiceProvider.php @@ -3,6 +3,8 @@ namespace Step2dev\LazyAdmin; use Illuminate\Database\Eloquent\Model; +use Illuminate\Contracts\Debug\ExceptionHandler; +use Step2dev\LazyAdmin\Exceptions\AdminErrorPages; use Illuminate\Routing\Router; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Gate; @@ -203,6 +205,12 @@ public function bootingPackage(): void public function packageBooted(): void { + $handler = $this->app->make(ExceptionHandler::class); + + if (method_exists($handler, 'renderable')) { + $handler->renderable(new AdminErrorPages); + } + View::composer('lazy::header', static function ($view): void { $guard = (string) config('lazy.auth.guard', 'web'); $user = Auth::guard($guard)->user(); diff --git a/tests/Feature/AdminErrorPagesTest.php b/tests/Feature/AdminErrorPagesTest.php new file mode 100644 index 0000000..b630c54 --- /dev/null +++ b/tests/Feature/AdminErrorPagesTest.php @@ -0,0 +1,40 @@ +set('lazy.admin.route.prefix', 'admin'); + + $render = new AdminErrorPages; + + $admin = $render(new HttpException(404), Request::create('/admin/missing')); + expect($admin)->not->toBeNull() + ->and($admin->getStatusCode())->toBe(404) + ->and($admin->getContent())->toContain('Page not found'); + + expect($render(new HttpException(404), Request::create('/public/missing')))->toBeNull(); + expect($render(new HttpException(404), Request::create('/administrator/missing')))->toBeNull(); + expect($render(new HttpException(404), Request::create('/admin/missing', 'GET', [], [], [], ['HTTP_ACCEPT' => 'application/json'])))->toBeNull(); + expect($render(new HttpException(404), Request::create('/livewire/update')))->toBeNull(); +}); + +it('supports localized admin paths and custom prefixes', function (): void { + config()->set('lazy.admin.route.prefix', 'control'); + + $render = new AdminErrorPages; + + expect($render(new HttpException(403), Request::create('/uk/control/private'))?->getStatusCode())->toBe(403) + ->and($render(new HttpException(503), Request::create('/control/offline'))?->getStatusCode())->toBe(503) + ->and($render(new HttpException(404), Request::create('/uk/admin/missing')))->toBeNull(); +}); + +it('honors a configured admin domain', function (): void { + config()->set('lazy.admin.route.domain', 'admin.example.test'); + + $render = new AdminErrorPages; + + expect($render(new HttpException(404), Request::create('https://example.test/admin/missing')))->toBeNull() + ->and($render(new HttpException(404), Request::create('https://admin.example.test/admin/missing'))?->getStatusCode())->toBe(404); +}); From 81f73ffe295376d010695b3e1ff47dffe496885c Mon Sep 17 00:00:00 2001 From: CrazyBoy49z <3448603+CrazyBoy49z@users.noreply.github.com> Date: Sat, 10 Oct 2026 18:34:39 +0000 Subject: [PATCH 2/2] Fix styling --- src/Exceptions/AdminErrorPages.php | 3 ++- src/LazyAdminServiceProvider.php | 4 ++-- tests/Feature/AdminErrorPagesTest.php | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Exceptions/AdminErrorPages.php b/src/Exceptions/AdminErrorPages.php index 1a766a8..0c8d9dd 100644 --- a/src/Exceptions/AdminErrorPages.php +++ b/src/Exceptions/AdminErrorPages.php @@ -3,12 +3,13 @@ namespace Step2dev\LazyAdmin\Exceptions; use Illuminate\Http\Request; +use Illuminate\Http\Response; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Throwable; final class AdminErrorPages { - public function __invoke(Throwable $exception, Request $request): ?\Illuminate\Http\Response + public function __invoke(Throwable $exception, Request $request): ?Response { if ($request->expectsJson() || $request->is('livewire/*') || ! $this->isAdminRequest($request)) { return null; diff --git a/src/LazyAdminServiceProvider.php b/src/LazyAdminServiceProvider.php index 3ab94b4..7aa9fa2 100644 --- a/src/LazyAdminServiceProvider.php +++ b/src/LazyAdminServiceProvider.php @@ -2,9 +2,8 @@ namespace Step2dev\LazyAdmin; -use Illuminate\Database\Eloquent\Model; use Illuminate\Contracts\Debug\ExceptionHandler; -use Step2dev\LazyAdmin\Exceptions\AdminErrorPages; +use Illuminate\Database\Eloquent\Model; use Illuminate\Routing\Router; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Gate; @@ -40,6 +39,7 @@ use Step2dev\LazyAdmin\Controllers\UserController; use Step2dev\LazyAdmin\Dashboard\DashboardRegistry; use Step2dev\LazyAdmin\Database\Seeders\DatabaseSeeder; +use Step2dev\LazyAdmin\Exceptions\AdminErrorPages; use Step2dev\LazyAdmin\Facades\Route as RouteFacade; use Step2dev\LazyAdmin\Http\Livewire\Activity\Page as ActivityPage; use Step2dev\LazyAdmin\Http\Livewire\Dashboard\Page as DashboardPage; diff --git a/tests/Feature/AdminErrorPagesTest.php b/tests/Feature/AdminErrorPagesTest.php index b630c54..f25bd97 100644 --- a/tests/Feature/AdminErrorPagesTest.php +++ b/tests/Feature/AdminErrorPagesTest.php @@ -1,8 +1,8 @@ set('lazy.admin.route.prefix', 'admin');