22-05-2026 Optimierung der User und Admin Panels
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled

This commit is contained in:
Kevin Adametz 2026-05-22 11:18:59 +02:00
parent d2ba22c0cf
commit e8c47b7553
73 changed files with 10282 additions and 1546 deletions

View file

@ -10,340 +10,364 @@
Flash beim allerersten Aufruf ist akzeptiert.
--}}
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" @class(['dark' => request()->cookie('flux_appearance') === 'dark'])>
<head>
@include('partials.head')
</head>
<body class="min-h-screen bg-bg text-ink antialiased">
<flux:sidebar sticky stashable class="border-e border-bg-rule">
<flux:sidebar.toggle class="lg:hidden" icon="x-mark" />
{{-- Brand-Block: Wortmarke + Hub-Eyebrow --}}
<a href="{{ config('domains.domain_main_url') }}" class="block px-2 pt-1 pb-3 no-underline">
<span class="text-[19px] font-bold tracking-[-0.4px] leading-none">
<x-web.brand-mark brand="pressekonto" :serif="false" />
</span>
<div class="mt-1.5 text-[10px] font-semibold tracking-[0.18em] uppercase text-ink-3">
Publisher · Hub
</div>
</a>
<head>
@include('partials.head')
</head>
@php
$user = auth()->user();
$impersonation = app(\App\Actions\Admin\UserImpersonation::class);
$impersonator = $impersonation->impersonator();
$isImpersonating = $impersonation->isActive();
$canAdmin = ($user?->canAccessAdmin() ?? false) && ! $isImpersonating;
$canCustomer = $user?->canAccessCustomer() ?? false;
$reviewCount = $canAdmin
? app(\App\Services\Admin\AdminPerformanceCache::class)->pressReleaseReviewCount()
: 0;
@endphp
<body class="min-h-screen bg-bg text-ink antialiased">
<flux:sidebar sticky stashable class="border-e border-bg-rule">
<flux:sidebar.toggle class="lg:hidden" icon="x-mark" />
<flux:navlist variant="outline">
{{-- Dashboard (Admin/Editor) --}}
@if($canAdmin)
<flux:navlist.item icon="chart-bar" :href="route('dashboard')" :current="request()->routeIs('dashboard')" wire:navigate class="mb-4">
{{ __('Dashboard') }}
</flux:navlist.item>
@endif
{{-- Brand-Block: Wortmarke + Hub-Eyebrow --}}
<a href="{{ config('domains.domain_main_url') }}" class="block px-2 pt-1 pb-3 no-underline">
<span class="text-[19px] font-bold tracking-[-0.4px] leading-none">
<x-web.brand-mark brand="pressekonto" :serif="false" />
</span>
<div class="mt-1.5 text-[10px] font-semibold tracking-[0.18em] uppercase text-ink-3">
Publisher · Hub
</div>
</a>
{{-- Mein Bereich sichtbar für alle Panel-User --}}
@if($canCustomer)
<flux:navlist.group :heading="__('Mein Bereich')" class="grid mb-4">
<flux:navlist.item icon="home" :href="route('me.dashboard')" :current="request()->routeIs('me.dashboard')" wire:navigate>
{{ __('Übersicht') }}
</flux:navlist.item>
<flux:navlist.item icon="newspaper" :href="route('me.press-releases.index')" :current="request()->routeIs('me.press-releases.*')" wire:navigate>
{{ __('Meine Pressemitteilungen') }}
</flux:navlist.item>
<flux:navlist.item icon="building-office" :href="route('me.press-kits.index')" :current="request()->routeIs('me.press-kits.*')" wire:navigate>
{{ __('Firmen') }}
</flux:navlist.item>
<flux:navlist.item icon="shopping-bag" :href="route('me.bookings.index')" :current="request()->routeIs('me.bookings.*')" wire:navigate>
{{ __('Buchungen & Add-ons') }}
</flux:navlist.item>
<div class="px-3 py-1.5 text-sm text-zinc-400 dark:text-zinc-500">
{{ __('Statistiken') }} <span class="text-xs">{{ __('später') }}</span>
</div>
</flux:navlist.group>
@php
$user = auth()->user();
$impersonation = app(\App\Actions\Admin\UserImpersonation::class);
$impersonator = $impersonation->impersonator();
$isImpersonating = $impersonation->isActive();
$canAdmin = ($user?->canAccessAdmin() ?? false) && !$isImpersonating;
$canCustomer = $user?->canAccessCustomer() ?? false;
$reviewCount = $canAdmin
? app(\App\Services\Admin\AdminPerformanceCache::class)->pressReleaseReviewCount()
: 0;
@endphp
<flux:navlist.group :heading="__('Finanzen')" class="grid mb-4">
<div class="px-3 py-1.5 text-sm text-zinc-400 dark:text-zinc-500">
{{ __('Credits & Tarif') }} <span class="text-xs">{{ __('später') }}</span>
</div>
<flux:navlist.item icon="document-text" :href="route('me.invoices.index')" :current="request()->routeIs('me.invoices.*')" wire:navigate>
{{ __('Rechnungen') }}
</flux:navlist.item>
<div class="px-3 py-1.5 text-sm text-zinc-400 dark:text-zinc-500">
{{ __('Zahlungsarten') }} <span class="text-xs">{{ __('später') }}</span>
</div>
</flux:navlist.group>
<flux:navlist.group :heading="__('Konto')" class="grid mb-4">
<flux:navlist.item icon="user" :href="route('me.profile')" :current="request()->routeIs('me.profile')" wire:navigate>
{{ __('Profil') }}
</flux:navlist.item>
<flux:navlist.item icon="shield-check" :href="route('me.security')" :current="request()->routeIs('me.security')" wire:navigate>
{{ __('Sicherheit') }}
</flux:navlist.item>
<flux:navlist.item icon="key" :href="route('me.tokens.index')" :current="request()->routeIs('me.tokens.*')" wire:navigate>
{{ __('API & Integrationen') }}
</flux:navlist.item>
<div class="px-3 py-1.5 text-sm text-zinc-400 dark:text-zinc-500">
{{ __('Benachrichtigungen') }} <span class="text-xs">{{ __('später') }}</span>
</div>
</flux:navlist.group>
@endif
{{-- Content Management (Admin/Editor) --}}
@if($canAdmin)
<flux:navlist.group :heading="__('Content')" class="grid mb-4">
<flux:navlist.item
icon="newspaper"
:href="route('admin.press-releases.index', $reviewCount > 0 ? ['status' => 'review'] : [])"
:current="request()->routeIs('admin.press-releases.*')"
:badge="$reviewCount > 0 ? $reviewCount : null"
badge-color="yellow"
wire:navigate
>
{{ __('Pressemitteilungen') }}
</flux:navlist.item>
<flux:navlist.item icon="folder" :href="route('admin.categories.index')" :current="request()->routeIs('admin.categories.*')" wire:navigate>
{{ __('Kategorien') }}
</flux:navlist.item>
<flux:navlist.item icon="code-bracket-square" :href="route('admin.footer-codes.index')" :current="request()->routeIs('admin.footer-codes.*')" wire:navigate>
{{ __('Footer-Codes') }}
</flux:navlist.item>
</flux:navlist.group>
{{-- CRM --}}
<flux:navlist.group :heading="__('CRM')" class="grid mb-4">
<flux:navlist.item icon="building-office" :href="route('admin.companies.index')" :current="request()->routeIs('admin.companies.*')" wire:navigate>
{{ __('Firmen') }}
</flux:navlist.item>
<flux:navlist.item icon="user-group" :href="route('admin.contacts.index')" :current="request()->routeIs('admin.contacts.*')" wire:navigate>
{{ __('Kontakte') }}
</flux:navlist.item>
</flux:navlist.group>
{{-- Billing --}}
<flux:navlist.group :heading="__('Billing')" class="grid mb-4">
<flux:navlist.item icon="archive-box" :href="route('admin.invoices.index')" :current="request()->routeIs('admin.invoices.*')" wire:navigate>
{{ __('Legacy Rechnungen') }}
</flux:navlist.item>
<flux:navlist.item icon="credit-card" :href="route('admin.payments.index')" :current="request()->routeIs('admin.payments.*')" wire:navigate>
{{ __('Zahlungen') }}
</flux:navlist.item>
<flux:navlist.item icon="ticket" :href="route('admin.coupons.index')" :current="request()->routeIs('admin.coupons.*')" wire:navigate>
{{ __('Gutscheine') }}
</flux:navlist.item>
<flux:navlist.item icon="envelope" :href="route('admin.newsletter.sync')" :current="request()->routeIs('admin.newsletter.sync')" wire:navigate>
{{ __('Newsletter Sync') }}
</flux:navlist.item>
</flux:navlist.group>
{{-- Administration --}}
<flux:navlist.group :heading="__('Administration')" class="grid mb-4">
<flux:navlist.item icon="cog" :href="route('admin.presets.index')" :current="request()->routeIs('admin.presets.*')" wire:navigate>
{{ __('Voreinstellungen') }}
</flux:navlist.item>
<flux:navlist.item icon="users" :href="route('admin.users.index')" :current="request()->routeIs('admin.users.*')" wire:navigate>
{{ __('Benutzer') }}
</flux:navlist.item>
<flux:navlist.item icon="shield-check" :href="route('admin.roles.index')" :current="request()->routeIs('admin.roles.*')" wire:navigate>
{{ __('Rollen & Rechte') }}
</flux:navlist.item>
</flux:navlist.group>
{{-- Reports --}}
<flux:navlist.group :heading="__('Reports')" class="grid mb-4">
<flux:navlist.item icon="chart-bar-square" :href="route('admin.reports.slow-requests')" :current="request()->routeIs('admin.reports.*')" wire:navigate>
{{ __('Performance') }}
</flux:navlist.item>
</flux:navlist.group>
@endif
</flux:navlist>
{{-- Portal-Filter für Admin-Benutzer (P2.6) --}}
@auth
@if($canAdmin)
<div class="border-t border-zinc-200 dark:border-zinc-700 mt-2 pt-2">
<livewire:admin.portal-switcher />
</div>
@endif
@endauth
@if($impersonator)
{{-- Testmodus-Block im Hub-Stil (statt Amber-Warnfarbe).
Dunkles Hub-Blau-Panel mit Bernstein-Eyebrow, klare
CTA „Zurück zum Admin" als helle Pille. --}}
<div class="mt-3 relative overflow-hidden rounded-[5px] bg-hub p-4 text-ink-on-dark">
<div class="absolute -top-6 -right-6 w-16 h-16 rounded-full bg-hub-3 opacity-50"></div>
<div class="absolute -bottom-8 -left-8 w-20 h-20 rounded-full bg-hub-3 opacity-30"></div>
<div class="relative">
<div class="flex items-center gap-2 mb-2">
<span class="w-1.5 h-1.5 rounded-full bg-accent animate-pulse"></span>
<span class="text-[10.5px] font-bold tracking-[0.20em] uppercase text-accent-soft">
{{ __('Testmodus aktiv') }}
</span>
</div>
<p class="text-[12px] leading-[1.5] text-ink-on-dark-2 m-0">
{{ __('Angemeldet als') }}
<strong class="text-white font-semibold">{{ $user?->name }}</strong>.<br/>
{{ __('Admin:') }}
<strong class="text-white font-semibold">{{ $impersonator->name }}</strong>
</p>
<form method="POST" action="{{ route('admin.impersonate.leave') }}" class="mt-3">
@csrf
<button
type="submit"
class="w-full px-3 py-2 bg-white text-hub text-[12px] font-semibold rounded-[3px] hover:bg-bg transition-colors flex items-center justify-center gap-1.5"
>
<svg width="11" height="11" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="M9 3L3 9M3 9H8M3 9V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
{{ __('Zurück zum Admin') }}
</button>
</form>
</div>
</div>
<flux:navlist variant="outline">
{{-- Dashboard (Admin/Editor) --}}
@if ($canAdmin)
<flux:navlist.item icon="chart-bar" :href="route('dashboard')"
:current="request()->routeIs('dashboard')" wire:navigate class="mb-4">
{{ __('Dashboard') }}
</flux:navlist.item>
@endif
<flux:spacer />
<!-- Desktop User Menu -->
<flux:dropdown position="bottom" align="start">
<flux:profile
:name="auth()->user()->name"
:initials="auth()->user()->initials()"
icon-trailing="chevrons-up-down"
/>
{{-- Mein Bereich sichtbar für alle Panel-User --}}
@if ($canCustomer)
<flux:navlist.group :heading="__('Mein Bereich')" class="grid mb-4">
<flux:navlist.item icon="home" :href="route('me.dashboard')"
:current="request()->routeIs('me.dashboard')" wire:navigate>
{{ __('Übersicht') }}
</flux:navlist.item>
<flux:navlist.item icon="newspaper" :href="route('me.press-releases.index')"
:current="request()->routeIs('me.press-releases.*')" wire:navigate>
{{ __('Pressemitteilungen') }}
</flux:navlist.item>
<flux:navlist.item icon="building-office" :href="route('me.press-kits.index')"
:current="request()->routeIs('me.press-kits.*')" wire:navigate>
{{ __('Firmen') }}
</flux:navlist.item>
<flux:navlist.item icon="shopping-bag" :href="route('me.bookings.index')"
:current="request()->routeIs('me.bookings.*')" wire:navigate>
{{ __('Buchungen & Add-ons') }}
</flux:navlist.item>
<div class="px-3 py-1.5 text-sm text-zinc-400 dark:text-zinc-500">
{{ __('Statistiken') }} <span class="text-xs">{{ __('später') }}</span>
</div>
</flux:navlist.group>
<flux:menu class="w-[220px]">
<flux:menu.radio.group>
<div class="p-0 text-sm font-normal">
<div class="flex items-center gap-2 px-1 py-1.5 text-start text-sm">
<span class="relative flex h-8 w-8 shrink-0 overflow-hidden rounded-lg">
<span
class="flex h-full w-full items-center justify-center rounded-lg bg-neutral-200 text-black dark:bg-neutral-700 dark:text-white"
>
{{ auth()->user()->initials() }}
</span>
<flux:navlist.group :heading="__('Finanzen')" class="grid mb-4">
<div class="px-3 py-1.5 text-sm text-zinc-400 dark:text-zinc-500">
{{ __('Credits & Tarif') }} <span class="text-xs">{{ __('später') }}</span>
</div>
<flux:navlist.item icon="document-text" :href="route('me.invoices.index')"
:current="request()->routeIs('me.invoices.*')" wire:navigate>
{{ __('Rechnungen') }}
</flux:navlist.item>
<div class="px-3 py-1.5 text-sm text-zinc-400 dark:text-zinc-500">
{{ __('Zahlungsarten') }} <span class="text-xs">{{ __('später') }}</span>
</div>
</flux:navlist.group>
<flux:navlist.group :heading="__('Konto')" class="grid mb-4">
<flux:navlist.item icon="user" :href="route('me.profile')"
:current="request()->routeIs('me.profile')" wire:navigate>
{{ __('Profil') }}
</flux:navlist.item>
<flux:navlist.item icon="shield-check" :href="route('me.security')"
:current="request()->routeIs('me.security')" wire:navigate>
{{ __('Sicherheit') }}
</flux:navlist.item>
<flux:navlist.item icon="key" :href="route('me.tokens.index')"
:current="request()->routeIs('me.tokens.*')" wire:navigate>
{{ __('API & Integrationen') }}
</flux:navlist.item>
<div class="px-3 py-1.5 text-sm text-zinc-400 dark:text-zinc-500">
{{ __('Benachrichtigungen') }} <span class="text-xs">{{ __('später') }}</span>
</div>
</flux:navlist.group>
@endif
{{-- Content Management (Admin/Editor) --}}
@if ($canAdmin)
<flux:navlist.group :heading="__('Content')" class="grid mb-4">
<flux:navlist.item icon="newspaper"
:href="route('admin.press-releases.index', $reviewCount > 0 ? ['status' => 'review'] : [])"
:current="request()->routeIs('admin.press-releases.*')"
:badge="$reviewCount > 0 ? $reviewCount : null" badge-color="yellow" wire:navigate>
{{ __('Pressemitteilungen') }}
</flux:navlist.item>
<flux:navlist.item icon="folder" :href="route('admin.categories.index')"
:current="request()->routeIs('admin.categories.*')" wire:navigate>
{{ __('Kategorien') }}
</flux:navlist.item>
<flux:navlist.item icon="code-bracket-square" :href="route('admin.footer-codes.index')"
:current="request()->routeIs('admin.footer-codes.*')" wire:navigate>
{{ __('Footer-Codes') }}
</flux:navlist.item>
</flux:navlist.group>
{{-- CRM --}}
<flux:navlist.group :heading="__('CRM')" class="grid mb-4">
<flux:navlist.item icon="building-office" :href="route('admin.companies.index')"
:current="request()->routeIs('admin.companies.*')" wire:navigate>
{{ __('Firmen') }}
</flux:navlist.item>
<flux:navlist.item icon="user-group" :href="route('admin.contacts.index')"
:current="request()->routeIs('admin.contacts.*')" wire:navigate>
{{ __('Kontakte') }}
</flux:navlist.item>
</flux:navlist.group>
{{-- Billing --}}
<flux:navlist.group :heading="__('Billing')" class="grid mb-4">
<flux:navlist.item icon="archive-box" :href="route('admin.invoices.index')"
:current="request()->routeIs('admin.invoices.*')" wire:navigate>
{{ __('Legacy Rechnungen') }}
</flux:navlist.item>
<flux:navlist.item icon="credit-card" :href="route('admin.payments.index')"
:current="request()->routeIs('admin.payments.*')" wire:navigate>
{{ __('Zahlungen') }}
</flux:navlist.item>
<flux:navlist.item icon="ticket" :href="route('admin.coupons.index')"
:current="request()->routeIs('admin.coupons.*')" wire:navigate>
{{ __('Gutscheine') }}
</flux:navlist.item>
<flux:navlist.item icon="envelope" :href="route('admin.newsletter.sync')"
:current="request()->routeIs('admin.newsletter.sync')" wire:navigate>
{{ __('Newsletter Sync') }}
</flux:navlist.item>
</flux:navlist.group>
{{-- Administration --}}
<flux:navlist.group :heading="__('Administration')" class="grid mb-4">
<flux:navlist.item icon="cog" :href="route('admin.presets.index')"
:current="request()->routeIs('admin.presets.*')" wire:navigate>
{{ __('Voreinstellungen') }}
</flux:navlist.item>
<flux:navlist.item icon="users" :href="route('admin.users.index')"
:current="request()->routeIs('admin.users.*')" wire:navigate>
{{ __('Benutzer') }}
</flux:navlist.item>
<flux:navlist.item icon="shield-check" :href="route('admin.roles.index')"
:current="request()->routeIs('admin.roles.*')" wire:navigate>
{{ __('Rollen & Rechte') }}
</flux:navlist.item>
</flux:navlist.group>
{{-- Reports --}}
<flux:navlist.group :heading="__('Reports')" class="grid mb-4">
<flux:navlist.item icon="chart-bar-square" :href="route('admin.reports.slow-requests')"
:current="request()->routeIs('admin.reports.*')" wire:navigate>
{{ __('Performance') }}
</flux:navlist.item>
</flux:navlist.group>
@endif
</flux:navlist>
{{-- Portal-Filter für Admin-Benutzer (P2.6) --}}
@auth
@if ($canAdmin)
<div class="border-t border-zinc-200 dark:border-zinc-700 mt-2 pt-2">
<livewire:admin.portal-switcher />
</div>
@endif
@endauth
@if ($impersonator)
{{-- Testmodus-Block im Hub-Stil (statt Amber-Warnfarbe).
Dunkles Hub-Blau-Panel mit Bernstein-Eyebrow, klare
CTA „Zurück zum Admin" als helle Pille. --}}
<div class="mt-3 relative overflow-hidden rounded-[5px] bg-hub p-4 text-ink-on-dark">
<div class="absolute -top-6 -right-6 w-16 h-16 rounded-full bg-hub-3 opacity-50"></div>
<div class="absolute -bottom-8 -left-8 w-20 h-20 rounded-full bg-hub-3 opacity-30"></div>
<div class="relative">
<div class="flex items-center gap-2 mb-2">
<span class="w-1.5 h-1.5 rounded-full bg-accent animate-pulse"></span>
<span class="text-[10.5px] font-bold tracking-[0.20em] uppercase text-accent-soft">
{{ __('Testmodus aktiv') }}
</span>
</div>
<p class="text-[12px] leading-[1.5] text-ink-on-dark-2 m-0">
{{ __('Angemeldet als') }}
<strong class="text-white font-semibold">{{ $user?->name }}</strong>.<br />
{{ __('Admin:') }}
<strong class="text-white font-semibold">{{ $impersonator->name }}</strong>
</p>
<form method="POST" action="{{ route('admin.impersonate.leave') }}" class="mt-3">
@csrf
<button type="submit"
class="w-full px-3 py-2 bg-white text-hub text-[12px] font-semibold rounded-[3px] hover:bg-bg transition-colors flex items-center justify-center gap-1.5">
<svg width="11" height="11" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="M9 3L3 9M3 9H8M3 9V4" stroke="currentColor" stroke-width="1.5"
stroke-linecap="round" stroke-linejoin="round" />
</svg>
{{ __('Zurück zum Admin') }}
</button>
</form>
</div>
</div>
@endif
<flux:spacer />
<!-- Desktop User Menu -->
<flux:dropdown position="bottom" align="start">
<flux:profile :name="auth()->user()->name" :initials="auth()->user()->initials()"
icon-trailing="chevrons-up-down" />
<flux:menu class="w-[220px]">
<flux:menu.radio.group>
<div class="p-0 text-sm font-normal">
<div class="flex items-center gap-2 px-1 py-1.5 text-start text-sm">
<span class="relative flex h-8 w-8 shrink-0 overflow-hidden rounded-lg">
<span
class="flex h-full w-full items-center justify-center rounded-lg bg-neutral-200 text-black dark:bg-neutral-700 dark:text-white">
{{ auth()->user()->initials() }}
</span>
</span>
<div class="grid flex-1 text-start text-sm leading-tight">
<span class="truncate font-semibold">{{ auth()->user()->name }}</span>
<span class="truncate text-xs">{{ auth()->user()->email }}</span>
</div>
<div class="grid flex-1 text-start text-sm leading-tight">
<span class="truncate font-semibold">{{ auth()->user()->name }}</span>
<span class="truncate text-xs">{{ auth()->user()->email }}</span>
</div>
</div>
</flux:menu.radio.group>
</div>
</flux:menu.radio.group>
<flux:menu.separator />
<flux:menu.separator />
<flux:menu.radio.group>
<flux:menu.item :href="route('settings.profile')" icon="cog" wire:navigate>{{ __('Settings') }}</flux:menu.item>
</flux:menu.radio.group>
<flux:menu.radio.group>
<flux:menu.item :href="route('me.profile')" icon="user" wire:navigate>
{{ __('Profil') }}</flux:menu.item>
</flux:menu.radio.group>
<flux:menu.separator />
<flux:menu.separator />
{{-- Phase 5: Appearance-Switcher direkt im User-Menü.
{{-- Phase 5: Appearance-Switcher direkt im User-Menü.
`$flux.appearance` ist FluxUIs Magic-Object, persistent
über LocalStorage. Werte: 'light' | 'dark' | 'system'. --}}
<div class="px-3 py-2">
<div class="mb-1.5 text-[10px] font-semibold tracking-[0.16em] uppercase text-[color:var(--color-ink-3)]">
{{ __('Erscheinung') }}
</div>
<flux:radio.group x-data variant="segmented" size="sm" x-model="$flux.appearance" class="w-full">
<flux:radio value="light" icon="sun" :title="__('Hell')" />
<flux:radio value="dark" icon="moon" :title="__('Dunkel')" />
<flux:radio value="system" icon="computer-desktop" :title="__('System')" />
</flux:radio.group>
<div class="px-3 py-2">
<div
class="mb-1.5 text-[10px] font-semibold tracking-[0.16em] uppercase text-[color:var(--color-ink-3)]">
{{ __('Erscheinung') }}
</div>
<flux:radio.group x-data variant="segmented" size="sm" x-model="$flux.appearance"
class="w-full">
<flux:radio value="light" icon="sun" :title="__('Hell')" />
<flux:radio value="dark" icon="moon" :title="__('Dunkel')" />
<flux:radio value="system" icon="computer-desktop" :title="__('System')" />
</flux:radio.group>
</div>
<flux:menu.separator />
<flux:menu.separator />
<form method="POST" action="{{ route('logout') }}" class="w-full">
@csrf
<flux:menu.item as="button" type="submit" icon="arrow-right-start-on-rectangle" class="w-full">
{{ __('Log Out') }}
</flux:menu.item>
</form>
</flux:menu>
</flux:dropdown>
</flux:sidebar>
<form method="POST" action="{{ route('logout') }}" class="w-full">
@csrf
<flux:menu.item as="button" type="submit" icon="arrow-right-start-on-rectangle"
class="w-full">
{{ __('Abmelden') }}
</flux:menu.item>
</form>
</flux:menu>
</flux:dropdown>
</flux:sidebar>
<!-- Mobile User Menu -->
<flux:header class="lg:hidden">
<flux:sidebar.toggle class="lg:hidden" icon="bars-2" inset="left" />
<!-- Mobile User Menu -->
<flux:header class="lg:hidden">
<flux:sidebar.toggle class="lg:hidden" icon="bars-2" inset="left" />
<a href="{{ config('domains.domain_main_url') }}" class="me-5 ml-2 flex items-baseline no-underline">
<span class="text-[16px] font-bold tracking-[-0.3px] leading-none">
<x-web.brand-mark brand="pressekonto" :serif="false" />
</span>
</a>
<flux:spacer />
<a href="{{ config('domains.domain_main_url') }}" class="me-5 ml-2 flex items-baseline no-underline">
<span class="text-[16px] font-bold tracking-[-0.3px] leading-none">
<x-web.brand-mark brand="pressekonto" :serif="false" />
</span>
</a>
<flux:spacer />
<flux:dropdown position="top" align="end">
<flux:profile
:initials="auth()->user()->initials()"
icon-trailing="chevron-down"
/>
<flux:dropdown position="top" align="end">
<flux:profile :initials="auth()->user()->initials()" icon-trailing="chevron-down" />
<flux:menu>
<flux:menu.radio.group>
<div class="p-0 text-sm font-normal">
<div class="flex items-center gap-2 px-1 py-1.5 text-start text-sm">
<span class="relative flex h-8 w-8 shrink-0 overflow-hidden rounded-lg">
<span
class="flex h-full w-full items-center justify-center rounded-lg bg-neutral-200 text-black dark:bg-neutral-700 dark:text-white"
>
{{ auth()->user()->initials() }}
</span>
<flux:menu>
<flux:menu.radio.group>
<div class="p-0 text-sm font-normal">
<div class="flex items-center gap-2 px-1 py-1.5 text-start text-sm">
<span class="relative flex h-8 w-8 shrink-0 overflow-hidden rounded-lg">
<span
class="flex h-full w-full items-center justify-center rounded-lg bg-neutral-200 text-black dark:bg-neutral-700 dark:text-white">
{{ auth()->user()->initials() }}
</span>
</span>
<div class="grid flex-1 text-start text-sm leading-tight">
<span class="truncate font-semibold">{{ auth()->user()->name }}</span>
<span class="truncate text-xs">{{ auth()->user()->email }}</span>
</div>
<div class="grid flex-1 text-start text-sm leading-tight">
<span class="truncate font-semibold">{{ auth()->user()->name }}</span>
<span class="truncate text-xs">{{ auth()->user()->email }}</span>
</div>
</div>
</flux:menu.radio.group>
<flux:menu.separator />
<flux:menu.radio.group>
<flux:menu.item :href="route('settings.profile')" icon="cog" wire:navigate>{{ __('Settings') }}</flux:menu.item>
</flux:menu.radio.group>
<flux:menu.separator />
{{-- Phase 5: Appearance-Switcher (Mobile-Dropdown). --}}
<div class="px-3 py-2">
<div class="mb-1.5 text-[10px] font-semibold tracking-[0.16em] uppercase text-[color:var(--color-ink-3)]">
{{ __('Erscheinung') }}
</div>
<flux:radio.group x-data variant="segmented" size="sm" x-model="$flux.appearance" class="w-full">
<flux:radio value="light" icon="sun" :title="__('Hell')" />
<flux:radio value="dark" icon="moon" :title="__('Dunkel')" />
<flux:radio value="system" icon="computer-desktop" :title="__('System')" />
</flux:radio.group>
</div>
</flux:menu.radio.group>
<flux:menu.separator />
<flux:menu.separator />
<form method="POST" action="{{ route('logout') }}" class="w-full">
@csrf
<flux:menu.item as="button" type="submit" icon="arrow-right-start-on-rectangle" class="w-full">
{{ __('Log Out') }}
</flux:menu.item>
</form>
</flux:menu>
</flux:dropdown>
</flux:header>
<flux:menu.radio.group>
<flux:menu.item :href="route('me.profile')" icon="user" wire:navigate>
{{ __('Profil') }}</flux:menu.item>
</flux:menu.radio.group>
{{ $slot }}
<flux:menu.separator />
{{-- Phase 5: Appearance-Switcher (Mobile-Dropdown). --}}
<div class="px-3 py-2">
<div
class="mb-1.5 text-[10px] font-semibold tracking-[0.16em] uppercase text-[color:var(--color-ink-3)]">
{{ __('Erscheinung') }}
</div>
<flux:radio.group x-data variant="segmented" size="sm" x-model="$flux.appearance"
class="w-full">
<flux:radio value="light" icon="sun" :title="__('Hell')" />
<flux:radio value="dark" icon="moon" :title="__('Dunkel')" />
<flux:radio value="system" icon="computer-desktop" :title="__('System')" />
</flux:radio.group>
</div>
<flux:menu.separator />
<form method="POST" action="{{ route('logout') }}" class="w-full">
@csrf
<flux:menu.item as="button" type="submit" icon="arrow-right-start-on-rectangle"
class="w-full">
{{ __('Abmelden') }}
</flux:menu.item>
</form>
</flux:menu>
</flux:dropdown>
</flux:header>
{{ $slot }}
@persist('toast')
<flux:toast position="top end" class="pt-24" />
@endpersist
@vite(['resources/js/portal-form-hooks.js'], 'build/portal')
@fluxScripts
</body>
@fluxScripts
</body>
</html>

View file

@ -40,9 +40,7 @@
<link rel="icon" href="{{ asset(\App\Helpers\ThemeHelper::getFaviconPath()) }}" />
<link rel="preconnect" href="https://fonts.bunny.net" />
<link rel="preconnect" href="https://fonts.bunny.net" crossorigin />
<link href="https://fonts.bunny.net/css?family=inter-tight:400,500,600,700|source-serif-4:400,500,600,700|jetbrains-mono:400,500,600" rel="stylesheet" />
@include('partials.local-fonts')
{{-- Nur CSS aus dem Web-Build laden. Alpine bringt @livewireScripts mit;
würden wir hier zusätzlich resources/js/app.js mit Alpine.start()

View file

@ -0,0 +1,112 @@
@php
if (! isset($scrollTo)) {
$scrollTo = 'body';
}
$scrollIntoViewJsSnippet = ($scrollTo !== false)
? <<<JS
(\$el.closest('{$scrollTo}') || document.querySelector('{$scrollTo}')).scrollIntoView()
JS
: '';
$pageName = $paginator->getPageName();
$isLengthAware = method_exists($paginator, 'total');
@endphp
@if ($paginator->hasPages())
<nav role="navigation" aria-label="{{ __('Pagination Navigation') }}" class="portal-pagination flex flex-col gap-3 rounded-md border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-card)] px-4 py-3 sm:flex-row sm:items-center sm:justify-between">
<p class="m-0 text-[12px] leading-5 text-[color:var(--color-ink-3)]">
@if ($isLengthAware)
{{ __('Zeige') }}
<span class="font-mono font-semibold text-[color:var(--color-ink)]">{{ $paginator->firstItem() }}</span>
{{ __('bis') }}
<span class="font-mono font-semibold text-[color:var(--color-ink)]">{{ $paginator->lastItem() }}</span>
{{ __('von') }}
<span class="font-mono font-semibold text-[color:var(--color-ink)]">{{ $paginator->total() }}</span>
@else
{{ __('Seite') }}
<span class="font-mono font-semibold text-[color:var(--color-ink)]">{{ $paginator->currentPage() }}</span>
<span class="text-[color:var(--color-ink-4)]">·</span>
{{ __('einfache Navigation') }}
@endif
</p>
<div class="flex flex-wrap items-center gap-1.5">
@if ($paginator->onFirstPage())
<span aria-disabled="true" aria-label="{{ __('pagination.previous') }}" class="inline-flex h-9 min-w-9 cursor-default items-center justify-center rounded-[4px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] px-3 text-[12px] font-semibold text-[color:var(--color-ink-4)]">
{{ __('Zurück') }}
</span>
@else
<button
type="button"
wire:click="previousPage('{{ $pageName }}')"
x-on:click="{{ $scrollIntoViewJsSnippet }}"
wire:loading.attr="disabled"
aria-label="{{ __('pagination.previous') }}"
class="inline-flex h-9 min-w-9 cursor-pointer items-center justify-center rounded-[4px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] px-3 text-[12px] font-semibold text-[color:var(--color-hub)] transition hover:border-[color:var(--color-hub)] hover:bg-[color:var(--color-hub-soft)] focus:outline-none focus:ring-2 focus:ring-[color:var(--color-hub)]/25 disabled:cursor-wait disabled:opacity-60"
>
{{ __('Zurück') }}
</button>
@endif
@if ($isLengthAware)
@foreach ($elements as $element)
@if (is_string($element))
<span aria-disabled="true" class="inline-flex h-9 min-w-9 cursor-default items-center justify-center rounded-[4px] px-2 text-[12px] font-semibold text-[color:var(--color-ink-4)]">
{{ $element }}
</span>
@endif
@if (is_array($element))
@foreach ($element as $page => $url)
@if ($page === $paginator->currentPage())
<span
aria-current="page"
class="is-active inline-flex h-9 min-w-9 cursor-default items-center justify-center rounded-[4px] border border-[color:var(--color-hub)] bg-[color:var(--color-hub)] px-3 text-[12px] font-bold text-[color:var(--color-ink-on-dark)] shadow-[0_0_0_2px_var(--color-hub-soft)]"
>
{{ $page }}
</span>
@else
<button
type="button"
wire:key="paginator-{{ $pageName }}-page-{{ $page }}"
wire:click="gotoPage({{ $page }}, '{{ $pageName }}')"
x-on:click="{{ $scrollIntoViewJsSnippet }}"
wire:loading.attr="disabled"
aria-label="{{ __('Go to page :page', ['page' => $page]) }}"
class="inline-flex h-9 min-w-9 cursor-pointer items-center justify-center rounded-[4px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] px-3 text-[12px] font-semibold text-[color:var(--color-ink-2)] transition hover:border-[color:var(--color-hub)] hover:bg-[color:var(--color-hub-soft)] hover:text-[color:var(--color-hub)] focus:outline-none focus:ring-2 focus:ring-[color:var(--color-hub)]/25 disabled:cursor-wait disabled:opacity-60"
>
{{ $page }}
</button>
@endif
@endforeach
@endif
@endforeach
@else
<span
aria-current="page"
class="is-active inline-flex h-9 min-w-9 cursor-default items-center justify-center rounded-[4px] border border-[color:var(--color-hub)] bg-[color:var(--color-hub)] px-3 text-[12px] font-bold text-[color:var(--color-ink-on-dark)] shadow-[0_0_0_2px_var(--color-hub-soft)]"
>
{{ $paginator->currentPage() }}
</span>
@endif
@if ($paginator->hasMorePages())
<button
type="button"
wire:click="nextPage('{{ $pageName }}')"
x-on:click="{{ $scrollIntoViewJsSnippet }}"
wire:loading.attr="disabled"
aria-label="{{ __('pagination.next') }}"
class="inline-flex h-9 min-w-9 cursor-pointer items-center justify-center rounded-[4px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] px-3 text-[12px] font-semibold text-[color:var(--color-hub)] transition hover:border-[color:var(--color-hub)] hover:bg-[color:var(--color-hub-soft)] focus:outline-none focus:ring-2 focus:ring-[color:var(--color-hub)]/25 disabled:cursor-wait disabled:opacity-60"
>
{{ __('Weiter') }}
</button>
@else
<span aria-disabled="true" aria-label="{{ __('pagination.next') }}" class="inline-flex h-9 min-w-9 cursor-default items-center justify-center rounded-[4px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] px-3 text-[12px] font-semibold text-[color:var(--color-ink-4)]">
{{ __('Weiter') }}
</span>
@endif
</div>
</nav>
@endif