presseportale/resources/views/components/press-release-submit-modal.blade.php
Kevin Adametz 4dffb1368f press-releases: Mail "wird persoenlich geprueft" beim Block, Blade-Guard fuer @php-Bloecke (Roadmap 1d, Nachtrag)
Der Autor bekommt beim Block nach dem letzten Rot (oder Gelb mit
eingeschalteter Queue) eine kurze Mail statt Stillstand: was passiert ist,
dass nichts zu tun ist, und dass das Ergebnis per Mail kommt. Laeuft ueber
notifyAuthor() wie Veroeffentlichung und Ablehnung (API-Regel, Sperrliste,
adressbezogene Abmeldung, Bounce-Referenz); Opt-out am Typ "abgelehnt".
Neue ConversionSource mail_review fuer den Link ins Konto.

BladePhpBlockGuardTest wendet das Compiler-Muster auf alle Views an und
faengt inline @php( vor einem spaeteren @endphp ab — fand den eigenen
Erklaer-Kommentar im Modal, der nur zufaellig funktionierte. Regel in
CLAUDE.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 15:05:47 +00:00

232 lines
14 KiB
PHP

@props([
'name' => 'confirm-submit-review',
'action',
'bookingAction' => null,
'confirmLabel' => null,
'quotaTotal' => null,
'quotaRemaining' => null,
'hasTitleImage' => true,
])
{{--
Wiederverwendbares Einreichungs-Modal für Pressemitteilungen.
Wird in Detailansicht, Bearbeiten und Erstellen eingebunden. Der
`action`-Prop bestimmt die Livewire-Methode der Eltern-Komponente, die beim
Bestätigen ausgeführt wird (z. B. `submitForReview`, `saveAndSubmit`,
`save('review')`). Quota-Block wird nur angezeigt, wenn Werte übergeben
sind — null bedeutet unbegrenztes Kontingent (Bestandsschutz bzw.
Launch-Schalter aus) und blendet den Block aus.
Submit-Gate (Decision-Update §5.1): Ohne aktive Buchung zeigt das Modal
statt des Prüf-Flows einen Buchungs-Hinweis — der Button konvertiert,
er verschwindet nicht. Serverseitig sichert submitForReview() das Gate ab.
`booking-action` (optional): Livewire-Methode, die den Entwurf zuerst
speichert und dann zur Buchungsseite leitet. ZWINGEND in Erstellen/Bearbeiten
setzen — sonst ginge der erfasste Inhalt beim Sprung verloren. Fehlt sie
(z. B. Detailansicht, dort ist bereits gespeichert), wird direkt verlinkt.
`has-title-image`: Ohne eigenes Titelbild (nur Platzhalter) entfällt die
Bildrechte-Bestätigung — es gibt nichts zu bestätigen. Default true, damit
die Checkbox im Zweifel lieber einmal zu viel erscheint.
--}}
{{-- Blockform, nicht Inline-Form: die Inline-Variante würde bis zum Ende des nächsten Blocks als Rohblock gelesen (BladeCompiler::storePhpBlocks, Guard: BladePhpBlockGuardTest). --}}
@php
$bookingRequired = ! (auth()->user()?->hasActiveBooking() ?? true);
@endphp
<flux:modal :name="$name" class="w-full max-w-xl">
@if ($bookingRequired)
<div class="space-y-5">
<div>
<flux:text class="eyebrow muted">{{ __('Veröffentlichung') }}</flux:text>
<flux:heading size="lg">{{ __('Buchung erforderlich') }}</flux:heading>
</div>
<div class="rounded-[6px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-4 text-[12.5px] leading-[1.6] text-[color:var(--color-ink-2)]">
<p class="m-0 mb-2">
{{ __('Zum Einreichen einer Pressemitteilung wird eine aktive Buchung benötigt. Ihr Entwurf wird dabei gespeichert und bleibt jederzeit bearbeitbar.') }}
</p>
<p class="m-0 text-[11.5px] text-[color:var(--color-ink-3)]">
{{ __('Nach der Buchung reichen Sie die Pressemitteilung mit einem Klick zur Prüfung ein.') }}
</p>
</div>
<div class="flex justify-end gap-2">
<flux:modal.close>
<flux:button variant="filled">{{ __('Später') }}</flux:button>
</flux:modal.close>
@if ($bookingAction)
{{-- Entwurf zuerst speichern, dann zur Buchung — kein Datenverlust. --}}
<flux:button variant="primary" icon="credit-card"
wire:click="{{ $bookingAction }}" wire:loading.attr="disabled">
{{ __('Speichern & Buchung auswählen') }}
</flux:button>
@else
<flux:button variant="primary" :href="route('me.bookings.index')" wire:navigate>
{{ __('Buchung auswählen') }}
</flux:button>
@endif
</div>
</div>
@else
{{-- Ohne eigenes Titelbild startet `images` auf true: die Checkbox ist
ausgeblendet und darf den Bestätigen-Button nicht blockieren. --}}
<div class="space-y-5" x-data="{ agb: false, images: {{ $hasTitleImage ? 'false' : 'true' }}, contact: false, waiver: false, ai: false }">
<div>
<flux:text class="eyebrow muted">{{ __('Veröffentlichung') }}</flux:text>
<flux:heading size="lg">{{ __('Pressemitteilung zur Prüfung freigeben') }}</flux:heading>
</div>
{{-- E3 (16.09.2026): unmissverständlich, dass die Freigabe nicht
„online" heißt — erst die Prüfung, dann die Veröffentlichung. --}}
@php
$maxAttempts = max(1, (int) config('scoring.classification.max_automatic_attempts', 3));
@endphp
<article class="panel overflow-hidden" style="border-color:var(--color-hub); border-left-width:3px;">
<div class="p-4 text-[12.5px] leading-[1.6] text-[color:var(--color-ink-2)]">
<p class="m-0 mb-2 font-semibold text-[color:var(--color-ink)]">{{ __('Was nach der Freigabe passiert') }}</p>
<ol class="m-0 list-decimal space-y-1 ps-5">
<li>{{ __('Der Text geht zur automatischen Inhaltsprüfung — er ist damit noch nicht online.') }}</li>
<li>{{ __('Grün oder Gelb: die Mitteilung wird veröffentlicht. Rot: sie kommt mit Begründung zurück, Sie können nachbessern und erneut freigeben.') }}</li>
<li>{{ __('Nach :max Prüfungen mit Rot sieht sich die Redaktion den Text an und entscheidet.', ['max' => $maxAttempts]) }}</li>
</ol>
</div>
</article>
{{-- Rechtliche Hinweise (Platzhalter — vor Go-Live anwaltlich prüfen) --}}
<article class="panel overflow-hidden">
<div class="panel-head" style="padding:10px 16px;">
<span class="section-eyebrow">{{ __('Rechtliche Hinweise') }}</span>
</div>
<div class="p-4 text-[12.5px] leading-[1.6] text-[color:var(--color-ink-2)]">
<p class="m-0 mb-2 font-semibold text-[color:var(--color-ink)]">{{ __('Mit der Freigabe versichern Sie:') }}</p>
<ul class="m-0 list-disc space-y-1 ps-5">
<li>{{ __('Sie sind befugt, den Inhalt zu veröffentlichen.') }}</li>
<li>{{ __('Alle verwendeten Bilder, Logos und Zitate liegen in Ihrer Nutzungsbefugnis.') }}</li>
<li>{{ __('Personenbezogene Daten sind nur im zwingend erforderlichen Umfang enthalten.') }}</li>
<li>{{ __('Aussagen entsprechen Ihrem Wissensstand und sind sachlich richtig.') }}</li>
</ul>
<p class="m-0 mt-3 pt-3 border-t border-[color:var(--color-bg-rule)] text-[11.5px] text-[color:var(--color-ink-3)]">
{{ __('Sie stellen die Plattform von Ansprüchen Dritter frei, die aus einer unberechtigten Nutzung von Inhalten resultieren. Die endgültige Veröffentlichung erfolgt nach Abschluss der Inhaltsprüfung.') }}
</p>
</div>
</article>
{{-- Kontingent (optional) --}}
@if (! is_null($quotaRemaining) && ! is_null($quotaTotal))
<article class="panel overflow-hidden">
<div class="flex items-center justify-between gap-4 px-4 py-3">
<div class="min-w-0 text-[12.5px] text-[color:var(--color-ink-2)]">
<div class="font-semibold text-[color:var(--color-ink)]">{{ __('PM-Kontingent diesen Monat') }}</div>
<div class="text-[color:var(--color-ink-3)]">{{ __('Wird erst bei Veröffentlichung verbraucht — abgelehnte Pressemitteilungen kosten keinen Slot.') }}</div>
</div>
{{-- shrink-0 + nowrap: der Flex-Container darf den Badge
nicht zusammendrücken (QA-Befund 07.07., Umbruch „3 /|3"). --}}
<span @class(['badge', 'dot', 'shrink-0', 'whitespace-nowrap', $quotaRemaining > 0 ? 'ok' : 'warn'])>
{{ $quotaRemaining }} / {{ $quotaTotal }}
</span>
</div>
</article>
@endif
{{-- KI-Kennzeichnung (Art. 50 KI-VO, Decision-Update 08.07.2026):
Pflicht-Selbstdeklaration in jedem Veröffentlichungsprozess.
Die Eltern-Komponente stellt die Properties über den Trait
App\Livewire\Concerns\InteractsWithAiDeclaration bereit. --}}
<article class="panel overflow-hidden">
<div class="panel-head" style="padding:10px 16px;">
<span class="section-eyebrow">
{{ __('KI-Kennzeichnung') }}
<span class="text-[color:var(--color-err)]">*</span>
</span>
</div>
<div class="p-4 space-y-3">
<p class="m-0 text-[12.5px] leading-[1.6] text-[color:var(--color-ink-2)]">
{{ __('Wie wurde der Text dieser Pressemitteilung erstellt? Diese Angabe ist gesetzlich verpflichtend (Art. 50 KI-VO).') }}
</p>
{{-- Zwei Gruppen (Entscheidung 10.07.2026): Option 1 = kein
Label, Option 2 = Hinweis „KI-generiert" am Pressetext. --}}
<flux:radio.group wire:model="aiTextDeclaration">
@foreach (\App\Enums\AiTextDeclaration::formCases() as $aiOption)
<flux:radio :value="$aiOption->value" :label="$aiOption->label()" />
@endforeach
</flux:radio.group>
<flux:error name="aiTextDeclaration" />
<p class="m-0 pt-2 border-t border-[color:var(--color-bg-rule)] text-[11.5px] leading-[1.5] text-[color:var(--color-ink-3)]">
{{ __('Bei der ersten Option erscheint kein Label an der Veröffentlichung. Bei der zweiten Option wird der Pressetext mit dem Hinweis „KI-generiert" in Ihrer Stimme gekennzeichnet („Nach Angaben des einreichenden Unternehmens …"). KI-generierte oder KI-bearbeitete Bilder kennzeichnen Sie direkt beim Bild-Upload.') }}
</p>
</div>
</article>
{{-- Bestätigungen — Flux-Switches (Einheitlichkeit, 12.06.2026);
der Alpine-State steuert weiterhin den Bestätigen-Button. --}}
<article class="panel overflow-hidden">
<div class="panel-head" style="padding:10px 16px;">
<span class="section-eyebrow">{{ __('Ihre Bestätigungen') }}</span>
<span class="shrink-0 whitespace-nowrap text-[11.5px] tabular-nums text-[color:var(--color-ink-3)]"
x-text="[agb, {{ $hasTitleImage ? 'images, ' : '' }}contact, waiver, ai].filter(Boolean).length + ' / {{ $hasTitleImage ? 5 : 4 }}'"></span>
</div>
<div class="divide-y divide-[color:var(--color-bg-rule)]">
<div class="px-4 py-3">
<flux:switch
align="left"
x-on:change="agb = $event.target.checked"
:label="__('Der Inhalt entspricht den AGB und gesetzlichen Vorgaben.')"
/>
</div>
@if ($hasTitleImage)
<div class="px-4 py-3">
<flux:switch
align="left"
x-on:change="images = $event.target.checked"
:label="__('Alle Bildrechte sind geklärt.')"
/>
</div>
@endif
<div class="px-4 py-3">
<flux:switch
align="left"
x-on:change="contact = $event.target.checked"
:label="__('Die Angaben zum Pressekontakt sind korrekt.')"
/>
</div>
{{-- Archiv-Hinweis (AGB § 13 Abs. 1, T2neu): die frühere
Erlöschens-Checkbox entfiel mit der B2B-Umstellung (E2).
Der Bestätigungszeitpunkt wird serverseitig protokolliert
(PressReleaseService). --}}
<div class="px-4 py-3">
<flux:switch
align="left"
x-on:change="waiver = $event.target.checked"
:label="__('Mir ist bekannt, dass veröffentlichte Pressemitteilungen dauerhaft archiviert werden; eine spätere Entfernung ist nur als kostenpflichtige Serviceleistung möglich (gesetzliche Ansprüche bleiben unberührt).')"
/>
</div>
{{-- Verbindliche KI-Bestätigung (Art. 50 KI-VO): wire-gebunden,
weil der Bestätigungszeitpunkt zur Nachweisbarkeit im
Audit-Trail persistiert wird. --}}
<div class="px-4 py-3">
<flux:switch
align="left"
wire:model="aiDeclarationConfirmed"
x-on:change="ai = $event.target.checked"
:label="__('Ich bestätige, dass meine Angaben zum Einsatz generativer KI vollständig und wahrheitsgemäß sind.')"
/>
<flux:error name="aiDeclarationConfirmed" />
</div>
</div>
</article>
<div class="flex justify-end gap-2">
<flux:modal.close>
<flux:button variant="filled">{{ __('Abbrechen') }}</flux:button>
</flux:modal.close>
<flux:button variant="primary" wire:click="{{ $action }}"
wire:loading.attr="disabled"
x-bind:disabled="! (agb && images && contact && waiver && ai)">
{{ $confirmLabel ?? __('Zur Prüfung freigeben') }}
</flux:button>
</div>
</div>
@endif
</flux:modal>