Display CMS Optimierungen 29-05-2026
- Mediathek: Video-Vorschaubilder statt Icons (FFmpeg-Thumbnails + Backfill-Command), Kategorie "Sonstiges" - B2in Media-Picker zeigt alle Medientypen, Typ wird automatisch erkannt; Thumbnail-Preview vor allen Medien-URL-Feldern - B2in Marke/Footer: Footer ein/aus, Logo+Claim frei positionierbar (Ecken) mit Constraints, separate Anzeige-Schalter - Angebote-Modul dynamisch: kein Slide-Typ mehr, einheitliches Detail-Layout mit ein-/ausblendbaren Bloecken, Logo/Brand pro Slide, Streichpreis-Option - Player: leere Module stoppen Endlosschleife, dynamische Layout-Anpassung bei verstecktem Footer/Header - Fix: Script-Ladereihenfolge (Livewire vor Flux), entfernte stale public/flux/flux.js, Modal-Crash beim Aktualisieren behoben Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
9262132325
commit
6c6d683b9a
42 changed files with 2267 additions and 13905 deletions
|
|
@ -121,9 +121,13 @@
|
|||
type="video"
|
||||
label="Video aus Mediathek"
|
||||
:key="'picker-video-' . ($itemId ?? 'new')" />
|
||||
<flux:input wire:model="videoFilename" label="Video-Pfad / URL" placeholder="Wird automatisch gesetzt oder manuell eingeben..."
|
||||
description="Über die Mediathek auswählen oder direkt Pfad/URL eingeben." />
|
||||
<flux:input wire:model="videoTitle" label="Titel (optional)" placeholder="z.B. Herbst Kollektion 2025" />
|
||||
<div class="flex items-end gap-3">
|
||||
<x-media-thumb :url="$videoFilename" />
|
||||
<flux:input wire:model.live.debounce.500ms="videoFilename" label="Video-Pfad / URL" placeholder="Wird automatisch gesetzt oder manuell eingeben..."
|
||||
description="Über die Mediathek auswählen oder direkt Pfad/URL eingeben." class="flex-1" />
|
||||
</div>
|
||||
<flux:input wire:model="videoTitle" label="Name" placeholder="z.B. Herbst Kollektion 2025"
|
||||
description="Interner Name des Videos – wird nicht im Video eingeblendet." />
|
||||
<flux:input wire:model="videoPosition" type="number" min="0" max="100" label="Position (%)"
|
||||
description="Vertikale Position im Video (0 = oben, 100 = unten)" />
|
||||
<flux:checkbox wire:model="videoIsActive" label="Aktiv" />
|
||||
|
|
@ -140,22 +144,29 @@
|
|||
|
||||
{{-- Media fields (B2in) --}}
|
||||
@if($itemType === 'media')
|
||||
<flux:select wire:model="mediaType" label="Medientyp">
|
||||
<option value="image">Bild</option>
|
||||
<option value="video">Video</option>
|
||||
</flux:select>
|
||||
<flux:select wire:model="mediaCategory" label="Kategorie">
|
||||
<option value="immobilien">Immobilien</option>
|
||||
<option value="moebel">Möbel</option>
|
||||
</flux:select>
|
||||
<livewire:admin.cms.display-media-picker
|
||||
:value="null"
|
||||
field="mediaUrl"
|
||||
:type="$mediaType === 'video' ? 'video' : 'image'"
|
||||
type="all"
|
||||
label="Aus Mediathek"
|
||||
:key="'picker-media-' . ($itemId ?? 'new')" />
|
||||
<flux:input wire:model="mediaUrl" label="Medien-URL" placeholder="Wird automatisch gesetzt oder manuell eingeben..."
|
||||
description="Über die Mediathek auswählen oder direkt URL eingeben." />
|
||||
<div class="flex items-end gap-3">
|
||||
<x-media-thumb :url="$mediaUrl" />
|
||||
<flux:input wire:model.live.debounce.500ms="mediaUrl" label="Medien-URL" placeholder="Wird automatisch gesetzt oder manuell eingeben..."
|
||||
description="Über die Mediathek auswählen oder direkt URL eingeben." class="flex-1" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:text class="text-sm">{{ __('Medientyp:') }}</flux:text>
|
||||
<flux:badge size="sm" :color="$mediaType === 'video' ? 'purple' : 'sky'">
|
||||
{{ $mediaType === 'video' ? __('Video') : __('Bild') }}
|
||||
</flux:badge>
|
||||
<flux:text class="text-xs text-zinc-400">{{ __('wird automatisch aus dem gewählten Medium erkannt') }}</flux:text>
|
||||
</div>
|
||||
<flux:select wire:model="mediaCategory" label="Kategorie">
|
||||
<option value="immobilien">Immobilien</option>
|
||||
<option value="moebel">Möbel</option>
|
||||
<option value="sonstiges">Sonstiges</option>
|
||||
</flux:select>
|
||||
<flux:input wire:model="mediaHeadline" label="Überschrift" placeholder="z.B. Ihr Zuhause. Weltweit." />
|
||||
<flux:input wire:model="mediaSubline" label="Unterzeile" placeholder="z.B. Beratung und Vermittlung." />
|
||||
@if($mediaType === 'image')
|
||||
|
|
@ -165,47 +176,79 @@
|
|||
<flux:checkbox wire:model="mediaIsActive" label="Aktiv" />
|
||||
@endif
|
||||
|
||||
{{-- Slide fields (Offers) --}}
|
||||
{{-- Slide fields (Offers) – einheitliches Detail-Layout mit Ein-/Ausblende-Schaltern --}}
|
||||
@if($itemType === 'slide')
|
||||
{{-- Basis --}}
|
||||
<flux:select wire:model.live="slideType" label="Slide-Typ">
|
||||
<option value="intro">Intro</option>
|
||||
<option value="product-hero">Produkt-Hero</option>
|
||||
<option value="product-details">Produkt-Details</option>
|
||||
<option value="product-impulse">Produkt-Impuls</option>
|
||||
</flux:select>
|
||||
<flux:input wire:model="slideDuration" type="number" min="1000" label="Dauer (ms)" />
|
||||
<livewire:admin.cms.display-media-picker
|
||||
:value="null"
|
||||
field="slideImageUrl"
|
||||
type="image"
|
||||
label="Bild aus Mediathek"
|
||||
:key="'picker-slide-' . ($itemId ?? 'new')" />
|
||||
<flux:input wire:model="slideImageUrl" label="Bild-URL" placeholder="Wird automatisch gesetzt oder manuell eingeben..."
|
||||
description="Über die Mediathek auswählen oder direkt URL eingeben." />
|
||||
<flux:input wire:model="slideBadge" label="Badge-Text" placeholder="z.B. Einzelstück" />
|
||||
<flux:input wire:model="slideEyebrow" label="Eyebrow" placeholder="z.B. Hersteller: Sudbrock" />
|
||||
<flux:input wire:model="slideTitle" label="Titel" placeholder="z.B. GOYA Sideboard" />
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">
|
||||
{{ __('Jedes Angebot nutzt dasselbe Detail-Layout. Über die Schalter blendest du einzelne Bausteine ein oder aus und befüllst sie mit Inhalten.') }}
|
||||
</flux:text>
|
||||
|
||||
{{-- Intro-spezifisch --}}
|
||||
@if($slideType === 'intro')
|
||||
<flux:input wire:model="slideDisclaimer" label="Disclaimer" placeholder="z.B. Zwischenverkauf vorbehalten" />
|
||||
<flux:checkbox wire:model="slideShowBrandText" label="Brand-Text anzeigen" />
|
||||
@if($slideShowBrandText)
|
||||
<flux:input wire:model="slideBrandTagline" label="Brand-Tagline" placeholder="z.B. Planung • Beratung • Lieferung & Montage" />
|
||||
{{-- Logo & Marke (Kopfbereich) --}}
|
||||
<div class="space-y-4 rounded-xl border border-zinc-200 p-4 dark:border-zinc-700">
|
||||
<flux:heading size="sm">{{ __('Logo & Marke') }}</flux:heading>
|
||||
<flux:switch wire:model.live="slideShowLogo" label="Logo & Marken-Text anzeigen"
|
||||
description="Kopfbereich oben mit Logo, Marken-Text und Tagline." />
|
||||
@if($slideShowLogo)
|
||||
<livewire:admin.cms.display-media-picker
|
||||
:value="null"
|
||||
field="slideLogoUrl"
|
||||
type="image"
|
||||
label="Logo aus Mediathek"
|
||||
:key="'picker-slide-logo-' . ($itemId ?? 'new')" />
|
||||
<div class="flex items-end gap-3">
|
||||
<x-media-thumb :url="$slideLogoUrl" />
|
||||
<flux:input wire:model.live.debounce.500ms="slideLogoUrl" label="Logo-URL" placeholder="Standard: CABINET-Logo"
|
||||
description="Leer = Standard-Logo wird genutzt." class="flex-1" />
|
||||
</div>
|
||||
<flux:input wire:model="slideBrandText" label="Marken-Text" placeholder="z.B. Bielefeld"
|
||||
description="Text direkt neben dem Logo (optional)." />
|
||||
<flux:input wire:model="slideBrandTagline" label="Tagline" placeholder="z.B. Planung • Beratung • Lieferung & Montage"
|
||||
description="Rechts im Kopfbereich (optional)." />
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Product-Hero --}}
|
||||
@if($slideType === 'product-hero')
|
||||
<flux:input wire:model="slidePrice" label="Preis" placeholder="z.B. 489 €" />
|
||||
<flux:input wire:model="slideOriginalPrice" label="Originalpreis" placeholder="z.B. statt 4.744 €" />
|
||||
@endif
|
||||
{{-- Bild & Badge (wichtigstes Element – farblich hervorgehoben) --}}
|
||||
<div class="space-y-4 rounded-xl border-2 border-blue-300 bg-blue-50/60 p-4 ring-1 ring-blue-200 dark:border-blue-700 dark:bg-blue-950/30 dark:ring-blue-900">
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:heading size="sm">{{ __('Bild & Badge') }}</flux:heading>
|
||||
<flux:badge color="blue" size="sm">{{ __('Wichtigstes Element') }}</flux:badge>
|
||||
</div>
|
||||
<livewire:admin.cms.display-media-picker
|
||||
:value="null"
|
||||
field="slideImageUrl"
|
||||
type="image"
|
||||
label="Bild aus Mediathek"
|
||||
:key="'picker-slide-' . ($itemId ?? 'new')" />
|
||||
<div class="flex items-end gap-3">
|
||||
<x-media-thumb :url="$slideImageUrl" />
|
||||
<flux:input wire:model.live.debounce.500ms="slideImageUrl" label="Bild-URL" placeholder="Wird automatisch gesetzt oder manuell eingeben..."
|
||||
description="Über die Mediathek auswählen oder direkt URL eingeben." class="flex-1" />
|
||||
</div>
|
||||
<flux:switch wire:model.live="slideShowBadge" label="Badge anzeigen" description="Kleine Markierung über dem Bild." />
|
||||
@if($slideShowBadge)
|
||||
<flux:input wire:model="slideBadge" label="Badge-Text" placeholder="z.B. Einzelstück" />
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Product-Details --}}
|
||||
@if($slideType === 'product-details')
|
||||
<div>
|
||||
<flux:heading size="sm" class="mb-2">{{ __('Aufzählungspunkte') }}</flux:heading>
|
||||
{{-- Texte --}}
|
||||
<div class="space-y-4 rounded-xl border border-zinc-200 p-4 dark:border-zinc-700">
|
||||
<flux:heading size="sm">{{ __('Texte') }}</flux:heading>
|
||||
<flux:input wire:model="slideTitle" label="Titel" placeholder="z.B. GOYA Sideboard"
|
||||
description="Hauptüberschrift des Angebots (Zeilenumbruch mit Enter möglich)." />
|
||||
<flux:switch wire:model.live="slideShowEyebrow" label="Eyebrow anzeigen" description="Kleine Überzeile über dem Titel." />
|
||||
@if($slideShowEyebrow)
|
||||
<flux:input wire:model="slideEyebrow" label="Eyebrow" placeholder="z.B. Hersteller: Sudbrock" />
|
||||
@endif
|
||||
<flux:switch wire:model.live="slideShowSubline" label="Unterzeile anzeigen" />
|
||||
@if($slideShowSubline)
|
||||
<flux:input wire:model="slideSubline" label="Unterzeile" placeholder="z.B. Heute mitnehmen" />
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Aufzählung --}}
|
||||
<div class="space-y-4 rounded-xl border border-zinc-200 p-4 dark:border-zinc-700">
|
||||
<flux:heading size="sm">{{ __('Aufzählungspunkte') }}</flux:heading>
|
||||
<flux:switch wire:model.live="slideShowBullets" label="Aufzählung anzeigen" description="Liste mit Stichpunkten, z.B. Produktdetails." />
|
||||
@if($slideShowBullets)
|
||||
<div class="space-y-2">
|
||||
@foreach($slideBullets as $i => $bullet)
|
||||
<div class="flex items-center gap-2" wire:key="bullet-{{ $i }}">
|
||||
|
|
@ -214,30 +257,58 @@
|
|||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
<flux:button wire:click="addBullet" size="xs" variant="ghost" icon="plus" class="mt-2">
|
||||
<flux:button wire:click="addBullet" size="xs" variant="ghost" icon="plus">
|
||||
{{ __('Punkt hinzufügen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Product-Impulse --}}
|
||||
@if($slideType === 'product-impulse')
|
||||
<flux:input wire:model="slideSubline" label="Subline" placeholder="z.B. Heute mitnehmen" />
|
||||
<flux:input wire:model="slidePrice" label="Preis" placeholder="z.B. 199 €" />
|
||||
<flux:input wire:model="slideTagText" label="Tag-Text" placeholder="z.B. Im Store verfügbar" />
|
||||
@endif
|
||||
|
||||
{{-- QR --}}
|
||||
<div class="border-t border-zinc-200 dark:border-zinc-700 pt-4 mt-2">
|
||||
<flux:heading size="sm" class="mb-3">{{ __('QR-Code & Kontakt') }}</flux:heading>
|
||||
<div class="space-y-4">
|
||||
<flux:input wire:model="slideQrUrl" label="QR-URL" placeholder="z.B. https://cabinet-bielefeld.de" />
|
||||
<flux:input wire:model="slideQrTitle" label="QR-Titel" placeholder="z.B. Reservieren" />
|
||||
<flux:input wire:model="slideContact" label="Kontakt" placeholder="z.B. 0521 98620100 / Tel. oder WhatsApp" />
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<flux:checkbox wire:model="slideIsActive" label="Aktiv" />
|
||||
{{-- Preis --}}
|
||||
<div class="space-y-4 rounded-xl border border-zinc-200 p-4 dark:border-zinc-700">
|
||||
<flux:heading size="sm">{{ __('Preis') }}</flux:heading>
|
||||
<flux:switch wire:model.live="slideShowPrice" label="Preis anzeigen" />
|
||||
@if($slideShowPrice)
|
||||
<flux:input wire:model="slidePrice" label="Preis" placeholder="z.B. 489 €" />
|
||||
<flux:input wire:model="slideOriginalPrice" label="Originalpreis (optional)" placeholder="z.B. statt 4.744 €" />
|
||||
@if(trim($slideOriginalPrice) !== '')
|
||||
<flux:switch wire:model.live="slideStrikeOriginalPrice" label="Streichpreis"
|
||||
description="Originalpreis wird rot durchgestrichen dargestellt." />
|
||||
@endif
|
||||
<flux:input wire:model="slideTagText" label="Hinweis-Tag (optional)" placeholder="z.B. Im Store verfügbar" />
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Hinweis --}}
|
||||
<div class="space-y-4 rounded-xl border border-zinc-200 p-4 dark:border-zinc-700">
|
||||
<flux:heading size="sm">{{ __('Hinweis') }}</flux:heading>
|
||||
<flux:switch wire:model.live="slideShowDisclaimer" label="Disclaimer anzeigen" />
|
||||
@if($slideShowDisclaimer)
|
||||
<flux:input wire:model="slideDisclaimer" label="Disclaimer" placeholder="z.B. Zwischenverkauf vorbehalten" />
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- QR & Kontakt --}}
|
||||
<div class="space-y-4 rounded-xl border border-zinc-200 p-4 dark:border-zinc-700">
|
||||
<flux:heading size="sm">{{ __('QR-Code & Kontakt') }}</flux:heading>
|
||||
<flux:switch wire:model.live="slideShowQr" label="QR-Code anzeigen" />
|
||||
@if($slideShowQr)
|
||||
<flux:input wire:model="slideQrUrl" label="QR-URL" placeholder="z.B. https://cabinet-bielefeld.de"
|
||||
description="Leer = es wird die Web/QR-URL aus den Einstellungen genutzt." />
|
||||
<flux:input wire:model="slideQrTitle" label="QR-Titel" placeholder="z.B. Reservieren" />
|
||||
@endif
|
||||
<flux:switch wire:model.live="slideShowContact" label="Kontakt anzeigen" />
|
||||
@if($slideShowContact)
|
||||
<flux:input wire:model="slideContact" label="Kontakt" placeholder="z.B. 0521 98620100 / Tel. oder WhatsApp" />
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Anzeige --}}
|
||||
<div class="space-y-4 rounded-xl border border-zinc-200 p-4 dark:border-zinc-700">
|
||||
<flux:heading size="sm">{{ __('Anzeige') }}</flux:heading>
|
||||
<flux:input wire:model="slideDuration" type="number" min="1000" step="500" label="Dauer (ms)"
|
||||
description="Wie lange dieses Angebot eingeblendet wird." />
|
||||
<flux:switch wire:model="slideIsActive" label="Aktiv" description="Inaktive Angebote werden im Display übersprungen." />
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="space-y-3 border-t border-zinc-200 pt-6 dark:border-zinc-700">
|
||||
|
|
@ -246,22 +317,37 @@
|
|||
<flux:subheading>{{ __('Zeigt nur den aktuell bearbeiteten Inhalt im Display-Player') }}</flux:subheading>
|
||||
</div>
|
||||
|
||||
<div class="mx-auto aspect-[9/16] w-full max-w-[320px] overflow-hidden rounded-xl border border-zinc-200 bg-black shadow-sm dark:border-zinc-700">
|
||||
{{-- Stable iframe element: only its `src` changes between the
|
||||
"new" (about:blank) and "saved" state. Swapping the element
|
||||
structure inside the teleported Flux modal crashes Livewire's
|
||||
morph/cleanup, so we keep the DOM shape constant. --}}
|
||||
<div class="relative mx-auto aspect-[9/16] w-full max-w-[320px] overflow-hidden rounded-xl border border-zinc-200 bg-black shadow-sm dark:border-zinc-700">
|
||||
<iframe
|
||||
wire:key="item-modal-module-preview-{{ $previewFrameRefreshCounter }}"
|
||||
src="{{ $this->itemPreviewUrl() }}"
|
||||
wire:key="item-modal-preview"
|
||||
src="{{ $itemId ? $this->itemPreviewUrl() : 'about:blank' }}"
|
||||
class="h-full w-full border-0"
|
||||
loading="lazy"
|
||||
title="{{ __('Einzel-Vorschau im Bearbeiten-Dialog') }}"
|
||||
></iframe>
|
||||
|
||||
@unless($itemId)
|
||||
<div class="absolute inset-0 flex items-center justify-center bg-zinc-50 text-center dark:bg-zinc-900">
|
||||
<div class="px-6 text-sm text-zinc-500 dark:text-zinc-400">
|
||||
<flux:icon.eye class="mx-auto mb-2 h-8 w-8 opacity-40" />
|
||||
{{ __('Die Vorschau erscheint, sobald der Inhalt gespeichert wurde.') }}
|
||||
</div>
|
||||
</div>
|
||||
@endunless
|
||||
</div>
|
||||
|
||||
<a href="{{ $this->itemPreviewUrl() }}"
|
||||
target="_blank"
|
||||
class="mx-auto flex max-w-[320px] items-center justify-center gap-1 text-xs text-blue-600 hover:underline dark:text-blue-400">
|
||||
<flux:icon.arrow-top-right-on-square class="w-3 h-3" />
|
||||
{{ __('Vollbild öffnen') }}
|
||||
</a>
|
||||
@if($itemId)
|
||||
<a href="{{ $this->itemPreviewUrl() }}"
|
||||
target="_blank"
|
||||
class="mx-auto flex max-w-[320px] items-center justify-center gap-1 text-xs text-blue-600 hover:underline dark:text-blue-400">
|
||||
<flux:icon.arrow-top-right-on-square class="w-3 h-3" />
|
||||
{{ __('Vollbild öffnen') }}
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap justify-end gap-3 border-t border-zinc-200 pt-4 dark:border-zinc-700">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue