Titelbild: Bildnachweis als Pflichtfeld, Lizenzdetail-Reset bei Typwechsel

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-06-12 15:44:52 +00:00
parent a7c30d4ecc
commit 6e0b2b1814
3 changed files with 371 additions and 150 deletions

View file

@ -0,0 +1,159 @@
**Version:** Juni 2026 **Datum:** 11.06.2026 **Status:** Abgestimmt bereit zur Integration ins Konzept-/Decision-Log **Scope:** Definition von Boost und Veröffentlichungsnachweis (Launch), des Magic-Link-Zugangs- und Änderungsprozesses sowie der Phase-2-Funktionen (Vorab-Prüfung/Redigieren, Prüfzähler, höheres Prüfkontingent, kostenpflichtige Änderungspfade).
---
## 1. Kontext
Dieses Update definiert die credit-nahen Funktionen und den Änderungsprozess für bestehende Pressemitteilungen. Leitlinie: kleiner, ehrlicher Launch-Umfang; alles, was an die volle Credit-/Prüf-Mechanik gekoppelt ist, wandert kontrolliert in Phase 2. Gesetzlich verpflichtende Pfade bleiben unabhängig davon ab Launch erreichbar.
---
## 2. Launch-Funktionen
### 2.1 Boost (Platzierung)
- **Was:** bezahlte Hervorhebung einer PM Platzierung auf Startseite und Branchen-/Kategorieseite
- **Gate:** nur **grüne** PMs sind boostbar; gelb/rot nicht
- **Mechanik:** nachträglich kaufbar, sobald die PM live und grün ist
- **Dauer:** Featured-Zeitraum in Stufen (z. B. 7 / 14 / 30 Tage), Preis gestaffelt
- **Bezahlung:** über die Credit-Wallet (kleine Wallet ist Launch-Bestandteil)
### 2.2 Veröffentlichungsnachweis / PDF
- **Was:** generiertes PDF „PM XY wurde am … auf … veröffentlicht" inkl. URL, Datum, Vorschau
- **Zweck:** Reporting an Vorgesetzte/Kunden klassische PR-Mitnahme
- **Bezahlung:** kleiner Credit-Betrag, keine KI nötig
- **Generierung:** on-demand aus vorhandenen PM-Daten
---
## 3. Magic-Link: Zugang & Änderungsprozess
### 3.1 Zugangsmodell eine Verwaltung, zwei Eintrittswege
**Grundsatz:** Die Verwaltung bestehender Pressemitteilungen ist **ein und dieselbe** Funktion im pressekonto. Es gibt nur zwei Wege hinein:
1. **Registrierter Account** → normaler Login → verwaltet seine PMs direkt im pressekonto.
2. **Pressekontakt ohne Account****Magic-Link** → loggt sich zu den PMs ein, die mit seiner hinterlegten E-Mail verknüpft sind, und nimmt dort Änderungen vor.
Der Magic-Link ist also **kein eigenes System**, sondern die Zugangsbrücke für Pressekontakte bzw. Firmen, deren E-Mail-Adressen im System hinterlegt sind, die aber noch nicht direkt registriert sind. Die dahinterliegende Verwaltungsoberfläche und die Änderungspfade sind identisch zum eingeloggten Account.
**Account-Konvertierung:** Aus dem Magic-Link-Zugang heraus kann der Pressekontakt jederzeit „Permanenten Account anlegen" wählen (Passwort vergeben). Danach läuft der Zugang über den regulären Login der Magic-Link wird für ihn überflüssig.
**Authentifizierung (Sicherheit):**
- Auf jeder PM dezenter Link „Sie sind als Pressekontakt hinterlegt? Pressemitteilung verwalten →"
- E-Mail-Eingabe + Captcha; **identische Antwort unabhängig vom Match** (verhindert User-Enumeration)
- Bei Match: Magic-Link-Mail mit 30-Min-Token → authentifizierte Session
- Dashboard listet alle PMs mit dieser E-Mail als Pressekontakt (über mehrere Firmen/Jahre)
### 3.2 Änderungs- & Lösch-Pfade (AG)
Kein wahlloses Ändern/Löschen Friction nach Anliegen. Alle Pfade laufen über dieselbe Verwaltung (Login **oder** Magic-Link).
|Pfad|Anliegen|Kosten|Phase|Public Hint|
|---|---|---|---|---|
|**A**|Tippfehler/Grammatik|kostenfrei|Launch*|nein|
|**B**|Pressekontakt-Daten ändern|kostenfrei|Launch*|nein|
|**C**|Inhaltliche Korrektur (sachlicher Fehler)|kostenpflichtig|**Phase 2**|ja|
|**D**|Update/Ergänzung (neue Information)|kostenpflichtig|**Phase 2**|ja|
|**E**|DSGVO-Anonymisierung|kostenfrei|**Launch (Pflicht)**|nein|
|**F**|Persönlichkeitsrechtsverletzung|kostenfrei|**Launch (Pflicht)**|je nach Outcome|
|**G**|Depublizieren|kostenpflichtig + Bedenkzeit|**Phase 2**|Tombstone|
* A/B sind kostenfrei und credit-unabhängig technisch Launch-fähig, aber kein Muss (siehe offene Punkte).
**Pfad-Details (Kurzfassung):**
- **A Tippfehler:** Inline-Editor mit Diff; KI prüft, ob nur kosmetisch → ja: übernommen ohne Hinweis; nein: Umleitung zu C
- **B Kontaktdaten:** Formular, direkt übernommen, Versionierung im Hintergrund
- **C Korrektur:** Editor + Pflichtfeld „Was war falsch / was ist korrekt?"; KI erlaubt Fakt-Korrektur (Zahl/Datum/Name), blockiert Umschreibung der Aussage; PM erhält Korrektur-Hinweis
- **D Update:** Ergänzung wird unten mit Datum angehängt, Original bleibt unverändert; KI-Check auf Spam/Werbung
- **E DSGVO:** Aufklärung (Art. 85 DSGVO, Medienprivileg → keine Volllöschung, aber kostenfreie Entfernung personenbezogener Daten); Checkbox-Auswahl (Name, Durchwahl, persönliche E-Mail, Freitext); KI-Plausibilitätscheck; sofort umgesetzt
- **F Persönlichkeitsrecht:** Pflichtfelder (betroffene Stelle, Art, Begründung, Belege) → Review-Queue mit KI-Vorklassifikation; Outcomes: Anonymisierung, Anpassung, Tombstone, Ablehnung mit Begründung
- **G Depublizieren:** Aufklärungsseite → Begründungspflicht (KI lenkt „veraltet" → D, „falsch/peinlich" → C) → kostenpflichtige Bestätigung → 2448 h Bedenkzeit mit Widerrufslink → Tombstone (`noindex`, raus aus Listen/Suche, URL bleibt)
### 3.3 Compliance-Minimum zum Launch (E & F)
**E und F können nicht auf Phase 2 warten.** Sobald PMs live sind, besteht ein gesetzliches Recht auf Anonymisierung personenbezogener Daten (E) und auf Meldung von Persönlichkeitsrechtsverletzungen (F). Beide müssen ab Tag 1 erreichbar sein zur Not als einfaches Formular/manueller Prozess statt vollem Wizard. Beide sind **kostenfrei** (eine legitime Rechtsanfrage darf nie hinter einer Gebühr stehen) und damit unabhängig vom Phase-2-Credit-Build.
### 3.4 Missbrauchsschutz, Edge Cases & Standard-Antwort
- **Rate-Limit** auf Magic-Link-Anfragen pro E-Mail/IP; Cooldown nach Depublizierung (Widerruf-Fenster); Audit-Log mit IP/User-Agent je Edit-Aktion
- **Keine valide E-Mail** (alte connektar-PMs): Fallback „Verifikation per Domain-Inhaberschaft / Impressums-Match", manuelle Prüfung
- **E-Mail geändert / Person verlässt Firma:** manuelle Anfrage mit Bestätigung über `info@`/Impressum
- **Massenanträge:** Bulk möglich, Friction (Gebühr, Bedenkzeit) wird **pro PM** angewendet
- **Standard-Antwort auf unautorisierte Lösch-Mails:** „Änderungen an Pressemitteilungen sind ausschließlich über das Verwaltungs-Portal der jeweiligen PM möglich." → beendet das wahllose „löscht das mal eben"-Mailaufkommen ab Launch
### 3.5 Abgrenzung zum öffentlichen „Melden"-Button
||„Melden" (öffentlich)|Änderungs-Flow (autorisiert)|
|---|---|---|
|Wer|jeder Dritte|nur Pressekontakt (Login/Magic-Link)|
|Auth|keine|Login oder Magic-Link|
|Use Case|Urheberrecht, Verleumdung, Spam|eigene PM ändern|
|Outcome|Quarantäne, Prüfung|siehe Pfade AG|
Treffpunkt: Eine über „Melden" angezeigte Persönlichkeitsrechtsverletzung läuft in dieselbe Queue wie Pfad F.
---
## 4. Phase 2 (nachgelagert)
Diese Funktionen entstehen gemeinsam, weil sie alle die Re-Check-/Credit-Mechanik voraussetzen.
### 4.1 Vorab-Prüfung / Redigieren
Erzeugt erst die Situation „prüfen, ohne (noch) zu veröffentlichen" und den Edit→Neu-Prüfen-Loop. Damit werden auch die kostenpflichtigen Pfade C/D technisch real.
### 4.2 Prüfzähler
- **Eigener Zähler**, getrennt von der Credit-Wallet → „Prüfungen inklusive" bleibt sauberes Versprechen
- **Aggregiert pro Account/Monat** gedeckelt (nicht pro PM) → löst Klon-Abuse ohne Klon-Erkennung
- **Prüf-Tageslimit** als Burst-Schutz (~10/Tag)
- **Overflow:** Zähler leer → weitere Prüfungen ziehen 1 Credit/Prüfung aus der Wallet
### 4.3 Höheres Prüfkontingent (Tier-gestaffelt)
|Tier|Freie Prüfungen/Monat|
|---|---|
|Einzel|4|
|Starter|12|
|Business|30|
|Pro|60|
|Agency|120|
Schnitt ~34 Prüfungen pro inkludierter PM; ehrlicher Normalfall (≈2 Prüfungen) stößt nie an. Dies sind die früheren „Bonus-Credit"-Zahlen, umgewidmet zum Prüf-Kontingent.
### 4.4 Kostenpflichtige Magic-Link-Pfade (C / D / G)
Kosten-Anker bei 1 Credit = 1 € (zu bestätigen):
|Pfad|Aktion|Anker|
|---|---|---|
|C|Inhaltliche Korrektur|≈ 8 Credits|
|D|Update/Ergänzung|≈ 4 Credits|
|G|Depublizieren|≈ 1929 Credits + 2448 h Bedenkzeit|
Depublizieren bewusst am teuersten und mit Bedenkzeit, weil irreversibelste Aktion.
---
## 5. Anti-Zombie-Check (dieser Stand)
- ✅ Gesetzliche Anfragen (E/F) immer kostenfrei und ab Launch erreichbar
- ✅ Kosten nur bei echtem Mehraufwand (Korrektur, Update, Depublizierung), nicht bei Pflicht-Rechten
- ✅ Eine Verwaltung, zwei Eintrittswege keine künstliche Trennung registrierter/unregistrierter Nutzer
- ✅ Prüf-Kontingent großzügig genug, dass der Normalfall nie ansteht
- ✅ Depublizierung mit Aufklärung + Bedenkzeit statt Hard-Delete schützt den Kunden vor sich selbst
---
## 6. Offene Punkte
- **A/B im Launch?** Kostenfrei und credit-unabhängig → könnten den Mail-Aufwand sofort senken. Entscheidung: A/B mit in den Launch nehmen oder kompletten Änderungs-Wizard (inkl. A/B) erst in Phase 2, zum Launch nur E/F als Pflicht-Minimum.
- **Kosten-Anker C/D/G** final bestätigen, sobald das Credit-/Prüf-System gebaut wird.
- **Boost-Preisstaffel** (7/14/30 Tage) in Credits festlegen.
- **PDF-Preis** in Credits festlegen.

