- Laravel 11, Blade, Alpine.js, Tailwind CSS - Produkt- & Kategorieverwaltung mit Varianten - Checkout (Vorkasse + Rechnung), Session-Warenkorb - Admin-Backend (Dashboard, Bestellungen, Kunden, CMS) - DSGVO-konform: Cookie-Consent, Impressum, Widerruf, AGB - PDF-Rechnung via dompdf, SMTP-Mailversand - Kundenverwaltung mit manueller Freischaltung Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
216 lines
11 KiB
PHP
216 lines
11 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>@yield('title', \App\Models\Setting::get('shop_name', 'Shop'))</title>
|
|
<meta name="description" content="@yield('meta_description', '')">
|
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
|
</head>
|
|
<body class="bg-gray-50 text-gray-800 flex flex-col min-h-screen">
|
|
|
|
{{-- Cookie-Consent Banner --}}
|
|
@if(\App\Models\Setting::get('cookie_consent') && !Cookie::has('cookie_consent'))
|
|
<div id="cookie-banner" class="fixed bottom-0 left-0 right-0 bg-gray-900 text-white p-4 z-50 flex flex-col sm:flex-row items-center justify-between gap-3">
|
|
<p class="text-sm">Wir verwenden Cookies, um Ihnen die bestmögliche Nutzererfahrung zu bieten. Mit dem Klick auf „Akzeptieren" stimmen Sie der Verwendung aller Cookies zu. <a href="{{ route('page', 'datenschutz') }}" class="underline">Mehr erfahren</a></p>
|
|
<div class="flex gap-2 shrink-0">
|
|
<button onclick="setCookieConsent(false)" class="px-4 py-2 text-sm border border-gray-500 rounded hover:bg-gray-700">Ablehnen</button>
|
|
<button onclick="setCookieConsent(true)" class="px-4 py-2 text-sm bg-blue-500 rounded hover:bg-blue-600">Akzeptieren</button>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
function setCookieConsent(accept) {
|
|
document.cookie = 'cookie_consent=' + (accept ? '1' : '0') + '; path=/; max-age=31536000';
|
|
document.getElementById('cookie-banner').remove();
|
|
}
|
|
</script>
|
|
@endif
|
|
|
|
@php
|
|
$legalSlugs = ['impressum','datenschutz','agb','widerrufsbelehrung'];
|
|
$navPages = \App\Models\Page::where('published', true)
|
|
->where('show_in_nav', true)
|
|
->orderBy('nav_sort')
|
|
->orderBy('title')
|
|
->get();
|
|
@endphp
|
|
|
|
{{-- Header: Logo-Leiste --}}
|
|
<header class="bg-white border-b shadow-sm">
|
|
<div class="max-w-6xl mx-auto px-4 py-3 flex items-center justify-between gap-4">
|
|
|
|
{{-- Logo: Bild (wenn hinterlegt) oder Shop-Name als Text --}}
|
|
<a href="{{ url('/') }}" class="shrink-0 flex items-center">
|
|
@php $logo = \App\Models\Setting::get('shop_logo'); @endphp
|
|
@if($logo)
|
|
<img src="{{ asset('storage/'.$logo) }}" alt="{{ \App\Models\Setting::get('shop_name', 'Shop') }}" class="h-12 w-auto object-contain">
|
|
@else
|
|
<span class="text-2xl font-bold text-blue-700 tracking-tight">
|
|
{{ \App\Models\Setting::get('shop_name', 'Shop') }}
|
|
</span>
|
|
@endif
|
|
</a>
|
|
|
|
{{-- Haupt-Navigation rechts --}}
|
|
<nav class="flex items-center gap-1">
|
|
<a href="{{ route('shop.products.index') }}"
|
|
class="px-3 py-2 rounded-lg text-sm font-medium text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition">
|
|
Alle Produkte
|
|
</a>
|
|
@foreach($navPages as $navPage)
|
|
<a href="{{ route('page', $navPage->slug) }}"
|
|
class="px-3 py-2 rounded-lg text-sm font-medium text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition
|
|
{{ request()->route('slug') === $navPage->slug ? 'bg-blue-50 text-blue-700' : '' }}">
|
|
{{ $navPage->title }}
|
|
</a>
|
|
@endforeach
|
|
@auth
|
|
<a href="{{ route('account.index') }}"
|
|
class="px-3 py-2 rounded-lg text-sm font-medium text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition">
|
|
Mein Konto
|
|
</a>
|
|
@if(auth()->user()->isAdmin())
|
|
<a href="{{ route('admin.dashboard') }}"
|
|
class="px-3 py-2 rounded-lg text-sm font-medium text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition">
|
|
Admin
|
|
</a>
|
|
@endif
|
|
@else
|
|
<a href="{{ route('login') }}"
|
|
class="px-3 py-2 rounded-lg text-sm font-medium text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition">
|
|
Anmelden
|
|
</a>
|
|
<a href="{{ route('register') }}"
|
|
class="px-3 py-2 rounded-lg text-sm font-medium text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition">
|
|
Registrieren
|
|
</a>
|
|
@endauth
|
|
|
|
<a href="{{ route('widerruf') }}"
|
|
class="px-3 py-2 rounded-lg text-sm font-medium border border-gray-300 text-gray-600 hover:border-blue-400 hover:text-blue-700 transition flex items-center gap-1.5"
|
|
title="Widerrufsformular">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"/>
|
|
</svg>
|
|
Widerruf
|
|
</a>
|
|
|
|
<a href="{{ route('cart.index') }}"
|
|
class="relative ml-2 px-3 py-2 rounded-lg text-sm font-medium bg-blue-700 text-white hover:bg-blue-800 transition flex items-center gap-1.5">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"/>
|
|
</svg>
|
|
Warenkorb
|
|
@php $cartCount = array_sum(array_column(session('cart', []), 'qty')); @endphp
|
|
@if($cartCount > 0)
|
|
<span class="bg-white text-blue-700 text-xs font-bold rounded-full w-5 h-5 flex items-center justify-center leading-none">{{ $cartCount }}</span>
|
|
@endif
|
|
</a>
|
|
</nav>
|
|
</div>
|
|
|
|
{{-- Kategorien-Navigation --}}
|
|
@php $navCats = \App\Models\Category::whereNull('parent_id')->where('active', true)->orderBy('sort_order')->get(); @endphp
|
|
@if($navCats->count())
|
|
<div class="bg-blue-700">
|
|
<div class="max-w-6xl mx-auto px-4 flex gap-1 overflow-x-auto">
|
|
@foreach($navCats as $cat)
|
|
<a href="{{ route('shop.category', $cat->slug) }}"
|
|
class="whitespace-nowrap px-4 py-2.5 text-sm font-medium text-blue-100 hover:text-white hover:bg-blue-600 transition
|
|
{{ request()->route('slug') === $cat->slug ? 'bg-blue-800 text-white' : '' }}">
|
|
{{ $cat->name }}
|
|
</a>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</header>
|
|
|
|
{{-- Flash Messages --}}
|
|
<div class="max-w-6xl mx-auto px-4 mt-4 w-full">
|
|
@if(session('success'))
|
|
<div class="bg-green-100 border border-green-300 text-green-800 px-4 py-3 rounded-lg mb-3">{{ session('success') }}</div>
|
|
@endif
|
|
@if(session('error'))
|
|
<div class="bg-red-100 border border-red-300 text-red-800 px-4 py-3 rounded-lg mb-3">{{ session('error') }}</div>
|
|
@endif
|
|
</div>
|
|
|
|
<main class="flex-1 max-w-6xl mx-auto px-4 py-6 w-full">
|
|
@yield('content')
|
|
</main>
|
|
|
|
{{-- Footer --}}
|
|
@php
|
|
$footerPages = \App\Models\Page::where('published', true)
|
|
->where('type', 'static')
|
|
->where('show_in_footer', true)
|
|
->whereNotIn('slug', $legalSlugs)
|
|
->orderBy('title')
|
|
->get();
|
|
$legalPages = \App\Models\Page::where('published', true)
|
|
->where('type', 'static')
|
|
->whereIn('slug', $legalSlugs)
|
|
->get()
|
|
->sortBy(fn($p) => array_search($p->slug, $legalSlugs))
|
|
->values();
|
|
$hasBlog = \App\Models\Page::where('type','blog')->where('published', true)->exists();
|
|
$hasFaq = \App\Models\Faq::exists();
|
|
@endphp
|
|
<footer class="bg-gray-900 text-gray-400 text-sm mt-12">
|
|
<div class="max-w-6xl mx-auto px-4 py-8 grid grid-cols-2 md:grid-cols-4 gap-6">
|
|
<div>
|
|
@if($logo)
|
|
<img src="{{ asset('storage/'.$logo) }}" alt="{{ \App\Models\Setting::get('shop_name') }}" class="h-8 w-auto object-contain mb-2 brightness-0 invert opacity-70">
|
|
@else
|
|
<h3 class="text-white font-semibold mb-2">{{ \App\Models\Setting::get('shop_name') }}</h3>
|
|
@endif
|
|
<p class="text-xs leading-relaxed">{{ \App\Models\Setting::get('shop_address') }}</p>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-white font-semibold mb-2">Service</h3>
|
|
<ul class="space-y-1">
|
|
@if($hasBlog)
|
|
<li><a href="{{ route('blog') }}" class="hover:text-white transition">Blog / News</a></li>
|
|
@endif
|
|
@if($hasFaq)
|
|
<li><a href="{{ route('faq') }}" class="hover:text-white transition">FAQ</a></li>
|
|
@endif
|
|
<li><a href="{{ route('widerruf') }}" class="hover:text-white transition">Widerruf</a></li>
|
|
<li><a href="{{ route('order.track') }}" class="hover:text-white transition">Bestellung verfolgen</a></li>
|
|
@foreach($footerPages as $fp)
|
|
<li><a href="{{ route('page', $fp->slug) }}" class="hover:text-white transition">{{ $fp->title }}</a></li>
|
|
@endforeach
|
|
</ul>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-white font-semibold mb-2">Rechtliches</h3>
|
|
<ul class="space-y-1">
|
|
@forelse($legalPages as $lp)
|
|
<li><a href="{{ route('page', $lp->slug) }}" class="hover:text-white transition">{{ $lp->title }}</a></li>
|
|
@empty
|
|
<li class="text-gray-600 text-xs">Noch keine Seiten angelegt.</li>
|
|
@endforelse
|
|
</ul>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-white font-semibold mb-2">Konto</h3>
|
|
<ul class="space-y-1">
|
|
@auth
|
|
<li><a href="{{ route('account.index') }}" class="hover:text-white transition">Mein Konto</a></li>
|
|
<li><a href="{{ route('account.orders') }}" class="hover:text-white transition">Bestellungen</a></li>
|
|
@else
|
|
<li><a href="{{ route('login') }}" class="hover:text-white transition">Anmelden</a></li>
|
|
<li><a href="{{ route('register') }}" class="hover:text-white transition">Registrieren</a></li>
|
|
@endauth
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="border-t border-gray-700 text-center py-4 text-xs text-gray-500">
|
|
© {{ date('Y') }} {{ \App\Models\Setting::get('shop_name') }}. Alle Rechte vorbehalten.
|
|
</div>
|
|
</footer>
|
|
@stack('scripts')
|
|
</body>
|
|
</html>
|