Werbeplaetze reaktiviert: Eigenwerbe-Slots oeffnen das Kontakt-Widget
Die im Spoke-Frontend-Audit entfernten Anzeige-Flaechen (focus-ad, feed-ad, Inline-Ad) kehren als dezente Eigenwerbung zurueck: - neue Komponente x-web.ad-slot im frueheren "ANZEIGE"-Stil mit Hinweis "Hier koennte Ihre Werbung stehen" (DE/EN) - eingebaut an den drei frueheren Stellen: Hero-Sidebar der Startseiten, Feed nach der dritten Meldung, PM-Detailseite nach der Unternehmensbeschreibung (nicht in der privaten Vorschau) - Klick oeffnet das schwebende Kontakt-Widget mit vorgewaehltem neuen Thema "Werbung buchen" (ContactMessageTopic::Advertising); Anfragen landen wie gewohnt in der Admin-Queue "Kontakt" Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
0cda19458d
commit
0665553c88
12 changed files with 174 additions and 2 deletions
|
|
@ -5,6 +5,7 @@ namespace App\Enums;
|
||||||
enum ContactMessageTopic: string
|
enum ContactMessageTopic: string
|
||||||
{
|
{
|
||||||
case Contact = 'contact';
|
case Contact = 'contact';
|
||||||
|
case Advertising = 'advertising';
|
||||||
case BugReport = 'bug_report';
|
case BugReport = 'bug_report';
|
||||||
case PressReleaseReport = 'press_release_report';
|
case PressReleaseReport = 'press_release_report';
|
||||||
case Feedback = 'feedback';
|
case Feedback = 'feedback';
|
||||||
|
|
@ -16,6 +17,7 @@ enum ContactMessageTopic: string
|
||||||
{
|
{
|
||||||
return match ($this) {
|
return match ($this) {
|
||||||
self::Contact => 'Kontaktaufnahme',
|
self::Contact => 'Kontaktaufnahme',
|
||||||
|
self::Advertising => 'Werbeanfrage',
|
||||||
self::BugReport => 'Fehlermeldung',
|
self::BugReport => 'Fehlermeldung',
|
||||||
self::PressReleaseReport => 'PM-Meldung',
|
self::PressReleaseReport => 'PM-Meldung',
|
||||||
self::Feedback => 'Feedback',
|
self::Feedback => 'Feedback',
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,15 @@ return [
|
||||||
'title' => 'Meistgelesen',
|
'title' => 'Meistgelesen',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'ad_slot' => [
|
||||||
|
'label' => 'Anzeige',
|
||||||
|
'end_label' => 'Ende Anzeige',
|
||||||
|
'eyebrow' => 'Werbeplatz verfügbar',
|
||||||
|
'title' => 'Hier könnte Ihre Werbung stehen',
|
||||||
|
'text' => 'Erreichen Sie Entscheider aus dem DACH-Raum — direkt im redaktionellen Umfeld.',
|
||||||
|
'cta' => 'Jetzt Werbeplatz anfragen',
|
||||||
|
],
|
||||||
|
|
||||||
'newsrooms' => [
|
'newsrooms' => [
|
||||||
'title' => 'Aktive Newsrooms',
|
'title' => 'Aktive Newsrooms',
|
||||||
'today' => 'Heute',
|
'today' => 'Heute',
|
||||||
|
|
@ -189,6 +198,8 @@ return [
|
||||||
'intro' => 'Wählen Sie Ihr Anliegen — wir melden uns werktags schnellstmöglich zurück.',
|
'intro' => 'Wählen Sie Ihr Anliegen — wir melden uns werktags schnellstmöglich zurück.',
|
||||||
'topic_contact' => 'Kontaktaufnahme',
|
'topic_contact' => 'Kontaktaufnahme',
|
||||||
'topic_contact_desc' => 'Allgemeine Frage oder Anfrage an uns',
|
'topic_contact_desc' => 'Allgemeine Frage oder Anfrage an uns',
|
||||||
|
'topic_advertising' => 'Werbung buchen',
|
||||||
|
'topic_advertising_desc' => 'Werbeplätze und Konditionen anfragen',
|
||||||
'topic_bug' => 'Fehler melden',
|
'topic_bug' => 'Fehler melden',
|
||||||
'topic_bug_desc' => 'Etwas funktioniert nicht wie erwartet',
|
'topic_bug_desc' => 'Etwas funktioniert nicht wie erwartet',
|
||||||
'topic_press' => 'Pressemitteilung melden',
|
'topic_press' => 'Pressemitteilung melden',
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,15 @@ return [
|
||||||
'title' => 'Most read',
|
'title' => 'Most read',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'ad_slot' => [
|
||||||
|
'label' => 'Advertisement',
|
||||||
|
'end_label' => 'End of advertisement',
|
||||||
|
'eyebrow' => 'Ad space available',
|
||||||
|
'title' => 'Your advertising could be here',
|
||||||
|
'text' => 'Reach decision-makers across the DACH region — right within the editorial environment.',
|
||||||
|
'cta' => 'Enquire about this ad space',
|
||||||
|
],
|
||||||
|
|
||||||
'newsrooms' => [
|
'newsrooms' => [
|
||||||
'title' => 'Active newsrooms',
|
'title' => 'Active newsrooms',
|
||||||
'today' => 'Today',
|
'today' => 'Today',
|
||||||
|
|
@ -184,6 +193,8 @@ return [
|
||||||
'intro' => 'Choose your topic — we will get back to you as soon as possible on business days.',
|
'intro' => 'Choose your topic — we will get back to you as soon as possible on business days.',
|
||||||
'topic_contact' => 'Get in touch',
|
'topic_contact' => 'Get in touch',
|
||||||
'topic_contact_desc' => 'General question or inquiry',
|
'topic_contact_desc' => 'General question or inquiry',
|
||||||
|
'topic_advertising' => 'Book advertising',
|
||||||
|
'topic_advertising_desc' => 'Ask about ad placements and rates',
|
||||||
'topic_bug' => 'Report a bug',
|
'topic_bug' => 'Report a bug',
|
||||||
'topic_bug_desc' => 'Something is not working as expected',
|
'topic_bug_desc' => 'Something is not working as expected',
|
||||||
'topic_press' => 'Report a press release',
|
'topic_press' => 'Report a press release',
|
||||||
|
|
|
||||||
47
resources/views/components/web/ad-slot.blade.php
Normal file
47
resources/views/components/web/ad-slot.blade.php
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
{{--
|
||||||
|
Werbeplatz-Hinweis (Eigenwerbung): dezenter „Anzeige"-Rahmen im Stil der
|
||||||
|
früheren Ad-Slots (focus-ad/feed-ad), solange die Fläche nicht vermarktet
|
||||||
|
ist. Klick öffnet das schwebende Kontakt-Widget mit vorgewähltem Thema
|
||||||
|
„Werbeanfrage" (Window-Event `open-contact-modal` + topic-Detail).
|
||||||
|
|
||||||
|
Varianten steuern nur die Außenabstände der jeweiligen Einbauorte:
|
||||||
|
- sidebar → Hero-Sidebar der Startseiten
|
||||||
|
- feed → Meldungs-Feed
|
||||||
|
- inline → Fließtext der PM-Detailseite
|
||||||
|
--}}
|
||||||
|
|
||||||
|
@props(['variant' => 'sidebar'])
|
||||||
|
|
||||||
|
<div @class([
|
||||||
|
'mt-4' => $variant === 'sidebar',
|
||||||
|
'my-6' => $variant === 'feed',
|
||||||
|
'my-8 max-w-[680px]' => $variant === 'inline',
|
||||||
|
])>
|
||||||
|
<div class="flex items-center gap-2.5 mb-2.5">
|
||||||
|
<span class="h-px bg-bg-rule-strong flex-1"></span>
|
||||||
|
<span class="text-[9px] font-bold tracking-[0.22em] text-ink-on-dark-muted uppercase font-mono">{{ __('web.ad_slot.label') }}</span>
|
||||||
|
<span class="h-px bg-bg-rule-strong flex-1"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" x-data @click="$dispatch('open-contact-modal', { topic: '{{ \App\Enums\ContactMessageTopic::Advertising->value }}' })"
|
||||||
|
class="block w-full text-left px-4 py-4 bg-bg-card-warm border border-bg-card-warm-border cursor-pointer hover:border-brand/50 hover:bg-bg-card-warm-hover transition-colors">
|
||||||
|
<div class="text-[9.5px] font-bold tracking-[0.16em] text-card-warm-cat uppercase mb-1.5">
|
||||||
|
{{ __('web.ad_slot.eyebrow') }}
|
||||||
|
</div>
|
||||||
|
<div class="font-serif text-[15.5px] leading-[1.3] font-medium text-card-warm-title">
|
||||||
|
{{ __('web.ad_slot.title') }}
|
||||||
|
</div>
|
||||||
|
<div class="text-[11.5px] leading-[1.5] mt-1.5 text-card-warm-cat">
|
||||||
|
{{ __('web.ad_slot.text') }}
|
||||||
|
</div>
|
||||||
|
<span class="inline-block mt-2.5 text-[11.5px] font-semibold underline underline-offset-2 text-card-warm-title">
|
||||||
|
{{ __('web.ad_slot.cta') }} →
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2.5 mt-2.5">
|
||||||
|
<span class="h-px bg-bg-rule-strong flex-1"></span>
|
||||||
|
<span class="text-[9px] font-bold tracking-[0.22em] text-ink-on-dark-muted uppercase font-mono">{{ __('web.ad_slot.end_label') }}</span>
|
||||||
|
<span class="h-px bg-bg-rule-strong flex-1"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -25,6 +25,11 @@
|
||||||
'label' => __('web.launcher.topic_contact'),
|
'label' => __('web.launcher.topic_contact'),
|
||||||
'description' => __('web.launcher.topic_contact_desc'),
|
'description' => __('web.launcher.topic_contact_desc'),
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'value' => \App\Enums\ContactMessageTopic::Advertising->value,
|
||||||
|
'label' => __('web.launcher.topic_advertising'),
|
||||||
|
'description' => __('web.launcher.topic_advertising_desc'),
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'value' => \App\Enums\ContactMessageTopic::BugReport->value,
|
'value' => \App\Enums\ContactMessageTopic::BugReport->value,
|
||||||
'label' => __('web.launcher.topic_bug'),
|
'label' => __('web.launcher.topic_bug'),
|
||||||
|
|
@ -50,7 +55,8 @@
|
||||||
topic: @js(old('topic')),
|
topic: @js(old('topic')),
|
||||||
topicLabels: @js($launcherTopicLabels),
|
topicLabels: @js($launcherTopicLabels),
|
||||||
}"
|
}"
|
||||||
@open-contact-modal.window="open = true"
|
{{-- Optionales Event-Detail `topic` wählt das Anliegen direkt vor (z. B. Werbeplätze). --}}
|
||||||
|
@open-contact-modal.window="open = true; if ($event.detail?.topic && topicLabels[$event.detail.topic]) { topic = $event.detail.topic }"
|
||||||
@keydown.escape.window="open = false"
|
@keydown.escape.window="open = false"
|
||||||
class="fixed bottom-5 right-5 z-50 flex flex-col items-end gap-3 print:hidden"
|
class="fixed bottom-5 right-5 z-50 flex flex-col items-end gap-3 print:hidden"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,8 @@
|
||||||
@foreach ($sideReleases as $index => $release)
|
@foreach ($sideReleases as $index => $release)
|
||||||
<x-web.focus-side-item :release="$release" :index="$index + 1" :first="$loop->first" />
|
<x-web.focus-side-item :release="$release" :index="$index + 1" :first="$loop->first" />
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
||||||
|
<x-web.ad-slot variant="sidebar" />
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,11 @@ new class extends Component
|
||||||
|
|
||||||
@foreach ($rest as $release)
|
@foreach ($rest as $release)
|
||||||
<x-web.feed-item :release="$release" :recommended="($release->content_score ?? 0) >= 80" />
|
<x-web.feed-item :release="$release" :recommended="($release->content_score ?? 0) >= 80" />
|
||||||
|
|
||||||
|
@if ($loop->index === 2)
|
||||||
|
{{-- Werbeplatz nach der dritten Feed-Meldung (Position der früheren feed-ad). --}}
|
||||||
|
<x-web.ad-slot variant="feed" />
|
||||||
|
@endif
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
||||||
@if ($releases->hasMorePages())
|
@if ($releases->hasMorePages())
|
||||||
|
|
|
||||||
|
|
@ -353,6 +353,11 @@
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
{{-- Inline-Werbeplatz (Position der früheren Inline-Ad); in der privaten Vorschau ausgeblendet --}}
|
||||||
|
@unless ($isPreview)
|
||||||
|
<x-web.ad-slot variant="inline" />
|
||||||
|
@endunless
|
||||||
|
|
||||||
{{-- Pressekontakt --}}
|
{{-- Pressekontakt --}}
|
||||||
@if ($contact)
|
@if ($contact)
|
||||||
<div class="mt-8 px-5 sm:px-7 py-6 bg-white border border-bg-rule-strong max-w-[680px]">
|
<div class="mt-8 px-5 sm:px-7 py-6 bg-white border border-bg-rule-strong max-w-[680px]">
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,9 @@ test('preview renders the public release detail layout with preview banner and n
|
||||||
->assertSeeText('Vorschau')
|
->assertSeeText('Vorschau')
|
||||||
->assertSeeText('So erscheint die Pressemitteilung nach der Veröffentlichung.')
|
->assertSeeText('So erscheint die Pressemitteilung nach der Veröffentlichung.')
|
||||||
->assertSeeText('Status: Entwurf')
|
->assertSeeText('Status: Entwurf')
|
||||||
->assertSeeText('Entwurf im Portal-Design');
|
->assertSeeText('Entwurf im Portal-Design')
|
||||||
|
// Der Werbeplatz-Hinweis erscheint nur auf der öffentlichen Seite.
|
||||||
|
->assertDontSeeText('Hier könnte Ihre Werbung stehen');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('preview applies the theme of the target portal', function () {
|
test('preview applies the theme of the target portal', function () {
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,34 @@ test('a widget submission stores topic and page url', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('an advertising enquiry from the ad slot stores the advertising topic', function () {
|
||||||
|
/** @var TestCase $this */
|
||||||
|
Mail::fake();
|
||||||
|
|
||||||
|
$this->post('https://presseecho.test/de/kontakt', [
|
||||||
|
'name' => 'Willi Werbung',
|
||||||
|
'email' => 'willi@example.test',
|
||||||
|
'message' => 'Ich interessiere mich für Werbeplätze auf Ihrem Portal.',
|
||||||
|
'topic' => 'advertising',
|
||||||
|
'page_url' => 'https://presseecho.test/de',
|
||||||
|
])->assertSessionHas('contact-status');
|
||||||
|
|
||||||
|
$message = ContactMessage::query()->sole();
|
||||||
|
|
||||||
|
expect($message->topic)->toBe(ContactMessageTopic::Advertising)
|
||||||
|
->and($message->portal)->toBe('presseecho');
|
||||||
|
|
||||||
|
Mail::assertSent(ContactRequestSubmitted::class, fn (ContactRequestSubmitted $mail) => $mail->topicLabel === 'Werbeanfrage');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the spoke pages offer the advertising topic in the contact launcher', function () {
|
||||||
|
/** @var TestCase $this */
|
||||||
|
$this->get('https://businessportal24.test/de/faq')
|
||||||
|
->assertSuccessful()
|
||||||
|
->assertSee('Werbung buchen')
|
||||||
|
->assertSee('Werbeplätze und Konditionen anfragen');
|
||||||
|
});
|
||||||
|
|
||||||
test('an unknown topic is rejected', function () {
|
test('an unknown topic is rejected', function () {
|
||||||
/** @var TestCase $this */
|
/** @var TestCase $this */
|
||||||
Mail::fake();
|
Mail::fake();
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,39 @@ test('presseecho homepage renders the editorial shell with real data', function
|
||||||
->assertSeeText('Heute im Fokus');
|
->assertSeeText('Heute im Fokus');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('presseecho homepage shows the ad slot self-promotion in the hero sidebar', function () {
|
||||||
|
/** @var TestCase $this */
|
||||||
|
$category = Category::factory()->create([
|
||||||
|
'portal' => Portal::Presseecho,
|
||||||
|
]);
|
||||||
|
$category->translations()->create([
|
||||||
|
'locale' => 'de',
|
||||||
|
'name' => 'Energie & Klima',
|
||||||
|
'slug' => 'energie-klima',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$company = Company::factory()->presseecho()->create();
|
||||||
|
|
||||||
|
PressRelease::factory()
|
||||||
|
->published()
|
||||||
|
->forPortal(Portal::Presseecho)
|
||||||
|
->for($category)
|
||||||
|
->for($company)
|
||||||
|
->create([
|
||||||
|
'title' => 'Ad-Slot Hero Meldung',
|
||||||
|
'slug' => 'ad-slot-hero-meldung',
|
||||||
|
'published_at' => now()->subHour(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->get('https://presseecho.test/de')
|
||||||
|
->assertSuccessful()
|
||||||
|
->assertSeeText('Hier könnte Ihre Werbung stehen')
|
||||||
|
->assertSeeText('Jetzt Werbeplatz anfragen')
|
||||||
|
// Klick öffnet das Kontakt-Widget mit vorgewähltem Thema „Werbeanfrage".
|
||||||
|
->assertSee('open-contact-modal', false)
|
||||||
|
->assertSee("{ topic: 'advertising' }", false);
|
||||||
|
});
|
||||||
|
|
||||||
test('presseecho homepage hides data sections and shows the empty feed state without content', function () {
|
test('presseecho homepage hides data sections and shows the empty feed state without content', function () {
|
||||||
/** @var TestCase $this */
|
/** @var TestCase $this */
|
||||||
$this->get('https://presseecho.test/de')
|
$this->get('https://presseecho.test/de')
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,26 @@ test('release detail without chosen placeholder falls back to the generic hero',
|
||||||
->assertDontSee('images/press-release-placeholders/', false);
|
->assertDontSee('images/press-release-placeholders/', false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('release detail shows the ad slot self-promotion inline', function () {
|
||||||
|
/** @var TestCase $this */
|
||||||
|
// Werbeplatz-Hinweis (Eigenwerbung): Klick öffnet das Kontakt-Widget
|
||||||
|
// mit vorgewähltem Thema „Werbeanfrage".
|
||||||
|
[$category, $company] = detailCategoryAndCompany(Portal::Businessportal24);
|
||||||
|
|
||||||
|
$release = PressRelease::factory()
|
||||||
|
->published()
|
||||||
|
->forPortal(Portal::Businessportal24)
|
||||||
|
->for($category)
|
||||||
|
->for($company)
|
||||||
|
->create(['slug' => 'meldung-mit-werbeplatz', 'published_at' => now()->subDay()]);
|
||||||
|
|
||||||
|
$this->get('https://businessportal24.test/de/press-release/'.$release->slug)
|
||||||
|
->assertSuccessful()
|
||||||
|
->assertSeeText('Hier könnte Ihre Werbung stehen')
|
||||||
|
->assertSeeText('Jetzt Werbeplatz anfragen')
|
||||||
|
->assertSee("{ topic: 'advertising' }", false);
|
||||||
|
});
|
||||||
|
|
||||||
test('release detail shows prominent report links below the article', function () {
|
test('release detail shows prominent report links below the article', function () {
|
||||||
/** @var TestCase $this */
|
/** @var TestCase $this */
|
||||||
// T13 (Meldeverfahren): Die Melde-Links stehen sichtbar unter jeder
|
// T13 (Meldeverfahren): Die Melde-Links stehen sichtbar unter jeder
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue