19-05-2026 Rebrand Pressekonto, Hub-Flux UI und Legacy-Media-Migration
Umbenennung presseportale → pressekonto in Domains, Themes und Dokumentation. Design-Tokens, Portal-Shell, Customer-Dashboard, Auth- und Admin-PM-Views. Artisan-Befehl migrate:legacy-media mit Tests und Hub-Flux-Entwicklungsdocs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
092ee0e918
commit
0a3e52d603
112 changed files with 8464 additions and 1649 deletions
|
|
@ -137,44 +137,56 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
<flux:card>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<flux:heading size="lg">{{ __('Neue Pressemitteilung') }}</flux:heading>
|
||||
<flux:subheading>{{ __('Entwurf erstellen oder direkt zur Prüfung einreichen.') }}</flux:subheading>
|
||||
<div class="space-y-8">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-nowrap whitespace-nowrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Content · Neu anlegen') }}</span>
|
||||
</div>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Neue Pressemitteilung') }}
|
||||
</h1>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
|
||||
{{ __('Entwurf erstellen oder direkt zur Prüfung einreichen.') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.press-releases.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
</header>
|
||||
|
||||
<div class="grid gap-6 lg:grid-cols-[1fr,320px]">
|
||||
{{-- Hauptinhalt --}}
|
||||
{{-- ============== HAUPTINHALT ============== --}}
|
||||
<div class="space-y-6">
|
||||
<flux:card>
|
||||
<flux:heading size="md" class="mb-4">{{ __('Inhalt') }}</flux:heading>
|
||||
|
||||
<div class="space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Inhalt') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Titel') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:label>{{ __('Titel') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
||||
<flux:input wire:model.live.debounce.500ms="title" placeholder="{{ __('Aussagekräftiger Titel…') }}" />
|
||||
<flux:error name="title" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Text') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:label>{{ __('Text') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
||||
<flux:textarea wire:model="text" rows="16" placeholder="{{ __('Vollständiger Text der Pressemitteilung…') }}" />
|
||||
<flux:error name="text" />
|
||||
</flux:field>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
<flux:card>
|
||||
<flux:heading size="md" class="mb-4">{{ __('SEO & Links') }}</flux:heading>
|
||||
|
||||
<div class="space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('SEO & Links') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Stichwörter') }}</flux:label>
|
||||
<flux:input wire:model="keywords" placeholder="{{ __('Kommagetrennte Stichwörter…') }}" />
|
||||
|
|
@ -187,19 +199,20 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
<flux:error name="backlinkUrl" />
|
||||
</flux:field>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
{{-- Sidebar --}}
|
||||
<div class="space-y-4">
|
||||
<flux:card>
|
||||
<flux:heading size="md" class="mb-4">{{ __('Metadaten') }}</flux:heading>
|
||||
|
||||
<div class="space-y-4">
|
||||
{{-- ============== SIDEBAR ============== --}}
|
||||
<aside class="space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Metadaten') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Portal') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:label>{{ __('Portal') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
||||
<flux:select wire:model="portal">
|
||||
@foreach($portalOptions as $p)
|
||||
@foreach ($portalOptions as $p)
|
||||
<option value="{{ $p->value }}">{{ $p->label() }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
|
|
@ -215,7 +228,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Firma') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:label>{{ __('Firma') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
||||
<flux:select
|
||||
wire:model.live="companyId"
|
||||
variant="combobox"
|
||||
|
|
@ -229,14 +242,14 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
placeholder="{{ __('Name eingeben…') }}"
|
||||
/>
|
||||
</x-slot>
|
||||
@foreach($companies as $company)
|
||||
@foreach ($companies as $company)
|
||||
<flux:select.option :value="$company->id" wire:key="{{ $company->id }}">
|
||||
{{ $company->name }}
|
||||
</flux:select.option>
|
||||
@endforeach
|
||||
<x-slot name="empty">
|
||||
<flux:select.option.empty>
|
||||
@if(blank(trim($companySearch)))
|
||||
@if (blank(trim($companySearch)))
|
||||
{{ __('Mindestens 1 Zeichen eingeben…') }}
|
||||
@else
|
||||
{{ __('Keine Firma gefunden.') }}
|
||||
|
|
@ -248,11 +261,11 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Kategorie') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:label>{{ __('Kategorie') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
||||
<flux:select wire:model="categoryId">
|
||||
<option value="">{{ __('Bitte wählen…') }}</option>
|
||||
@foreach($categories as $cat)
|
||||
@php $catName = $cat->translations->firstWhere('locale', 'de')?->name ?? $cat->id; @endphp
|
||||
@foreach ($categories as $cat)
|
||||
@php($catName = $cat->translations->firstWhere('locale', 'de')?->name ?? $cat->id)
|
||||
<option value="{{ $cat->id }}">{{ $catName }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
|
|
@ -261,10 +274,13 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
|
||||
<flux:checkbox wire:model="noExport" label="{{ __('Kein Export') }}" />
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
<flux:card>
|
||||
<div class="space-y-2">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Aktionen') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-2">
|
||||
<flux:button
|
||||
type="button"
|
||||
variant="primary"
|
||||
|
|
@ -284,7 +300,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
{{ __('Als Entwurf speichern') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
</div>
|
||||
</article>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -264,53 +264,80 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
|
|||
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
@if(session('success'))
|
||||
<div class="rounded-md border border-green-200 bg-green-50 px-4 py-3 text-sm text-green-800 dark:border-green-800 dark:bg-green-900/20 dark:text-green-300">
|
||||
<div class="space-y-8">
|
||||
@php
|
||||
$statusClass = match ($currentStatus) {
|
||||
'published' => 'ok',
|
||||
'review' => 'warn',
|
||||
'rejected' => 'err',
|
||||
default => 'hub',
|
||||
};
|
||||
@endphp
|
||||
|
||||
@if (session('success'))
|
||||
<div class="px-4 py-3 rounded-[5px] border text-[12.5px]
|
||||
bg-[color:var(--color-ok-soft)] border-[color:var(--color-ok)]/30 text-[color:var(--color-gain-deep)]">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<flux:card>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<flux:heading size="lg">{{ __('Pressemitteilung bearbeiten') }}</flux:heading>
|
||||
<flux:subheading>ID: {{ $id }}</flux:subheading>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:badge :color="$statusColor" size="lg">{{ $statusEnum?->label() ?? $currentStatus }}</flux:badge>
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.press-releases.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
@if (session('error'))
|
||||
<div class="px-4 py-3 rounded-[5px] border text-[12.5px]
|
||||
bg-[color:var(--color-err-soft)] border-[color:var(--color-err)]/30 text-[color:var(--color-loss)]">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
</flux:card>
|
||||
@endif
|
||||
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-wrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Content · Bearbeiten') }}</span>
|
||||
<span @class(['badge', $statusClass])>{{ $statusEnum?->label() ?? $currentStatus }}</span>
|
||||
<span class="badge hub">ID {{ $id }}</span>
|
||||
</div>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Pressemitteilung bearbeiten') }}
|
||||
</h1>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
|
||||
{{ __('Inhalt, Metadaten und Status der PM aktualisieren. Änderungen werden sofort wirksam.') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.press-releases.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="grid gap-6 lg:grid-cols-[1fr,320px]">
|
||||
{{-- Hauptinhalt --}}
|
||||
{{-- ============== HAUPTINHALT ============== --}}
|
||||
<div class="space-y-6">
|
||||
<flux:card>
|
||||
<flux:heading size="md" class="mb-4">{{ __('Inhalt') }}</flux:heading>
|
||||
|
||||
<div class="space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Inhalt') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Titel') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:label>{{ __('Titel') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
||||
<flux:input wire:model="title" />
|
||||
<flux:error name="title" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Text') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:label>{{ __('Text') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
||||
<flux:textarea wire:model="text" rows="20" />
|
||||
<flux:error name="text" />
|
||||
</flux:field>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
<flux:card>
|
||||
<flux:heading size="md" class="mb-4">{{ __('SEO & Links') }}</flux:heading>
|
||||
|
||||
<div class="space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('SEO & Links') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Stichwörter') }}</flux:label>
|
||||
<flux:input wire:model="keywords" placeholder="{{ __('Kommagetrennt…') }}" />
|
||||
|
|
@ -322,22 +349,23 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
|
|||
<flux:error name="backlinkUrl" />
|
||||
</flux:field>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
<livewire:components.press-release-images-manager :press-release-id="$id" :wire:key="'pr-images-'.$id" />
|
||||
</div>
|
||||
|
||||
{{-- Sidebar --}}
|
||||
<div class="space-y-4">
|
||||
{{-- Status-Aktionen --}}
|
||||
<flux:card>
|
||||
<flux:heading size="sm" class="mb-3">{{ __('Status-Aktionen') }}</flux:heading>
|
||||
|
||||
<div class="space-y-3">
|
||||
{{-- ============== SIDEBAR ============== --}}
|
||||
<aside class="space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Status-Aktionen') }}</span>
|
||||
<span @class(['badge', $statusClass])>{{ $statusEnum?->label() ?? $currentStatus }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-3">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Neuer Status') }}</flux:label>
|
||||
<flux:select wire:model.live="targetStatus">
|
||||
@foreach($statusOptions as $statusOption)
|
||||
@foreach ($statusOptions as $statusOption)
|
||||
<option value="{{ $statusOption->value }}">
|
||||
{{ $statusOption->label() }}{{ $statusOption->value === $currentStatus ? ' (aktuell)' : '' }}
|
||||
</option>
|
||||
|
|
@ -352,17 +380,17 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
|
|||
</flux:button>
|
||||
</flux:modal.trigger>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
{{-- Metadaten --}}
|
||||
<flux:card>
|
||||
<flux:heading size="sm" class="mb-3">{{ __('Metadaten') }}</flux:heading>
|
||||
|
||||
<div class="space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Metadaten') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Portal') }}</flux:label>
|
||||
<flux:select wire:model="portal">
|
||||
@foreach($portalOptions as $p)
|
||||
@foreach ($portalOptions as $p)
|
||||
<option value="{{ $p->value }}">{{ $p->label() }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
|
|
@ -391,14 +419,14 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
|
|||
placeholder="{{ __('Name eingeben…') }}"
|
||||
/>
|
||||
</x-slot>
|
||||
@foreach($companies as $company)
|
||||
@foreach ($companies as $company)
|
||||
<flux:select.option :value="$company->id" wire:key="{{ $company->id }}">
|
||||
{{ $company->name }}
|
||||
</flux:select.option>
|
||||
@endforeach
|
||||
<x-slot name="empty">
|
||||
<flux:select.option.empty>
|
||||
@if(blank(trim($companySearch)))
|
||||
@if (blank(trim($companySearch)))
|
||||
{{ __('Mindestens 1 Zeichen eingeben…') }}
|
||||
@else
|
||||
{{ __('Keine Firma gefunden.') }}
|
||||
|
|
@ -413,8 +441,8 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
|
|||
<flux:label>{{ __('Kategorie') }}</flux:label>
|
||||
<flux:select wire:model="categoryId">
|
||||
<option value="">{{ __('Bitte wählen…') }}</option>
|
||||
@foreach($categories as $cat)
|
||||
@php $catName = $cat->translations->firstWhere('locale', 'de')?->name ?? $cat->id; @endphp
|
||||
@foreach ($categories as $cat)
|
||||
@php($catName = $cat->translations->firstWhere('locale', 'de')?->name ?? $cat->id)
|
||||
<option value="{{ $cat->id }}">{{ $catName }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
|
|
@ -423,18 +451,24 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
|
|||
|
||||
<flux:checkbox wire:model="noExport" label="{{ __('Kein Export') }}" />
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
<flux:button type="button" variant="primary" class="w-full" wire:click="save">
|
||||
{{ __('Änderungen speichern') }}
|
||||
</flux:button>
|
||||
|
||||
<flux:modal.trigger name="confirm-delete-press-release">
|
||||
<flux:button type="button" variant="danger" icon="trash" class="w-full">
|
||||
{{ __('Pressemitteilung löschen') }}
|
||||
</flux:button>
|
||||
</flux:modal.trigger>
|
||||
</div>
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Aktionen') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-2">
|
||||
<flux:button type="button" variant="primary" class="w-full" wire:click="save">
|
||||
{{ __('Änderungen speichern') }}
|
||||
</flux:button>
|
||||
<flux:modal.trigger name="confirm-delete-press-release">
|
||||
<flux:button type="button" variant="danger" icon="trash" class="w-full">
|
||||
{{ __('Pressemitteilung löschen') }}
|
||||
</flux:button>
|
||||
</flux:modal.trigger>
|
||||
</div>
|
||||
</article>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<flux:modal name="confirm-status-change" class="max-w-lg">
|
||||
|
|
|
|||
|
|
@ -349,43 +349,68 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="space-y-8">
|
||||
@if (session('success'))
|
||||
<div
|
||||
class="rounded-md border border-green-200 bg-green-50 px-4 py-3 text-sm text-green-800 dark:border-green-800 dark:bg-green-900/20 dark:text-green-300">
|
||||
<div class="px-4 py-3 rounded-[5px] border text-[12.5px]
|
||||
bg-[color:var(--color-ok-soft)] border-[color:var(--color-ok)]/30 text-[color:var(--color-gain-deep)]">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
@if (session('error'))
|
||||
<div class="px-4 py-3 rounded-[5px] border text-[12.5px]
|
||||
bg-[color:var(--color-err-soft)] border-[color:var(--color-err)]/30 text-[color:var(--color-loss)]">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Statistiken --}}
|
||||
<div class="grid grid-cols-2 gap-4 sm:grid-cols-4">
|
||||
<flux:card>
|
||||
<flux:text class="text-xs text-zinc-500">{{ __('Gesamt') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $stats['total'] }}</flux:text>
|
||||
</flux:card>
|
||||
<flux:card>
|
||||
<flux:text class="text-xs text-green-600">{{ __('Veröffentlicht') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $stats['published'] }}</flux:text>
|
||||
</flux:card>
|
||||
<flux:card>
|
||||
<flux:text class="text-xs text-yellow-600">{{ __('In Prüfung') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $stats['review'] }}</flux:text>
|
||||
</flux:card>
|
||||
<flux:card>
|
||||
<flux:text class="text-xs text-zinc-500">{{ __('Entwürfe') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $stats['draft'] }}</flux:text>
|
||||
</flux:card>
|
||||
</div>
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-nowrap whitespace-nowrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Content · Pressemitteilungen') }}</span>
|
||||
</div>
|
||||
<h1 class="text-[34px] font-bold tracking-[-0.7px] leading-[1.1] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Pressemitteilungen') }}
|
||||
</h1>
|
||||
<p class="text-[13.5px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
|
||||
{{ __('Übersicht aller PMs beider Portale, mit Filter, Status-Workflow und Schnellaktionen.') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<flux:button icon="plus" variant="primary" href="{{ route('admin.press-releases.create') }}" wire:navigate>
|
||||
{{ __('Neue PM') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 flex-shrink-0">
|
||||
<flux:button icon="plus" variant="primary" href="{{ route('admin.press-releases.create') }}" wire:navigate>
|
||||
{{ __('Neue PM') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{-- Filter --}}
|
||||
<flux:card>
|
||||
<div class="flex flex-col gap-3">
|
||||
{{-- ============== KPI-Reihe ============== --}}
|
||||
<section class="grid gap-4 grid-cols-2 sm:grid-cols-4">
|
||||
<x-portal.stat-card variant="primary" :label="__('Gesamt')" :value="number_format($stats['total'])">
|
||||
<x-slot:meta>{{ now()->format('Y') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('über beide Portale') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
<x-portal.stat-card variant="ok" :label="__('Veröffentlicht')" :value="number_format($stats['published'])">
|
||||
<x-slot:meta>{{ __('live') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('öffentlich sichtbar') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
<x-portal.stat-card variant="warn" :label="__('In Prüfung')" :value="number_format($stats['review'])">
|
||||
<x-slot:meta>{{ __('queue') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('redaktionelle Prüfung') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
<x-portal.stat-card variant="muted" :label="__('Entwürfe')" :value="number_format($stats['draft'])">
|
||||
<x-slot:meta>{{ __('privat') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('nicht eingereicht') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
</section>
|
||||
|
||||
{{-- ============== FILTER-PANEL ============== --}}
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Filter & Suche') }}</span>
|
||||
</div>
|
||||
<div class="p-5 flex flex-col gap-3">
|
||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-6">
|
||||
<flux:input
|
||||
wire:model.live.debounce.300ms="search"
|
||||
|
|
@ -551,10 +576,16 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
{{-- Tabelle --}}
|
||||
<flux:card class="overflow-hidden">
|
||||
{{-- ============== TABELLE-PANEL ============== --}}
|
||||
<article class="panel overflow-hidden">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Alle Pressemitteilungen') }}</span>
|
||||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ __(':count Einträge', ['count' => $pressReleases->count()]) }}
|
||||
</span>
|
||||
</div>
|
||||
<flux:table>
|
||||
<flux:table.columns>
|
||||
<flux:table.column>{{ __('Aktionen') }}</flux:table.column>
|
||||
|
|
@ -604,16 +635,15 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
|
|||
</div>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<flux:badge
|
||||
color="{{ match ($pr->status->value) {
|
||||
'published' => 'green',
|
||||
'review' => 'yellow',
|
||||
'draft' => 'zinc',
|
||||
'rejected' => 'red',
|
||||
'archived' => 'blue',
|
||||
} }}">
|
||||
<span @class([
|
||||
'badge',
|
||||
'ok' => $pr->status->value === 'published',
|
||||
'warn' => $pr->status->value === 'review',
|
||||
'err' => $pr->status->value === 'rejected',
|
||||
'hub' => in_array($pr->status->value, ['archived', 'draft'], true),
|
||||
])>
|
||||
{{ $pr->status->label() }}
|
||||
</flux:badge>
|
||||
</span>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<flux:text class="text-sm">{{ $pr->portal->label() }}</flux:text>
|
||||
|
|
@ -704,16 +734,23 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
|
|||
@empty
|
||||
<flux:table.row>
|
||||
<flux:table.cell colspan="8">
|
||||
<div class="flex flex-col items-center justify-center py-10">
|
||||
<flux:icon.newspaper class="size-10 text-zinc-300" />
|
||||
<flux:text class="mt-3 text-zinc-500">{{ __('Keine Pressemitteilungen gefunden.') }}
|
||||
</flux:text>
|
||||
<div class="flex flex-col items-center justify-center py-12 text-center">
|
||||
<div class="w-14 h-14 rounded-[6px] flex items-center justify-center mb-4
|
||||
bg-[color:var(--color-hub-soft)] border border-[color:var(--color-hub-soft-2)] text-[color:var(--color-hub)]">
|
||||
<flux:icon.newspaper class="size-6" />
|
||||
</div>
|
||||
<div class="text-[14px] font-semibold text-[color:var(--color-ink)] mb-1">
|
||||
{{ __('Keine Pressemitteilungen gefunden') }}
|
||||
</div>
|
||||
<p class="text-[12px] text-[color:var(--color-ink-3)] max-w-md m-0">
|
||||
{{ __('Passen Sie die Filter an oder erstellen Sie eine neue Pressemitteilung.') }}
|
||||
</p>
|
||||
</div>
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@endforelse
|
||||
</flux:table>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
{{ $pressReleases->links() }}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -90,46 +90,75 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
@if(session('success'))
|
||||
<div class="rounded-md border border-green-200 bg-green-50 px-4 py-3 text-sm text-green-800 dark:border-green-800 dark:bg-green-900/20 dark:text-green-300">
|
||||
<div class="space-y-8">
|
||||
@php
|
||||
$statusClass = match ($pr->status->value) {
|
||||
'published' => 'ok',
|
||||
'review' => 'warn',
|
||||
'rejected' => 'err',
|
||||
default => 'hub',
|
||||
};
|
||||
@endphp
|
||||
|
||||
@if (session('success'))
|
||||
<div class="px-4 py-3 rounded-[5px] border text-[12.5px]
|
||||
bg-[color:var(--color-ok-soft)] border-[color:var(--color-ok)]/30 text-[color:var(--color-gain-deep)]">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<flux:card>
|
||||
<div class="flex flex-wrap items-start justify-between gap-3">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:badge :color="$statusColor">{{ $pr->status->label() }}</flux:badge>
|
||||
<flux:badge color="zinc" size="sm">{{ strtoupper($pr->language) }}</flux:badge>
|
||||
<flux:badge color="zinc" size="sm">{{ $pr->portal->label() }}</flux:badge>
|
||||
</div>
|
||||
<flux:heading size="xl" class="mt-2">{{ $pr->title }}</flux:heading>
|
||||
<flux:text class="mt-1 text-sm text-zinc-500">
|
||||
{{ __('Firma') }}: {{ $pr->company?->name ?? '–' }} ·
|
||||
{{ __('Kategorie') }}: {{ $categoryName }} ·
|
||||
{{ __('Autor') }}: {{ $pr->user?->name ?? '–' }}
|
||||
</flux:text>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<flux:button variant="ghost" icon="pencil" href="{{ route('admin.press-releases.edit', $pr->id) }}" wire:navigate>
|
||||
{{ __('Bearbeiten') }}
|
||||
</flux:button>
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.press-releases.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
@if (session('error'))
|
||||
<div class="px-4 py-3 rounded-[5px] border text-[12.5px]
|
||||
bg-[color:var(--color-err-soft)] border-[color:var(--color-err)]/30 text-[color:var(--color-loss)]">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
</flux:card>
|
||||
@endif
|
||||
|
||||
{{-- Status-Aktionen --}}
|
||||
@if($pr->status === \App\Enums\PressReleaseStatus::Review)
|
||||
<flux:card>
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<flux:text weight="medium" class="text-yellow-700 dark:text-yellow-400">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-wrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Content · Pressemitteilung') }}</span>
|
||||
<span @class(['badge', $statusClass])>{{ $pr->status->label() }}</span>
|
||||
<span class="badge hub">{{ strtoupper($pr->language) }}</span>
|
||||
<span class="badge hub">{{ $pr->portal->label() }}</span>
|
||||
</div>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ $pr->title }}
|
||||
</h1>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[720px] text-[color:var(--color-ink-2)]">
|
||||
<strong class="font-semibold text-[color:var(--color-ink)]">{{ __('Firma') }}:</strong>
|
||||
{{ $pr->company?->name ?? '–' }}
|
||||
<span class="text-[color:var(--color-bg-rule)] mx-1">·</span>
|
||||
<strong class="font-semibold text-[color:var(--color-ink)]">{{ __('Kategorie') }}:</strong>
|
||||
{{ $categoryName }}
|
||||
<span class="text-[color:var(--color-bg-rule)] mx-1">·</span>
|
||||
<strong class="font-semibold text-[color:var(--color-ink)]">{{ __('Autor') }}:</strong>
|
||||
{{ $pr->user?->name ?? '–' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="pencil" href="{{ route('admin.press-releases.edit', $pr->id) }}" wire:navigate>
|
||||
{{ __('Bearbeiten') }}
|
||||
</flux:button>
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.press-releases.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{-- ============== STATUS-WORKFLOW ============== --}}
|
||||
@if ($pr->status === \App\Enums\PressReleaseStatus::Review)
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Status-Workflow') }}</span>
|
||||
<span class="badge warn dot">{{ __('Wartet auf Prüfung') }}</span>
|
||||
</div>
|
||||
<div class="p-5 flex flex-wrap items-center gap-3">
|
||||
<p class="text-[13px] text-[color:var(--color-ink-2)] m-0 flex-1 min-w-[200px]">
|
||||
{{ __('Diese PM wartet auf Prüfung.') }}
|
||||
</flux:text>
|
||||
</p>
|
||||
<flux:modal.trigger name="confirm-show-publish">
|
||||
<flux:button type="button" variant="primary">{{ __('Veröffentlichen') }}</flux:button>
|
||||
</flux:modal.trigger>
|
||||
|
|
@ -137,136 +166,162 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
<flux:button type="button" variant="danger">{{ __('Ablehnen') }}</flux:button>
|
||||
</flux:modal.trigger>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
@endif
|
||||
@if($pr->status === \App\Enums\PressReleaseStatus::Published)
|
||||
<flux:card>
|
||||
<div class="flex items-center gap-3">
|
||||
@if ($pr->status === \App\Enums\PressReleaseStatus::Published)
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Status-Workflow') }}</span>
|
||||
<span class="badge ok dot">{{ __('Live') }}</span>
|
||||
</div>
|
||||
<div class="p-5 flex flex-wrap items-center gap-3">
|
||||
@if ($pr->hits > 0)
|
||||
<p class="text-[13px] text-[color:var(--color-ink-2)] m-0 flex-1 min-w-[200px]">
|
||||
<strong class="text-[color:var(--color-ink)] font-semibold">{{ number_format($pr->hits) }}</strong>
|
||||
{{ __('Aufrufe seit Veröffentlichung') }}
|
||||
</p>
|
||||
@endif
|
||||
<flux:modal.trigger name="confirm-show-archive">
|
||||
<flux:button type="button" variant="ghost">{{ __('Archivieren') }}</flux:button>
|
||||
</flux:modal.trigger>
|
||||
@if($pr->hits > 0)
|
||||
<flux:text class="text-sm text-zinc-500">{{ number_format($pr->hits) }} {{ __('Aufrufe') }}</flux:text>
|
||||
@endif
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
@endif
|
||||
|
||||
<div class="grid gap-6 lg:grid-cols-[1fr,280px]">
|
||||
{{-- Text --}}
|
||||
<flux:card>
|
||||
<div class="prose prose-zinc dark:prose-invert max-w-none">
|
||||
{!! nl2br(e($pr->text)) !!}
|
||||
{{-- ============== TEXT + SIDEBAR ============== --}}
|
||||
<div class="grid gap-6 lg:grid-cols-[1fr,300px]">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Inhalt') }}</span>
|
||||
</div>
|
||||
</flux:card>
|
||||
<div class="p-5">
|
||||
<div class="prose prose-zinc dark:prose-invert max-w-none text-[color:var(--color-ink)]">
|
||||
{!! nl2br(e($pr->text)) !!}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
{{-- Details --}}
|
||||
<div class="space-y-4">
|
||||
<flux:card>
|
||||
<flux:heading size="sm" class="mb-3">{{ __('Details') }}</flux:heading>
|
||||
<dl class="space-y-2 text-sm">
|
||||
<aside class="space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Details') }}</span>
|
||||
</div>
|
||||
<dl class="p-5 space-y-2.5 text-[12.5px]">
|
||||
<div class="flex justify-between gap-2">
|
||||
<dt class="text-zinc-500">{{ __('Status') }}</dt>
|
||||
<dd class="font-medium">{{ $pr->status->label() }}</dd>
|
||||
<dt class="text-[color:var(--color-ink-3)]">{{ __('Status') }}</dt>
|
||||
<dd class="font-semibold text-[color:var(--color-ink)]">{{ $pr->status->label() }}</dd>
|
||||
</div>
|
||||
<div class="flex justify-between gap-2">
|
||||
<dt class="text-zinc-500">{{ __('Erstellt') }}</dt>
|
||||
<dd>{{ $pr->created_at->format('d.m.Y H:i') }}</dd>
|
||||
<dt class="text-[color:var(--color-ink-3)]">{{ __('Erstellt') }}</dt>
|
||||
<dd class="text-[color:var(--color-ink)]">{{ $pr->created_at->format('d.m.Y H:i') }}</dd>
|
||||
</div>
|
||||
@if($pr->published_at)
|
||||
@if ($pr->published_at)
|
||||
<div class="flex justify-between gap-2">
|
||||
<dt class="text-zinc-500">{{ __('Veröffentlicht') }}</dt>
|
||||
<dd>{{ $pr->published_at->format('d.m.Y H:i') }}</dd>
|
||||
<dt class="text-[color:var(--color-ink-3)]">{{ __('Veröffentlicht') }}</dt>
|
||||
<dd class="text-[color:var(--color-ink)]">{{ $pr->published_at->format('d.m.Y H:i') }}</dd>
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex justify-between gap-2">
|
||||
<dt class="text-zinc-500">{{ __('Aufrufe') }}</dt>
|
||||
<dd>{{ number_format($pr->hits) }}</dd>
|
||||
<dt class="text-[color:var(--color-ink-3)]">{{ __('Aufrufe') }}</dt>
|
||||
<dd class="text-[color:var(--color-ink)] font-semibold">{{ number_format($pr->hits) }}</dd>
|
||||
</div>
|
||||
@if($pr->keywords)
|
||||
<div>
|
||||
<dt class="text-zinc-500">{{ __('Stichwörter') }}</dt>
|
||||
<dd class="mt-1">{{ $pr->keywords }}</dd>
|
||||
@if ($pr->keywords)
|
||||
<div class="pt-2 border-t border-[color:var(--color-bg-rule)]">
|
||||
<dt class="text-[color:var(--color-ink-3)] mb-1">{{ __('Stichwörter') }}</dt>
|
||||
<dd class="text-[color:var(--color-ink)]">{{ $pr->keywords }}</dd>
|
||||
</div>
|
||||
@endif
|
||||
@if($pr->backlink_url)
|
||||
<div>
|
||||
<dt class="text-zinc-500">{{ __('Backlink') }}</dt>
|
||||
<dd class="mt-1 break-all">
|
||||
<a href="{{ $pr->backlink_url }}" target="_blank" class="text-blue-600 underline dark:text-blue-400">
|
||||
@if ($pr->backlink_url)
|
||||
<div class="pt-2 border-t border-[color:var(--color-bg-rule)]">
|
||||
<dt class="text-[color:var(--color-ink-3)] mb-1">{{ __('Backlink') }}</dt>
|
||||
<dd class="break-all">
|
||||
<a href="{{ $pr->backlink_url }}" target="_blank"
|
||||
class="text-[color:var(--color-hub)] underline underline-offset-2 decoration-[color:var(--color-hub)]/40 hover:decoration-[color:var(--color-hub)]">
|
||||
{{ $pr->backlink_url }}
|
||||
</a>
|
||||
</dd>
|
||||
</div>
|
||||
@endif
|
||||
@if($pr->no_export)
|
||||
<div class="rounded bg-zinc-100 px-2 py-1 text-xs text-zinc-600 dark:bg-zinc-700 dark:text-zinc-300">
|
||||
{{ __('Kein Export') }}
|
||||
@if ($pr->no_export)
|
||||
<div class="mt-2 pt-2 border-t border-[color:var(--color-bg-rule)]">
|
||||
<span class="badge hub">{{ __('Kein Export') }}</span>
|
||||
</div>
|
||||
@endif
|
||||
</dl>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
@if($pr->images->isNotEmpty())
|
||||
<flux:card>
|
||||
<flux:heading size="sm" class="mb-3">{{ __('Bilder') }}</flux:heading>
|
||||
<div class="space-y-2">
|
||||
@foreach($pr->images as $image)
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<flux:icon.photo class="size-4 text-zinc-400" />
|
||||
<span class="truncate text-zinc-600 dark:text-zinc-400">{{ basename($image->path) }}</span>
|
||||
@if($image->is_preview)
|
||||
<flux:badge size="sm" color="blue">{{ __('Preview') }}</flux:badge>
|
||||
@if ($pr->images->isNotEmpty())
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Bilder') }}</span>
|
||||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ $pr->images->count() }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-2">
|
||||
@foreach ($pr->images as $image)
|
||||
<div class="flex items-center gap-2 text-[12.5px]">
|
||||
<flux:icon.photo class="size-4 flex-shrink-0 text-[color:var(--color-ink-3)]" />
|
||||
<span class="truncate text-[color:var(--color-ink-2)]">{{ basename($image->path) }}</span>
|
||||
@if ($image->is_preview)
|
||||
<span class="badge hub">{{ __('Preview') }}</span>
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
@endif
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
@if($statusLogs->isNotEmpty())
|
||||
<flux:card>
|
||||
<flux:heading size="sm" class="mb-3">{{ __('Status-Verlauf') }}</flux:heading>
|
||||
<ol class="space-y-3 border-s border-zinc-200 ps-4 dark:border-zinc-700">
|
||||
@foreach($statusLogs as $log)
|
||||
<li class="text-sm">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@php
|
||||
$color = match($log->to_status?->value) {
|
||||
'published' => 'green',
|
||||
'review' => 'yellow',
|
||||
'rejected' => 'red',
|
||||
'archived' => 'blue',
|
||||
default => 'zinc',
|
||||
};
|
||||
@endphp
|
||||
<flux:badge size="sm" :color="$color">{{ $log->to_status?->label() ?? $log->to_status }}</flux:badge>
|
||||
@if($log->from_status)
|
||||
<span class="text-xs text-zinc-500">
|
||||
{{ __('von') }} {{ $log->from_status->label() }}
|
||||
{{-- ============== STATUS-VERLAUF ============== --}}
|
||||
@if ($statusLogs->isNotEmpty())
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Status-Verlauf') }}</span>
|
||||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ $statusLogs->count() }} {{ __('Einträge') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<ol class="space-y-3 border-s border-[color:var(--color-bg-rule)] ps-4">
|
||||
@foreach ($statusLogs as $log)
|
||||
<li class="text-[12.5px]">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@php
|
||||
$logClass = match ($log->to_status?->value) {
|
||||
'published' => 'ok',
|
||||
'review' => 'warn',
|
||||
'rejected' => 'err',
|
||||
default => 'hub',
|
||||
};
|
||||
@endphp
|
||||
<span @class(['badge', $logClass])>{{ $log->to_status?->label() ?? $log->to_status }}</span>
|
||||
@if ($log->from_status)
|
||||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ __('von') }} {{ $log->from_status->label() }}
|
||||
</span>
|
||||
@endif
|
||||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">·</span>
|
||||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ $log->created_at->format('d.m.Y H:i') }}
|
||||
</span>
|
||||
@if ($log->changedBy)
|
||||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">·</span>
|
||||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">{{ $log->changedBy->name }}</span>
|
||||
@endif
|
||||
@if ($log->source !== 'admin')
|
||||
<span class="badge hub">{{ $log->source }}</span>
|
||||
@endif
|
||||
</div>
|
||||
@if ($log->reason)
|
||||
<p class="mt-1.5 text-[color:var(--color-ink-2)] m-0">{{ $log->reason }}</p>
|
||||
@endif
|
||||
<span class="text-xs text-zinc-500">·</span>
|
||||
<span class="text-xs text-zinc-500">
|
||||
{{ $log->created_at->format('d.m.Y H:i') }}
|
||||
</span>
|
||||
@if($log->changedBy)
|
||||
<span class="text-xs text-zinc-500">·</span>
|
||||
<span class="text-xs text-zinc-500">{{ $log->changedBy->name }}</span>
|
||||
@endif
|
||||
@if($log->source !== 'admin')
|
||||
<flux:badge size="xs" color="zinc">{{ $log->source }}</flux:badge>
|
||||
@endif
|
||||
</div>
|
||||
@if($log->reason)
|
||||
<p class="mt-1 text-zinc-600 dark:text-zinc-400">{{ $log->reason }}</p>
|
||||
@endif
|
||||
</li>
|
||||
@endforeach
|
||||
</ol>
|
||||
</flux:card>
|
||||
</li>
|
||||
@endforeach
|
||||
</ol>
|
||||
</div>
|
||||
</article>
|
||||
@endif
|
||||
|
||||
@if($pr->status === \App\Enums\PressReleaseStatus::Review)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use Illuminate\Validation\ValidationException;
|
|||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new #[Layout('components.layouts.auth')] class extends Component {
|
||||
new #[Layout('components.layouts.auth.pressekonto', ['heading' => 'Passwort bestätigen', 'eyebrow' => 'Sicherheitsbereich', 'showFromBanner' => false])] class extends Component {
|
||||
public string $password = '';
|
||||
|
||||
/**
|
||||
|
|
@ -32,26 +32,43 @@ new #[Layout('components.layouts.auth')] class extends Component {
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="flex flex-col gap-6">
|
||||
<x-auth-header
|
||||
:title="__('Confirm password')"
|
||||
:description="__('This is a secure area of the application. Please confirm your password before continuing.')"
|
||||
/>
|
||||
<div>
|
||||
<p class="text-[13.5px] text-ink-2 leading-[1.65] !-mt-4 mb-6">
|
||||
Dieser Bereich des Publisher-Hubs ist besonders geschützt. Bitte bestätigen Sie zur
|
||||
Sicherheit erneut Ihr Passwort, bevor Sie fortfahren.
|
||||
</p>
|
||||
|
||||
<!-- Session Status -->
|
||||
<x-auth-session-status class="text-center" :status="session('status')" />
|
||||
<form wire:submit="confirmPassword" class="space-y-[18px]" x-data="{ showPassword: false }" novalidate>
|
||||
|
||||
<form wire:submit="confirmPassword" class="flex flex-col gap-6">
|
||||
<!-- Password -->
|
||||
<flux:input
|
||||
wire:model="password"
|
||||
:label="__('Password')"
|
||||
type="password"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
:placeholder="__('Password')"
|
||||
/>
|
||||
<div>
|
||||
<label class="field-label" for="auth-password">Passwort</label>
|
||||
<div class="field-pw-wrap">
|
||||
<input
|
||||
id="auth-password"
|
||||
wire:model="password"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
required
|
||||
autofocus
|
||||
autocomplete="current-password"
|
||||
class="field-input pr-[72px]"
|
||||
placeholder="••••••••••"
|
||||
@error('password') aria-invalid="true" @enderror
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="field-affix"
|
||||
@click="showPassword = !showPassword"
|
||||
x-text="showPassword ? 'Verbergen' : 'Anzeigen'"
|
||||
>Anzeigen</button>
|
||||
</div>
|
||||
@error('password')
|
||||
<p class="field-error">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<flux:button variant="primary" type="submit" class="w-full">{{ __('Confirm') }}</flux:button>
|
||||
<button type="submit" class="auth-btn-primary !mt-[22px]" wire:loading.attr="disabled" wire:target="confirmPassword">
|
||||
<span wire:loading.remove wire:target="confirmPassword">Bestätigen</span>
|
||||
<span wire:loading wire:target="confirmPassword">Wird geprüft …</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use Illuminate\Support\Facades\Password;
|
|||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new #[Layout('components.layouts.auth')] class extends Component {
|
||||
new #[Layout('components.layouts.auth.pressekonto', ['heading' => 'Passwort vergessen', 'eyebrow' => 'Passwort zurücksetzen', 'topRightLabel' => 'Doch erinnert?', 'topRightLinkText' => 'Zur Anmeldung', 'topRightLinkHref' => '/login'])] class extends Component {
|
||||
public string $email = '';
|
||||
|
||||
/**
|
||||
|
|
@ -22,28 +22,45 @@ new #[Layout('components.layouts.auth')] class extends Component {
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="flex flex-col gap-6">
|
||||
<x-auth-header :title="__('Forgot password')" :description="__('Enter your email to receive a password reset link')" />
|
||||
<div>
|
||||
<p class="text-[13.5px] text-ink-2 leading-[1.6] !-mt-4 mb-6">
|
||||
Geben Sie die E-Mail-Adresse Ihres Kontos ein. Sie erhalten innerhalb weniger Minuten
|
||||
einen Link, mit dem Sie ein neues Passwort vergeben können.
|
||||
</p>
|
||||
|
||||
<!-- Session Status -->
|
||||
<x-auth-session-status class="text-center" :status="session('status')" />
|
||||
@if (session('status'))
|
||||
<div class="field-status mb-4" role="status">
|
||||
{{ session('status') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form wire:submit="sendPasswordResetLink" class="flex flex-col gap-6">
|
||||
<!-- Email Address -->
|
||||
<flux:input
|
||||
wire:model="email"
|
||||
:label="__('Email Address')"
|
||||
type="email"
|
||||
required
|
||||
autofocus
|
||||
placeholder="email@example.com"
|
||||
/>
|
||||
<form wire:submit="sendPasswordResetLink" class="space-y-[18px]" novalidate>
|
||||
|
||||
<flux:button variant="primary" type="submit" class="w-full">{{ __('Email password reset link') }}</flux:button>
|
||||
<div>
|
||||
<label class="field-label" for="auth-email">E-Mail-Adresse</label>
|
||||
<input
|
||||
id="auth-email"
|
||||
type="email"
|
||||
wire:model="email"
|
||||
required
|
||||
autofocus
|
||||
autocomplete="username"
|
||||
class="field-input"
|
||||
placeholder="redaktion@ihr-unternehmen.de"
|
||||
@error('email') aria-invalid="true" @enderror
|
||||
/>
|
||||
@error('email')
|
||||
<p class="field-error">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<button type="submit" class="auth-btn-primary !mt-[22px]" wire:loading.attr="disabled" wire:target="sendPasswordResetLink">
|
||||
<span wire:loading.remove wire:target="sendPasswordResetLink">Reset-Link senden</span>
|
||||
<span wire:loading wire:target="sendPasswordResetLink">Link wird gesendet …</span>
|
||||
</button>
|
||||
|
||||
<div class="flex items-center justify-center pt-2 text-[12.5px] text-ink-3">
|
||||
<a href="{{ route('login') }}" class="link-hub" wire:navigate>← Zurück zur Anmeldung</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="space-x-1 text-center text-sm text-zinc-400">
|
||||
{{ __('Or, return to') }}
|
||||
<flux:link :href="route('login')" wire:navigate>{{ __('log in') }}</flux:link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Auth\Events\Lockout;
|
||||
use App\Mail\MagicLoginLink;
|
||||
use App\Services\Auth\MagicLinkGenerator;
|
||||
use App\Models\User;
|
||||
use App\Services\Auth\MagicLinkGenerator;
|
||||
use Illuminate\Auth\Events\Lockout;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
|
|
@ -15,7 +15,7 @@ use Livewire\Attributes\Layout;
|
|||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new #[Layout('components.layouts.auth')] class extends Component {
|
||||
new #[Layout('components.layouts.auth.pressekonto', ['heading' => 'Willkommen zurück', 'eyebrow' => 'Anmeldung im Publisher-Hub', 'topRightLabel' => 'Noch kein Konto?', 'topRightLinkText' => 'Konto erstellen', 'topRightLinkHref' => '/register'])] class extends Component {
|
||||
#[Validate('required|string|email')]
|
||||
public string $email = '';
|
||||
|
||||
|
|
@ -52,7 +52,18 @@ new #[Layout('components.layouts.auth')] class extends Component {
|
|||
RateLimiter::clear($this->throttleKey());
|
||||
Session::regenerate();
|
||||
|
||||
$this->redirectIntended(default: route('dashboard', absolute: false), navigate: true);
|
||||
// Rollen-basierter Default-Redirect:
|
||||
// Admin/Editor → /dashboard, Customer → /admin/me.
|
||||
// Ohne navigate:true, weil das Portal ein anderes Vite-Bundle nutzt
|
||||
// (build/portal mit FluxUI) als das Hub-Auth-Layout (build/web).
|
||||
// SPA-Navigation kann den Bundle-Wechsel nicht handhaben.
|
||||
$defaultRoute = $authenticatedUser?->canAccessAdmin()
|
||||
? route('dashboard', absolute: false)
|
||||
: ($authenticatedUser?->canAccessCustomer()
|
||||
? route('me.dashboard', absolute: false)
|
||||
: '/');
|
||||
|
||||
$this->redirectIntended(default: $defaultRoute);
|
||||
}
|
||||
|
||||
public function sendMagicLink(): void
|
||||
|
|
@ -107,63 +118,94 @@ new #[Layout('components.layouts.auth')] class extends Component {
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="flex flex-col gap-6">
|
||||
<x-auth-header :title="__('Log in to your account')" :description="__('Enter your email and password below to log in')" />
|
||||
|
||||
<!-- Session Status -->
|
||||
<x-auth-session-status class="text-center" :status="session('status')" />
|
||||
|
||||
<form wire:submit="login" class="flex flex-col gap-6">
|
||||
<!-- Email Address -->
|
||||
<flux:input
|
||||
wire:model="email"
|
||||
:label="__('Email address')"
|
||||
type="email"
|
||||
required
|
||||
autofocus
|
||||
autocomplete="email"
|
||||
placeholder="email@example.com"
|
||||
/>
|
||||
|
||||
<!-- Password -->
|
||||
<div class="relative">
|
||||
<flux:input
|
||||
wire:model="password"
|
||||
:label="__('Password')"
|
||||
type="password"
|
||||
required
|
||||
autocomplete="current-password"
|
||||
:placeholder="__('Password')"
|
||||
/>
|
||||
|
||||
@if (Route::has('password.request'))
|
||||
<flux:link class="absolute right-0 top-0 text-sm" :href="route('password.request')" wire:navigate>
|
||||
{{ __('Forgot your password?') }}
|
||||
</flux:link>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Remember Me -->
|
||||
<flux:checkbox wire:model="remember" :label="__('Remember me')" />
|
||||
|
||||
<div class="flex items-center justify-end">
|
||||
<flux:button variant="primary" type="submit" class="w-full">{{ __('Log in') }}</flux:button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="rounded-lg border border-zinc-200 p-4 dark:border-zinc-700">
|
||||
<p class="mb-3 text-sm text-zinc-600 dark:text-zinc-300">
|
||||
{{ __('Login without password? Request a one-time email link.') }}
|
||||
</p>
|
||||
<flux:button variant="subtle" wire:click="sendMagicLink" class="w-full">
|
||||
{{ __('Send magic login link') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
||||
@if (Route::has('register'))
|
||||
<div class="space-x-1 text-center text-sm text-zinc-600 dark:text-zinc-400">
|
||||
{{ __('Don\'t have an account?') }}
|
||||
<flux:link :href="route('register')" wire:navigate>{{ __('Sign up') }}</flux:link>
|
||||
<div>
|
||||
@if (session('status'))
|
||||
<div class="field-status mb-4" role="status">
|
||||
{{ session('status') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form wire:submit="login" class="space-y-[18px]" x-data="{ showPassword: false }" novalidate>
|
||||
|
||||
<div>
|
||||
<label class="field-label" for="auth-email">E-Mail-Adresse</label>
|
||||
<input
|
||||
id="auth-email"
|
||||
type="email"
|
||||
wire:model="email"
|
||||
autocomplete="username"
|
||||
required
|
||||
autofocus
|
||||
class="field-input"
|
||||
placeholder="redaktion@ihr-unternehmen.de"
|
||||
@error('email') aria-invalid="true" @enderror
|
||||
/>
|
||||
@error('email')
|
||||
<p class="field-error">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex items-baseline justify-between mb-1.5">
|
||||
<label class="field-label !mb-0" for="auth-password">Passwort</label>
|
||||
@if (\Illuminate\Support\Facades\Route::has('password.request'))
|
||||
<a href="{{ route('password.request') }}" class="link-hub text-[12px]" wire:navigate>
|
||||
Passwort vergessen?
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
<div class="field-pw-wrap">
|
||||
<input
|
||||
id="auth-password"
|
||||
wire:model="password"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
class="field-input pr-[72px]"
|
||||
placeholder="••••••••••"
|
||||
@error('password') aria-invalid="true" @enderror
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="field-affix"
|
||||
@click="showPassword = !showPassword"
|
||||
x-text="showPassword ? 'Verbergen' : 'Anzeigen'"
|
||||
>Anzeigen</button>
|
||||
</div>
|
||||
@error('password')
|
||||
<p class="field-error">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<label class="flex items-center gap-2.5 text-[12.5px] text-ink-2 cursor-pointer select-none">
|
||||
<input type="checkbox" wire:model="remember" class="auth-check" />
|
||||
Angemeldet bleiben
|
||||
</label>
|
||||
|
||||
<button type="submit" class="auth-btn-primary !mt-[22px]" wire:loading.attr="disabled" wire:target="login">
|
||||
<span wire:loading.remove wire:target="login">Anmelden</span>
|
||||
<span wire:loading wire:target="login">Anmelden …</span>
|
||||
</button>
|
||||
|
||||
<div class="flex items-center gap-3 !mt-[22px] !mb-[14px]">
|
||||
<span class="flex-1 h-px bg-bg-rule"></span>
|
||||
<span class="text-[11px] font-semibold tracking-[0.18em] uppercase text-ink-3">oder</span>
|
||||
<span class="flex-1 h-px bg-bg-rule"></span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
wire:click="sendMagicLink"
|
||||
wire:loading.attr="disabled"
|
||||
wire:target="sendMagicLink"
|
||||
class="auth-btn-outline !mt-0"
|
||||
>
|
||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<rect x="2" y="3" width="12" height="10" stroke="currentColor" stroke-width="1.4" />
|
||||
<path d="M2.5 4l5.5 5 5.5-5" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round" />
|
||||
</svg>
|
||||
<span wire:loading.remove wire:target="sendMagicLink">Magic-Link senden</span>
|
||||
<span wire:loading wire:target="sendMagicLink">Magic-Link wird gesendet …</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,12 +8,17 @@ use Illuminate\Validation\Rules;
|
|||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new #[Layout('components.layouts.auth')] class extends Component {
|
||||
new #[Layout('components.layouts.auth.pressekonto', ['heading' => 'Konto erstellen', 'eyebrow' => 'Registrierung im Publisher-Hub', 'topRightLabel' => 'Bereits Konto?', 'topRightLinkText' => 'Anmelden', 'topRightLinkHref' => '/login'])] class extends Component {
|
||||
public string $name = '';
|
||||
|
||||
public string $email = '';
|
||||
|
||||
public string $password = '';
|
||||
|
||||
public string $password_confirmation = '';
|
||||
|
||||
public bool $terms_accepted = false;
|
||||
|
||||
/**
|
||||
* Handle an incoming registration request.
|
||||
*/
|
||||
|
|
@ -21,77 +26,150 @@ new #[Layout('components.layouts.auth')] class extends Component {
|
|||
{
|
||||
$validated = $this->validate([
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:' . User::class],
|
||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
|
||||
'password' => ['required', 'string', 'confirmed', Rules\Password::defaults()],
|
||||
'terms_accepted' => ['accepted'],
|
||||
], [
|
||||
'terms_accepted.accepted' => 'Bitte bestätigen Sie unsere AGB und die Datenschutzerklärung.',
|
||||
]);
|
||||
|
||||
unset($validated['terms_accepted']);
|
||||
|
||||
$validated['password'] = Hash::make($validated['password']);
|
||||
|
||||
event(new Registered(($user = User::create($validated))));
|
||||
event(new Registered($user = User::create($validated)));
|
||||
|
||||
Auth::login($user);
|
||||
|
||||
$this->redirectIntended(route('dashboard', absolute: false), navigate: true);
|
||||
// Frisch registrierte User sind in der Regel Customer ohne Admin-
|
||||
// Rollen → /admin/me. Ohne navigate:true, weil das Panel ein
|
||||
// anderes Vite-Bundle nutzt als das Hub-Auth-Layout.
|
||||
$defaultRoute = $user->canAccessAdmin()
|
||||
? route('dashboard', absolute: false)
|
||||
: ($user->canAccessCustomer()
|
||||
? route('me.dashboard', absolute: false)
|
||||
: '/');
|
||||
|
||||
$this->redirectIntended($defaultRoute);
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div class="flex flex-col gap-6">
|
||||
<x-auth-header :title="__('Create an account')" :description="__('Enter your details below to create your account')" />
|
||||
|
||||
<!-- Session Status -->
|
||||
<x-auth-session-status class="text-center" :status="session('status')" />
|
||||
|
||||
<form wire:submit="register" class="flex flex-col gap-6">
|
||||
<!-- Name -->
|
||||
<flux:input
|
||||
wire:model="name"
|
||||
:label="__('Name')"
|
||||
type="text"
|
||||
required
|
||||
autofocus
|
||||
autocomplete="name"
|
||||
:placeholder="__('Full name')"
|
||||
/>
|
||||
|
||||
<!-- Email Address -->
|
||||
<flux:input
|
||||
wire:model="email"
|
||||
:label="__('Email address')"
|
||||
type="email"
|
||||
required
|
||||
autocomplete="email"
|
||||
placeholder="email@example.com"
|
||||
/>
|
||||
|
||||
<!-- Password -->
|
||||
<flux:input
|
||||
wire:model="password"
|
||||
:label="__('Password')"
|
||||
type="password"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
:placeholder="__('Password')"
|
||||
/>
|
||||
|
||||
<!-- Confirm Password -->
|
||||
<flux:input
|
||||
wire:model="password_confirmation"
|
||||
:label="__('Confirm password')"
|
||||
type="password"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
:placeholder="__('Confirm password')"
|
||||
/>
|
||||
|
||||
<div class="flex items-center justify-end">
|
||||
<flux:button type="submit" variant="primary" class="w-full">
|
||||
{{ __('Create account') }}
|
||||
</flux:button>
|
||||
<div>
|
||||
@if (session('status'))
|
||||
<div class="field-status mb-4" role="status">
|
||||
{{ session('status') }}
|
||||
</div>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
<div class="space-x-1 text-center text-sm text-zinc-600 dark:text-zinc-400">
|
||||
{{ __('Already have an account?') }}
|
||||
<flux:link :href="route('login')" wire:navigate>{{ __('Log in') }}</flux:link>
|
||||
</div>
|
||||
<form wire:submit="register" class="space-y-[18px]" x-data="{ showPassword: false, showPasswordConfirmation: false }" novalidate>
|
||||
|
||||
<div>
|
||||
<label class="field-label" for="auth-name">Name</label>
|
||||
<input
|
||||
id="auth-name"
|
||||
type="text"
|
||||
wire:model="name"
|
||||
required
|
||||
autofocus
|
||||
autocomplete="name"
|
||||
class="field-input"
|
||||
placeholder="Vor- und Nachname"
|
||||
@error('name') aria-invalid="true" @enderror
|
||||
/>
|
||||
@error('name')
|
||||
<p class="field-error">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="field-label" for="auth-email">E-Mail-Adresse</label>
|
||||
<input
|
||||
id="auth-email"
|
||||
type="email"
|
||||
wire:model="email"
|
||||
required
|
||||
autocomplete="email"
|
||||
class="field-input"
|
||||
placeholder="redaktion@ihr-unternehmen.de"
|
||||
@error('email') aria-invalid="true" @enderror
|
||||
/>
|
||||
@error('email')
|
||||
<p class="field-error">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="field-label" for="auth-password">Passwort</label>
|
||||
<div class="field-pw-wrap">
|
||||
<input
|
||||
id="auth-password"
|
||||
wire:model="password"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
class="field-input pr-[72px]"
|
||||
placeholder="Mindestens 8 Zeichen"
|
||||
@error('password') aria-invalid="true" @enderror
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="field-affix"
|
||||
@click="showPassword = !showPassword"
|
||||
x-text="showPassword ? 'Verbergen' : 'Anzeigen'"
|
||||
>Anzeigen</button>
|
||||
</div>
|
||||
@error('password')
|
||||
<p class="field-error">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="field-label" for="auth-password-confirmation">Passwort bestätigen</label>
|
||||
<div class="field-pw-wrap">
|
||||
<input
|
||||
id="auth-password-confirmation"
|
||||
wire:model="password_confirmation"
|
||||
:type="showPasswordConfirmation ? 'text' : 'password'"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
class="field-input pr-[72px]"
|
||||
placeholder="Passwort wiederholen"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="field-affix"
|
||||
@click="showPasswordConfirmation = !showPasswordConfirmation"
|
||||
x-text="showPasswordConfirmation ? 'Verbergen' : 'Anzeigen'"
|
||||
>Anzeigen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="!mt-5">
|
||||
<label for="auth-terms" class="flex items-start gap-3 cursor-pointer select-none">
|
||||
<input
|
||||
id="auth-terms"
|
||||
type="checkbox"
|
||||
wire:model="terms_accepted"
|
||||
required
|
||||
class="auth-check !mt-[3px]"
|
||||
@error('terms_accepted') aria-invalid="true" @enderror
|
||||
/>
|
||||
<span class="text-[12.5px] text-ink-2 leading-[1.55]">
|
||||
Ich habe die
|
||||
<a href="{{ route('agb') }}" target="_blank" rel="noopener" class="link-hub">AGB</a>
|
||||
und die
|
||||
<a href="{{ route('datenschutz') }}" target="_blank" rel="noopener" class="link-hub">Datenschutzerklärung</a>
|
||||
gelesen und stimme der Verarbeitung meiner Daten zur Konto-Erstellung ausdrücklich zu.
|
||||
</span>
|
||||
</label>
|
||||
@error('terms_accepted')
|
||||
<p class="field-error !ml-7">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<button type="submit" class="auth-btn-primary !mt-[18px]" wire:loading.attr="disabled" wire:target="register">
|
||||
<span wire:loading.remove wire:target="register">Konto erstellen</span>
|
||||
<span wire:loading wire:target="register">Konto wird angelegt …</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,11 +10,14 @@ use Livewire\Attributes\Layout;
|
|||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new #[Layout('components.layouts.auth')] class extends Component {
|
||||
new #[Layout('components.layouts.auth.pressekonto', ['heading' => 'Neues Passwort vergeben', 'eyebrow' => 'Passwort zurücksetzen'])] class extends Component {
|
||||
#[Locked]
|
||||
public string $token = '';
|
||||
|
||||
public string $email = '';
|
||||
|
||||
public string $password = '';
|
||||
|
||||
public string $password_confirmation = '';
|
||||
|
||||
/**
|
||||
|
|
@ -38,9 +41,6 @@ new #[Layout('components.layouts.auth')] class extends Component {
|
|||
'password' => ['required', 'string', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
// Here we will attempt to reset the user's password. If it is successful we
|
||||
// will update the password on an actual user model and persist it to the
|
||||
// database. Otherwise we will parse the error and return the response.
|
||||
$status = Password::reset(
|
||||
$this->only('email', 'password', 'password_confirmation', 'token'),
|
||||
function ($user) {
|
||||
|
|
@ -53,9 +53,6 @@ new #[Layout('components.layouts.auth')] class extends Component {
|
|||
}
|
||||
);
|
||||
|
||||
// If the password was successfully reset, we will redirect the user back to
|
||||
// the application's home authenticated view. If there is an error we can
|
||||
// redirect them back to where they came from with their error message.
|
||||
if ($status != Password::PasswordReset) {
|
||||
$this->addError('email', __($status));
|
||||
|
||||
|
|
@ -68,46 +65,79 @@ new #[Layout('components.layouts.auth')] class extends Component {
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="flex flex-col gap-6">
|
||||
<x-auth-header :title="__('Reset password')" :description="__('Please enter your new password below')" />
|
||||
<div>
|
||||
<p class="text-[13.5px] text-ink-2 leading-[1.6] !-mt-4 mb-6">
|
||||
Vergeben Sie ein neues Passwort für Ihr Konto. Mindestens 8 Zeichen, idealerweise eine
|
||||
Kombination aus Buchstaben, Zahlen und Sonderzeichen.
|
||||
</p>
|
||||
|
||||
<!-- Session Status -->
|
||||
<x-auth-session-status class="text-center" :status="session('status')" />
|
||||
<form wire:submit="resetPassword" class="space-y-[18px]" x-data="{ showPassword: false, showPasswordConfirmation: false }" novalidate>
|
||||
|
||||
<form wire:submit="resetPassword" class="flex flex-col gap-6">
|
||||
<!-- Email Address -->
|
||||
<flux:input
|
||||
wire:model="email"
|
||||
:label="__('Email')"
|
||||
type="email"
|
||||
required
|
||||
autocomplete="email"
|
||||
/>
|
||||
|
||||
<!-- Password -->
|
||||
<flux:input
|
||||
wire:model="password"
|
||||
:label="__('Password')"
|
||||
type="password"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
:placeholder="__('Password')"
|
||||
/>
|
||||
|
||||
<!-- Confirm Password -->
|
||||
<flux:input
|
||||
wire:model="password_confirmation"
|
||||
:label="__('Confirm password')"
|
||||
type="password"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
:placeholder="__('Confirm password')"
|
||||
/>
|
||||
|
||||
<div class="flex items-center justify-end">
|
||||
<flux:button type="submit" variant="primary" class="w-full">
|
||||
{{ __('Reset password') }}
|
||||
</flux:button>
|
||||
<div>
|
||||
<label class="field-label" for="auth-email">E-Mail-Adresse</label>
|
||||
<input
|
||||
id="auth-email"
|
||||
type="email"
|
||||
wire:model="email"
|
||||
required
|
||||
autocomplete="email"
|
||||
class="field-input"
|
||||
@error('email') aria-invalid="true" @enderror
|
||||
/>
|
||||
@error('email')
|
||||
<p class="field-error">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="field-label" for="auth-password">Neues Passwort</label>
|
||||
<div class="field-pw-wrap">
|
||||
<input
|
||||
id="auth-password"
|
||||
wire:model="password"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
class="field-input pr-[72px]"
|
||||
placeholder="Mindestens 8 Zeichen"
|
||||
@error('password') aria-invalid="true" @enderror
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="field-affix"
|
||||
@click="showPassword = !showPassword"
|
||||
x-text="showPassword ? 'Verbergen' : 'Anzeigen'"
|
||||
>Anzeigen</button>
|
||||
</div>
|
||||
@error('password')
|
||||
<p class="field-error">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="field-label" for="auth-password-confirmation">Passwort bestätigen</label>
|
||||
<div class="field-pw-wrap">
|
||||
<input
|
||||
id="auth-password-confirmation"
|
||||
wire:model="password_confirmation"
|
||||
:type="showPasswordConfirmation ? 'text' : 'password'"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
class="field-input pr-[72px]"
|
||||
placeholder="Neues Passwort wiederholen"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="field-affix"
|
||||
@click="showPasswordConfirmation = !showPasswordConfirmation"
|
||||
x-text="showPasswordConfirmation ? 'Verbergen' : 'Anzeigen'"
|
||||
>Anzeigen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="auth-btn-primary !mt-[22px]" wire:loading.attr="disabled" wire:target="resetPassword">
|
||||
<span wire:loading.remove wire:target="resetPassword">Passwort zurücksetzen</span>
|
||||
<span wire:loading wire:target="resetPassword">Passwort wird gespeichert …</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use Illuminate\Support\Facades\Session;
|
|||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new #[Layout('components.layouts.auth')] class extends Component {
|
||||
new #[Layout('components.layouts.auth.pressekonto', ['heading' => 'E-Mail-Adresse bestätigen', 'eyebrow' => 'Konto-Verifizierung', 'showFromBanner' => false])] class extends Component {
|
||||
/**
|
||||
* Send an email verification notification to the user.
|
||||
*/
|
||||
|
|
@ -34,24 +34,37 @@ new #[Layout('components.layouts.auth')] class extends Component {
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="mt-4 flex flex-col gap-6">
|
||||
<flux:text class="text-center">
|
||||
{{ __('Please verify your email address by clicking on the link we just emailed to you.') }}
|
||||
</flux:text>
|
||||
<div>
|
||||
<p class="text-[13.5px] text-ink-2 leading-[1.65] !-mt-4 mb-6">
|
||||
Wir haben Ihnen einen Bestätigungslink an
|
||||
<strong class="text-ink font-semibold">{{ Auth::user()?->email }}</strong>
|
||||
gesendet. Bitte öffnen Sie die Mail und klicken Sie auf den Link, um Ihre E-Mail-Adresse zu bestätigen.
|
||||
</p>
|
||||
|
||||
@if (session('status') == 'verification-link-sent')
|
||||
<flux:text class="text-center font-medium !dark:text-green-400 !text-green-600">
|
||||
{{ __('A new verification link has been sent to the email address you provided during registration.') }}
|
||||
</flux:text>
|
||||
@if (session('status') === 'verification-link-sent')
|
||||
<div class="field-status mb-6" role="status">
|
||||
Ein neuer Bestätigungslink wurde an Ihre E-Mail-Adresse versendet.
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="flex flex-col items-center justify-between space-y-3">
|
||||
<flux:button wire:click="sendVerification" variant="primary" class="w-full">
|
||||
{{ __('Resend verification email') }}
|
||||
</flux:button>
|
||||
<div class="space-y-3">
|
||||
<button
|
||||
type="button"
|
||||
wire:click="sendVerification"
|
||||
wire:loading.attr="disabled"
|
||||
wire:target="sendVerification"
|
||||
class="auth-btn-primary"
|
||||
>
|
||||
<span wire:loading.remove wire:target="sendVerification">Bestätigungs-Mail erneut senden</span>
|
||||
<span wire:loading wire:target="sendVerification">Mail wird gesendet …</span>
|
||||
</button>
|
||||
|
||||
<flux:link class="text-sm cursor-pointer" wire:click="logout">
|
||||
{{ __('Log out') }}
|
||||
</flux:link>
|
||||
<button
|
||||
type="button"
|
||||
wire:click="logout"
|
||||
class="w-full text-center text-[12.5px] text-ink-3 hover:text-hub transition-colors py-2"
|
||||
>
|
||||
Abmelden
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\PressReleaseStatus;
|
||||
use App\Models\BillingAddress;
|
||||
use App\Models\Company;
|
||||
use App\Models\Contact;
|
||||
use App\Models\PressRelease;
|
||||
use App\Models\Profile;
|
||||
use App\Models\User;
|
||||
use App\Services\Customer\CustomerCompanyContext;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Attributes\Title;
|
||||
use Livewire\Volt\Component;
|
||||
|
|
@ -35,6 +38,9 @@ new #[Layout('components.layouts.app'), Title('Mein Dashboard')] class extends C
|
|||
->limit(5)
|
||||
->get(['id', 'title', 'status', 'company_id', 'created_at']);
|
||||
|
||||
$profile = $user->profile;
|
||||
$billingAddress = $user->billingAddress;
|
||||
|
||||
return [
|
||||
'user' => $user,
|
||||
'selectedCompany' => $selectedCompany,
|
||||
|
|
@ -43,36 +49,123 @@ new #[Layout('components.layouts.app'), Title('Mein Dashboard')] class extends C
|
|||
'published' => $myPRs->get('published', 0),
|
||||
'review' => $myPRs->get('review', 0),
|
||||
'draft' => $myPRs->get('draft', 0),
|
||||
'deltaMonth' => $this->totalDeltaToPreviousMonth(clone $pressReleaseQuery),
|
||||
],
|
||||
'qualityHints' => $this->qualityHints($user, $selectedCompany, $pressReleaseQuery),
|
||||
'profileCompleteness' => $this->profileCompleteness($profile),
|
||||
'billingCompleteness' => $this->billingCompleteness($billingAddress),
|
||||
'qualityHints' => $this->qualityHints(
|
||||
$user,
|
||||
$profile,
|
||||
$billingAddress,
|
||||
$selectedCompany,
|
||||
$pressReleaseQuery,
|
||||
),
|
||||
'recent' => $recent,
|
||||
'companies' => $context->companiesFor($user),
|
||||
'bridgeStatus' => [
|
||||
/* Heute hardcoded — perspektivisch aus echtem Sync-Service. */
|
||||
'presseecho' => ['state' => 'connected', 'subline' => __('Archiv · Branchen-Tiefe')],
|
||||
'businessportal24' => ['state' => 'connected', 'subline' => __('Wirtschaft · Live')],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function qualityHints(User $user, ?Company $selectedCompany, Builder $pressReleaseQuery): array
|
||||
/**
|
||||
* Heuristische Profil-Vollständigkeit in %.
|
||||
* 6 Kernfelder, jedes ~17 %.
|
||||
*/
|
||||
private function profileCompleteness(?Profile $profile): int
|
||||
{
|
||||
if (! $profile) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$fields = [
|
||||
'salutation_key',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'phone',
|
||||
'address',
|
||||
'country_code',
|
||||
];
|
||||
|
||||
$filled = collect($fields)->filter(fn (string $field) => filled($profile->{$field}))->count();
|
||||
|
||||
return (int) round(($filled / count($fields)) * 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rechnungsadressen-Vollständigkeit in %.
|
||||
* Ohne hinterlegte Adresse: 0; sonst je nach gefüllten Pflichtfeldern.
|
||||
*/
|
||||
private function billingCompleteness(?BillingAddress $address): int
|
||||
{
|
||||
if (! $address) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$fields = ['name', 'address1', 'postal_code', 'city', 'country_code'];
|
||||
$filled = collect($fields)->filter(fn (string $field) => filled($address->{$field}))->count();
|
||||
|
||||
return (int) round(($filled / count($fields)) * 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vergleicht PRs im aktuellen Monat mit dem Vormonat (Differenz, Vorzeichen mit Pfeil im View).
|
||||
*/
|
||||
private function totalDeltaToPreviousMonth(Builder $pressReleaseQuery): int
|
||||
{
|
||||
$now = Carbon::now();
|
||||
$currentStart = $now->copy()->startOfMonth();
|
||||
$previousStart = $now->copy()->subMonthNoOverflow()->startOfMonth();
|
||||
$previousEnd = $now->copy()->subMonthNoOverflow()->endOfMonth();
|
||||
|
||||
$currentCount = (clone $pressReleaseQuery)
|
||||
->where('created_at', '>=', $currentStart)
|
||||
->count();
|
||||
|
||||
$previousCount = (clone $pressReleaseQuery)
|
||||
->whereBetween('created_at', [$previousStart, $previousEnd])
|
||||
->count();
|
||||
|
||||
return $currentCount - $previousCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{icon: string, title: string, description: string, href: string, action: string, percent?: int}>
|
||||
*/
|
||||
private function qualityHints(
|
||||
User $user,
|
||||
?Profile $profile,
|
||||
?BillingAddress $billingAddress,
|
||||
?Company $selectedCompany,
|
||||
Builder $pressReleaseQuery,
|
||||
): array {
|
||||
$hints = [];
|
||||
|
||||
if (! $user->profile()->exists()) {
|
||||
$profilePercent = $this->profileCompleteness($profile);
|
||||
if ($profilePercent < 100) {
|
||||
$hints[] = [
|
||||
'color' => 'amber',
|
||||
'icon' => 'user',
|
||||
'title' => __('Profil unvollständig'),
|
||||
'description' => __('Ergänzen Sie Ihre Profildaten für eine sauberere Kundenakte.'),
|
||||
'description' => __('Ergänzen Sie Salutation, Telefon und Adresse für eine saubere Kundenakte.'),
|
||||
'href' => route('me.profile').'#profil',
|
||||
'action' => __('Profil öffnen'),
|
||||
'percent' => $profilePercent,
|
||||
];
|
||||
}
|
||||
|
||||
if (! $user->billingAddress()->exists()) {
|
||||
$billingPercent = $this->billingCompleteness($billingAddress);
|
||||
if ($billingPercent < 100) {
|
||||
$hints[] = [
|
||||
'color' => 'amber',
|
||||
'icon' => 'archive-box',
|
||||
'title' => __('Rechnungsadresse fehlt'),
|
||||
'title' => $billingAddress
|
||||
? __('Rechnungsadresse unvollständig')
|
||||
: __('Rechnungsadresse fehlt'),
|
||||
'description' => __('Hinterlegen Sie eine Rechnungsadresse, damit spätere Buchungen sauber abgerechnet werden können.'),
|
||||
'href' => route('me.profile').'#rechnungsadresse',
|
||||
'action' => __('Rechnungsadresse ergänzen'),
|
||||
'percent' => $billingPercent,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -83,7 +176,6 @@ new #[Layout('components.layouts.app'), Title('Mein Dashboard')] class extends C
|
|||
|
||||
if ($contactsCount === 0) {
|
||||
$hints[] = [
|
||||
'color' => 'blue',
|
||||
'icon' => 'user-group',
|
||||
'title' => __('Keine Pressekontakte hinterlegt'),
|
||||
'description' => __('Ergänzen Sie Pressekontakte für diese Firma.'),
|
||||
|
|
@ -98,9 +190,12 @@ new #[Layout('components.layouts.app'), Title('Mein Dashboard')] class extends C
|
|||
|
||||
if ($unassignedPressReleasesCount > 0) {
|
||||
$hints[] = [
|
||||
'color' => 'amber',
|
||||
'icon' => 'newspaper',
|
||||
'title' => trans_choice(':count Pressemitteilung ohne Firma|:count Pressemitteilungen ohne Firma', $unassignedPressReleasesCount, ['count' => $unassignedPressReleasesCount]),
|
||||
'title' => trans_choice(
|
||||
':count Pressemitteilung ohne Firma|:count Pressemitteilungen ohne Firma',
|
||||
$unassignedPressReleasesCount,
|
||||
['count' => $unassignedPressReleasesCount],
|
||||
),
|
||||
'description' => __('Ordnen Sie Legacy-Pressemitteilungen einer Firma zu, damit Portal und Pressekontakte eindeutig sind.'),
|
||||
'href' => route('me.press-releases.index', ['company' => 'unassigned']),
|
||||
'action' => __('Pressemitteilungen prüfen'),
|
||||
|
|
@ -112,126 +207,371 @@ new #[Layout('components.layouts.app'), Title('Mein Dashboard')] class extends C
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
<flux:card>
|
||||
<flux:heading size="lg">{{ __('Willkommen, :name', ['name' => $user->name]) }}</flux:heading>
|
||||
<flux:subheading>
|
||||
{{ $selectedCompany
|
||||
? __('Übersicht für :company', ['company' => $selectedCompany->name])
|
||||
: __('Übersicht Ihres Kundenkontos') }}
|
||||
</flux:subheading>
|
||||
</flux:card>
|
||||
<div class="space-y-8">
|
||||
|
||||
{{-- Statistiken --}}
|
||||
<div class="grid grid-cols-2 gap-4 sm:grid-cols-4">
|
||||
<flux:card>
|
||||
<flux:text class="text-xs text-zinc-500">{{ __('Gesamt') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $stats['total'] }}</flux:text>
|
||||
</flux:card>
|
||||
<flux:card>
|
||||
<flux:text class="text-xs text-green-600">{{ __('Veröffentlicht') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $stats['published'] }}</flux:text>
|
||||
</flux:card>
|
||||
<flux:card>
|
||||
<flux:text class="text-xs text-yellow-600">{{ __('In Prüfung') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $stats['review'] }}</flux:text>
|
||||
</flux:card>
|
||||
<flux:card>
|
||||
<flux:text class="text-xs text-zinc-500">{{ __('Entwürfe') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $stats['draft'] }}</flux:text>
|
||||
</flux:card>
|
||||
</div>
|
||||
|
||||
@if($qualityHints)
|
||||
<flux:card>
|
||||
<div class="mb-4">
|
||||
<flux:heading size="sm">{{ __('Datenqualität') }}</flux:heading>
|
||||
<flux:text class="text-sm text-zinc-500">{{ __('Diese Hinweise helfen, Ihr User Backend vollständig und sauber zu halten.') }}</flux:text>
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-nowrap whitespace-nowrap">
|
||||
<span class="badge hub dot">{{ __('User Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Mein Bereich · A · 01') }}</span>
|
||||
</div>
|
||||
<h1 class="text-[34px] font-bold tracking-[-0.7px] leading-[1.1] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Mein Dashboard') }}
|
||||
</h1>
|
||||
<p class="text-[13.5px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
|
||||
{{ __('Willkommen zurück, ') }}<strong class="font-semibold text-[color:var(--color-ink)]">{{ $user->name }}</strong>.
|
||||
@if ($selectedCompany)
|
||||
{{ __('Übersicht für :company', ['company' => $selectedCompany->name]) }} —
|
||||
@endif
|
||||
{{ __('Hier sehen Sie Status und Reichweite Ihres Kundenkontos für presseecho und businessportal24.') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-3 lg:grid-cols-3">
|
||||
@foreach($qualityHints as $hint)
|
||||
<a href="{{ $hint['href'] }}" wire:navigate class="rounded-lg border border-zinc-200 p-4 transition hover:bg-zinc-50 dark:border-zinc-700 dark:hover:bg-zinc-900">
|
||||
<div class="flex items-start gap-3">
|
||||
<flux:badge color="{{ $hint['color'] }}" size="sm" icon="{{ $hint['icon'] }}" />
|
||||
<div class="min-w-0 flex-1">
|
||||
<flux:text weight="semibold">{{ $hint['title'] }}</flux:text>
|
||||
<flux:text class="mt-1 text-sm text-zinc-500">{{ $hint['description'] }}</flux:text>
|
||||
<flux:text class="mt-3 text-xs font-medium text-zinc-700 dark:text-zinc-300">
|
||||
{{ $hint['action'] ?? __('Öffnen') }} →
|
||||
</flux:text>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 flex-shrink-0">
|
||||
@if ($selectedCompany)
|
||||
<span class="badge hub dot">
|
||||
{{ __('Aktive Firma:') }} <strong class="font-semibold">{{ $selectedCompany->name }}</strong>
|
||||
</span>
|
||||
@else
|
||||
<a href="{{ route('me.profile') }}#firmen" wire:navigate
|
||||
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-[4px] text-[12px] font-semibold whitespace-nowrap bg-[color:var(--color-warn-soft)] border border-[color:var(--color-warn)]/30 text-[color:var(--color-accent-deep)] hover:bg-[color:var(--color-warn-soft)]/80 transition">
|
||||
<flux:icon.exclamation-triangle class="size-[13px] flex-shrink-0" />
|
||||
{{ __('Keine Firma zugeordnet') }}
|
||||
<span class="underline underline-offset-[3px] decoration-[color:var(--color-accent-deep)]/40">{{ __('zuordnen') }} →</span>
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{-- ============== STAT-CARDS — KPI-Reihe ============== --}}
|
||||
<section class="grid grid-cols-2 gap-4 sm:grid-cols-4">
|
||||
<x-portal.stat-card variant="primary" :label="__('Gesamt')" :value="$stats['total']">
|
||||
<x-slot:meta>{{ now()->format('Y') }}</x-slot:meta>
|
||||
<x-slot:trend>
|
||||
@php
|
||||
$delta = $stats['deltaMonth'];
|
||||
$deltaLabel = $delta === 0
|
||||
? __('keine Veränderung ggü. Vormonat')
|
||||
: trans_choice(':sign:abs ggü. Vormonat', abs($delta), [
|
||||
'sign' => $delta > 0 ? '+' : '−',
|
||||
'abs' => abs($delta),
|
||||
]);
|
||||
@endphp
|
||||
<span class="flex items-center gap-1">
|
||||
@if ($delta > 0)
|
||||
<flux:icon.arrow-trending-up class="size-[11px] text-[color:var(--color-ok)]" />
|
||||
@elseif ($delta < 0)
|
||||
<flux:icon.arrow-trending-down class="size-[11px] text-[color:var(--color-loss)]" />
|
||||
@else
|
||||
<flux:icon.minus class="size-[11px] text-[color:var(--color-ink-4)]" />
|
||||
@endif
|
||||
{{ $deltaLabel }}
|
||||
</span>
|
||||
</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
|
||||
<x-portal.stat-card variant="ok" :label="__('Veröffentlicht')" :value="$stats['published']">
|
||||
<x-slot:meta>
|
||||
<span class="badge ok" style="font-size:9.5px;padding:1px 6px;">{{ __('live') }}</span>
|
||||
</x-slot:meta>
|
||||
<x-slot:trend>{{ __('auf beiden Portalen') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
|
||||
<x-portal.stat-card variant="warn" :label="__('In Prüfung')" :value="$stats['review']">
|
||||
<x-slot:meta>{{ __('Ø 4 h') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('redaktionelle Prüfung') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
|
||||
<x-portal.stat-card variant="muted" :label="__('Entwürfe')" :value="$stats['draft']">
|
||||
<x-slot:meta>{{ __('privat') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('gespeichert, nicht eingereicht') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
</section>
|
||||
|
||||
{{-- ============== ZWEISPALTEN-GRID ============== --}}
|
||||
<section class="grid gap-6 lg:grid-cols-[2fr_1fr]">
|
||||
|
||||
{{-- LINKS: Pressemitteilungen-Liste / Empty-State --}}
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Meine letzten Pressemitteilungen') }}</span>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ $recent->count() }} {{ __('von') }} {{ $stats['total'] }}
|
||||
</span>
|
||||
<a href="{{ route('me.press-releases.index') }}" wire:navigate
|
||||
class="text-[12px] font-semibold text-[color:var(--color-hub)] hover:underline underline-offset-[3px] decoration-[color:var(--color-hub)]/30">
|
||||
{{ __('Alle anzeigen') }} →
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
@endif
|
||||
|
||||
<div class="grid gap-6 lg:grid-cols-[1fr,280px]">
|
||||
{{-- Letzte Pressemitteilungen --}}
|
||||
<flux:card class="p-0">
|
||||
<div class="flex items-center justify-between border-b border-zinc-200 px-4 py-3 dark:border-zinc-700">
|
||||
<flux:heading size="sm">{{ __('Meine letzten Pressemitteilungen') }}</flux:heading>
|
||||
<flux:button size="sm" variant="ghost" href="{{ route('me.press-releases.index') }}" wire:navigate>
|
||||
{{ __('Alle anzeigen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
<div class="divide-y divide-zinc-100 dark:divide-zinc-800">
|
||||
@forelse($recent as $pr)
|
||||
<a href="{{ route('me.press-releases.show', $pr->id) }}" wire:navigate
|
||||
class="flex items-center justify-between gap-3 px-4 py-3 transition hover:bg-zinc-50 dark:hover:bg-zinc-800">
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-medium">{{ $pr->title }}</p>
|
||||
<p class="text-xs text-zinc-500">{{ $pr->company?->name ?? '–' }} · {{ $pr->created_at->format('d.m.Y') }}</p>
|
||||
</div>
|
||||
<flux:badge color="{{ match($pr->status->value) {
|
||||
'published' => 'green',
|
||||
'review' => 'yellow',
|
||||
'rejected' => 'red',
|
||||
'archived' => 'blue',
|
||||
default => 'zinc',
|
||||
} }}" size="sm">
|
||||
{{ $pr->status->label() }}
|
||||
</flux:badge>
|
||||
</a>
|
||||
@empty
|
||||
<div class="flex flex-col items-center justify-center px-4 py-10 text-center">
|
||||
<flux:icon.newspaper class="size-10 text-zinc-300" />
|
||||
<flux:text weight="semibold" class="mt-3">{{ __('Noch keine Pressemitteilungen') }}</flux:text>
|
||||
<flux:text class="mt-1 max-w-md text-sm text-zinc-500">
|
||||
{{ __('Starten Sie mit einer ersten Pressemitteilung für die aktive Firma oder für Ihr Kundenkonto.') }}
|
||||
</flux:text>
|
||||
<flux:button class="mt-4" variant="primary" href="{{ route('me.press-releases.create') }}" wire:navigate>
|
||||
@forelse ($recent as $pr)
|
||||
<a href="{{ route('me.press-releases.show', $pr->id) }}" wire:navigate
|
||||
class="flex items-center justify-between gap-3 px-5 py-3 border-b border-[color:var(--color-bg-rule)] last:border-b-0 hover:bg-[color:var(--color-bg)] transition-colors">
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-[13px] font-medium text-[color:var(--color-ink)] m-0">{{ $pr->title }}</p>
|
||||
<p class="text-[11px] text-[color:var(--color-ink-3)] mt-0.5 m-0">
|
||||
{{ $pr->company?->name ?? __('Ohne Firma') }} · {{ $pr->created_at->format('d.m.Y') }}
|
||||
</p>
|
||||
</div>
|
||||
<span @class([
|
||||
'badge',
|
||||
'ok' => $pr->status === PressReleaseStatus::Published,
|
||||
'warn' => $pr->status === PressReleaseStatus::Review,
|
||||
'err' => $pr->status === PressReleaseStatus::Rejected,
|
||||
'hub' => ! in_array($pr->status, [PressReleaseStatus::Published, PressReleaseStatus::Review, PressReleaseStatus::Rejected], true),
|
||||
])>
|
||||
{{ $pr->status->label() }}
|
||||
</span>
|
||||
</a>
|
||||
@empty
|
||||
<div class="px-10 py-14 flex flex-col items-center text-center">
|
||||
<div class="w-16 h-16 rounded-[6px] flex items-center justify-center mb-5 relative
|
||||
bg-[color:var(--color-hub-soft)] border border-[color:var(--color-hub-soft-2)] text-[color:var(--color-hub)]">
|
||||
<flux:icon.newspaper class="size-7" />
|
||||
<span class="absolute -top-1.5 -right-1.5 w-5 h-5 rounded-full text-[10px] font-bold flex items-center justify-center font-mono
|
||||
bg-[color:var(--color-accent)] text-white">0</span>
|
||||
</div>
|
||||
<div class="text-[16px] font-semibold m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Noch keine Pressemitteilungen') }}
|
||||
</div>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[460px] text-[color:var(--color-ink-3)]">
|
||||
{{ __('Starten Sie mit einer ersten Mitteilung für die aktive Firma oder Ihr Kundenkonto. Veröffentlichung erfolgt nach redaktioneller Prüfung auf beiden Portalen.') }}
|
||||
</p>
|
||||
<div class="mt-6 flex items-center gap-2.5">
|
||||
<flux:button variant="primary" icon="plus" href="{{ route('me.press-releases.create') }}" wire:navigate>
|
||||
{{ __('Erste Pressemitteilung erstellen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
{{-- Zugeordnete Firmen --}}
|
||||
<flux:card>
|
||||
<flux:heading size="sm" class="mb-3">{{ __('Meine Firmen') }}</flux:heading>
|
||||
@forelse($companies as $company)
|
||||
<div class="py-2 text-sm">
|
||||
<p class="font-medium">{{ $company->name }}</p>
|
||||
</div>
|
||||
@empty
|
||||
<div class="rounded-lg border border-dashed border-zinc-200 p-4 text-sm text-zinc-500 dark:border-zinc-700">
|
||||
{{ __('Keine Firmen zugeordnet. Wenn hier eine Firma fehlen sollte, prüfen Sie bitte Ihr Profil oder wenden Sie sich an den Support.') }}
|
||||
<div class="mt-9 grid gap-3 w-full max-w-[560px] grid-cols-1 sm:grid-cols-3">
|
||||
@foreach ([
|
||||
['num' => '01', 'label' => __('Firma zuordnen')],
|
||||
['num' => '02', 'label' => __('Mitteilung verfassen')],
|
||||
['num' => '03', 'label' => __('Zur Prüfung senden')],
|
||||
] as $step)
|
||||
<div class="text-left px-3 py-2.5 rounded-[3px]
|
||||
bg-[color:var(--color-bg-elev)] border border-[color:var(--color-bg-rule)]">
|
||||
<div class="font-mono text-[9.5px] tracking-[0.16em] font-bold mb-1 text-[color:var(--color-accent-deep)]">
|
||||
{{ $step['num'] }}
|
||||
</div>
|
||||
<div class="text-[11.5px] font-semibold leading-tight text-[color:var(--color-ink)]">
|
||||
{{ $step['label'] }}
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endforelse
|
||||
|
||||
<div class="mt-4 border-t border-zinc-100 pt-4 dark:border-zinc-800">
|
||||
<flux:button size="sm" variant="ghost" href="{{ route('me.profile') }}" wire:navigate>
|
||||
{{ __('Profil & Firma verwalten') }}
|
||||
</flux:button>
|
||||
<div class="px-5 py-3 border-t border-[color:var(--color-bg-rule)] flex items-center gap-2.5 text-[11.5px]
|
||||
bg-[color:var(--color-bg-elev)] text-[color:var(--color-ink-3)]">
|
||||
<flux:icon.shield-check class="size-[12px] text-[color:var(--color-hub)] flex-shrink-0" />
|
||||
{{ __('Tipp: Geprüfte Mitteilungen erscheinen i. d. R. binnen') }}
|
||||
<strong class="font-semibold text-[color:var(--color-ink-2)]">{{ __('4 Stunden') }}</strong>
|
||||
{{ __('werktags auf beiden Portalen.') }}
|
||||
</div>
|
||||
</flux:card>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<flux:button variant="primary" icon="plus" href="{{ route('me.press-releases.create') }}" wire:navigate>
|
||||
{{ __('Neue Pressemitteilung') }}
|
||||
</flux:button>
|
||||
{{-- RECHTS: Datenqualität --}}
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Datenqualität') }}</span>
|
||||
@if (count($qualityHints) > 0)
|
||||
<span class="badge warn dot">{{ count($qualityHints) }} {{ __('offen') }}</span>
|
||||
@else
|
||||
<span class="badge ok dot">{{ __('vollständig') }}</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="px-5 py-5">
|
||||
<p class="text-[12px] leading-[1.55] m-0 mb-4 text-[color:var(--color-ink-3)]">
|
||||
{{ __('Diese Hinweise helfen, Ihr User Backend vollständig und sauber zu halten.') }}
|
||||
</p>
|
||||
|
||||
@if (count($qualityHints) > 0)
|
||||
<div class="space-y-3">
|
||||
@foreach ($qualityHints as $hint)
|
||||
<x-portal.hint-card
|
||||
:icon="$hint['icon']"
|
||||
:title="$hint['title']"
|
||||
:percent="$hint['percent'] ?? null"
|
||||
:href="$hint['href']"
|
||||
:action="$hint['action']"
|
||||
>
|
||||
{{ $hint['description'] }}
|
||||
</x-portal.hint-card>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<div class="flex flex-col items-center text-center py-6">
|
||||
<flux:icon.check-badge class="size-8 text-[color:var(--color-ok)] mb-2" />
|
||||
<div class="text-[13px] font-semibold text-[color:var(--color-ink)]">
|
||||
{{ __('Alles im grünen Bereich') }}
|
||||
</div>
|
||||
<p class="text-[11.5px] text-[color:var(--color-ink-3)] mt-1 m-0">
|
||||
{{ __('Profil, Rechnungsadresse und Firmen-Daten sind vollständig.') }}
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
{{-- ============== UNTERER GRID: FIRMEN + BRAND-BRIDGE ============== --}}
|
||||
<section class="grid gap-6 lg:grid-cols-[2fr_1fr]">
|
||||
|
||||
{{-- Firmen --}}
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Meine Firmen') }}</span>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="badge hub" style="font-size:9.5px;padding:1px 6px;">
|
||||
{{ $companies->count() }} {{ __('zugeordnet') }}
|
||||
</span>
|
||||
<a href="{{ route('me.profile') }}" wire:navigate
|
||||
class="text-[12px] font-semibold text-[color:var(--color-hub)] hover:underline underline-offset-[3px] decoration-[color:var(--color-hub)]/30">
|
||||
{{ __('Profil & Firma verwalten') }} →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
@if ($companies->isNotEmpty())
|
||||
<div class="grid gap-3 grid-cols-1 md:grid-cols-2">
|
||||
@foreach ($companies as $company)
|
||||
<a href="{{ route('me.press-kits.show', $company->id) }}" wire:navigate
|
||||
class="block relative rounded-[5px] p-5 transition-colors
|
||||
bg-[color:var(--color-bg-elev)] border border-[color:var(--color-bg-rule)]
|
||||
hover:border-[color:var(--color-hub)]/50">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<span class="w-10 h-10 rounded-[4px] flex items-center justify-center
|
||||
bg-[color:var(--color-hub-soft)] border border-[color:var(--color-hub-soft-2)]
|
||||
text-[color:var(--color-hub)] text-[12px] font-bold">
|
||||
{{ \Illuminate\Support\Str::of($company->name)->take(2)->upper() }}
|
||||
</span>
|
||||
<div class="min-w-0">
|
||||
<div class="text-[13.5px] font-semibold truncate text-[color:var(--color-ink)]">
|
||||
{{ $company->name }}
|
||||
</div>
|
||||
<div class="text-[11px] mt-0.5 text-[color:var(--color-ink-3)]">
|
||||
{{ __('Presse-Kit öffnen') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<div class="grid gap-3 grid-cols-1 md:grid-cols-2">
|
||||
<div class="relative rounded-[5px] p-5 transition-colors
|
||||
border border-dashed border-[color:var(--color-bg-rule)] hover:bg-[color:var(--color-bg-elev)]">
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<span class="w-10 h-10 rounded-[4px] flex items-center justify-center
|
||||
border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)]
|
||||
text-[color:var(--color-ink-4)]">
|
||||
<flux:icon.building-office class="size-[18px]" />
|
||||
</span>
|
||||
<div>
|
||||
<div class="text-[13.5px] font-semibold text-[color:var(--color-ink-2)]">
|
||||
{{ __('Firma hinzufügen') }}
|
||||
</div>
|
||||
<div class="text-[11px] mt-0.5 text-[color:var(--color-ink-3)]">
|
||||
{{ __('Slot frei') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-[11.5px] leading-[1.5] m-0 text-[color:var(--color-ink-3)]">
|
||||
{{ __('Pressestellen, für die Sie Mitteilungen erstellen — mit eigenem Logo, Kontaktperson und Themen-Tags.') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="p-5 rounded-[5px]
|
||||
bg-[color:var(--color-bg-elev)] border border-[color:var(--color-bg-rule)]">
|
||||
<div class="eyebrow muted mb-2">{{ __('Hinweis') }}</div>
|
||||
<div class="text-[13px] leading-[1.55] m-0 text-[color:var(--color-ink-2)]">
|
||||
{{ __('Keine Firmen zugeordnet. Wenn hier eine Firma fehlen sollte, prüfen Sie bitte Ihr Profil oder wenden Sie sich an den Support.') }}
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<flux:button size="sm" variant="ghost" href="{{ route('me.profile') }}" wire:navigate>
|
||||
{{ __('Profil prüfen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</article>
|
||||
|
||||
{{-- Brand-Bridge (dark) --}}
|
||||
<article class="panel-dark">
|
||||
<div class="panel-head">
|
||||
<span class="eyebrow on-dark">{{ __('Brand-Bridge') }}</span>
|
||||
<span class="font-mono text-[10px] tracking-[0.14em] uppercase text-[color:var(--color-ink-on-dark-3)]">A · B</span>
|
||||
</div>
|
||||
|
||||
<div class="px-5 py-5">
|
||||
<div class="text-[12.5px] leading-[1.55] m-0 mb-4 text-[color:var(--color-ink-on-dark-2)]">
|
||||
{{ __('Ein Konto, beide Portale — Veröffentlichungen werden parallel auf presseecho und businessportal24 ausgespielt.') }}
|
||||
</div>
|
||||
|
||||
<div class="grid gap-3 grid-cols-2">
|
||||
{{-- Konstantes dunkles Hub-Blau (Phase 5): bleibt in beiden Modi gleich. --}}
|
||||
<div class="rounded-[4px] px-3.5 py-3 border bg-[color:var(--color-panel-dark-2)] border-white/5">
|
||||
<div class="flex items-center gap-2 mb-1.5">
|
||||
<span class="dot-pe"></span>
|
||||
<span class="text-[11px] font-bold tracking-[0.14em] uppercase text-white/85">presseecho</span>
|
||||
</div>
|
||||
<div class="font-mono text-[15px] font-semibold text-white tabular-nums">
|
||||
{{ __($bridgeStatus['presseecho']['state']) }}
|
||||
</div>
|
||||
<div class="text-[10.5px] mt-0.5 text-[color:var(--color-ink-on-dark-3)]">
|
||||
{{ $bridgeStatus['presseecho']['subline'] }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-[4px] px-3.5 py-3 border bg-[color:var(--color-panel-dark-2)] border-white/5">
|
||||
<div class="flex items-center gap-2 mb-1.5">
|
||||
<span class="dot-bp"></span>
|
||||
<span class="text-[11px] font-bold tracking-[0.14em] uppercase text-white/85">businessportal24</span>
|
||||
</div>
|
||||
<div class="font-mono text-[15px] font-semibold text-white tabular-nums">
|
||||
{{ __($bridgeStatus['businessportal24']['state']) }}
|
||||
</div>
|
||||
<div class="text-[10.5px] mt-0.5 text-[color:var(--color-ink-on-dark-3)]">
|
||||
{{ $bridgeStatus['businessportal24']['subline'] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="mt-5 mb-4 border-0 h-px bg-white/10" />
|
||||
|
||||
<div class="space-y-2 text-[11.5px] text-[color:var(--color-ink-on-dark-2)]">
|
||||
<div class="flex items-center justify-between">
|
||||
<span>{{ __('API-Status') }}</span>
|
||||
<span class="flex items-center gap-1.5 text-white">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse"></span>
|
||||
{{ __('operational') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>{{ __('Tarif') }}</span>
|
||||
<span class="font-mono text-white">{{ __('Starter') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
{{-- ============== FOOTER ============== --}}
|
||||
<footer class="flex items-center justify-between pt-4 pb-2 text-[11px]
|
||||
border-t border-[color:var(--color-bg-rule)] text-[color:var(--color-ink-3)]">
|
||||
<span>© {{ now()->format('Y') }} pressekonto.de · Publisher-Hub</span>
|
||||
<span class="flex items-center gap-5">
|
||||
<a href="{{ route('me.security') }}" wire:navigate class="hover:text-[color:var(--color-hub)]">{{ __('Sicherheit') }}</a>
|
||||
<a href="{{ route('me.tokens.index') }}" wire:navigate class="hover:text-[color:var(--color-hub)]">{{ __('API & Tokens') }}</a>
|
||||
<a href="{{ route('me.profile') }}" wire:navigate class="hover:text-[color:var(--color-hub)]">{{ __('Profil') }}</a>
|
||||
</span>
|
||||
</footer>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -129,32 +129,44 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
<flux:card>
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<flux:heading size="lg">{{ __('Neue Pressemitteilung') }}</flux:heading>
|
||||
<flux:subheading>{{ __('Entwurf erstellen oder direkt zur Prüfung einreichen.') }}</flux:subheading>
|
||||
<div class="space-y-8">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-nowrap whitespace-nowrap">
|
||||
<span class="badge hub dot">{{ __('User Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Mein Bereich · Neue PM') }}</span>
|
||||
</div>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Neue Pressemitteilung') }}
|
||||
</h1>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
|
||||
{{ __('Entwurf erstellen oder direkt zur Prüfung einreichen.') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('me.press-releases.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
</header>
|
||||
|
||||
<div class="grid gap-6 lg:grid-cols-[1fr,300px]">
|
||||
<div class="space-y-6">
|
||||
<flux:card>
|
||||
<flux:heading size="md" class="mb-4">{{ __('Inhalt') }}</flux:heading>
|
||||
<div class="space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Inhalt') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Titel') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:label>{{ __('Titel') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
||||
<flux:input wire:model="title" placeholder="{{ __('Aussagekräftiger Titel…') }}" />
|
||||
<flux:error name="title" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Text') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:label>{{ __('Text') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
||||
<flux:textarea wire:model="text" rows="16" placeholder="{{ __('Vollständiger Text…') }}" />
|
||||
<flux:error name="text" />
|
||||
</flux:field>
|
||||
|
|
@ -170,18 +182,20 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
<flux:error name="backlinkUrl" />
|
||||
</flux:field>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<flux:card>
|
||||
<flux:heading size="sm" class="mb-3">{{ __('Metadaten') }}</flux:heading>
|
||||
<div class="space-y-4">
|
||||
<aside class="space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Metadaten') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Firma') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:label>{{ __('Firma') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
||||
<flux:select wire:model="companyId">
|
||||
<option value="">{{ __('Bitte wählen…') }}</option>
|
||||
@foreach($myCompanies as $c)
|
||||
@foreach ($myCompanies as $c)
|
||||
<option value="{{ $c->id }}">{{ $c->name }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
|
|
@ -189,11 +203,11 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Kategorie') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:label>{{ __('Kategorie') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
||||
<flux:select wire:model="categoryId">
|
||||
<option value="">{{ __('Bitte wählen…') }}</option>
|
||||
@foreach($categories as $cat)
|
||||
@php $catName = $cat->translations->firstWhere('locale', 'de')?->name ?? $cat->id; @endphp
|
||||
@foreach ($categories as $cat)
|
||||
@php($catName = $cat->translations->firstWhere('locale', 'de')?->name ?? $cat->id)
|
||||
<option value="{{ $cat->id }}">{{ $catName }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
|
|
@ -210,10 +224,13 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
</flux:select>
|
||||
</flux:field>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
<flux:card>
|
||||
<div class="space-y-2">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Aktionen') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-2">
|
||||
<flux:button type="button" variant="primary" class="w-full" wire:click="save('review')">
|
||||
{{ __('Zur Prüfung einreichen') }}
|
||||
</flux:button>
|
||||
|
|
@ -221,7 +238,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
{{ __('Als Entwurf speichern') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
</div>
|
||||
</article>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -139,32 +139,45 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
<flux:card>
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<flux:heading size="lg">{{ __('Pressemitteilung bearbeiten') }}</flux:heading>
|
||||
<flux:subheading>{{ __('Inhalt und Metadaten der Pressemitteilung aktualisieren.') }}</flux:subheading>
|
||||
<div class="space-y-8">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-nowrap whitespace-nowrap">
|
||||
<span class="badge hub dot">{{ __('User Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Mein Bereich · Bearbeiten') }}</span>
|
||||
<span class="badge hub">ID {{ $id }}</span>
|
||||
</div>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Pressemitteilung bearbeiten') }}
|
||||
</h1>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
|
||||
{{ __('Inhalt und Metadaten der Pressemitteilung aktualisieren.') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('me.press-releases.show', $id) }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
</header>
|
||||
|
||||
<div class="grid gap-6 lg:grid-cols-[1fr,300px]">
|
||||
<div class="space-y-6">
|
||||
<flux:card>
|
||||
<flux:heading size="md" class="mb-4">{{ __('Inhalt') }}</flux:heading>
|
||||
<div class="space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Inhalt') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Titel') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:label>{{ __('Titel') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
||||
<flux:input wire:model="title" />
|
||||
<flux:error name="title" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Text') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:label>{{ __('Text') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
||||
<flux:textarea wire:model="text" rows="20" />
|
||||
<flux:error name="text" />
|
||||
</flux:field>
|
||||
|
|
@ -180,19 +193,21 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
|
|||
<flux:error name="backlinkUrl" />
|
||||
</flux:field>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
<livewire:components.press-release-images-manager :press-release-id="$id" :wire:key="'pr-images-'.$id" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<flux:card>
|
||||
<flux:heading size="sm" class="mb-3">{{ __('Metadaten') }}</flux:heading>
|
||||
<div class="space-y-4">
|
||||
<aside class="space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Metadaten') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Firma') }}</flux:label>
|
||||
<flux:select wire:model="companyId">
|
||||
@foreach($myCompanies as $c)
|
||||
@foreach ($myCompanies as $c)
|
||||
<option value="{{ $c->id }}">{{ $c->name }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
|
|
@ -203,8 +218,8 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
|
|||
<flux:label>{{ __('Kategorie') }}</flux:label>
|
||||
<flux:select wire:model="categoryId">
|
||||
<option value="">{{ __('Bitte wählen…') }}</option>
|
||||
@foreach($categories as $cat)
|
||||
@php $catName = $cat->translations->firstWhere('locale', 'de')?->name ?? $cat->id; @endphp
|
||||
@foreach ($categories as $cat)
|
||||
@php($catName = $cat->translations->firstWhere('locale', 'de')?->name ?? $cat->id)
|
||||
<option value="{{ $cat->id }}">{{ $catName }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
|
|
@ -221,11 +236,18 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
|
|||
</flux:select>
|
||||
</flux:field>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
<flux:button type="button" variant="primary" class="w-full" wire:click="save">
|
||||
{{ __('Speichern') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Aktionen') }}</span>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<flux:button type="button" variant="primary" class="w-full" wire:click="save">
|
||||
{{ __('Speichern') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</article>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -95,29 +95,52 @@ new #[Layout('components.layouts.app'), Title('Meine Pressemitteilungen')] class
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="space-y-8">
|
||||
@if(session('success'))
|
||||
<div class="rounded-md border border-green-200 bg-green-50 px-4 py-3 text-sm text-green-800 dark:border-green-800 dark:bg-green-900/20 dark:text-green-300">
|
||||
<div class="px-4 py-3 rounded-[5px] border text-[12.5px]
|
||||
bg-[color:var(--color-ok-soft)] border-[color:var(--color-ok)]/30 text-[color:var(--color-gain-deep)]">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
@if(session('error'))
|
||||
<div class="px-4 py-3 rounded-[5px] border text-[12.5px]
|
||||
bg-[color:var(--color-err-soft)] border-[color:var(--color-err)]/30 text-[color:var(--color-loss)]">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<flux:card>
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<flux:heading size="lg">{{ __('Meine Pressemitteilungen') }}</flux:heading>
|
||||
@if($selectedCompany)
|
||||
<flux:subheading>{{ __('Gefiltert auf :company', ['company' => $selectedCompany->name]) }}</flux:subheading>
|
||||
@endif
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-nowrap whitespace-nowrap">
|
||||
<span class="badge hub dot">{{ __('User Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Mein Bereich · Pressemitteilungen') }}</span>
|
||||
</div>
|
||||
<h1 class="text-[34px] font-bold tracking-[-0.7px] leading-[1.1] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Meine Pressemitteilungen') }}
|
||||
</h1>
|
||||
<p class="text-[13.5px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
|
||||
@if ($selectedCompany)
|
||||
{{ __('Gefiltert auf :company', ['company' => $selectedCompany->name]) }}
|
||||
@else
|
||||
{{ __('Übersicht aller PMs Ihres Kundenkontos, mit Filter und Schnellaktionen.') }}
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3 flex-shrink-0">
|
||||
<flux:button variant="primary" icon="plus" href="{{ route('me.press-releases.create') }}" wire:navigate>
|
||||
{{ __('Neue Pressemitteilung') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
</header>
|
||||
|
||||
<flux:card>
|
||||
<div class="flex flex-col gap-3 sm:flex-row">
|
||||
{{-- ============== FILTER-PANEL ============== --}}
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Filter & Suche') }}</span>
|
||||
</div>
|
||||
<div class="p-5 flex flex-col gap-3 sm:flex-row">
|
||||
<flux:input wire:model.live.debounce.300ms="search" placeholder="{{ __('Titel suchen…') }}" icon="magnifying-glass" class="flex-1" />
|
||||
<flux:select wire:model.live="statusFilter" class="sm:w-44">
|
||||
<option value="all">{{ __('Alle Status') }}</option>
|
||||
|
|
@ -133,9 +156,16 @@ new #[Layout('components.layouts.app'), Title('Meine Pressemitteilungen')] class
|
|||
</flux:select>
|
||||
@endif
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
<flux:card class="p-0">
|
||||
{{-- ============== TABELLE-PANEL ============== --}}
|
||||
<article class="panel overflow-hidden">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Alle Pressemitteilungen') }}</span>
|
||||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ __(':count Einträge', ['count' => $pressReleases->count()]) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<flux:table>
|
||||
<flux:table.columns>
|
||||
|
|
@ -155,13 +185,13 @@ new #[Layout('components.layouts.app'), Title('Meine Pressemitteilungen')] class
|
|||
<flux:text class="text-sm">{{ $pr->company?->name ?? '–' }}</flux:text>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<flux:badge color="{{ match($pr->status->value) {
|
||||
'published' => 'green',
|
||||
'review' => 'yellow',
|
||||
'rejected' => 'red',
|
||||
'archived' => 'blue',
|
||||
default => 'zinc',
|
||||
} }}">{{ $pr->status->label() }}</flux:badge>
|
||||
<span @class([
|
||||
'badge',
|
||||
'ok' => $pr->status->value === 'published',
|
||||
'warn' => $pr->status->value === 'review',
|
||||
'err' => $pr->status->value === 'rejected',
|
||||
'hub' => in_array($pr->status->value, ['archived', 'draft'], true),
|
||||
])>{{ $pr->status->label() }}</span>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<flux:text class="text-sm text-zinc-500">{{ $pr->created_at->format('d.m.Y') }}</flux:text>
|
||||
|
|
@ -180,13 +210,18 @@ new #[Layout('components.layouts.app'), Title('Meine Pressemitteilungen')] class
|
|||
@empty
|
||||
<flux:table.row>
|
||||
<flux:table.cell colspan="5">
|
||||
<div class="flex flex-col items-center justify-center px-4 py-10 text-center">
|
||||
<flux:icon.newspaper class="size-10 text-zinc-300" />
|
||||
<flux:text weight="semibold" class="mt-3">{{ __('Keine Pressemitteilungen gefunden') }}</flux:text>
|
||||
<flux:text class="mt-1 max-w-md text-sm text-zinc-500">
|
||||
<div class="flex flex-col items-center justify-center px-4 py-12 text-center">
|
||||
<div class="w-14 h-14 rounded-[6px] flex items-center justify-center mb-4
|
||||
bg-[color:var(--color-hub-soft)] border border-[color:var(--color-hub-soft-2)] text-[color:var(--color-hub)]">
|
||||
<flux:icon.newspaper class="size-6" />
|
||||
</div>
|
||||
<div class="text-[14px] font-semibold text-[color:var(--color-ink)] mb-1">
|
||||
{{ __('Keine Pressemitteilungen gefunden') }}
|
||||
</div>
|
||||
<p class="text-[12px] text-[color:var(--color-ink-3)] max-w-md m-0 mb-4">
|
||||
{{ __('Passen Sie die Filter an oder erstellen Sie eine neue Pressemitteilung.') }}
|
||||
</flux:text>
|
||||
<flux:button class="mt-4" size="sm" variant="primary" icon="plus" href="{{ route('me.press-releases.create') }}" wire:navigate>
|
||||
</p>
|
||||
<flux:button size="sm" variant="primary" icon="plus" href="{{ route('me.press-releases.create') }}" wire:navigate>
|
||||
{{ __('Neue Pressemitteilung') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
@ -195,7 +230,7 @@ new #[Layout('components.layouts.app'), Title('Meine Pressemitteilungen')] class
|
|||
@endforelse
|
||||
</flux:table>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
{{ $pressReleases->links() }}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -104,76 +104,122 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
@if(session('success'))
|
||||
<div class="rounded-md border border-green-200 bg-green-50 px-4 py-3 text-sm text-green-800 dark:border-green-800 dark:bg-green-900/20 dark:text-green-300">
|
||||
<div class="space-y-8">
|
||||
@php
|
||||
$statusClass = match ($pr->status->value) {
|
||||
'published' => 'ok',
|
||||
'review' => 'warn',
|
||||
'rejected' => 'err',
|
||||
default => 'hub',
|
||||
};
|
||||
@endphp
|
||||
|
||||
@if (session('success'))
|
||||
<div class="px-4 py-3 rounded-[5px] border text-[12.5px]
|
||||
bg-[color:var(--color-ok-soft)] border-[color:var(--color-ok)]/30 text-[color:var(--color-gain-deep)]">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<flux:card>
|
||||
<div class="flex flex-wrap items-start justify-between gap-3">
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:badge :color="$statusColor">{{ $pr->status->label() }}</flux:badge>
|
||||
<flux:badge color="zinc" size="sm">{{ strtoupper($pr->language) }}</flux:badge>
|
||||
</div>
|
||||
<flux:heading size="xl" class="mt-2">{{ $pr->title }}</flux:heading>
|
||||
<flux:text class="mt-1 text-sm text-zinc-500">
|
||||
{{ $pr->company?->name ?? '–' }} · {{ $categoryName }} · {{ $pr->created_at->format('d.m.Y') }}
|
||||
</flux:text>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
@if($canEdit)
|
||||
<flux:button variant="ghost" icon="pencil" href="{{ route('me.press-releases.edit', $pr->id) }}" wire:navigate>
|
||||
{{ __('Bearbeiten') }}
|
||||
</flux:button>
|
||||
@endif
|
||||
<flux:button variant="ghost" icon="link" wire:click="generateShareLink">
|
||||
{{ __('Vorschau-Link') }}
|
||||
</flux:button>
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('me.press-releases.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
@if (session('error'))
|
||||
<div class="px-4 py-3 rounded-[5px] border text-[12.5px]
|
||||
bg-[color:var(--color-err-soft)] border-[color:var(--color-err)]/30 text-[color:var(--color-loss)]">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
|
||||
@if($shareUrl)
|
||||
<div class="mt-4 rounded-md border border-emerald-300 bg-emerald-50 p-4 dark:border-emerald-700 dark:bg-emerald-900/20">
|
||||
<flux:heading size="sm" class="mb-2">{{ __('Öffentlicher Vorschau-Link erstellt') }}</flux:heading>
|
||||
<flux:text class="mb-2 text-xs text-zinc-500">{{ __('Gültig bis :date.', ['date' => $shareExpiresAt]) }}</flux:text>
|
||||
<flux:input readonly :value="$shareUrl" />
|
||||
</div>
|
||||
@endif
|
||||
</flux:card>
|
||||
|
||||
@if($pr->status === PressReleaseStatus::Rejected && $latestRejection)
|
||||
<flux:callout color="red" icon="exclamation-triangle">
|
||||
<flux:callout.heading>{{ __('Diese Pressemitteilung wurde abgelehnt') }}</flux:callout.heading>
|
||||
<flux:callout.text>
|
||||
@if($latestRejection->reason)
|
||||
<strong>{{ __('Begründung') }}:</strong>
|
||||
<span class="block mt-1 whitespace-pre-line">{{ $latestRejection->reason }}</span>
|
||||
@else
|
||||
{{ __('Bitte überarbeiten Sie den Inhalt und reichen Sie die Pressemitteilung erneut ein.') }}
|
||||
@endif
|
||||
<span class="mt-2 block text-xs text-red-700/70 dark:text-red-300/70">
|
||||
{{ __('Abgelehnt am') }} {{ $latestRejection->created_at->format('d.m.Y H:i') }}
|
||||
</span>
|
||||
</flux:callout.text>
|
||||
</flux:callout>
|
||||
@endif
|
||||
|
||||
@if($pr->status === PressReleaseStatus::Draft || $pr->status === PressReleaseStatus::Rejected)
|
||||
<flux:card>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<flux:text class="text-sm text-zinc-500">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-wrap">
|
||||
<span class="badge hub dot">{{ __('User Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Mein Bereich · Pressemitteilung') }}</span>
|
||||
<span @class(['badge', $statusClass])>{{ $pr->status->label() }}</span>
|
||||
<span class="badge hub">{{ strtoupper($pr->language) }}</span>
|
||||
</div>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ $pr->title }}
|
||||
</h1>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[720px] text-[color:var(--color-ink-2)]">
|
||||
{{ $pr->company?->name ?? '–' }}
|
||||
<span class="text-[color:var(--color-bg-rule)] mx-1">·</span>
|
||||
{{ $categoryName }}
|
||||
<span class="text-[color:var(--color-bg-rule)] mx-1">·</span>
|
||||
{{ $pr->created_at->format('d.m.Y') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
@if ($canEdit)
|
||||
<flux:button variant="ghost" icon="pencil" href="{{ route('me.press-releases.edit', $pr->id) }}" wire:navigate>
|
||||
{{ __('Bearbeiten') }}
|
||||
</flux:button>
|
||||
@endif
|
||||
<flux:button variant="ghost" icon="link" wire:click="generateShareLink">
|
||||
{{ __('Vorschau-Link') }}
|
||||
</flux:button>
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('me.press-releases.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{-- ============== SHARE-LINK ERFOLG ============== --}}
|
||||
@if ($shareUrl)
|
||||
<article class="panel" style="border-color:var(--color-ok);">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Öffentlicher Vorschau-Link erstellt') }}</span>
|
||||
<span class="badge ok dot">{{ __('gültig bis :date', ['date' => $shareExpiresAt]) }}</span>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<flux:input readonly :value="$shareUrl" />
|
||||
</div>
|
||||
</article>
|
||||
@endif
|
||||
|
||||
{{-- ============== REJECTION-HINWEIS ============== --}}
|
||||
@if ($pr->status === PressReleaseStatus::Rejected && $latestRejection)
|
||||
<article class="panel" style="border-color:var(--color-err); border-left-width:3px;">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Diese Pressemitteilung wurde abgelehnt') }}</span>
|
||||
<span class="badge err dot">{{ __('Handlung erforderlich') }}</span>
|
||||
</div>
|
||||
<div class="p-5 flex items-start gap-3">
|
||||
<div class="w-9 h-9 rounded-[5px] flex items-center justify-center flex-shrink-0
|
||||
bg-[color:var(--color-err-soft)] border border-[color:var(--color-err)]/30 text-[color:var(--color-loss)]">
|
||||
<flux:icon.exclamation-triangle class="size-[18px]" />
|
||||
</div>
|
||||
<div class="flex-1 text-[13px] text-[color:var(--color-ink-2)]">
|
||||
@if ($latestRejection->reason)
|
||||
<strong class="text-[color:var(--color-ink)] font-semibold">{{ __('Begründung') }}:</strong>
|
||||
<span class="block mt-1 whitespace-pre-line">{{ $latestRejection->reason }}</span>
|
||||
@else
|
||||
{{ __('Bitte überarbeiten Sie den Inhalt und reichen Sie die Pressemitteilung erneut ein.') }}
|
||||
@endif
|
||||
<span class="mt-2 block text-[11.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ __('Abgelehnt am') }} {{ $latestRejection->created_at->format('d.m.Y H:i') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
@endif
|
||||
|
||||
{{-- ============== STATUS-WORKFLOW ============== --}}
|
||||
@if ($pr->status === PressReleaseStatus::Draft || $pr->status === PressReleaseStatus::Rejected)
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Status-Workflow') }}</span>
|
||||
<span @class(['badge', 'dot', $pr->status === PressReleaseStatus::Rejected ? 'err' : 'hub'])>
|
||||
{{ $pr->status === PressReleaseStatus::Rejected ? __('Überarbeiten') : __('Entwurf') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="p-5 flex flex-wrap items-center gap-3">
|
||||
<p class="text-[13px] text-[color:var(--color-ink-2)] m-0 flex-1 min-w-[220px]">
|
||||
{{ $pr->status === PressReleaseStatus::Rejected
|
||||
? __('Sie können den Text bearbeiten und erneut zur Prüfung einreichen.')
|
||||
: __('Reichen Sie den Entwurf ein, sobald er vollständig ist.') }}
|
||||
</flux:text>
|
||||
<div class="flex items-center gap-2">
|
||||
@if($canEdit)
|
||||
</p>
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
@if ($canEdit)
|
||||
<flux:button variant="ghost" icon="pencil" href="{{ route('me.press-releases.edit', $pr->id) }}" wire:navigate>
|
||||
{{ __('Bearbeiten') }}
|
||||
</flux:button>
|
||||
|
|
@ -184,140 +230,180 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
@endif
|
||||
|
||||
@if($pr->status === PressReleaseStatus::Review)
|
||||
<flux:callout color="yellow" icon="clock">
|
||||
{{ __('Ihre Pressemitteilung wird gerade geprüft. Sie werden benachrichtigt, sobald eine Entscheidung vorliegt.') }}
|
||||
</flux:callout>
|
||||
@endif
|
||||
|
||||
<div class="grid gap-6 xl:grid-cols-2">
|
||||
<flux:card>
|
||||
<div class="mb-4 flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<flux:heading size="lg">{{ __('Zugeordnete Pressekontakte') }}</flux:heading>
|
||||
<flux:text class="text-sm text-zinc-500">
|
||||
{{ __('Kontakte, die dieser Pressemitteilung zugeordnet sind.') }}
|
||||
</flux:text>
|
||||
@if ($pr->status === PressReleaseStatus::Review)
|
||||
<article class="panel" style="border-color:var(--color-warn); border-left-width:3px;">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('In Prüfung') }}</span>
|
||||
<span class="badge warn dot">{{ __('Geduld bitte') }}</span>
|
||||
</div>
|
||||
<div class="p-5 flex items-start gap-3">
|
||||
<div class="w-9 h-9 rounded-[5px] flex items-center justify-center flex-shrink-0
|
||||
bg-[color:var(--color-warn-soft)] border border-[color:var(--color-warn)]/30 text-[color:var(--color-accent-deep)]">
|
||||
<flux:icon.clock class="size-[18px]" />
|
||||
</div>
|
||||
<p class="flex-1 text-[13px] text-[color:var(--color-ink-2)] m-0">
|
||||
{{ __('Ihre Pressemitteilung wird gerade geprüft. Sie werden benachrichtigt, sobald eine Entscheidung vorliegt.') }}
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
@endif
|
||||
|
||||
@if($pr->company)
|
||||
{{-- ============== KONTAKTE + STATUS/VERLAUF ============== --}}
|
||||
<div class="grid gap-6 xl:grid-cols-2">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Zugeordnete Pressekontakte') }}</span>
|
||||
@if ($pr->company)
|
||||
<flux:button size="sm" variant="ghost" icon="building-office" href="{{ route('me.press-kits.show', $pr->company->id) }}" wire:navigate>
|
||||
{{ __('Firma') }}
|
||||
</flux:button>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
@forelse($contacts as $contact)
|
||||
<div class="rounded-lg bg-zinc-50 p-3 dark:bg-zinc-900">
|
||||
<flux:text weight="semibold">
|
||||
{{ trim(($contact->first_name ?? '').' '.($contact->last_name ?? '')) ?: __('Kontakt ohne Name') }}
|
||||
</flux:text>
|
||||
<flux:text class="text-sm text-zinc-500">{{ $contact->responsibility ?: __('Keine Rolle hinterlegt') }}</flux:text>
|
||||
<div class="mt-1 flex flex-wrap gap-x-3 gap-y-1 text-xs text-zinc-500">
|
||||
@if($contact->email)
|
||||
<a href="mailto:{{ $contact->email }}" class="text-blue-600 hover:underline dark:text-blue-400">{{ $contact->email }}</a>
|
||||
@endif
|
||||
@if($contact->phone)
|
||||
<span>{{ $contact->phone }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="rounded-lg border border-dashed border-zinc-200 p-4 text-sm text-zinc-500 dark:border-zinc-700">
|
||||
{{ __('Dieser Pressemitteilung ist noch kein Pressekontakt zugeordnet.') }}
|
||||
@if($pr->company)
|
||||
<a href="{{ route('me.press-kits.show', $pr->company->id) }}" wire:navigate class="font-medium text-blue-600 hover:underline dark:text-blue-400">
|
||||
{{ __('Kontakte in der Firma prüfen.') }}
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
<flux:card>
|
||||
<flux:heading size="lg" class="mb-4">{{ __('Status & Verlauf') }}</flux:heading>
|
||||
|
||||
<div class="grid gap-3 sm:grid-cols-2">
|
||||
<div class="rounded-lg bg-zinc-50 p-3 dark:bg-zinc-900">
|
||||
<flux:text class="text-xs text-zinc-500">{{ __('Aktueller Status') }}</flux:text>
|
||||
<flux:badge class="mt-1" :color="$statusColor">{{ $pr->status->label() }}</flux:badge>
|
||||
</div>
|
||||
<div class="rounded-lg bg-zinc-50 p-3 dark:bg-zinc-900">
|
||||
<flux:text class="text-xs text-zinc-500">{{ __('Erstellt') }}</flux:text>
|
||||
<flux:text weight="semibold">{{ $pr->created_at?->format('d.m.Y H:i') ?? '–' }}</flux:text>
|
||||
</div>
|
||||
<div class="rounded-lg bg-zinc-50 p-3 dark:bg-zinc-900">
|
||||
<flux:text class="text-xs text-zinc-500">{{ __('Veröffentlicht') }}</flux:text>
|
||||
<flux:text weight="semibold">{{ $pr->published_at?->format('d.m.Y H:i') ?? '–' }}</flux:text>
|
||||
</div>
|
||||
<div class="rounded-lg bg-zinc-50 p-3 dark:bg-zinc-900">
|
||||
<flux:text class="text-xs text-zinc-500">{{ __('Aufrufe') }}</flux:text>
|
||||
<flux:text weight="semibold">{{ number_format($pr->hits, 0, ',', '.') }}</flux:text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<flux:separator class="my-4" />
|
||||
|
||||
@if($statusLogs->isNotEmpty())
|
||||
<ol class="space-y-3 border-s border-zinc-200 ps-4 dark:border-zinc-700">
|
||||
@foreach($statusLogs as $log)
|
||||
<li class="text-sm">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@php
|
||||
$color = match($log->to_status?->value) {
|
||||
'published' => 'green',
|
||||
'review' => 'yellow',
|
||||
'rejected' => 'red',
|
||||
'archived' => 'blue',
|
||||
default => 'zinc',
|
||||
};
|
||||
@endphp
|
||||
<flux:badge size="sm" :color="$color">{{ $log->to_status?->label() }}</flux:badge>
|
||||
<span class="text-xs text-zinc-500">
|
||||
{{ $log->created_at->format('d.m.Y H:i') }}
|
||||
</span>
|
||||
@if($log->changedBy)
|
||||
<span class="text-xs text-zinc-500">
|
||||
{{ __('durch :name', ['name' => $log->changedBy->name]) }}
|
||||
</span>
|
||||
<div class="p-5">
|
||||
<p class="text-[12px] text-[color:var(--color-ink-3)] mt-0 mb-4">
|
||||
{{ __('Kontakte, die dieser Pressemitteilung zugeordnet sind.') }}
|
||||
</p>
|
||||
<div class="space-y-2">
|
||||
@forelse ($contacts as $contact)
|
||||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
|
||||
<div class="text-[13px] font-semibold text-[color:var(--color-ink)]">
|
||||
{{ trim(($contact->first_name ?? '').' '.($contact->last_name ?? '')) ?: __('Kontakt ohne Name') }}
|
||||
</div>
|
||||
<div class="text-[12px] text-[color:var(--color-ink-3)]">
|
||||
{{ $contact->responsibility ?: __('Keine Rolle hinterlegt') }}
|
||||
</div>
|
||||
<div class="mt-1 flex flex-wrap gap-x-3 gap-y-1 text-[11.5px] text-[color:var(--color-ink-3)]">
|
||||
@if ($contact->email)
|
||||
<a href="mailto:{{ $contact->email }}"
|
||||
class="text-[color:var(--color-hub)] underline underline-offset-2 decoration-[color:var(--color-hub)]/40 hover:decoration-[color:var(--color-hub)]">
|
||||
{{ $contact->email }}
|
||||
</a>
|
||||
@endif
|
||||
@if ($contact->phone)
|
||||
<span>{{ $contact->phone }}</span>
|
||||
@endif
|
||||
</div>
|
||||
@if($log->reason)
|
||||
<p class="mt-1 text-zinc-600 dark:text-zinc-400">{{ $log->reason }}</p>
|
||||
</div>
|
||||
@empty
|
||||
<div class="rounded-[5px] border border-dashed border-[color:var(--color-bg-rule)] p-4 text-[12.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ __('Dieser Pressemitteilung ist noch kein Pressekontakt zugeordnet.') }}
|
||||
@if ($pr->company)
|
||||
<a href="{{ route('me.press-kits.show', $pr->company->id) }}" wire:navigate
|
||||
class="font-medium text-[color:var(--color-hub)] hover:underline">
|
||||
{{ __('Kontakte in der Firma prüfen.') }}
|
||||
</a>
|
||||
@endif
|
||||
</li>
|
||||
@endforeach
|
||||
</ol>
|
||||
@else
|
||||
<flux:text class="text-sm text-zinc-500">
|
||||
{{ __('Noch keine Statusänderungen protokolliert.') }}
|
||||
</flux:text>
|
||||
@endif
|
||||
</flux:card>
|
||||
</div>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<flux:card>
|
||||
<div class="prose prose-zinc dark:prose-invert max-w-none">
|
||||
{!! nl2br(e($pr->text)) !!}
|
||||
</div>
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Status & Verlauf') }}</span>
|
||||
<span @class(['badge', $statusClass])>{{ $pr->status->label() }}</span>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<div class="grid gap-2 sm:grid-cols-2">
|
||||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
|
||||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold">{{ __('Aktueller Status') }}</div>
|
||||
<div class="mt-1.5">
|
||||
<span @class(['badge', $statusClass])>{{ $pr->status->label() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
|
||||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold">{{ __('Erstellt') }}</div>
|
||||
<div class="text-[13px] font-semibold text-[color:var(--color-ink)] mt-1">
|
||||
{{ $pr->created_at?->format('d.m.Y H:i') ?? '–' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
|
||||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold">{{ __('Veröffentlicht') }}</div>
|
||||
<div class="text-[13px] font-semibold text-[color:var(--color-ink)] mt-1">
|
||||
{{ $pr->published_at?->format('d.m.Y H:i') ?? '–' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
|
||||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold">{{ __('Aufrufe') }}</div>
|
||||
<div class="text-[13px] font-semibold text-[color:var(--color-ink)] mt-1">
|
||||
{{ number_format($pr->hits, 0, ',', '.') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($pr->keywords || $pr->backlink_url)
|
||||
<div class="mt-6 space-y-2 border-t border-zinc-200 pt-4 text-sm text-zinc-500 dark:border-zinc-700">
|
||||
@if($pr->keywords)
|
||||
<p><strong>{{ __('Stichwörter') }}:</strong> {{ $pr->keywords }}</p>
|
||||
@endif
|
||||
@if($pr->backlink_url)
|
||||
<p><strong>{{ __('Backlink') }}:</strong>
|
||||
<a href="{{ $pr->backlink_url }}" target="_blank" class="text-blue-600 underline">{{ $pr->backlink_url }}</a>
|
||||
<div class="my-4 border-t border-[color:var(--color-bg-rule)]"></div>
|
||||
|
||||
@if ($statusLogs->isNotEmpty())
|
||||
<ol class="space-y-3 border-s border-[color:var(--color-bg-rule)] ps-4">
|
||||
@foreach ($statusLogs as $log)
|
||||
<li class="text-[12.5px]">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@php
|
||||
$logClass = match ($log->to_status?->value) {
|
||||
'published' => 'ok',
|
||||
'review' => 'warn',
|
||||
'rejected' => 'err',
|
||||
default => 'hub',
|
||||
};
|
||||
@endphp
|
||||
<span @class(['badge', $logClass])>{{ $log->to_status?->label() }}</span>
|
||||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ $log->created_at->format('d.m.Y H:i') }}
|
||||
</span>
|
||||
@if ($log->changedBy)
|
||||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ __('durch :name', ['name' => $log->changedBy->name]) }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
@if ($log->reason)
|
||||
<p class="mt-1.5 text-[color:var(--color-ink-2)] m-0">{{ $log->reason }}</p>
|
||||
@endif
|
||||
</li>
|
||||
@endforeach
|
||||
</ol>
|
||||
@else
|
||||
<p class="text-[12.5px] text-[color:var(--color-ink-3)] m-0">
|
||||
{{ __('Noch keine Statusänderungen protokolliert.') }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</flux:card>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
{{-- ============== INHALT ============== --}}
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Inhalt') }}</span>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<div class="prose prose-zinc dark:prose-invert max-w-none text-[color:var(--color-ink)]">
|
||||
{!! nl2br(e($pr->text)) !!}
|
||||
</div>
|
||||
|
||||
@if ($pr->keywords || $pr->backlink_url)
|
||||
<div class="mt-6 space-y-2 border-t border-[color:var(--color-bg-rule)] pt-4 text-[12.5px] text-[color:var(--color-ink-2)]">
|
||||
@if ($pr->keywords)
|
||||
<p class="m-0">
|
||||
<strong class="text-[color:var(--color-ink)] font-semibold">{{ __('Stichwörter') }}:</strong>
|
||||
{{ $pr->keywords }}
|
||||
</p>
|
||||
@endif
|
||||
@if ($pr->backlink_url)
|
||||
<p class="m-0">
|
||||
<strong class="text-[color:var(--color-ink)] font-semibold">{{ __('Backlink') }}:</strong>
|
||||
<a href="{{ $pr->backlink_url }}" target="_blank"
|
||||
class="text-[color:var(--color-hub)] underline underline-offset-2 decoration-[color:var(--color-hub)]/40 hover:decoration-[color:var(--color-hub)]">
|
||||
{{ $pr->backlink_url }}
|
||||
</a>
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue