Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 41 additions & 38 deletions resources/views/errors/admin-shell.blade.php
Original file line number Diff line number Diff line change
@@ -1,46 +1,49 @@
@php
$errors = [
401 => ['Authentication required', 'Sign in to continue to the admin area.'],
403 => ['Access denied', 'You do not have permission to view this page.'],
404 => ['Page not found', 'This admin page may have been moved, deleted, or never existed.'],
419 => ['Session expired', 'Your session has expired. Refresh the page to continue.'],
429 => ['Too many requests', 'Please wait a moment before trying again.'],
$pages = [
403 => [
'title' => 'Access denied',
'eyebrow' => 'RESTRICTED ACCESS',
'message' => 'You do not have permission to access this section.',
'hint' => 'If you need access, contact an administrator.',
],
404 => [
'title' => 'Page not found',
'eyebrow' => 'PAGE UNAVAILABLE',
'message' => 'We could not find the admin page you requested.',
'hint' => 'The address may be incorrect, or the page may have been moved.',
],
];
[$errorTitle, $errorMessage] = $errors[$status] ?? $errors[404];
$page = $pages[$status] ?? $pages[404];
@endphp
<x-lazy-layout :title="$errorTitle" :routes="['create' => null, 'show' => null, 'edit' => null, 'destroy' => null]">
{{-- Keep the central error illustration independent from host Vite builds. --}}
<style>
.lazy-admin-error { display: flex; align-items: center; justify-content: center; min-height: min(65vh, 640px); padding: 3rem 1rem 4rem; }
.lazy-admin-error__inner { width: 100%; max-width: 560px; text-align: center; }
.lazy-admin-error__icon { width: 72px; height: 72px; margin: 0 auto 1.75rem; display: grid; place-items: center; border: 1px solid var(--color-base-300, currentColor); border-radius: 20px; background: var(--color-base-200, transparent); color: var(--color-primary, #605dff); }
.lazy-admin-error__icon svg { width: 34px; height: 34px; }
.lazy-admin-error__code { margin: 0; font-size: clamp(5.5rem, 15vw, 9rem); line-height: .95; font-weight: 800; letter-spacing: -.085em; color: var(--color-primary, #605dff); font-variant-numeric: tabular-nums; }
.lazy-admin-error__title { margin: 1.5rem 0 .75rem; font-size: clamp(1.5rem, 4vw, 2rem); font-weight: 700; letter-spacing: -.035em; line-height: 1.25; }
.lazy-admin-error__description { max-width: 420px; margin: 0 auto; color: color-mix(in oklab, currentColor 63%, transparent); font-size: .95rem; line-height: 1.7; }
.lazy-admin-error__actions { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: .75rem; margin-top: 2rem; }
.lazy-admin-error__footnote { margin: 2.75rem auto 0; padding-top: 1rem; border-top: 1px solid var(--color-base-300, #343b49); max-width: 320px; color: color-mix(in oklab, currentColor 48%, transparent); font-size: .75rem; }
@media (max-width: 640px) { .lazy-admin-error { min-height: 55vh; padding: 2rem 0; } .lazy-admin-error__icon { width: 60px; height: 60px; } }
</style>
<section class="lazy-admin-error" aria-labelledby="lazy-admin-error-title">
<div class="lazy-admin-error__inner">
<div class="lazy-admin-error__icon" aria-hidden="true">
@if($status === 404)
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="10.8" cy="10.8" r="6.8"/><path d="m16 16 5 5M8.5 10.5h4.5"/></svg>
@elseif($status === 403 || $status === 401)
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2 20 6v6c0 5-3.5 8-8 10-4.5-2-8-5-8-10V6l8-4Z"/><path d="M12 8v5M12 17h.01"/></svg>
@else
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>
@endif
<x-lazy-layout :title="$page['title']" :routes="['create' => null, 'show' => null, 'edit' => null, 'destroy' => null]">
<section class="mx-auto w-full max-w-5xl py-6 sm:py-12" aria-labelledby="admin-error-title">
<div class="grid overflow-hidden rounded-xl border border-base-300 bg-base-100 shadow-sm lg:grid-cols-[minmax(0,1fr)_minmax(0,0.8fr)]">
<div class="flex flex-col justify-center p-7 sm:p-10 lg:p-14">
<div class="mb-7 flex items-center gap-3">
<span class="flex size-10 items-center justify-center rounded-lg bg-primary/10 text-primary">
@if($status === 404)
<svg xmlns="http://www.w3.org/2000/svg" class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="m16 16 5 5M8 11h6"/></svg>
@else
<svg xmlns="http://www.w3.org/2000/svg" class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M12 2 20 6v6c0 5-3.5 8-8 10-4.5-2-8-5-8-10V6l8-4Z"/><path d="M12 8v5m0 4h.01"/></svg>
@endif
</span>
<span class="text-xs font-bold tracking-widest text-base-content/60">{{ $page['eyebrow'] }}</span>
</div>
<h2 id="admin-error-title" class="text-3xl font-bold tracking-tight text-base-content sm:text-4xl">{{ $page['title'] }}</h2>
<p class="mt-4 text-base leading-7 text-base-content/70">{{ $page['message'] }}</p>
<p class="mt-2 text-sm leading-6 text-base-content/50">{{ $page['hint'] }}</p>
<div class="mt-8 flex flex-wrap items-center gap-3">
<x-lazy-btn :href="$homeUrl" primary :label="__('Back to dashboard')" />
<x-lazy-btn type="button" outline :label="__('Try again')" onclick="window.location.reload()" />
</div>
</div>
<p class="lazy-admin-error__code" aria-hidden="true">{{ $status }}</p>
<h2 id="lazy-admin-error-title" class="lazy-admin-error__title">{{ $errorTitle }}</h2>
<p class="lazy-admin-error__description">{{ $errorMessage }}</p>
<div class="lazy-admin-error__actions">
<x-lazy-btn :href="$homeUrl" primary :label="__('Back to dashboard')" />
<x-lazy-btn type="button" outline :label="__('Try again')" onclick="window.location.reload()" />
<div class="flex min-h-56 flex-col items-center justify-center border-t border-base-300 bg-base-200/60 px-6 py-10 lg:min-h-96 lg:border-l lg:border-t-0">
<div class="flex size-20 items-center justify-center rounded-2xl border border-base-300 bg-base-100 text-primary shadow-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="size-9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 4h16v16H4zM4 9h16M9 9v11"/><path d="m12 13 2 2 4-4" opacity=".45"/></svg>
</div>
<span class="mt-5 text-7xl font-extrabold tracking-tight text-primary sm:text-8xl" aria-label="HTTP {{ $status }}">{{ $status }}</span>
<span class="mt-3 rounded-lg border border-base-300 bg-base-100 px-3 py-1.5 text-xs font-medium text-base-content/60">HTTP {{ $status }}</span>
</div>
<p class="lazy-admin-error__footnote">HTTP {{ $status }} · {{ config('app.name', 'Lazy Admin') }}</p>
</div>
</section>
</x-lazy-layout>
4 changes: 2 additions & 2 deletions resources/views/errors/page.blade.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{{-- Severe failures use a dependency-free fallback to prevent secondary rendering errors. --}}
@if(in_array($status, [500, 503], true))
{{-- Only authenticated 403/404 responses may render the full admin shell. --}}
@if(! $useAdminLayout)
@php
$pages = [
401 => ['title' => 'Authentication required', 'text' => 'Sign in to continue to the admin area.', 'icon' => 'lock'],
Expand Down
5 changes: 5 additions & 0 deletions src/Exceptions/AdminErrorPages.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Step2dev\LazyAdmin\Exceptions;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Response;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Throwable;
Expand All @@ -25,8 +26,12 @@ public function __invoke(Throwable $exception, Request $request): ?Response
return null;
}

$guard = (string) config('lazy.auth.guard', 'web');
$useAdminLayout = in_array($status, [403, 404], true) && Auth::guard($guard)->check();

return response()->view('lazy::errors.page', [
'status' => $status,
'useAdminLayout' => $useAdminLayout,
'homeUrl' => url('/'.trim((string) config('lazy.admin.route.prefix', 'admin'), '/')),
], $status, $exception->getHeaders());
}
Expand Down
23 changes: 23 additions & 0 deletions tests/Feature/AdminErrorPagesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,26 @@
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);
});

it('avoids the admin shell for guest requests and recoverable session errors', function (): void {
$renderer = new AdminErrorPages;

foreach ([401, 403, 404, 419, 429, 500, 503] as $status) {
$response = $renderer(new HttpException($status), Request::create('/admin/missing'));

expect($response?->getStatusCode())->toBe($status)
->and($response?->getContent())->not->toContain('data-lazy-admin-shell');
}
});

it('uses the actual admin layout for signed-in missing pages', function (): void {
$user = new class extends \\Illuminate\\Foundation\\Auth\\User {};
$user->id = 123;
$this->be($user);

$response = (new AdminErrorPages)(new HttpException(404), Request::create('/admin/missing'));

expect($response?->getStatusCode())->toBe(404)
->and($response?->getContent())->toContain('data-lazy-admin-shell')
->and($response?->getContent())->toContain('Back to dashboard');
});
Loading