R0.5: eigene Fehlerseiten 403/404/419/500/503 (self-contained, domain-aware)

Bisher griffen die Laravel-Default-Seiten. Gemeinsames Inline-CSS-Layout
ohne Vite-Abhaengigkeit (rendert auch bei kaputtem Asset-Build), nutzt
die geteilte domainName-Variable mit Fallback, noindex, Dark-Mode via
prefers-color-scheme.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-07-03 08:01:29 +00:00
parent 17eaa80135
commit c3db80c651
7 changed files with 144 additions and 0 deletions

View file

@ -0,0 +1,5 @@
@extends('errors.layout')
@section('code', '403')
@section('title', 'Zugriff verweigert')
@section('message', 'Für diese Seite fehlt Ihnen die Berechtigung. Melden Sie sich mit einem passenden Konto an oder kehren Sie zur Startseite zurück.')

View file

@ -0,0 +1,5 @@
@extends('errors.layout')
@section('code', '404')
@section('title', 'Seite nicht gefunden')
@section('message', 'Die aufgerufene Seite existiert nicht oder wurde verschoben. Prüfen Sie die Adresse oder starten Sie auf der Startseite neu.')

View file

@ -0,0 +1,5 @@
@extends('errors.layout')
@section('code', '419')
@section('title', 'Sitzung abgelaufen')
@section('message', 'Ihre Sitzung ist abgelaufen. Laden Sie die Seite neu und versuchen Sie es noch einmal.')

View file

@ -0,0 +1,5 @@
@extends('errors.layout')
@section('code', '500')
@section('title', 'Interner Fehler')
@section('message', 'Da ist etwas schiefgelaufen. Wir sind informiert – bitte versuchen Sie es in wenigen Minuten erneut.')

View file

@ -0,0 +1,5 @@
@extends('errors.layout')
@section('code', '503')
@section('title', 'Wartungsmodus')
@section('message', 'Wir führen gerade eine kurze Wartung durch und sind gleich wieder für Sie da.')

View file

@ -0,0 +1,93 @@
{{--
Gemeinsames Grundgerüst der Fehlerseiten. Bewusst self-contained
(Inline-CSS, keine Vite-Assets), damit die Seiten auch bei kaputtem
Asset-Build oder vor dem ersten `npm run build` zuverlässig rendern.
--}}
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex">
<title>@yield('code') · @yield('title') – {{ $domainName ?? 'Pressekonto' }}</title>
<style>
:root {
color-scheme: light dark;
--bg: #f6f7f8;
--panel: #ffffff;
--ink: #1c2427;
--ink-2: #5b6b70;
--rule: #e3e7e9;
--accent: #526266;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #161b1d;
--panel: #1f2629;
--ink: #e8ecee;
--ink-2: #9fb0b5;
--rule: #313b3f;
--accent: #82a0a7;
}
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
background: var(--bg);
color: var(--ink);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
}
main {
background: var(--panel);
border: 1px solid var(--rule);
border-radius: 8px;
padding: 48px 40px;
max-width: 460px;
width: 100%;
text-align: center;
}
.code {
font-size: 13px;
font-weight: 700;
letter-spacing: 0.18em;
color: var(--ink-2);
text-transform: uppercase;
}
h1 { font-size: 22px; margin: 12px 0 10px; }
p { font-size: 14.5px; line-height: 1.6; color: var(--ink-2); }
a.home {
display: inline-block;
margin-top: 24px;
padding: 10px 22px;
border-radius: 6px;
background: var(--accent);
color: #fff;
font-size: 14px;
font-weight: 600;
text-decoration: none;
}
.brand {
margin-top: 28px;
padding-top: 18px;
border-top: 1px solid var(--rule);
font-size: 12px;
letter-spacing: 0.08em;
color: var(--ink-2);
text-transform: uppercase;
}
</style>
</head>
<body>
<main>
<div class="code">@yield('code')</div>
<h1>@yield('title')</h1>
<p>@yield('message')</p>
<a class="home" href="/">Zur Startseite</a>
<div class="brand">{{ $domainName ?? 'Pressekonto' }}</div>
</main>
</body>
</html>

View file

@ -0,0 +1,26 @@
<?php
use Tests\TestCase;
test('unknown urls render the custom 404 page', function () {
/** @var TestCase $this */
$this->get('https://businessportal24.test/de/gibt-es-nicht')
->assertNotFound()
->assertSee('Seite nicht gefunden')
->assertSee('Zur Startseite');
});
test('custom error views render self-contained', function (string $view, string $expected) {
/** @var TestCase $this */
$rendered = $this->view("errors.{$view}");
$rendered->assertSee($expected);
// Kein Vite-Asset: Die Seiten müssen auch bei kaputtem Build rendern.
expect((string) $rendered)->not->toContain('/build/');
})->with([
['403', 'Zugriff verweigert'],
['404', 'Seite nicht gefunden'],
['419', 'Sitzung abgelaufen'],
['500', 'Interner Fehler'],
['503', 'Wartungsmodus'],
]);