Hub (Web): statische Seiten (Rechtstexte, API, FAQ ...) rendern das Hub-Geruest

x-web.static-page ist jetzt host-aware: Auf der pressekonto-Domain laden
Utility-Bar + Hub-Header/-Footer (inkl. Kontakt- und Widerrufs-Modal,
damit die Footer-Buttons ueberall funktionieren) statt des
Spoke-Geruests; Breadcrumb fuehrt zur Hub-Landing. Hub-Anker-Navigation
(#tarife/#funktion/#familie/#faq) absolut auf die Landing, damit sie
auch von Unterseiten aus funktioniert. Spokes unveraendert (Test).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-07-03 10:47:47 +00:00
parent 6621b030e6
commit c7450da301
4 changed files with 70 additions and 11 deletions

View file

@ -44,7 +44,7 @@
<li><a href="{{ route('login') }}" class="hover:text-white transition-colors">Anmelden</a></li> <li><a href="{{ route('login') }}" class="hover:text-white transition-colors">Anmelden</a></li>
<li><a href="{{ route('register') }}" class="hover:text-white transition-colors">Konto erstellen</a></li> <li><a href="{{ route('register') }}" class="hover:text-white transition-colors">Konto erstellen</a></li>
@endauth @endauth
<li><a href="#tarife" class="hover:text-white transition-colors">Tarife</a></li> <li><a href="{{ route('home') }}#tarife" class="hover:text-white transition-colors">Tarife</a></li>
<li><button type="button" x-data @click="$dispatch('open-contact-modal')" class="hover:text-white transition-colors cursor-pointer">Kontakt aufnehmen</button></li> <li><button type="button" x-data @click="$dispatch('open-contact-modal')" class="hover:text-white transition-colors cursor-pointer">Kontakt aufnehmen</button></li>
</ul> </ul>
</div> </div>
@ -52,8 +52,8 @@
<div> <div>
<div class="eyebrow on-dark mb-3.5">Plattform</div> <div class="eyebrow on-dark mb-3.5">Plattform</div>
<ul class="space-y-2 text-[13px] text-white/75 list-none p-0 m-0"> <ul class="space-y-2 text-[13px] text-white/75 list-none p-0 m-0">
<li><a href="#funktion" class="hover:text-white transition-colors">So funktioniert es</a></li> <li><a href="{{ route('home') }}#funktion" class="hover:text-white transition-colors">So funktioniert es</a></li>
<li><a href="#familie" class="hover:text-white transition-colors">Die Portale</a></li> <li><a href="{{ route('home') }}#familie" class="hover:text-white transition-colors">Die Portale</a></li>
<li><a href="{{ route('docs.api.v1') }}" class="hover:text-white transition-colors">API-Referenz</a></li> <li><a href="{{ route('docs.api.v1') }}" class="hover:text-white transition-colors">API-Referenz</a></li>
</ul> </ul>
</div> </div>

View file

@ -14,11 +14,13 @@
]); ]);
$brandTagline = $brand['tagline_short'] ?? 'Publisher · Hub'; $brandTagline = $brand['tagline_short'] ?? 'Publisher · Hub';
// Absolute Anker auf die Landing, damit die Navigation auch von den
// statischen Unterseiten (Impressum, AGB, API …) aus funktioniert.
$navigation = $navigation ?? [ $navigation = $navigation ?? [
['label' => 'Tarife', 'href' => '#tarife'], ['label' => 'Tarife', 'href' => route('home').'#tarife'],
['label' => 'So funktioniert es', 'href' => '#funktion'], ['label' => 'So funktioniert es', 'href' => route('home').'#funktion'],
['label' => 'Plattform-Familie', 'href' => '#familie'], ['label' => 'Plattform-Familie', 'href' => route('home').'#familie'],
['label' => 'FAQ', 'href' => '#faq'], ['label' => 'FAQ', 'href' => route('home').'#faq'],
]; ];
@endphp @endphp

View file

