377 lines
20 KiB
PHP
377 lines
20 KiB
PHP
<!DOCTYPE html>
|
||
{{--
|
||
Hub × FluxUI Phase 5 — Portal-Shell im Hub-Design.
|
||
Erscheinung (Light/Dark) wird über FluxUI Appearance-Switcher
|
||
gesteuert. Server liest das `flux_appearance`-Cookie (gesetzt vom
|
||
JS-Bridge in partials/head.blade.php) und rendert class="dark"
|
||
direkt im <html>, damit es bei wire:navigate kein Theme-Flash gibt.
|
||
Bei fehlendem Cookie (Erstbesuch) wird Light gerendert und das JS
|
||
schaltet bei dunkler Präferenz nach Page-Load nach — der einmalige
|
||
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 breakpoint="1280px" class="border-e border-bg-rule">
|
||
<flux:sidebar.toggle class="xl: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>
|
||
|
||
@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 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
|
||
|
||
{{-- 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: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.index')" wire:navigate>
|
||
{{ __('Zahlungen') }}
|
||
</flux:navlist.item>
|
||
<flux:navlist.item icon="rectangle-stack" :href="route('admin.payments.plans')"
|
||
:current="request()->routeIs('admin.payments.plans')" wire:navigate>
|
||
{{ __('Tarife & Pakete') }}
|
||
</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>
|
||
</div>
|
||
</flux:menu.radio.group>
|
||
|
||
<flux:menu.separator />
|
||
|
||
<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 />
|
||
|
||
{{-- 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>
|
||
|
||
<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:sidebar>
|
||
|
||
<!-- Mobile User Menu -->
|
||
<flux:header class="xl:hidden">
|
||
<flux:sidebar.toggle class="xl: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 />
|
||
|
||
<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>
|
||
</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>
|
||
</div>
|
||
</flux:menu.radio.group>
|
||
|
||
<flux:menu.separator />
|
||
|
||
<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 />
|
||
|
||
{{-- 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>
|
||
|
||
</html>
|