From f6edb4aec53775a849dd1ec52df33c2d4ba3120e Mon Sep 17 00:00:00 2001 From: Kevin Adametz Date: Fri, 3 Jul 2026 09:24:04 +0000 Subject: [PATCH] R2.2: HubPageData - Plan-Katalog + echte Plattform-Kennzahlen fuer die Hub-Landing Liefert Plan::active(), Einzel-PM-Preis und stuendlich gecachte echte Zahlen (Archiv-Mitteilungen gesamt/je Portal, aktive Newsrooms, Branchen) an web.pressekonto. Tests: Katalog-Preise statt Fake-Liste, keine toten Links/erfundenen Referenzen, Rechtsseiten auf der Hub-Domain erreichbar. (Gehoert zum Web-Commit der Hub-Ueberarbeitung.) Co-Authored-By: Claude Fable 5 --- app/Services/Web/HubPageData.php | 49 ++++++++++++++++ routes/web.php | 3 +- tests/Feature/Web/PressekontoHubHomeTest.php | 62 +++++++++++++++++--- 3 files changed, 104 insertions(+), 10 deletions(-) create mode 100644 app/Services/Web/HubPageData.php diff --git a/app/Services/Web/HubPageData.php b/app/Services/Web/HubPageData.php new file mode 100644 index 00000000..e5f8d402 --- /dev/null +++ b/app/Services/Web/HubPageData.php @@ -0,0 +1,49 @@ + + */ + public function build(): array + { + return [ + 'plans' => Plan::query()->active()->get(), + 'singlePmPriceCents' => (int) config('billing.single_pm_price_cents'), + 'hubStats' => Cache::remember(self::StatsCacheKey, self::StatsTtlSeconds, function (): array { + $published = PressRelease::query() + ->withoutGlobalScope(PortalScope::class) + ->visibleInArchive(); + + return [ + 'releases_total' => (clone $published)->count(), + 'releases_presseecho' => (clone $published)->forPortalValues([Portal::Presseecho->value, Portal::Both->value])->count(), + 'releases_businessportal' => (clone $published)->forPortalValues([Portal::Businessportal24->value, Portal::Both->value])->count(), + 'newsrooms' => Company::query() + ->withoutGlobalScope(PortalScope::class) + ->whereHas('pressReleases', fn ($query) => $query->withoutGlobalScope(PortalScope::class)->visibleInArchive()) + ->count(), + 'categories' => Category::query()->withoutGlobalScope(PortalScope::class)->count(), + ]; + }), + ]; + } +} diff --git a/routes/web.php b/routes/web.php index 689fe1f0..b35f4fa1 100644 --- a/routes/web.php +++ b/routes/web.php @@ -16,6 +16,7 @@ use App\Models\Category; use App\Models\Company; use App\Models\Plan; use App\Scopes\PortalScope; +use App\Services\Web\HubPageData; use App\Support\DomainAssetContext; use Illuminate\Support\Facades\Route; @@ -39,7 +40,7 @@ Route::get('/', function () use ($applyWebDomainConfig) { if (str_contains($domain, 'pressekonto')) { $applyWebDomainConfig('pressekonto'); - return view('web.pressekonto'); + return view('web.pressekonto', app(HubPageData::class)->build()); } return redirect(request()->getSchemeAndHttpHost().'/'.SetEdition::DEFAULT_EDITION, 301); diff --git a/tests/Feature/Web/PressekontoHubHomeTest.php b/tests/Feature/Web/PressekontoHubHomeTest.php index fa434b66..5cafbd70 100644 --- a/tests/Feature/Web/PressekontoHubHomeTest.php +++ b/tests/Feature/Web/PressekontoHubHomeTest.php @@ -1,9 +1,12 @@ seed(PlanSeeder::class); + $this->get('https://pressekonto.test/') ->assertSuccessful() ->assertSee('presse', false) @@ -14,15 +17,57 @@ test('pressekonto hub homepage renders the publisher landing shell', function () ->assertSeeText('So funktioniert es') ->assertSeeText('Vier Schritte') ->assertSeeText('Tarife') - ->assertSeeText('Starter') - ->assertSeeText('Standard') - ->assertSeeText('Pro') ->assertSeeText('Enterprise') ->assertSeeText('Hinter pressekonto') + ->assertSeeText('Die Plattform in Zahlen') ->assertSeeText('Plattform im Überblick') ->assertSeeText('Häufige Fragen') - ->assertSeeText('Loslegen') - ->assertSeeText('Alle Systeme betriebsbereit'); + ->assertSeeText('Loslegen'); +}); + +test('pressekonto hub pricing is driven by the plan catalog', function () { + /** @var TestCase $this */ + $this->seed(PlanSeeder::class); + + $response = $this->get('https://pressekonto.test/')->assertSuccessful(); + + // Alle aktiven Tarife aus dem Katalog inkl. Kontingent; die frühere + // hartcodierte Fake-Preisliste (0/49/149 €, "Standard") ist raus. + $response + ->assertSeeText('Starter') + ->assertSeeText('Business') + ->assertSeeText('Pro') + ->assertSeeText('Agency') + ->assertSee('29 €', false) + ->assertSee('199 €', false) + ->assertDontSeeText('Standard') + ->assertDontSee('149 €', false); +}); + +test('pressekonto hub homepage has no dead links or fabricated references', function () { + /** @var TestCase $this */ + $this->seed(PlanSeeder::class); + + $response = $this->get('https://pressekonto.test/')->assertSuccessful(); + $content = $response->getContent(); + + expect($content) + ->not->toContain('href="#"') + ->not->toContain('Siemens') + ->not->toContain('Alle Systeme betriebsbereit') + ->not->toContain('14 Tage') + ->not->toContain('ISO 27001'); + + // Rechtsseiten sind auf der Hub-Domain verlinkt und erreichbar. + expect($content)->toContain('/de/impressum')->toContain('/de/datenschutz'); +}); + +test('legal pages are reachable on the hub domain', function () { + /** @var TestCase $this */ + $this->get('https://pressekonto.test/de/impressum')->assertSuccessful(); + $this->get('https://pressekonto.test/de/datenschutz')->assertSuccessful(); + $this->get('https://pressekonto.test/de/agb')->assertSuccessful(); + $this->get('https://pressekonto.test/de/cookies')->assertSuccessful(); }); test('pressekonto hub homepage uses the brand-mark splitting without TLDs', function () { @@ -42,10 +87,9 @@ test('pressekonto hub homepage uses the brand-mark splitting without TLDs', func ->toContain('>echo<') ->toContain('>konto<'); - // pressekonto darf ausschliesslich in der Vertriebs-Mailto-Adresse als Domain auftauchen, - // niemals als Marken-Schreibweise im Lese-Text. - expect(substr_count($content, 'pressekonto.de')) - ->toBe(substr_count($content, 'vertrieb@pressekonto.de')); + // pressekonto darf als Domain nirgends im Lese-Text auftauchen — nur die + // gesplittete Marken-Schreibweise. + expect($content)->not->toContain('pressekonto.de'); }); test('pressekonto hub homepage loads the hub theme assets, not portal admin', function () {