View file

@ -59,6 +59,17 @@ new class extends Component {
$this->isUploadFormOpen = true;
}
/**
* Beim Wechsel des Lizenztyps das Detail-Feld leeren sonst klebt
* z. B. der zuvor gewählte CC-Wert (cc_by) im Freitextfeld
* „Lizenzdetails / Begründung" von „Sonstiges".
*/
public function updatedNewLicenseType(): void
{
$this->newLicenseDetail = '';
$this->resetErrorBag('newLicenseDetail');
}
public function closeUploadForm(): void
{
$this->resetUploadForm();
@ -92,7 +103,7 @@ new class extends Component {
[
'newImage' => ['required', 'image', 'mimes:jpeg,jpg,png,webp', 'max:' . (int) (ImageService::MAX_PRESS_RELEASE_IMAGE_BYTES / 1024)],
'newTitle' => ['nullable', 'string', 'max:120'],
'newCopyright' => ['nullable', 'string', 'max:255'],
'newCopyright' => ['required', 'string', 'max:255'],
'newAuthor' => ['required', 'string', 'max:255'],
'newLicenseType' => ['required', Rule::enum(ImageLicenseType::class)],
'newLicenseDetail' => [$requiresLicenseDetail ? 'required' : 'nullable', 'string', 'max:120'],
@ -104,6 +115,7 @@ new class extends Component {
'newRightsConfirmed' => ['accepted'],
],
[
'newCopyright.required' => __('Bitte einen öffentlichen Bildnachweis angeben, z. B. Foto: Max Mustermann / Beispiel GmbH.'),
'newAuthor.required' => __('Bitte Urheber, Fotograf oder Rechteinhaber angeben.'),
'newLicenseType.required' => __('Bitte einen Lizenztyp wählen.'),
'newLicenseDetail.required' => __('Bitte die Lizenz genauer angeben.'),
@ -355,17 +367,20 @@ new class extends Component {
</flux:button>
</div>
@else
<form wire:submit="saveImage" class="mt-4 space-y-6 rounded-md border border-zinc-200 p-4 dark:border-zinc-700 sm:p-5">
<form wire:submit="saveImage"
class="mt-4 space-y-6 rounded-md border border-zinc-200 p-4 dark:border-zinc-700 sm:p-5">
<flux:heading size="xs">{{ __('Titelbild hochladen') }}</flux:heading>
{{-- ===== Schritt 1 · Bild auswählen ===== --}}
<section class="space-y-3">
<div class="flex items-center gap-2">
<span class="flex size-5 shrink-0 items-center justify-center rounded-full bg-zinc-100 text-[11px] font-bold text-zinc-600 dark:bg-zinc-700 dark:text-zinc-300">1</span>
<span class="text-[11px] font-semibold uppercase tracking-wider text-zinc-500">{{ __('Bild auswählen') }}</span>
<span
class="flex size-5 shrink-0 items-center justify-center rounded-full bg-zinc-100 text-[11px] font-bold text-zinc-600 dark:bg-zinc-700 dark:text-zinc-300">1</span>
<span
class="text-[11px] font-semibold uppercase tracking-wider text-zinc-500">{{ __('Bild auswählen') }}</span>
</div>
@if (! $newImage)
@if (!$newImage)
<flux:file-upload wire:model="newImage" accept="image/jpeg,image/png,image/webp"
:description="__('JPG/PNG/WebP, max. 16 MB. Wird als Titelbild gespeichert und ersetzt den Platzhalter.')">
<flux:file-upload.dropzone :heading="__('Bild hierher ziehen oder klicken')"
@ -382,7 +397,8 @@ new class extends Component {
<div class="overflow-hidden rounded-md border border-zinc-200 dark:border-zinc-700">
<div class="relative aspect-[16/9] bg-zinc-50 dark:bg-zinc-800">
@if ($this->newImagePreviewUrl())
<img src="{{ $this->newImagePreviewUrl() }}" alt="{{ __('Vorschau des gewählten Titelbilds') }}"
<img src="{{ $this->newImagePreviewUrl() }}"
alt="{{ __('Vorschau des gewählten Titelbilds') }}"
class="absolute inset-0 size-full object-cover" />
@else
<div class="absolute inset-0 flex items-center justify-center">
@ -390,13 +406,16 @@ new class extends Component {
</div>
@endif
</div>
<div class="flex flex-wrap items-center justify-between gap-3 border-t border-zinc-200 px-3 py-2 dark:border-zinc-700">
<div
class="flex flex-wrap items-center justify-between gap-3 border-t border-zinc-200 px-3 py-2 dark:border-zinc-700">
<div class="min-w-0 text-xs text-zinc-500">
<span class="font-medium text-zinc-700 dark:text-zinc-300">{{ $newImage->getClientOriginalName() }}</span>
<span
class="font-medium text-zinc-700 dark:text-zinc-300">{{ $newImage->getClientOriginalName() }}</span>
<span class="mx-1">·</span>
<span>{{ number_format($newImage->getSize() / 1048576, 2, ',', '.') }} MB</span>
</div>
<flux:button size="xs" variant="filled" icon="arrow-path" wire:click="removeNewImage">
<flux:button size="xs" variant="filled" icon="arrow-path"
wire:click="removeNewImage">
{{ __('Anderes Bild wählen') }}
</flux:button>
</div>
@ -408,26 +427,32 @@ new class extends Component {
{{-- ===== Schritt 2 · Öffentliche Bildinfos ===== --}}
<section class="space-y-3 border-t border-zinc-200 pt-5 dark:border-zinc-700">
<div class="flex items-center gap-2">
<span class="flex size-5 shrink-0 items-center justify-center rounded-full bg-zinc-100 text-[11px] font-bold text-zinc-600 dark:bg-zinc-700 dark:text-zinc-300">2</span>
<span class="text-[11px] font-semibold uppercase tracking-wider text-zinc-500">{{ __('Bildinformationen (öffentlich sichtbar)') }}</span>
<span
class="flex size-5 shrink-0 items-center justify-center rounded-full bg-zinc-100 text-[11px] font-bold text-zinc-600 dark:bg-zinc-700 dark:text-zinc-300">2</span>
<span
class="text-[11px] font-semibold uppercase tracking-wider text-zinc-500">{{ __('Bildinformationen (öffentlich sichtbar)') }}</span>
</div>
<div class="grid gap-3 sm:grid-cols-2">
<flux:input wire:model="newTitle" :label="__('Titel / Alt-Text (optional)')" />
<flux:input wire:model="newCopyright" :label="__('Öffentlicher Bildnachweis')"
:description="__('Wird öffentlich angezeigt, z. B. Foto: Max Mustermann / Beispiel GmbH.')" />
<flux:input wire:model="newCopyright" :label="__('Öffentlicher Bildnachweis')" :badge="__('Pflicht')"
:placeholder="__('Wird öffentlich angezeigt, z. B. Foto: Max Mustermann / Beispiel GmbH.')"
required />
</div>
</section>
{{-- ===== Schritt 3 · Herkunft & Lizenz ===== --}}
<section class="space-y-3 border-t border-zinc-200 pt-5 dark:border-zinc-700">
<div class="flex items-center gap-2">
<span class="flex size-5 shrink-0 items-center justify-center rounded-full bg-zinc-100 text-[11px] font-bold text-zinc-600 dark:bg-zinc-700 dark:text-zinc-300">3</span>
<span class="text-[11px] font-semibold uppercase tracking-wider text-zinc-500">{{ __('Herkunft & Lizenz') }}</span>
<span
class="flex size-5 shrink-0 items-center justify-center rounded-full bg-zinc-100 text-[11px] font-bold text-zinc-600 dark:bg-zinc-700 dark:text-zinc-300">3</span>
<span
class="text-[11px] font-semibold uppercase tracking-wider text-zinc-500">{{ __('Herkunft & Lizenz') }}</span>
</div>
<div class="grid gap-3 sm:grid-cols-2">
<flux:input wire:model="newAuthor" :label="__('Urheber / Fotograf / Rechteinhaber')" :badge="__('Pflicht')"
required />
<flux:select wire:model.live="newLicenseType" :label="__('Lizenztyp')" :badge="__('Pflicht')" required>
<flux:input wire:model="newAuthor" :label="__('Urheber / Fotograf / Rechteinhaber')"
:badge="__('Pflicht')" required />
<flux:select wire:model.live="newLicenseType" :label="__('Lizenztyp')" :badge="__('Pflicht')"
required>
<flux:select.option value="" disabled>{{ __('Bitte wählen…') }}</flux:select.option>
@foreach ($licenseTypeOptions as $value => $label)
<flux:select.option :value="$value">{{ $label }}</flux:select.option>
@ -436,7 +461,8 @@ new class extends Component {
</div>
@if ($newLicenseType === \App\Enums\ImageLicenseType::CreativeCommons->value)
<flux:select wire:model.live="newLicenseDetail" :label="__('Creative-Commons-Lizenz')" required>
<flux:select wire:model.live="newLicenseDetail" :label="__('Creative-Commons-Lizenz')"
required>
<flux:select.option value="" disabled>{{ __('Bitte wählen…') }}</flux:select.option>
@foreach ($ccLicenseOptions as $value => $label)
<flux:select.option :value="$value">{{ $label }}</flux:select.option>
@ -457,16 +483,18 @@ new class extends Component {
:label="$licenseUrlRequired ? __('Quelle oder Lizenznachweis-URL') : __(
'Quelle oder Lizenznachweis-URL (optional)')"
:required="$licenseUrlRequired" placeholder="https://…" />
<flux:input wire:model="newSourceUrl" type="url" :label="__('Weitere Quelle / Fundstelle (optional)')"
placeholder="https://…" />
<flux:input wire:model="newSourceUrl" type="url"
:label="__('Weitere Quelle / Fundstelle (optional)')" placeholder="https://…" />
</div>
</section>
{{-- ===== Schritt 4 · Personen & Rechte Dritter ===== --}}
<section class="space-y-3 border-t border-zinc-200 pt-5 dark:border-zinc-700">
<div class="flex items-center gap-2">
<span class="flex size-5 shrink-0 items-center justify-center rounded-full bg-zinc-100 text-[11px] font-bold text-zinc-600 dark:bg-zinc-700 dark:text-zinc-300">4</span>
<span class="text-[11px] font-semibold uppercase tracking-wider text-zinc-500">{{ __('Personen & Rechte Dritter') }}</span>
<span
class="flex size-5 shrink-0 items-center justify-center rounded-full bg-zinc-100 text-[11px] font-bold text-zinc-600 dark:bg-zinc-700 dark:text-zinc-300">4</span>
<span
class="text-[11px] font-semibold uppercase tracking-wider text-zinc-500">{{ __('Personen & Rechte Dritter') }}</span>
</div>
<div class="grid gap-4 sm:grid-cols-2">
<flux:radio.group wire:model.live="newPeopleRightsStatus"
@ -477,7 +505,8 @@ new class extends Component {
</flux:radio.group>
<flux:radio.group wire:model.live="newPropertyRightsStatus"
:label="__('Sind Marken, Kunstwerke, geschützte Werke oder private Orte sichtbar?')" required>
:label="__('Sind Marken, Kunstwerke, geschützte Werke oder private Orte sichtbar?')"
required>
@foreach ($propertyRightsOptions as $value => $label)
<flux:radio :value="$value" :label="$label" />
@endforeach
@ -502,8 +531,10 @@ new class extends Component {
{{-- ===== Schritt 5 · Bestätigung ===== --}}
<section class="space-y-3 border-t border-zinc-200 pt-5 dark:border-zinc-700">
<div class="flex items-center gap-2">
<span class="flex size-5 shrink-0 items-center justify-center rounded-full bg-zinc-100 text-[11px] font-bold text-zinc-600 dark:bg-zinc-700 dark:text-zinc-300">5</span>
<span class="text-[11px] font-semibold uppercase tracking-wider text-zinc-500">{{ __('Bestätigung') }}</span>
<span
class="flex size-5 shrink-0 items-center justify-center rounded-full bg-zinc-100 text-[11px] font-bold text-zinc-600 dark:bg-zinc-700 dark:text-zinc-300">5</span>
<span
class="text-[11px] font-semibold uppercase tracking-wider text-zinc-500">{{ __('Bestätigung') }}</span>
</div>
<flux:textarea wire:model="newRightsNotes"
@ -513,8 +544,10 @@ new class extends Component {
:description="__('Ich bestätige, dass ich über die erforderlichen Rechte zur Veröffentlichung dieses Bildes verfüge und die Verantwortung für die Richtigkeit meiner Angaben übernehme. Dies umfasst Urheberrechte, Nutzungsrechte, Persönlichkeitsrechte abgebildeter Personen sowie gegebenenfalls Marken-, Eigentums- oder sonstige Rechte Dritter. Mir ist bewusst, dass ich für fehlerhafte oder unvollständige Angaben verantwortlich bin.')" />
<div class="flex justify-end gap-2 border-t border-zinc-200 pt-4 dark:border-zinc-700">
<flux:button type="button" variant="filled" wire:click="closeUploadForm">{{ __('Abbrechen') }}</flux:button>
<flux:button type="submit" variant="primary" icon="arrow-up-tray">{{ __('Hochladen') }}</flux:button>
<flux:button type="button" variant="filled" wire:click="closeUploadForm">
{{ __('Abbrechen') }}</flux:button>
<flux:button type="submit" variant="primary" icon="arrow-up-tray">{{ __('Hochladen') }}
</flux:button>
</div>
</section>
</form>

View file

@ -226,6 +226,7 @@ test('valid cc upload stores license detail and license url', function () {
LivewireVolt::test('components.press-release-images-manager', ['pressReleaseId' => $pr->id])
->set('newImage', UploadedFile::fake()->image('foto.jpg', 1200, 800))
->set('newAuthor', 'Jane Doe')
->set('newCopyright', 'Foto: Jane Doe (CC BY 4.0)')
->set('newLicenseType', ImageLicenseType::CreativeCommons->value)
->set('newLicenseDetail', 'cc_by')
->set('newLicenseUrl', 'https://creativecommons.org/licenses/by/4.0/')
@ -243,6 +244,34 @@ test('valid cc upload stores license detail and license url', function () {
expect($image->license_url)->toBe('https://creativecommons.org/licenses/by/4.0/');
});
test('the public image credit is required', function () {
/** @var TestCase $this */
['owner' => $owner, 'pr' => $pr] = makeImageDraftOwner();
$this->actingAs($owner);
LivewireVolt::test('components.press-release-images-manager', ['pressReleaseId' => $pr->id])
->set('newImage', UploadedFile::fake()->image('foto.jpg', 1200, 800))
->set('newAuthor', 'Jane Doe')
->set('newLicenseType', ImageLicenseType::Own->value)
->set('newPeopleRightsStatus', 'none')
->set('newPropertyRightsStatus', 'none')
->set('newRightsConfirmed', true)
->call('saveImage')
->assertHasErrors(['newCopyright']);
});
test('switching the license type clears the stale license detail', function () {
/** @var TestCase $this */
['owner' => $owner, 'pr' => $pr] = makeImageDraftOwner();
$this->actingAs($owner);
LivewireVolt::test('components.press-release-images-manager', ['pressReleaseId' => $pr->id])
->set('newLicenseType', ImageLicenseType::CreativeCommons->value)
->set('newLicenseDetail', 'cc_by')
->set('newLicenseType', ImageLicenseType::Other->value)
->assertSet('newLicenseDetail', '');
});
test('existing title image hides upload form and can be removed', function () {
/** @var TestCase $this */
['owner' => $owner, 'pr' => $pr] = makeImageDraftOwner();