# Phase 8 · User-Panel-Konsolidierung & Pressemitteilungs-Lifecycle Stand: 2026-05-29 — **abgeschlossen** (alle Päckchen 8A–8K umgesetzt). Vorgänger: Phase 7 (Press-Release-Form-Refactor — abgeschlossen) Abgleich-Doku: [`docs/STATUS-ABGLEICH-USER-PANEL.md`](./STATUS-ABGLEICH-USER-PANEL.md) Roadmap-Abschluss: [`dev/frontend/hub-flux/20-PHASE-8-USER-PANEL.md`](../dev/frontend/hub-flux/20-PHASE-8-USER-PANEL.md) > **Status 29.05.2026**: Alle Sub-Päckchen abgeschlossen. Bewusste > Abweichung in 8H: Upload-Control bleibt `flux:input type=file` statt > `flux:file-upload` (Stabilität); die Lizenz-Pflichtfelder sind vollständig > umgesetzt. Der Rechtstext im Veröffentlichungs-Modal (8I) ist ein > Platzhalter und vor Go-Live anwaltlich zu prüfen. --- ## 0. Worum es geht Phase 8 bündelt drei thematisch zusammenhängende Bündel: 1. **User-Panel-Konsolidierung** — Lücken aus Phase 7 schließen, Firmen-Liste auf das Mockup-Niveau heben (`dev/frontend/tailwind_v3/User Firmen presseportale.html`). 2. **Pressemitteilungs-Titelbild & SVG-Platzhalter** — jede PM bekommt ein sichtbares Hero-Bild, entweder eigener Upload oder ein farbiger SVG-Platzhalter aus einem definierten Set. 3. **Veröffentlichungs-Modal mit rechtlichem Hinweis + Quota-Vorbereitung** — Pressemitteilungen werden nur über ein bewusstes Modal eingereicht; in dem Modal steht ein rechtlicher Hinweis und die Information, dass ein PM-Kontingent verbraucht wird. Alle Änderungen, die das User-Panel betreffen, werden **konsistent ins Admin-Panel übertragen**, sobald sie inhaltlich passen (z. B. Show-Pages, Listen-Indikatoren, Bild-Manager-Wrapper). --- ## 1. Sub-Päckchen-Übersicht | ID | Thema | Größe | Risiko | |---|---|---|---| | **8A** | Show-Page-Lücken schließen (subtitle, scheduling, embargo, boilerplate_override) — Customer + Admin | S | gering | | **8B** | Listen-Indikatoren für Scheduling/Embargo — Customer + Admin | S | gering | | **8C** | Pressekontakt-Warnung in Sidebar-Card (Customer + Admin) + Tests | XS | gering | | **8D** | Doku-Pflege: Phase-7-Schlussfeinheiten in `19-PHASE-7-…md` + Konzept-Anpassungen aus Abgleich | S | keine | | **8E** | Firmen-Liste auf Mockup-Niveau (Counter-Strip, Saved-Views, Filter-Chips, Card/List-Toggle, Rollen-Legende) | L | mittel | | **8F** | SVG-Platzhalter-Set extrahieren + auswählbar machen (Customer-Modal) | M | mittel | | **8G** | PressRelease-Titelbild — Schema, Default-Platzhalter, Vorschau im Form | M | mittel | | **8H** | FluxUI `flux:file-upload` im Image-Manager + Pflichtfelder Urheber/Lizenz/Rechte | M | mittel | | **8I** | Veröffentlichungs-Modal mit Rechts-Hinweisen + Quota-Anzeige (Customer) + Hook | M | mittel | | **8J** | Quota-Stub: Demo-Counter im Datenmodell + Decrement-Hook im PressReleaseService | M | hoch (Datenmodell) | | **8K** | Tests + Pint + Build + Roadmap-Update + Abschluss-Eintrag in `PROGRESS.md` | S | gering | **Abkürzungen**: XS = < 1 h, S = 1–3 h, M = 3–8 h, L = 8–16 h. Reihenfolge entspricht dem geplanten Ablauf. Nach jedem Päckchen ist ein Review-Stopp mit dem User vorgesehen, bevor das nächste startet. --- ## 2. Päckchen im Detail ### 8A · Show-Page-Lücken schließen **Ziel**: Customer-Show und Admin-Show zeigen die Phase-7-Felder. **Anpassungen**: - `resources/views/livewire/customer/press-releases/show.blade.php` - Untertitel direkt unter H1 als kleinere Headline anzeigen - „Geplante Veröffentlichung"-Card mit `scheduled_at`, falls gesetzt - „Embargo bis"-Card mit `embargo_at`, falls gesetzt - „Boilerplate (Override)"-Card, falls `boilerplate_override` befüllt - `resources/views/livewire/admin/press-releases/show.blade.php` - Untertitel - Boilerplate-Override (Scheduling/Embargo sind bereits da) **Tests**: bestehende Show-Tests erweitern um Assertions für die neuen Sichtbarkeiten. **Akzeptanz**: Customer- und Admin-Show stellen exakt dieselben PM-Felder dar, die in den Forms gepflegt werden können (außer Anhänge — deaktiviert). --- ### 8B · Listen-Indikatoren für Scheduling/Embargo **Ziel**: In beiden PM-Listen sieht man auf einen Blick, welche PMs geplant sind oder unter Embargo stehen. **Anpassungen**: - `customer/press-releases/index.blade.php`: - In der Datums-Spalte: zusätzliches Sub-Label „geplant · 21.05. 14:00" bzw. „Embargo bis 21.05." - `admin/press-releases/index.blade.php`: - Analog **UI-Pattern**: Mono-Sub-Zeile unter dem Hauptdatum, wenn `status = review` UND `scheduled_at`/`embargo_at` gesetzt. **Tests**: ein neuer Pest-Test pro Liste, der ein PM mit `scheduled_at`/`embargo_at` erstellt und die Sub-Zeile assertet. --- ### 8C · Pressekontakt-Warnung im Form-Sidebar **Ziel**: Wenn keine Pressekontakt in einer PM gewählt ist, zeigt die Sidebar-Card eine dezente Warn-Box (analog zur „Telefonnummer fehlt"- Warnung), damit klar ist, dass die PM zwar speicherbar ist, aber einen Kontakt empfehlen sollte. **Anpassungen**: - `customer/press-releases/create.blade.php` + `edit.blade.php`: - Im Pressekontakt-Sidebar-Card: ```blade @if (! $contactId)
Es wurde noch kein Pressekontakt ausgewählt. Empfohlen, aber nicht zwingend.
@endif ``` - `admin/press-releases/create.blade.php` + `edit.blade.php`: identisch **Tests**: Form-Render-Test mit ohne-Kontakt-Setup, das den Warn-String assertet. --- ### 8D · Doku-Pflege **Ziel**: `19-PHASE-7-PRESS-RELEASE-FORM.md` und Konzept-Dokumente an den IST-Stand anpassen, damit zukünftige Phasen auf einer sauberen Basis aufsetzen. **Konkret**: - `dev/frontend/hub-flux/19-PHASE-7-PRESS-RELEASE-FORM.md`: - Block ergänzen: „Anhänge deaktiviert; Tests skipped; siehe Security-Review" - Block ergänzen: „Pressekontakt nullable; Warnung im Sidebar" - Block ergänzen: „Sidebar-Reihenfolge: Status → Kategorie → Portal (Pill) → Pressekontakt → Themen-Tags → Veröffentlichung → Weitere Felder → Phase-2-Footer" - `docs/user-admin/Admin-User.md`: Aktualisierungen aus [`STATUS-ABGLEICH-USER-PANEL.md` Abschnitt 6.1](./STATUS-ABGLEICH-USER-PANEL.md#61-sofort-ohne-risiko-machbar). - `docs/user-admin/checkliste-user-backend.md`: neuen Phase-7-Block hinzufügen. **Akzeptanz**: Wer die `docs/`-Hauptdokumente liest, bekommt einen zutreffenden Eindruck vom IST-Stand. --- ### 8E · Firmen-Liste auf Mockup-Niveau **Status**: ✅ abgeschlossen (21.05.2026) **Was umgesetzt wurde** - `resources/views/livewire/customer/press-kits/index.blade.php` komplett überarbeitet (Volt + Hub-Tokens). - Neue Hub-Tokens in `resources/css/shared/hub-components.css`: `.firm-card`, `.add-tile`, `.seg-toggle`, `.role-pill`, `.mini-logo`, `.lg-*`-Logo-Varianten, `.menu-trigger`, `.card-action`, `.page-btn`. - Counter-Strip mit `Firmen · aktiv · PMs gesamt · Pressekontakte hinterlegt`. - Saved-View-Tabs `Alle/Aktiv/In Anlage/Inaktiv/Mit mir geteilt`, mit Live-Counts. „In Anlage" ist bewusst noch leer (Phase-2-Heuristik). - Filter-Chips (Portal, Rolle) via FluxUI-Dropdown + URL-Sync (`?view=…&portal=…&role=…&mode=…`). - View-Toggle Karten / Liste, persistiert in der URL als `?mode=list`. - Karten- und Listen-Ansicht mit Hub-Look, deterministische Logo-Varianten, Status-Badge, Portal-Pills, Rolle-Pill, KPIs (PMs / Kontakte / letzte PM), Aktionen „Firma öffnen" und „Neue PM". - Add-Tile auf der letzten Seite (CTA: `Firma anlegen anfragen` → Profil). - Empty-States: 3-Schritt-Onboarding (keine Firmen) und Reset-CTA (Filter ohne Treffer). - Rollen-Legende als `panel-warm` mit Owner / Verantwortlich / Mitglied. - `tests/Feature/CustomerPressKitIndexPhase8eTest.php` mit 14 Tests (Counter, Saved-Views, Filter, View-Mode, Empty-States, Add-Tile, Rollen-Legende). **Ziel**: `customer/press-kits/index.blade.php` entspricht dem Mockup `dev/frontend/tailwind_v3/User Firmen presseportale.html`. **Mockup-Komponenten** (alle CSS-Klassen aus `resources/css/shared/hub-components.css` bereits vorhanden oder leicht ergänzbar): - **Counter-Strip** (`.counter-strip`): `X Firmen · X aktiv · X PMs gesamt · X Pressekontakte` - **Saved-View-Tabs** (`.view-tabs`): `Alle / Aktiv / In Anlage / Inaktiv / Mit mir geteilt` - „In Anlage" = neue Firma, die noch keine Stammdaten hat (heuristisch: kein Logo + keine PMs) - „Mit mir geteilt" = `company_user.role IN (member, responsible)` ohne `owner_user_id = me` - **Filter-Chips** (`.filter-chip`): - Status (Aktiv/Inaktiv/Alle) - Portal (presseecho/businessportal24/Alle) - Rolle (Admin/Redakteur/Beobachter/Alle) — „Admin"-Begriff für Owner, „Redakteur" für `responsible`, „Beobachter" für `member` - Branche (Auswahl aus vorhandenen `Industry`-Werten) - **Seg-Toggle** (`.seg-toggle`): Karten- vs. Listen-Ansicht (Default Karten) - **Karten** (`.firm-card`): - Logo (Hub-Token-Box oder echtes Logo) - Status-Badge - Portal-Pills (eine oder zwei, je nach Firma-Portal) - Rolle-Pill (`.role-pill.admin` für Owner) - KPIs: PMs, Pressekontakte, Datum letzte PM - Aktionen: „Bearbeiten" (zur Firma) + „Neue PM" (Editor mit Firma vorgewählt) - `is-self`-Highlight für die aktive Firma aus dem Context-Switcher - **Add-Tile**: „Neue Firma anlegen" — derzeit nur Anfrage-Link auf Profil (Self-Service-Anlage ist Phase-2-Thema) - **Empty-States**: - Keine Firma: 3-Schritt-Onboarding (Stammdaten → Boilerplate → Pressekontakte) — derzeit nur Anfrage-CTA - Filter ohne Treffer: Reset-CTA - **Rollen-Legende** am Ende als `panel-warm` **Volt-Anpassungen**: - Computed Properties: `statusCounts`, `portalOptions`, `roleOptions`, `industryOptions` - Neue Properties: `$statusFilter`, `$portalFilter`, `$roleFilter`, `$industryFilter`, `$viewMode` (cards|list) - Methoden: `setView($status)`, `resetFilters()`, `toggleViewMode()` **Was bewusst NICHT in 8E kommt**: - Echte Self-Service-Firma-Anlage (Phase 2) - Statistik-Tab in Firmen-Detail (Phase 2) - Abrechnung pro Firma (Phase 2) **Tests**: - Komponente rendert mit 0/1/N Firmen - Filter-Kombinationen - View-Mode-Toggle - Counter-Strip-Zahlen stimmen mit den Filtern **Admin-Spillover**: Die `admin/companies/index.blade.php` hat einen ähnlichen, älteren Mockup-Stand. Wenn Zeit übrig: Counter-Strip und Saved-Views auch dort einbauen (separater Patch im selben Päckchen, nur wenn ohne Mehraufwand machbar — sonst Phase 9). --- ### 8F · SVG-Platzhalter-Set **Ziel**: Ein wiederverwendbares Set von Hero-Platzhaltern für PMs ohne echtes Titelbild. **Quelle**: Bestehende inline SVGs in den Landing-Page-Komponenten: - `resources/views/components/web/focus-hero.blade.php` (760×500, Punkt-Pattern + Kreise) - `resources/views/components/web/feed-top-item.blade.php` (240×160, Linien + Punkte) - ggf. weitere aus `industry-spotlight`, `quality-summary`, `live-ticker` **Neue Struktur**: ``` resources/views/components/portal/press-release-placeholder.blade.php public/images/press-release-placeholders/ 01-grid-blue.svg 02-grid-green.svg 03-grid-amber.svg 04-lines-blue.svg 05-lines-green.svg 06-lines-amber.svg 07-dots-blue.svg 08-dots-green.svg 09-dots-amber.svg ``` Jede SVG ist 1600×900 (Hero-Aspect-Ratio 16:9), entspricht dem Bildformat des `large`-Variant aus `ImageService`. **Komponente**: ```blade ``` **Modal-Auswahl** (Volt-Sub-Komponente): ``` resources/views/livewire/components/press-release-placeholder-picker.blade.php ``` - Grid 3×3 mit Vorschau aller Varianten - Wire-Event `placeholderSelected($variant)` → Parent setzt `placeholder_variant` und schließt das Modal **Tests**: Komponente rendert mit jeder Variante; Picker emittiert korrektes Event. --- ### 8G · Titelbild-Schema & Default-Logik **Ziel**: Jede PM hat **immer** ein Hero-Bild — entweder echtes Bild oder Platzhalter. **Schema-Änderung**: Migration: `add_placeholder_variant_to_press_releases.php` ```php $table->string('placeholder_variant', 32)->nullable()->after('boilerplate_override'); ``` **Default-Logik**: - `PressRelease::booted` → bei `creating`: wenn `placeholder_variant` leer, würfle eine Variante aus dem Set - alternativ in `customer/press-releases/create.blade.php`: nach `mount()` Default setzen **Cover-Image-Resolver** (Service): ``` app/Services/PressRelease/PressReleaseCoverImage.php ``` Public-Methoden: - `coverUrl(PressRelease $pr, string $variant = 'large'): string` - wenn echtes Preview-Bild da → `variantUrl($variant)` - sonst → `asset('images/press-release-placeholders/'.$pr->placeholder_variant.'.svg')` - `coverIsPlaceholder(PressRelease $pr): bool` **Verwendung**: - Hero in Customer-Show, Admin-Show, Public-Detail-Page - Thumb in beiden Listen - Vorschau im Form **Tests**: Resolver-Unit-Test für beide Fälle. --- ### 8H · FluxUI File-Upload + Lizenzfelder **Ziel**: Image-Manager nutzt `flux:file-upload`, erfasst die rechtlich nötigen Felder, eckende UI passt zum Mockup. **Anpassungen** in `resources/views/livewire/components/press-release-images-manager.blade.php`: - `` (Dropzone-Style aus FluxUI) - Zusätzliche Felder als FluxUI-Inputs: - **Urheber/Fotograf** (`flux:input` required) - **Lizenztyp** (`flux:select` mit Enum-Werten): - Eigene Aufnahme - CC-Lizenz (Lizenz-URL Pflicht) - Kommerzielle Lizenz erworben (Lizenz-URL Pflicht) - Einwilligung des Urhebers - Sonstiges - **Lizenz-URL** (`flux:input` conditional required) - **Personen-Einwilligung** (`flux:checkbox` optional) - **Rechte-Bestätigung** (`flux:checkbox` required, mit AGB-Text aus `Presseportal – Konzept für Relaunch.md` Abschnitt 2) **Schema**: Migration: `add_license_fields_to_press_release_images.php` ```php $table->string('author')->nullable(); $table->string('license_type', 32)->nullable(); $table->string('license_url')->nullable(); $table->boolean('persons_consent')->default(false); $table->timestamp('rights_confirmed_at')->nullable(); ``` Enum: `App\Enums\ImageLicenseType` (PHP-Enum mit Labels). **Tests**: - Upload ohne Urheber → Validierungs-Fehler - Upload mit `license_type = cc` ohne `license_url` → Fehler - Upload mit allen Pflichtfeldern → erfolgreich, `rights_confirmed_at` gesetzt **UI-Skizze** (Form-Reihenfolge): ``` ┌─────────────────────────────────────┐ │ [flux:file-upload Dropzone] │ ├─────────────────────────────────────┤ │ Urheber: [input] * │ │ Lizenztyp: [select] * │ │ Lizenz-URL: [input] (*) │ │ [ ] Personen-Einwilligung │ │ [ ] Ich bestätige die Rechte * │ │ │ │ [Hochladen] │ └─────────────────────────────────────┘ ``` --- ### 8I · Veröffentlichungs-Modal **Ziel**: Customer kann eine PM nur über ein explizites Modal mit rechtlichem Hinweis und Quota-Information einreichen. **Anpassungen** in `customer/press-releases/edit.blade.php`: - „Zur Prüfung einreichen"-Button löst Modal-Open aus statt direkt `submitForReview` zu rufen - Modal-Inhalt: - Eyebrow „Veröffentlichung" - H3 „Pressemitteilung zur Prüfung einreichen" - Block „Rechtliche Hinweise" (aus Konzept-Abschnitt 5 zur DSGVO-Position + Abschnitt 2 zu Bildrechten, plus AGB-Verweis) - Block „Kontingent" — Anzeige `Ihre verbrauchten PMs in diesem Monat: X / Y` - Block „Bestätigungen": - [ ] Inhalt entspricht den AGB - [ ] Bildrechte sind geklärt - [ ] Pressekontakt-Daten korrekt - Footer: „Abbrechen" (sekundär) + „Veröffentlichung anfordern" (primär, disabled bis alle 3 Checkboxen gesetzt) - Confirm-Button ruft `submitForReview()` (existiert bereits) **Schreibweise** (juristisch sicherer Ankertext): ``` Mit dem Einreichen dieser Pressemitteilung versichern Sie: - Sie sind befugt, den Inhalt zu veröffentlichen. - Alle verwendeten Bilder, Logos und Zitate liegen in Ihrer Nutzungsbefugnis. - Personenbezogene Daten sind nur in dem für die Berichterstattung zwingend erforderlichen Umfang enthalten. - Aussagen entsprechen Ihrem Wissensstand und sind sachlich richtig. Sie stellen [Plattform] von Ansprüchen Dritter frei, die aus einer unberechtigten Nutzung von Inhalten resultieren. Die endgültige Veröffentlichung erfolgt nach redaktioneller Prüfung. ``` (Exakte Formulierung muss vor Go-Live durch einen Anwalt geprüft werden — für die Bauphase reicht der Platzhalter.) **Quota-Anzeige**: In Phase 8I noch ohne echte Tarif-Logik: Anzeige eines **Demo-Counters**, der aus einer einfachen Aggregation (`User::pressReleasesPublishedThisMonth()`) kommt — und einer hartcodierten Obergrenze (z. B. 3 als Starter-Default). Das echte Tarif-System kommt in einer späteren Phase. **Tests**: - Modal öffnet sich bei Klick - Submit-Button bleibt disabled bis alle 3 Checkboxen gesetzt - Nach Bestätigung: PM-Status ist `review` - Toast-Bestätigung wird angezeigt **Admin-Spillover**: Im Admin-Editor reicht der Admin direkt ein (`publish`), kein Modal nötig — der Hinweis ist auf den Customer-Flow zugeschnitten. --- ### 8J · Quota-Stub **Ziel**: Die UI-Anzeige in 8I hängt am echten Datenmodell, auch wenn das vollständige Tarif-/Credit-System erst später kommt. **Datenmodell** (minimal): Migration: `create_user_quota_table.php` oder als JSON in `profiles`? > **Entscheidung**: Wir machen es als Migration-light auf `users`: > > ```php > $table->unsignedInteger('press_release_quota')->default(3)->after('settings'); > $table->unsignedInteger('press_release_quota_used_this_month')->default(0)->after('press_release_quota'); > ``` > > Diese Spalten sind temporär, das echte Tarif-Modell überschreibt sie > oder ersetzt sie durch eigene Tabellen. **Service** in `PressReleaseService::submitForReview`: ```php $user->increment('press_release_quota_used_this_month'); ``` (Reset des Counters per Scheduled-Command monatlich → eigener kleiner Befehl `ResetMonthlyPressReleaseQuota`.) **API für die View** (z. B. via `User`-Method): ```php public function pressReleaseQuotaRemaining(): int { return max(0, $this->press_release_quota - $this->press_release_quota_used_this_month); } ``` **Akzeptanz**: Veröffentlichungs-Modal zeigt sinnvolle Zahlen, der Counter erhöht sich nach Submit, der Scheduler-Command resettet ihn zum 1. des Monats. Tarif-Anbindung folgt später. **Tests**: - Counter inkrementiert bei `submitForReview` - Counter resettet via Scheduled-Command (Unit-Test) --- ### 8K · Tests, Pint, Build, Roadmap **Ziel**: Saubere Übergabe. - `vendor/bin/sail artisan test --compact` muss durchlaufen (außer pre-existing `ApiDocumentationTest`). - `vendor/bin/sail bin pint --dirty --format agent` clean. - `vendor/bin/sail npm run build:portal` clean. - `dev/frontend/hub-flux/20-PHASE-8-USER-PANEL.md` als neue Roadmap-Doku (analog zu `19-…`). - Eintrag in `dev/frontend/hub-flux/PROGRESS.md` mit allen Sub-Päckchen. - `docs/user-admin/checkliste-user-backend.md` um Phase-8-Block ergänzen. --- ## 3. Was außerhalb von Phase 8 bleibt Bewusst nicht in Phase 8: - **Magic-Link-Flow für Pressekontakte** → Phase 9 oder Phase 2 lt. Konzept - **Statistik-Tab in Firmen-Detail** → Phase 9 - **Self-Service-Firmen-Anlage** → Phase 9 - **Notice-and-Action für externe Meldungen** → Phase 2/3 - **KI-Vorprüfung** → Phase 2/3 - **Korrektur-/Update-Hinweis-System** → Phase 2/3 - **Echtes Tarif-/Credit-System mit Stripe** → eigene Phase - **Trust-Score / Score-System** → Phase 3 - **Anhänge-Reaktivierung** → eigener Sicherheits-Audit-Track --- ## 4. Reihenfolge & Review-Punkte Vorgeschlagene Reihenfolge der Päckchen: ``` 8D (Doku zuerst — bewegt sich nichts am Code) → 8A (Show-Page-Lücken) → 8B (Listen-Indikatoren) → 8C (Pressekontakt-Warnung) → Review-Stopp mit User → 8E (Firmen-Liste auf Mockup) — größtes Päckchen, Review davor → Review-Stopp → 8F (SVG-Platzhalter extrahieren) → 8G (Titelbild-Schema + Default) → 8H (FluxUI File-Upload + Lizenzfelder) → Review-Stopp → 8J (Quota-Stub im Datenmodell zuerst, damit 8I darauf aufsetzen kann) → 8I (Veröffentlichungs-Modal) → 8K (Abschluss) ``` **Begründung**: Doku zuerst, weil der Abgleich sonst veraltet. Dann kleine UX-Lücken (8A–8C), die schnelle Wins sind. Anschließend die größere Firmen-Liste (8E), die ein eigenes Päckchen ist. Bild- und Veröffentlichungs-Block am Ende, weil sie thematisch zusammengehören und Schema-Änderungen mitbringen. --- ## 5. Risiken & Annahmen - **Annahme**: FluxUI `flux:file-upload` ist in der aktuellen Version voll funktional und kompatibel mit `WithFileUploads` von Livewire. Fallback: bestehender Standard-Upload bleibt erhalten. - **Annahme**: SVG-Platzhalter (1600×900) sind klein genug, dass wir sie direkt aus `public/images/...` ausliefern — kein CDN-Setup nötig. - **Risiko**: Schema-Änderungen in 8G + 8H + 8J berühren produktive Tabellen (`press_releases`, `press_release_images`, `users`). Alle Migrations sind additive (nullable + default), Rollback-fähig. - **Risiko**: Quota-Stub in 8J wird vom echten Tarif-System abgelöst — Code-Schnittstelle (`pressReleaseQuotaRemaining()`) muss stabil bleiben, damit das Veröffentlichungs-Modal nicht neu gebaut werden muss. - **Risiko**: Rechtstext im Veröffentlichungs-Modal ist Platzhalter. Vor Go-Live durch Anwalt zu prüfen. --- ## 6. Akzeptanzkriterien Phase 8 gesamt - [x] Customer-Show + Admin-Show stellen alle Phase-7-Felder dar - [x] PM-Listen markieren Scheduling und Embargo - [x] Pressekontakt-Sidebar warnt bei leerer Auswahl - [x] `docs/user-admin/*` ist mit dem Code synchron - [x] Firmen-Liste entspricht dem Mockup zu ≥ 90 % - [x] Jede PM hat ein sichtbares Hero-Bild (echtes oder Platzhalter) - [x] Image-Upload erfasst Urheber + Lizenz-Typ + Rechte-Bestätigung - [x] „Zur Prüfung einreichen" erfordert eine bewusste Modal-Bestätigung - [x] Quota-Counter inkrementiert pro Einreichung, resettet monatlich - [x] Tests grün (375 passed, 4 skipped — inkl. pre-existing `ApiDocumentationTest`) - [x] Pint clean, Build clean - [x] Roadmap-Eintrag und `PROGRESS.md`-Block geschrieben --- ## 7. Nächster Schritt Mit **8D (Doku)** starten, weil das ohne Code-Änderungen funktioniert und den Boden für die folgenden Päckchen ebnet. Direkt im Anschluss **8A–8C** als Block, weil sie zusammen die Phase-7-Lücken schließen. Danach Review-Stopp für Phase 8E (Firmen-Liste) — das ist das sichtbarste Päckchen für den User und sollte mit klarem Mockup-Vergleich abgenommen werden.