@ -11,21 +11,34 @@
Liefert site-header, Breadcrumb, Masthead und site-footer; der Seiteninhalt Liefert site-header, Breadcrumb, Masthead und site-footer; der Seiteninhalt
kommt als Slot. Prosa-Seiten wrappen ihren Inhalt in `.pm-body` (zentrale kommt als Slot. Prosa-Seiten wrappen ihren Inhalt in `.pm-body` (zentrale
Editorial-Typografie aus shared-styles.css). Editorial-Typografie aus shared-styles.css).
Host-aware: Auf der Hub-Domain (pressekonto) rendert das Hub-Gerüst
(Utility-Bar, Hub-Header/-Footer inkl. Kontakt- und Widerrufs-Modal),
auf den Spokes das Portal-Gerüst.
--}} --}}
@php @php
$brandConfig = config('domains.domains.'.config('app.theme').'.brand', []); $isHub = str_contains(request()->getHost(), 'pressekonto');
$brandConfig = $isHub
? config('domains.domains.pressekonto.brand', [])
: config('domains.domains.'.config('app.theme').'.brand', []);
$brandName = $brandConfig['name'] ?? 'businessportal'; $brandName = $brandConfig['name'] ?? 'businessportal';
$brandAccent = $brandConfig['accent'] ?? '24'; $brandAccent = $brandConfig['accent'] ?? '24';
$homeHref = $isHub ? route('home') : route('web.home');
@endphp @endphp
<x-web.site-header /> @if ($isHub)
<x-web.hub.top-utility-bar />
<x-web.hub.site-header />
@else
<x-web.site-header />
@endif
<main class="bg-bg text-ink"> <main class="bg-bg text-ink">
{{-- Breadcrumb --}} {{-- Breadcrumb --}}
<div class="border-b border-bg-rule"> <div class="border-b border-bg-rule">
<div class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 py-2.5 flex items-center gap-2 text-[12px] text-ink-3"> <div class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 py-2.5 flex items-center gap-2 text-[12px] text-ink-3">
<a href="{{ route('web.home') }}" class="hover:text-ink transition-colors">{{ $brandName }}{{ $brandAccent }}</a> <a href="{{ $homeHref }}" class="hover:text-ink transition-colors">{{ $brandName }}{{ $brandAccent }}</a>
@if ($breadcrumb) @if ($breadcrumb)
<span class="text-ink-4" aria-hidden="true">›</span> <span class="text-ink-4" aria-hidden="true">›</span>
<span class="text-ink">{{ $breadcrumb }}</span> <span class="text-ink">{{ $breadcrumb }}</span>
@ -54,4 +67,10 @@
</section> </section>
</main> </main>
<x-web.site-footer /> @if ($isHub)
<x-web.hub.contact-modal />
<x-web.hub.withdrawal-modal />
<x-web.hub.site-footer />
@else
<x-web.site-footer />
@endif

View file

@ -0,0 +1,38 @@
<?php
use Tests\TestCase;
test('static pages on the hub domain render the hub shell', function (string $path) {
/** @var TestCase $this */
$response = $this->get('https://pressekonto.test/de/'.$path)->assertSuccessful();
$content = $response->getContent();
expect($content)
// Hub-Kopf: Utility-Bar + Hub-Header statt Spoke-Gerüst.
->toContain('Publisher-Hub für')
->toContain('Publisher · Hub')
// Hub-Footer inkl. gesetzlichem Widerrufsbutton + Modals.
->toContain('Vertrag widerrufen')
->toContain('open-withdrawal-modal')
->toContain('open-contact-modal')
// Kein Spoke-Portal-Branding im Gerüst.
->not->toContain('Pressemitteilungen · DACH');
})->with(['impressum', 'agb', 'datenschutz', 'cookies', 'api']);
test('the hub static page breadcrumb links back to the hub landing', function () {
/** @var TestCase $this */
$this->get('https://pressekonto.test/de/impressum')
->assertSuccessful()
->assertSee('href="https://pressekonto.test"', false)
->assertSeeText('pressekonto');
});
test('static pages on the spoke domains keep the spoke shell', function () {
/** @var TestCase $this */
$response = $this->get('https://businessportal24.test/de/impressum')->assertSuccessful();
$content = $response->getContent();
expect($content)
->not->toContain('Publisher-Hub für')
->not->toContain('Vertrag widerrufen');
});