Initial commit: WitShop Laravel Online-Shop

- 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>
This commit is contained in:
2026-06-20 00:34:09 +02:00
co-authored by Claude Sonnet 4.6
commit 41078fdc08
208 changed files with 21725 additions and 0 deletions
@@ -0,0 +1,108 @@
@extends('layouts.shop')
@section('title', 'Meine Adressen')
@section('content')
<h1 class="text-2xl font-bold mb-6">Mein Konto</h1>
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
{{-- Sidebar --}}
<div class="space-y-1 text-sm">
<a href="{{ route('account.index') }}" class="block px-3 py-2 rounded hover:bg-gray-50">Übersicht</a>
<a href="{{ route('account.orders') }}" class="block px-3 py-2 rounded hover:bg-gray-50">Bestellungen</a>
<a href="{{ route('account.addresses') }}" class="block px-3 py-2 rounded bg-blue-50 text-blue-700 font-semibold">Adressen</a>
<a href="{{ route('account.settings') }}" class="block px-3 py-2 rounded hover:bg-gray-50">Einstellungen</a>
<form method="POST" action="{{ route('logout') }}">@csrf
<button class="w-full text-left px-3 py-2 rounded text-red-600 hover:bg-red-50">Abmelden</button>
</form>
</div>
{{-- Content --}}
<div class="md:col-span-3 space-y-6">
{{-- Gespeicherte Adressen --}}
@if($addresses->count())
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
@foreach($addresses as $addr)
<div class="bg-white border rounded-lg p-4 text-sm relative">
@if($addr->is_default)
<span class="absolute top-3 right-3 text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded-full">Standard</span>
@endif
<p class="font-semibold">{{ $addr->first_name }} {{ $addr->last_name }}</p>
@if($addr->company)<p class="text-gray-500">{{ $addr->company }}</p>@endif
<p class="text-gray-700 mt-1">{{ $addr->street }}</p>
<p class="text-gray-700">{{ $addr->zip }} {{ $addr->city }}</p>
@if($addr->phone)<p class="text-gray-400 text-xs mt-1">{{ $addr->phone }}</p>@endif
<form method="POST" action="{{ route('account.addresses.destroy', $addr) }}" class="mt-3"
onsubmit="return confirm('Adresse löschen?')">
@csrf @method('DELETE')
<button class="text-red-500 text-xs hover:underline">Löschen</button>
</form>
</div>
@endforeach
</div>
@else
<div class="bg-white border rounded-lg p-8 text-center text-gray-400 text-sm">
Noch keine Adressen gespeichert.
</div>
@endif
{{-- Neue Adresse --}}
<div class="bg-white border rounded-lg p-6">
<h2 class="font-semibold mb-4">Neue Adresse hinzufügen</h2>
<form method="POST" action="{{ route('account.addresses.store') }}" class="grid grid-cols-2 gap-4">
@csrf
<div>
<label class="block text-sm font-medium mb-1">Vorname *</label>
<input type="text" name="first_name" value="{{ old('first_name') }}" required
class="w-full border rounded px-3 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium mb-1">Nachname *</label>
<input type="text" name="last_name" value="{{ old('last_name') }}" required
class="w-full border rounded px-3 py-2 text-sm">
</div>
<div class="col-span-2">
<label class="block text-sm font-medium mb-1">Firma (optional)</label>
<input type="text" name="company" value="{{ old('company') }}"
class="w-full border rounded px-3 py-2 text-sm">
</div>
<div class="col-span-2">
<label class="block text-sm font-medium mb-1">Straße und Hausnummer *</label>
<input type="text" name="street" value="{{ old('street') }}" required
class="w-full border rounded px-3 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium mb-1">PLZ *</label>
<input type="text" name="zip" value="{{ old('zip') }}" required
class="w-full border rounded px-3 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium mb-1">Ort *</label>
<input type="text" name="city" value="{{ old('city') }}" required
class="w-full border rounded px-3 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium mb-1">Land *</label>
<input type="text" name="country" value="{{ old('country', 'DE') }}" required maxlength="2"
class="w-full border rounded px-3 py-2 text-sm uppercase">
</div>
<div>
<label class="block text-sm font-medium mb-1">Telefon (optional)</label>
<input type="text" name="phone" value="{{ old('phone') }}"
class="w-full border rounded px-3 py-2 text-sm">
</div>
@if($errors->any())
<div class="col-span-2 bg-red-50 border border-red-200 rounded p-3 text-sm text-red-700">
@foreach($errors->all() as $e)<p>• {{ $e }}</p>@endforeach
</div>
@endif
<div class="col-span-2">
<button type="submit" class="bg-blue-600 text-white px-5 py-2 rounded hover:bg-blue-700 text-sm">
Adresse speichern
</button>
</div>
</form>
</div>
</div>
</div>
@endsection
@@ -0,0 +1,39 @@
@extends('layouts.shop')
@section('title', 'Mein Konto')
@section('content')
<h1 class="text-2xl font-bold mb-6">Mein Konto</h1>
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
<div class="space-y-1 text-sm">
<a href="{{ route('account.index') }}" class="block px-3 py-2 rounded {{ request()->routeIs('account.index') ? 'bg-blue-50 text-blue-700 font-semibold' : 'hover:bg-gray-50' }}">Übersicht</a>
<a href="{{ route('account.orders') }}" class="block px-3 py-2 rounded hover:bg-gray-50">Bestellungen</a>
<a href="{{ route('account.addresses') }}" class="block px-3 py-2 rounded hover:bg-gray-50">Adressen</a>
<a href="{{ route('account.settings') }}" class="block px-3 py-2 rounded hover:bg-gray-50">Einstellungen</a>
<form method="POST" action="{{ route('logout') }}">@csrf
<button class="w-full text-left px-3 py-2 rounded text-red-600 hover:bg-red-50">Abmelden</button>
</form>
</div>
<div class="md:col-span-3">
<div class="bg-white border rounded-lg p-6 mb-6">
<p class="text-gray-600">Hallo, <strong>{{ auth()->user()->name }}</strong>!</p>
</div>
@if($orders->count())
<h2 class="font-semibold mb-3">Letzte Bestellungen</h2>
<div class="bg-white border rounded-lg overflow-hidden">
<table class="w-full text-sm">
<thead class="bg-gray-50"><tr><th class="px-4 py-2 text-left">Bestellnr.</th><th class="px-4 py-2 text-left">Status</th><th class="px-4 py-2 text-right">Betrag</th><th class="px-4 py-2"></th></tr></thead>
<tbody class="divide-y">
@foreach($orders as $o)
<tr>
<td class="px-4 py-2 font-mono">{{ $o->order_number }}</td>
<td class="px-4 py-2">{{ $o->status_label }}</td>
<td class="px-4 py-2 text-right">{{ number_format($o->total, 2, ',', '.') }} €</td>
<td class="px-4 py-2"><a href="{{ route('account.order', $o) }}" class="text-blue-600 text-xs hover:underline">Details</a></td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endif
</div>
</div>
@endsection
@@ -0,0 +1,123 @@
@extends('layouts.shop')
@section('title', 'Bestellung ' . $order->order_number)
@section('content')
<h1 class="text-2xl font-bold mb-6">Mein Konto</h1>
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
{{-- Sidebar --}}
<div class="space-y-1 text-sm">
<a href="{{ route('account.index') }}" class="block px-3 py-2 rounded hover:bg-gray-50">Übersicht</a>
<a href="{{ route('account.orders') }}" class="block px-3 py-2 rounded bg-blue-50 text-blue-700 font-semibold">Bestellungen</a>
<a href="{{ route('account.addresses') }}" class="block px-3 py-2 rounded hover:bg-gray-50">Adressen</a>
<a href="{{ route('account.settings') }}" class="block px-3 py-2 rounded hover:bg-gray-50">Einstellungen</a>
<form method="POST" action="{{ route('logout') }}">@csrf
<button class="w-full text-left px-3 py-2 rounded text-red-600 hover:bg-red-50">Abmelden</button>
</form>
</div>
{{-- Content --}}
<div class="md:col-span-3 space-y-5">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-500">Bestellnummer</p>
<h2 class="text-lg font-mono font-bold">{{ $order->order_number }}</h2>
</div>
<span class="px-3 py-1 rounded-full text-sm font-medium
{{ match($order->status) {
'new' => 'bg-yellow-100 text-yellow-700',
'payment_received' => 'bg-blue-100 text-blue-700',
'processing' => 'bg-purple-100 text-purple-700',
'shipped' => 'bg-indigo-100 text-indigo-700',
'completed' => 'bg-green-100 text-green-700',
'cancelled' => 'bg-red-100 text-red-700',
default => 'bg-gray-100 text-gray-600',
} }}">
{{ $order->status_label }}
</span>
</div>
{{-- Artikel --}}
<div class="bg-white border rounded-lg overflow-hidden">
<div class="px-4 py-3 bg-gray-50 border-b text-sm font-semibold">Bestellte Artikel</div>
<table class="w-full text-sm">
<tbody class="divide-y">
@foreach($order->items as $item)
<tr>
<td class="px-4 py-3">
<p class="font-medium">{{ $item->product_name }}</p>
@if($item->variant_label)
<p class="text-gray-400 text-xs">{{ $item->variant_label }}</p>
@endif
</td>
<td class="px-4 py-3 text-gray-500 text-center">{{ $item->qty }}×</td>
<td class="px-4 py-3 text-right font-medium">{{ number_format($item->price * $item->qty, 2, ',', '.') }} €</td>
</tr>
@endforeach
</tbody>
<tfoot class="border-t bg-gray-50 text-sm">
<tr>
<td colspan="2" class="px-4 py-2 text-gray-500">Versand</td>
<td class="px-4 py-2 text-right">{{ number_format($order->shipping_cost, 2, ',', '.') }} €</td>
</tr>
<tr>
<td colspan="2" class="px-4 py-2 font-bold">Gesamt</td>
<td class="px-4 py-2 text-right font-bold text-lg">{{ number_format($order->total, 2, ',', '.') }} €</td>
</tr>
</tfoot>
</table>
</div>
{{-- Tracking --}}
@if($order->tracking_code)
<div class="bg-indigo-50 border border-indigo-200 rounded-lg px-4 py-3 text-sm">
<p class="font-semibold text-indigo-800">Sendungsverfolgung</p>
<p class="text-indigo-700 font-mono mt-1">{{ $order->tracking_code }}</p>
</div>
@endif
{{-- Zahlungshinweis Vorkasse --}}
@if($order->payment_method === 'vorkasse' && in_array($order->status, ['new']))
<div class="bg-amber-50 border border-amber-200 rounded-lg px-4 py-4 text-sm">
<p class="font-semibold text-amber-800 mb-2">Bitte überweisen Sie den Betrag auf folgendes Konto:</p>
<dl class="grid grid-cols-2 gap-1 text-amber-700">
<dt class="font-medium">Empfänger:</dt><dd>{{ \App\Models\Setting::get('shop_owner') }}</dd>
<dt class="font-medium">IBAN:</dt><dd class="font-mono">{{ \App\Models\Setting::get('bank_iban') }}</dd>
<dt class="font-medium">BIC:</dt><dd class="font-mono">{{ \App\Models\Setting::get('bank_bic') }}</dd>
<dt class="font-medium">Bank:</dt><dd>{{ \App\Models\Setting::get('bank_name') }}</dd>
<dt class="font-medium">Verwendungszweck:</dt><dd class="font-mono">{{ $order->order_number }}</dd>
<dt class="font-medium">Betrag:</dt><dd class="font-semibold">{{ number_format($order->total, 2, ',', '.') }} €</dd>
</dl>
</div>
@endif
{{-- Adressen --}}
<div class="grid grid-cols-2 gap-4 text-sm">
@php
$billing = $order->addresses->firstWhere('type', 'billing');
$shipping = $order->addresses->firstWhere('type', 'shipping');
@endphp
@if($billing)
<div class="bg-white border rounded-lg p-4">
<p class="text-xs text-gray-400 font-semibold uppercase mb-2">Rechnungsadresse</p>
<p class="font-medium">{{ $billing->first_name }} {{ $billing->last_name }}</p>
@if($billing->company)<p class="text-gray-500">{{ $billing->company }}</p>@endif
<p class="text-gray-700">{{ $billing->street }}</p>
<p class="text-gray-700">{{ $billing->zip }} {{ $billing->city }}</p>
</div>
@endif
@if($shipping)
<div class="bg-white border rounded-lg p-4">
<p class="text-xs text-gray-400 font-semibold uppercase mb-2">Lieferadresse</p>
<p class="font-medium">{{ $shipping->first_name }} {{ $shipping->last_name }}</p>
@if($shipping->company)<p class="text-gray-500">{{ $shipping->company }}</p>@endif
<p class="text-gray-700">{{ $shipping->street }}</p>
<p class="text-gray-700">{{ $shipping->zip }} {{ $shipping->city }}</p>
</div>
@endif
</div>
<a href="{{ route('account.orders') }}" class="inline-block text-sm text-gray-500 hover:text-blue-600">← Alle Bestellungen</a>
</div>
</div>
@endsection
@@ -0,0 +1,70 @@
@extends('layouts.shop')
@section('title', 'Meine Bestellungen')
@section('content')
<h1 class="text-2xl font-bold mb-6">Mein Konto</h1>
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
{{-- Sidebar --}}
<div class="space-y-1 text-sm">
<a href="{{ route('account.index') }}" class="block px-3 py-2 rounded hover:bg-gray-50">Übersicht</a>
<a href="{{ route('account.orders') }}" class="block px-3 py-2 rounded bg-blue-50 text-blue-700 font-semibold">Bestellungen</a>
<a href="{{ route('account.addresses') }}" class="block px-3 py-2 rounded hover:bg-gray-50">Adressen</a>
<a href="{{ route('account.settings') }}" class="block px-3 py-2 rounded hover:bg-gray-50">Einstellungen</a>
<form method="POST" action="{{ route('logout') }}">@csrf
<button class="w-full text-left px-3 py-2 rounded text-red-600 hover:bg-red-50">Abmelden</button>
</form>
</div>
{{-- Content --}}
<div class="md:col-span-3">
@if($orders->count())
<div class="bg-white border rounded-lg overflow-hidden">
<table class="w-full text-sm">
<thead class="bg-gray-50 border-b text-left">
<tr>
<th class="px-4 py-3">Bestellnr.</th>
<th class="px-4 py-3">Status</th>
<th class="px-4 py-3">Zahlungsart</th>
<th class="px-4 py-3 text-right">Betrag</th>
<th class="px-4 py-3">Datum</th>
<th class="px-4 py-3"></th>
</tr>
</thead>
<tbody class="divide-y">
@foreach($orders as $order)
<tr class="hover:bg-gray-50">
<td class="px-4 py-3 font-mono font-medium">{{ $order->order_number }}</td>
<td class="px-4 py-3">
<span class="px-2 py-0.5 rounded text-xs
{{ match($order->status) {
'new' => 'bg-yellow-100 text-yellow-700',
'payment_received' => 'bg-blue-100 text-blue-700',
'processing' => 'bg-purple-100 text-purple-700',
'shipped' => 'bg-indigo-100 text-indigo-700',
'completed' => 'bg-green-100 text-green-700',
'cancelled' => 'bg-red-100 text-red-700',
default => 'bg-gray-100 text-gray-600',
} }}">
{{ $order->status_label }}
</span>
</td>
<td class="px-4 py-3 text-gray-500">{{ $order->payment_label }}</td>
<td class="px-4 py-3 text-right font-medium">{{ number_format($order->total, 2, ',', '.') }} €</td>
<td class="px-4 py-3 text-gray-400">{{ $order->created_at->format('d.m.Y') }}</td>
<td class="px-4 py-3 text-right">
<a href="{{ route('account.order', $order) }}" class="text-blue-600 hover:underline text-xs">Details</a>
</td>
</tr>
@endforeach
</tbody>
</table>
<div class="px-4 py-3 border-t">{{ $orders->links() }}</div>
</div>
@else
<div class="bg-white border rounded-lg p-10 text-center text-gray-400 text-sm">
Sie haben noch keine Bestellungen aufgegeben.
</div>
@endif
</div>
</div>
@endsection
@@ -0,0 +1,58 @@
@extends('layouts.shop')
@section('title', 'Kontoeinstellungen')
@section('content')
<h1 class="text-2xl font-bold mb-6">Mein Konto</h1>
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
{{-- Sidebar --}}
<div class="space-y-1 text-sm">
<a href="{{ route('account.index') }}" class="block px-3 py-2 rounded hover:bg-gray-50">Übersicht</a>
<a href="{{ route('account.orders') }}" class="block px-3 py-2 rounded hover:bg-gray-50">Bestellungen</a>
<a href="{{ route('account.addresses') }}" class="block px-3 py-2 rounded hover:bg-gray-50">Adressen</a>
<a href="{{ route('account.settings') }}" class="block px-3 py-2 rounded bg-blue-50 text-blue-700 font-semibold">Einstellungen</a>
<form method="POST" action="{{ route('logout') }}">@csrf
<button class="w-full text-left px-3 py-2 rounded text-red-600 hover:bg-red-50">Abmelden</button>
</form>
</div>
{{-- Content --}}
<div class="md:col-span-3">
<div class="bg-white border rounded-lg p-6">
<h2 class="font-semibold mb-4">Persönliche Daten</h2>
<form method="POST" action="{{ route('account.settings.update') }}" class="space-y-4">
@csrf @method('PUT')
<div>
<label class="block text-sm font-medium mb-1">Name *</label>
<input type="text" name="name" value="{{ old('name', auth()->user()->name) }}" required
class="w-full border rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium mb-1">E-Mail-Adresse *</label>
<input type="email" name="email" value="{{ old('email', auth()->user()->email) }}" required
class="w-full border rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<hr>
<p class="text-sm text-gray-500">Passwort ändern — leer lassen, wenn Sie es nicht ändern möchten.</p>
<div>
<label class="block text-sm font-medium mb-1">Neues Passwort</label>
<input type="password" name="password" autocomplete="new-password"
class="w-full border rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium mb-1">Passwort bestätigen</label>
<input type="password" name="password_confirmation" autocomplete="new-password"
class="w-full border rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
@if($errors->any())
<div class="bg-red-50 border border-red-200 rounded p-3 text-sm text-red-700">
@foreach($errors->all() as $e)<p>• {{ $e }}</p>@endforeach
</div>
@endif
<button type="submit" class="bg-blue-600 text-white px-5 py-2 rounded hover:bg-blue-700 text-sm">
Speichern
</button>
</form>
</div>
</div>
</div>
@endsection
+19
View File
@@ -0,0 +1,19 @@
@extends('layouts.shop')
@section('title', 'Blog')
@section('content')
<h1 class="text-3xl font-bold mb-8">Blog / Aktuelles</h1>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
@forelse($posts as $post)
<a href="{{ route('blog.post', $post->slug) }}" class="block bg-white border rounded-lg overflow-hidden hover:shadow-md transition">
@if($post->image)<img src="{{ asset('storage/'.$post->image) }}" class="w-full h-48 object-cover">@endif
<div class="p-5">
<p class="text-xs text-gray-400 mb-1">{{ $post->published_at?->format('d.m.Y') }}</p>
<h2 class="font-semibold text-lg mb-2">{{ $post->title }}</h2>
</div>
</a>
@empty
<p class="col-span-2 text-gray-400 text-center py-16">Noch keine Beiträge.</p>
@endforelse
</div>
<div class="mt-8">{{ $posts->links() }}</div>
@endsection
+11
View File
@@ -0,0 +1,11 @@
@extends('layouts.shop')
@section('title', $post->meta_title ?: $post->title)
@section('content')
<div class="max-w-3xl mx-auto">
<p class="text-sm text-gray-400 mb-2">{{ $post->published_at?->format('d.m.Y') }}</p>
<h1 class="text-3xl font-bold mb-6">{{ $post->title }}</h1>
@if($post->image)<img src="{{ asset('storage/'.$post->image) }}" class="w-full rounded-lg mb-6">@endif
<div class="prose max-w-none text-gray-700">{!! $post->content !!}</div>
<div class="mt-8"><a href="{{ route('blog') }}" class="text-blue-600 hover:underline">← Zurück zum Blog</a></div>
</div>
@endsection
+83
View File
@@ -0,0 +1,83 @@
@extends('layouts.shop')
@section('title', 'Warenkorb')
@section('content')
<h1 class="text-2xl font-bold mb-6">Warenkorb</h1>
@if(empty($items))
<div class="text-center py-20 text-gray-400">
<div class="text-6xl mb-4">🛒</div>
<p class="text-lg mb-4">Ihr Warenkorb ist leer.</p>
<a href="{{ route('shop.products.index') }}" class="bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700">Weiter einkaufen</a>
</div>
@else
<form method="POST" action="{{ route('cart.update') }}">
@csrf
<div class="bg-white border rounded-lg overflow-hidden mb-6">
<table class="w-full">
<thead class="bg-gray-50 text-sm">
<tr>
<th class="px-4 py-3 text-left">Artikel</th>
<th class="px-4 py-3 text-center">Menge</th>
<th class="px-4 py-3 text-right">Einzelpreis</th>
<th class="px-4 py-3 text-right">Gesamt</th>
<th class="px-4 py-3"></th>
</tr>
</thead>
<tbody class="divide-y">
@foreach($items as $key => $item)
<tr>
<td class="px-4 py-4">
<div class="font-medium">{{ $item['product_name'] }}</div>
@if($item['variant_label'])<div class="text-sm text-gray-400">{{ $item['variant_label'] }}</div>@endif
</td>
<td class="px-4 py-4 text-center">
<input type="number" name="qty[{{ $key }}]" value="{{ $item['qty'] }}" min="0" class="w-16 border rounded px-2 py-1 text-sm text-center">
</td>
<td class="px-4 py-4 text-right text-sm">{{ number_format($item['price'], 2, ',', '.') }} €</td>
<td class="px-4 py-4 text-right font-semibold">{{ number_format($item['price'] * $item['qty'], 2, ',', '.') }} €</td>
<td class="px-4 py-4 text-center">
<form method="POST" action="{{ route('cart.remove', $key) }}">
@csrf
<button class="text-red-400 hover:text-red-600 text-xl">✕</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<div class="flex justify-between items-center mb-6">
<button type="submit" class="text-sm text-blue-600 hover:underline">Warenkorb aktualisieren</button>
</div>
</form>
<div class="flex justify-end">
<div class="w-80 bg-white border rounded-lg p-6">
<div class="space-y-2 text-sm mb-4">
<div class="flex justify-between"><span>Zwischensumme</span><span>{{ number_format($subtotal, 2, ',', '.') }} €</span></div>
<div class="flex justify-between">
<span>Versand</span>
<span>
@if($shippingCost == 0 && $shippingRate)
<span class="text-green-600">Kostenlos</span>
@elseif($shippingRate)
{{ number_format($shippingCost, 2, ',', '.') }} €
@else
Wird berechnet
@endif
</span>
</div>
@if($shippingRate && $shippingRate->free_from && $subtotal < $shippingRate->free_from)
<p class="text-xs text-gray-400">Noch {{ number_format($shippingRate->free_from - $subtotal, 2, ',', '.') }} € bis kostenloser Versand</p>
@endif
</div>
<div class="border-t pt-3 flex justify-between font-bold text-lg mb-5">
<span>Gesamt</span><span>{{ number_format($total, 2, ',', '.') }} €</span>
</div>
<a href="{{ route('checkout.index') }}" class="block w-full text-center bg-blue-600 text-white py-3 rounded-lg hover:bg-blue-700 font-semibold">
Zur Kasse →
</a>
</div>
</div>
@endif
@endsection
@@ -0,0 +1,60 @@
@extends('layouts.shop')
@section('title', 'Bestellung bestätigen')
@section('content')
<h1 class="text-2xl font-bold mb-6">Bestellung prüfen und absenden</h1>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<div class="md:col-span-2 space-y-6">
<div class="bg-white border rounded-lg p-6">
<h2 class="font-semibold mb-3">Lieferadresse</h2>
@php $addr = $checkoutData['shipping'] ?? $checkoutData['billing']; @endphp
<p class="text-sm">{{ $addr['first_name'] }} {{ $addr['last_name'] }}<br>{{ $addr['street'] }}<br>{{ $addr['zip'] }} {{ $addr['city'] }}</p>
</div>
<div class="bg-white border rounded-lg p-6">
<h2 class="font-semibold mb-3">Bestellpositionen</h2>
<table class="w-full text-sm">
<thead class="bg-gray-50"><tr><th class="px-3 py-2 text-left">Artikel</th><th class="px-3 py-2 text-center">Menge</th><th class="px-3 py-2 text-right">Gesamt</th></tr></thead>
<tbody class="divide-y">
@foreach($items as $item)
<tr>
<td class="px-3 py-2">{{ $item['product_name'] }}@if($item['variant_label']) <span class="text-gray-400">({{ $item['variant_label'] }})</span>@endif</td>
<td class="px-3 py-2 text-center">{{ $item['qty'] }}</td>
<td class="px-3 py-2 text-right">{{ number_format($item['price'] * $item['qty'], 2, ',', '.') }} €</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<div class="bg-white border rounded-lg p-6">
<h2 class="font-semibold mb-2">Zahlungsart</h2>
<p class="text-sm">{{ \App\Models\Order::$paymentLabels[$checkoutData['payment_method']] }}</p>
</div>
<form method="POST" action="{{ route('checkout.confirm') }}">
@csrf
<button class="w-full bg-green-600 text-white py-4 rounded-lg hover:bg-green-700 font-bold text-lg">
🛒 Jetzt kostenpflichtig bestellen
</button>
</form>
<p class="text-xs text-gray-400 text-center">
Mit der Bestellung akzeptieren Sie unsere <a href="{{ route('page', 'agb') }}" class="underline">AGB</a> und die <a href="{{ route('page', 'widerrufsbelehrung') }}" class="underline">Widerrufsbelehrung</a>.
</p>
</div>
<div>
<div class="bg-white border rounded-lg p-6 sticky top-4">
<div class="space-y-2 text-sm mb-3 border-b pb-3">
<div class="flex justify-between"><span>Zwischensumme</span><span>{{ number_format($subtotal, 2, ',', '.') }} €</span></div>
<div class="flex justify-between"><span>Versand</span><span>{{ $shippingCost == 0 ? 'Kostenlos' : number_format($shippingCost, 2, ',', '.') . ' €' }}</span></div>
</div>
<div class="flex justify-between font-bold text-lg">
<span>Gesamt</span><span>{{ number_format($total, 2, ',', '.') }} €</span>
</div>
<p class="text-xs text-gray-400 mt-1">inkl. MwSt.</p>
</div>
</div>
</div>
@endsection
@@ -0,0 +1,238 @@
@extends('layouts.shop')
@section('title', 'Kasse')
@section('content')
<h1 class="text-2xl font-bold mb-6">Kasse</h1>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<div class="md:col-span-2">
<form method="POST" action="{{ route('checkout.address') }}">
@csrf
{{-- Gastemail --}}
@guest
<div class="bg-white border rounded-lg p-6 mb-6">
<h2 class="font-semibold mb-4">Kontakt</h2>
<div>
<label class="block text-sm font-medium mb-1">E-Mail-Adresse *</label>
<input type="email" name="email" value="{{ old('email') }}" class="w-full border rounded px-3 py-2" required>
</div>
<p class="text-sm text-gray-500 mt-3">
Bereits Kunde? <a href="{{ route('login') }}" class="text-blue-600 hover:underline">Anmelden</a>
</p>
</div>
@endguest
{{-- Rechnungsadresse --}}
@php $defaultAddr = $userAddresses->firstWhere('is_default', true) ?? $userAddresses->first(); @endphp
<div class="bg-white border rounded-lg p-6 mb-6" x-data="addressForm({{ $userAddresses->toJson() }})">
<h2 class="font-semibold mb-4">Rechnungsadresse</h2>
{{-- Gespeicherte Adressen --}}
@if($userAddresses->count())
<div class="mb-5">
<p class="text-xs text-gray-500 mb-2">Gespeicherte Adressen:</p>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
@foreach($userAddresses as $addr)
<button type="button"
@click="fill({{ $addr->id }})"
:class="selected === {{ $addr->id }} ? 'border-blue-500 bg-blue-50 ring-1 ring-blue-500' : 'border-gray-200 hover:border-blue-300'"
class="text-left p-3 border rounded-lg text-sm transition">
<p class="font-medium">{{ $addr->first_name }} {{ $addr->last_name }}</p>
@if($addr->company)<p class="text-gray-400 text-xs">{{ $addr->company }}</p>@endif
<p class="text-gray-500 text-xs mt-0.5">{{ $addr->street }}, {{ $addr->zip }} {{ $addr->city }}</p>
</button>
@endforeach
<button type="button"
@click="clear()"
:class="selected === null ? 'border-blue-500 bg-blue-50 ring-1 ring-blue-500' : 'border-gray-200 hover:border-gray-300'"
class="text-left p-3 border rounded-lg text-sm transition text-gray-500">
+ Neue Adresse eingeben
</button>
</div>
</div>
@endif
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium mb-1">Vorname *</label>
<input type="text" name="billing[first_name]" x-model="form.first_name" value="{{ old('billing.first_name', $defaultAddr?->first_name) }}" class="w-full border rounded px-3 py-2 text-sm" required>
</div>
<div>
<label class="block text-sm font-medium mb-1">Nachname *</label>
<input type="text" name="billing[last_name]" x-model="form.last_name" value="{{ old('billing.last_name', $defaultAddr?->last_name) }}" class="w-full border rounded px-3 py-2 text-sm" required>
</div>
<div class="col-span-2">
<label class="block text-sm font-medium mb-1">Firma (optional)</label>
<input type="text" name="billing[company]" x-model="form.company" value="{{ old('billing.company', $defaultAddr?->company) }}" class="w-full border rounded px-3 py-2 text-sm">
</div>
<div class="col-span-2">
<label class="block text-sm font-medium mb-1">Straße und Hausnummer *</label>
<input type="text" name="billing[street]" x-model="form.street" value="{{ old('billing.street', $defaultAddr?->street) }}" class="w-full border rounded px-3 py-2 text-sm" required>
</div>
<div>
<label class="block text-sm font-medium mb-1">PLZ *</label>
<input type="text" name="billing[zip]" x-model="form.zip" value="{{ old('billing.zip', $defaultAddr?->zip) }}" class="w-full border rounded px-3 py-2 text-sm" required>
</div>
<div>
<label class="block text-sm font-medium mb-1">Stadt *</label>
<input type="text" name="billing[city]" x-model="form.city" value="{{ old('billing.city', $defaultAddr?->city) }}" class="w-full border rounded px-3 py-2 text-sm" required>
</div>
<div>
<label class="block text-sm font-medium mb-1">Land *</label>
<select name="billing[country]" x-model="form.country" class="w-full border rounded px-3 py-2 text-sm">
<option value="DE">Deutschland</option>
<option value="AT">Österreich</option>
<option value="CH">Schweiz</option>
</select>
</div>
<div>
<label class="block text-sm font-medium mb-1">Telefon</label>
<input type="tel" name="billing[phone]" x-model="form.phone" value="{{ old('billing.phone', $defaultAddr?->phone) }}" class="w-full border rounded px-3 py-2 text-sm">
</div>
</div>
</div>
{{-- Lieferadresse --}}
<div class="bg-white border rounded-lg p-6 mb-6" x-data="{ same: true }">
<div class="flex items-center justify-between mb-4">
<h2 class="font-semibold">Lieferadresse</h2>
<label class="flex items-center gap-2 text-sm cursor-pointer">
<input type="checkbox" name="shipping_same" value="1" x-model="same" checked>
Gleich wie Rechnungsadresse
</label>
</div>
<div x-show="!same" class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium mb-1">Vorname *</label>
<input type="text" name="shipping[first_name]" value="{{ old('shipping.first_name') }}" class="w-full border rounded px-3 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium mb-1">Nachname *</label>
<input type="text" name="shipping[last_name]" value="{{ old('shipping.last_name') }}" class="w-full border rounded px-3 py-2 text-sm">
</div>
<div class="col-span-2">
<label class="block text-sm font-medium mb-1">Straße und Hausnummer *</label>
<input type="text" name="shipping[street]" value="{{ old('shipping.street') }}" class="w-full border rounded px-3 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium mb-1">PLZ *</label>
<input type="text" name="shipping[zip]" value="{{ old('shipping.zip') }}" class="w-full border rounded px-3 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium mb-1">Stadt *</label>
<input type="text" name="shipping[city]" value="{{ old('shipping.city') }}" class="w-full border rounded px-3 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium mb-1">Land *</label>
<select name="shipping[country]" class="w-full border rounded px-3 py-2 text-sm">
<option value="DE" selected>Deutschland</option>
<option value="AT">Österreich</option>
<option value="CH">Schweiz</option>
</select>
</div>
</div>
</div>
{{-- Zahlungsart --}}
<div class="bg-white border rounded-lg p-6 mb-6">
<h2 class="font-semibold mb-4">Zahlungsart</h2>
<div class="space-y-3">
<label class="flex items-start gap-3 p-3 border rounded-lg cursor-pointer hover:bg-gray-50">
<input type="radio" name="payment_method" value="prepayment" class="mt-1" checked>
<div>
<div class="font-medium">Vorkasse (Überweisung)</div>
<div class="text-sm text-gray-500">Sie überweisen den Betrag auf unser Bankkonto. Wir versenden nach Zahlungseingang.</div>
</div>
</label>
<label class="flex items-start gap-3 p-3 border rounded-lg cursor-pointer hover:bg-gray-50">
<input type="radio" name="payment_method" value="invoice" class="mt-1">
<div>
<div class="font-medium">Kauf auf Rechnung</div>
<div class="text-sm text-gray-500">Sie erhalten die Rechnung per E-Mail. Zahlungsziel: {{ \App\Models\Setting::get('invoice_payment_days', 14) }} Tage.</div>
</div>
</label>
</div>
</div>
{{-- Rechtliches --}}
<div class="bg-white border rounded-lg p-6 mb-6 space-y-3">
<label class="flex items-start gap-3 cursor-pointer">
<input type="checkbox" name="agb" value="1" required class="mt-1">
<span class="text-sm">Ich habe die <a href="{{ route('page', 'agb') }}" class="text-blue-600 hover:underline" target="_blank">AGB</a> gelesen und akzeptiere sie. Ich habe die <a href="{{ route('page', 'widerrufsbelehrung') }}" class="text-blue-600 hover:underline" target="_blank">Widerrufsbelehrung</a> zur Kenntnis genommen. *</span>
</label>
<label class="flex items-start gap-3 cursor-pointer">
<input type="checkbox" name="privacy" value="1" required class="mt-1">
<span class="text-sm">Ich habe die <a href="{{ route('page', 'datenschutz') }}" class="text-blue-600 hover:underline" target="_blank">Datenschutzerklärung</a> gelesen. *</span>
</label>
</div>
@if($errors->any())
<div class="bg-red-50 border border-red-200 rounded-lg p-4 mb-4">
<ul class="text-sm text-red-700 space-y-1">
@foreach($errors->all() as $error)<li>• {{ $error }}</li>@endforeach
</ul>
</div>
@endif
<button type="submit" class="w-full bg-blue-600 text-white py-3 rounded-lg hover:bg-blue-700 font-semibold text-lg">
Bestellung prüfen →
</button>
</form>
</div>
{{-- Zusammenfassung --}}
<div>
<div class="bg-white border rounded-lg p-6 sticky top-4">
<h2 class="font-semibold mb-4">Zusammenfassung</h2>
<div class="space-y-2 text-sm mb-4 border-b pb-4">
<div class="flex justify-between"><span>Zwischensumme</span><span>{{ number_format($subtotal, 2, ',', '.') }} €</span></div>
<div class="flex justify-between"><span>Versand</span><span>{{ $shippingCost == 0 ? 'Kostenlos' : number_format($shippingCost, 2, ',', '.') . ' €' }}</span></div>
</div>
<div class="flex justify-between font-bold text-lg">
<span>Gesamt</span><span>{{ number_format($total, 2, ',', '.') }} €</span>
</div>
<p class="text-xs text-gray-400 mt-2">inkl. MwSt.</p>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
function addressForm(addresses) {
@php $defaultAddr = $userAddresses->firstWhere('is_default', true) ?? $userAddresses->first(); @endphp
const defaultId = {{ $defaultAddr?->id ?? 'null' }};
return {
addresses,
selected: defaultId,
form: {
first_name: '{{ old('billing.first_name', $defaultAddr?->first_name) }}',
last_name: '{{ old('billing.last_name', $defaultAddr?->last_name) }}',
company: '{{ old('billing.company', $defaultAddr?->company) }}',
street: '{{ old('billing.street', $defaultAddr?->street) }}',
zip: '{{ old('billing.zip', $defaultAddr?->zip) }}',
city: '{{ old('billing.city', $defaultAddr?->city) }}',
country: '{{ old('billing.country', $defaultAddr?->country ?? 'DE') }}',
phone: '{{ old('billing.phone', $defaultAddr?->phone) }}',
},
fill(id) {
const addr = this.addresses.find(a => a.id === id);
if (!addr) return;
this.selected = id;
this.form.first_name = addr.first_name ?? '';
this.form.last_name = addr.last_name ?? '';
this.form.company = addr.company ?? '';
this.form.street = addr.street ?? '';
this.form.zip = addr.zip ?? '';
this.form.city = addr.city ?? '';
this.form.country = addr.country ?? 'DE';
this.form.phone = addr.phone ?? '';
},
clear() {
this.selected = null;
Object.keys(this.form).forEach(k => this.form[k] = k === 'country' ? 'DE' : '');
}
}
}
</script>
@endpush
@@ -0,0 +1,35 @@
@extends('layouts.shop')
@section('title', 'Bestellung erfolgreich')
@section('content')
<div class="max-w-2xl mx-auto text-center py-16">
<div class="text-6xl mb-6">✅</div>
<h1 class="text-3xl font-bold mb-4">Vielen Dank für Ihre Bestellung!</h1>
<p class="text-gray-600 mb-2">Ihre Bestellnummer: <strong>{{ $order->order_number }}</strong></p>
<p class="text-gray-600 mb-8">Sie erhalten eine Bestätigungsmail mit allen Details.</p>
@if($order->payment_method === 'prepayment')
<div class="bg-blue-50 border border-blue-200 rounded-lg p-6 text-left mb-8">
<h2 class="font-semibold mb-3">Überweisungsdaten</h2>
<table class="w-full text-sm">
<tr><td class="py-1 text-gray-500">Empfänger</td><td class="py-1 font-medium">{{ \App\Models\Setting::get('shop_owner') }}</td></tr>
<tr><td class="py-1 text-gray-500">Bank</td><td class="py-1">{{ \App\Models\Setting::get('bank_name') }}</td></tr>
<tr><td class="py-1 text-gray-500">IBAN</td><td class="py-1 font-mono">{{ \App\Models\Setting::get('bank_iban') }}</td></tr>
<tr><td class="py-1 text-gray-500">BIC</td><td class="py-1 font-mono">{{ \App\Models\Setting::get('bank_bic') }}</td></tr>
<tr><td class="py-1 text-gray-500">Verwendungszweck</td><td class="py-1 font-bold">{{ $order->order_number }}</td></tr>
<tr><td class="py-1 text-gray-500">Betrag</td><td class="py-1 font-bold text-blue-700">{{ number_format($order->total, 2, ',', '.') }} €</td></tr>
</table>
</div>
@else
<div class="bg-green-50 border border-green-200 rounded-lg p-4 text-left mb-8">
<p class="text-sm">Sie erhalten Ihre Rechnung per E-Mail. Bitte zahlen Sie innerhalb von {{ \App\Models\Setting::get('invoice_payment_days', 14) }} Tagen.</p>
</div>
@endif
<div class="flex gap-4 justify-center">
<a href="{{ route('home') }}" class="bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700">Weiter einkaufen</a>
@auth
<a href="{{ route('account.orders') }}" class="border px-6 py-3 rounded-lg hover:bg-gray-50">Meine Bestellungen</a>
@endauth
</div>
</div>
@endsection
+23
View File
@@ -0,0 +1,23 @@
@extends('layouts.shop')
@section('title', 'FAQ')
@section('content')
<div class="max-w-3xl mx-auto">
<h1 class="text-3xl font-bold mb-8">Häufige Fragen</h1>
@foreach($groups as $group)
<h2 class="text-xl font-semibold mb-4 text-blue-700">{{ $group->name }}</h2>
<div class="space-y-3 mb-8" x-data>
@foreach($group->faqs as $faq)
<div class="border rounded-lg" x-data="{ open: false }">
<button @click="open = !open" class="w-full text-left px-5 py-4 flex justify-between items-center font-medium">
{{ $faq->question }}
<span x-text="open ? '▲' : '▼'" class="text-sm text-gray-400"></span>
</button>
<div x-show="open" x-collapse class="px-5 pb-4 text-gray-600 text-sm">
{!! nl2br(e($faq->answer)) !!}
</div>
</div>
@endforeach
</div>
@endforeach
</div>
@endsection
+95
View File
@@ -0,0 +1,95 @@
@extends('layouts.shop')
@section('title', \App\Models\Setting::get('shop_name', 'Shop'))
@section('content')
{{-- Banner --}}
@if($banners->count())
<div class="relative mb-10 -mx-4" x-data="{ current: 0 }">
@foreach($banners as $i => $banner)
<div x-show="current === {{ $i }}" class="relative">
<img src="{{ asset('storage/'.$banner->image) }}" alt="{{ $banner->headline }}" class="w-full h-72 object-cover rounded-lg">
@if($banner->headline)
<div class="absolute inset-0 bg-black/40 flex items-center justify-center rounded-lg">
<div class="text-center text-white">
<h2 class="text-3xl font-bold mb-2">{{ $banner->headline }}</h2>
@if($banner->subtext)<p class="text-lg mb-4">{{ $banner->subtext }}</p>@endif
@if($banner->button_link && $banner->button_label)
<a href="{{ $banner->button_link }}" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg font-semibold">{{ $banner->button_label }}</a>
@endif
</div>
</div>
@endif
</div>
@endforeach
@if($banners->count() > 1)
<div class="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2">
@foreach($banners as $i => $b)
<button @click="current = {{ $i }}" :class="current === {{ $i }} ? 'bg-white' : 'bg-white/50'" class="w-3 h-3 rounded-full"></button>
@endforeach
</div>
@endif
</div>
@endif
{{-- Kategorien --}}
@if($categories->count())
<h2 class="text-2xl font-bold mb-6">Kategorien</h2>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-12">
@foreach($categories as $cat)
<a href="{{ route('shop.category', $cat->slug) }}" class="group block bg-gray-50 rounded-lg overflow-hidden hover:shadow-md transition">
@if($cat->image)
<img src="{{ asset('storage/'.$cat->image) }}" alt="{{ $cat->name }}" class="w-full h-32 object-cover group-hover:scale-105 transition">
@else
<div class="w-full h-32 bg-blue-100 flex items-center justify-center text-4xl">🛍️</div>
@endif
<div class="p-3 font-semibold">{{ $cat->name }}</div>
</a>
@endforeach
</div>
@endif
{{-- Top Produkte --}}
@if($topProducts->count())
<div class="mb-12">
<div class="flex items-center gap-3 mb-6">
<svg class="w-6 h-6 text-yellow-400" fill="currentColor" viewBox="0 0 20 20">
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
</svg>
<h2 class="text-2xl font-bold">Top Produkte</h2>
</div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-6">
@foreach($topProducts as $product)
@include('shop.products._card', ['product' => $product, 'badge' => 'Top'])
@endforeach
</div>
</div>
@endif
{{-- Neue Produkte --}}
@if($newProducts->count())
<div class="mb-12">
<h2 class="text-2xl font-bold mb-6">Neu im Sortiment</h2>
<div class="grid grid-cols-2 md:grid-cols-4 gap-6">
@foreach($newProducts as $product)
@include('shop.products._card', ['product' => $product])
@endforeach
</div>
</div>
@endif
{{-- Blog --}}
@if($blogPosts->count())
<h2 class="text-2xl font-bold mb-6">Aktuelles</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
@foreach($blogPosts as $post)
<a href="{{ route('blog.post', $post->slug) }}" class="block bg-white border rounded-lg overflow-hidden hover:shadow-md transition">
@if($post->image)<img src="{{ asset('storage/'.$post->image) }}" class="w-full h-40 object-cover">@endif
<div class="p-4">
<p class="text-xs text-gray-400 mb-1">{{ $post->published_at?->format('d.m.Y') }}</p>
<h3 class="font-semibold">{{ $post->title }}</h3>
</div>
</a>
@endforeach
</div>
@endif
@endsection
@@ -0,0 +1,34 @@
@extends('layouts.shop')
@section('title', 'Bestellverfolgung')
@section('content')
<div class="max-w-xl mx-auto">
<h1 class="text-2xl font-bold mb-6">Bestellung verfolgen</h1>
<div class="bg-white border rounded-lg p-6 mb-6">
<form method="POST" action="{{ route('order.track.show') }}" class="space-y-4">
@csrf
<div>
<label class="block text-sm font-medium mb-1">Bestellnummer</label>
<input type="text" name="order_number" value="{{ old('order_number') }}" class="w-full border rounded px-3 py-2 text-sm font-mono" placeholder="ORD-20240101-XXXXX" required>
</div>
<div>
<label class="block text-sm font-medium mb-1">E-Mail-Adresse</label>
<input type="email" name="email" value="{{ old('email') }}" class="w-full border rounded px-3 py-2 text-sm" required>
</div>
<button class="bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700">Suchen</button>
</form>
</div>
@if(isset($order))
<div class="bg-white border rounded-lg p-6">
<h2 class="font-semibold mb-4">Bestellung {{ $order->order_number }}</h2>
<div class="space-y-2 text-sm mb-4">
<div class="flex justify-between"><span class="text-gray-500">Status</span><strong>{{ $order->status_label }}</strong></div>
<div class="flex justify-between"><span class="text-gray-500">Zahlungsart</span><span>{{ $order->payment_label }}</span></div>
<div class="flex justify-between"><span class="text-gray-500">Gesamtbetrag</span><span>{{ number_format($order->total, 2, ',', '.') }} €</span></div>
@if($order->tracking_code)<div class="flex justify-between"><span class="text-gray-500">Tracking-Nr.</span><span class="font-mono">{{ $order->tracking_code }}</span></div>@endif
</div>
</div>
@endif
</div>
@endsection
+11
View File
@@ -0,0 +1,11 @@
@extends('layouts.shop')
@section('title', $page->meta_title ?: $page->title)
@section('meta_description', $page->meta_description)
@section('content')
<div class="max-w-3xl mx-auto">
<h1 class="text-3xl font-bold mb-6">{{ $page->title }}</h1>
<div class="prose max-w-none text-gray-700">
{!! $page->content !!}
</div>
</div>
@endsection
@@ -0,0 +1,20 @@
<a href="{{ route('shop.product.show', $product->slug) }}" class="group block bg-white border rounded-lg overflow-hidden hover:shadow-md transition relative">
@if($product->images->first())
<img src="{{ asset('storage/'.$product->images->first()->path) }}" alt="{{ $product->name }}" class="w-full h-48 object-cover group-hover:scale-105 transition">
@else
<div class="w-full h-48 bg-gray-100 flex items-center justify-center text-5xl">📦</div>
@endif
@if(isset($badge))
<span class="absolute top-2 left-2 bg-yellow-400 text-yellow-900 text-xs font-bold px-2 py-0.5 rounded-full">
★ {{ $badge }}
</span>
@endif
<div class="p-4">
<p class="text-xs text-gray-400 mb-1">{{ $product->category?->name }}</p>
<h3 class="font-semibold text-sm mb-2 group-hover:text-blue-600">{{ $product->name }}</h3>
<div class="flex items-baseline gap-2">
<span class="text-lg font-bold text-blue-700">{{ number_format($product->base_price, 2, ',', '.') }} €</span>
<span class="text-xs text-gray-400">inkl. {{ number_format($product->tax_rate, 0) }}% MwSt.</span>
</div>
</div>
</a>
@@ -0,0 +1,26 @@
@extends('layouts.shop')
@section('title', isset($category) ? $category->name : 'Alle Produkte')
@section('content')
<h1 class="text-2xl font-bold mb-6">{{ isset($category) ? $category->name : 'Alle Produkte' }}</h1>
@if(isset($category) && $category->description)
<p class="text-gray-600 mb-6">{{ $category->description }}</p>
@endif
<div class="flex gap-3 mb-6">
<form method="GET" class="flex gap-2">
<input type="text" name="q" value="{{ request('q') }}" placeholder="Suchen…" class="border rounded px-3 py-2 text-sm">
<button class="bg-blue-600 text-white px-4 py-2 rounded text-sm hover:bg-blue-700">Suchen</button>
</form>
</div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-6">
@forelse($products as $product)
@include('shop.products._card', ['product' => $product])
@empty
<p class="col-span-4 text-center text-gray-400 py-16">Keine Produkte gefunden.</p>
@endforelse
</div>
<div class="mt-8">{{ $products->withQueryString()->links() }}</div>
@endsection
@@ -0,0 +1,139 @@
@extends('layouts.shop')
@section('title', $product->name)
@section('content')
<div class="grid grid-cols-1 md:grid-cols-2 gap-10 mb-12" x-data="productDetail(@json($product->variants))">
{{-- Bilder --}}
<div>
@if($product->images->count())
<img :src="mainImage" x-init="mainImage = '{{ asset('storage/'.$product->images->first()->path) }}'" class="w-full rounded-lg mb-3 border">
<div class="flex gap-2 flex-wrap">
@foreach($product->images as $img)
<img src="{{ asset('storage/'.$img->path) }}" @click="mainImage = '{{ asset('storage/'.$img->path) }}'"
class="w-16 h-16 object-cover rounded cursor-pointer border hover:border-blue-500">
@endforeach
</div>
@else
<div class="w-full h-72 bg-gray-100 rounded-lg flex items-center justify-center text-7xl">📦</div>
@endif
</div>
{{-- Info --}}
<div>
<p class="text-sm text-gray-400 mb-1">{{ $product->category?->name }}</p>
<h1 class="text-2xl font-bold mb-3">{{ $product->name }}</h1>
{{-- Preis --}}
<div class="mb-4">
<span class="text-3xl font-bold text-blue-700" x-text="formatPrice(currentPrice)"></span>
<span class="text-sm text-gray-500 ml-2">inkl. {{ number_format($product->tax_rate, 0) }}% MwSt.</span>
<div class="text-xs text-gray-400 mt-1" x-text="'Netto: ' + formatPrice(currentPrice / (1 + {{ $product->tax_rate }} / 100))"></div>
</div>
{{-- Varianten --}}
@php
$attributeGroups = $product->variants->flatMap->attributeValues->groupBy(fn($v) => $v->attribute->name)->map->unique('id');
$hasAttributes = $attributeGroups->isNotEmpty();
$defaultVariant = $product->variants->first();
@endphp
@if($hasAttributes)
@foreach($attributeGroups as $attrName => $values)
<div class="mb-4">
<label class="block text-sm font-medium mb-2">{{ $attrName }}</label>
<div class="flex flex-wrap gap-2">
@foreach($values as $val)
<button type="button"
@click="selectAttr('{{ $attrName }}', {{ $val->id }})"
:class="selectedAttrs['{{ $attrName }}'] === {{ $val->id }} ? 'bg-blue-600 text-white border-blue-600' : 'bg-white border-gray-300 hover:border-blue-400'"
class="px-4 py-2 text-sm border rounded-lg transition">
{{ $val->value }}
</button>
@endforeach
</div>
</div>
@endforeach
<div class="text-sm mb-3" :class="selectedVariant ? ({{ $product->track_stock ? 'true' : 'false' }} ? (selectedVariant.stock > 0 ? 'text-green-600' : 'text-red-600') : 'text-green-600') : 'text-gray-400'">
<span x-show="!selectedVariant">Bitte Variante wählen</span>
@if($product->track_stock)
<span x-show="selectedVariant && selectedVariant.stock > 0" x-text="'✓ Auf Lager (' + selectedVariant?.stock + ' verfügbar)'"></span>
<span x-show="selectedVariant && selectedVariant.stock <= 0">✕ Nicht auf Lager</span>
@else
<span x-show="selectedVariant">✓ Lieferbar</span>
@endif
</div>
@elseif($defaultVariant)
@if($product->track_stock)
<p class="text-sm mb-3 {{ $defaultVariant->stock > 0 ? 'text-green-600' : 'text-red-600' }}">
{{ $defaultVariant->stock > 0 ? '✓ Auf Lager' : '✕ Nicht auf Lager' }}
</p>
@else
<p class="text-sm mb-3 text-green-600">✓ Lieferbar</p>
@endif
@endif
{{-- In den Warenkorb --}}
<form method="POST" action="{{ route('cart.add') }}">
@csrf
@if($hasAttributes)
<input type="hidden" name="variant_id" :value="selectedVariant?.id ?? 0">
@else
<input type="hidden" name="variant_id" value="{{ $defaultVariant?->id }}">
@endif
<div class="flex gap-3 mt-4">
<input type="number" name="qty" value="1" min="1" class="w-20 border rounded px-3 py-2 text-sm">
<button type="submit"
@if($hasAttributes) :disabled="!selectedVariant || ({{ $product->track_stock ? 'true' : 'false' }} && selectedVariant && selectedVariant.stock <= 0)" @endif
@if(!$hasAttributes && $product->track_stock && (!$defaultVariant || $defaultVariant->stock <= 0)) disabled @endif
class="flex-1 bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed font-semibold">
In den Warenkorb
</button>
</div>
</form>
<div class="mt-6 prose text-sm text-gray-600 max-w-none">
{!! nl2br(e($product->description)) !!}
</div>
</div>
</div>
{{-- Ähnliche Produkte --}}
@if($related->count())
<h2 class="text-xl font-bold mb-4">Ähnliche Produkte</h2>
<div class="grid grid-cols-2 md:grid-cols-4 gap-6">
@foreach($related as $p)
@include('shop.products._card', ['product' => $p])
@endforeach
</div>
@endif
<script>
function productDetail(variants) {
return {
variants,
selectedAttrs: {},
selectedVariant: null,
mainImage: '',
currentPrice: {{ $product->base_price }},
selectAttr(name, id) {
this.selectedAttrs[name] = id;
this.findVariant();
},
findVariant() {
const selectedIds = Object.values(this.selectedAttrs);
this.selectedVariant = this.variants.find(v => {
const ids = v.attribute_values.map(a => a.id);
return selectedIds.every(id => ids.includes(id)) && ids.length === selectedIds.length;
});
if (this.selectedVariant) {
this.currentPrice = this.selectedVariant.price_override ?? {{ $product->base_price }};
}
},
formatPrice(p) {
return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(p);
}
}
}
</script>
@endsection
+176
View File
@@ -0,0 +1,176 @@
@extends('layouts.shop')
@section('title', 'Widerrufsformular')
@section('content')
<div class="max-w-2xl mx-auto">
<div class="mb-6">
<h1 class="text-2xl font-bold mb-1">Widerrufsformular</h1>
<p class="text-gray-500 text-sm">gemäß § 356 BGB – Füllen Sie dieses Formular aus, um Ihr Widerrufsrecht innerhalb von 14 Tagen ab Warenerhalt auszuüben.</p>
</div>
@if(session('success'))
<div class="bg-green-100 text-green-800 px-4 py-4 rounded-lg mb-6 flex items-start gap-3">
<svg class="w-5 h-5 mt-0.5 shrink-0" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg>
<div>
<p class="font-semibold">Widerruf erfolgreich übermittelt</p>
<p class="text-sm mt-0.5">{{ session('success') }}</p>
</div>
</div>
@endif
@if($errors->any())
<div class="bg-red-50 border border-red-200 rounded-lg p-4 mb-6 text-sm text-red-700">
@foreach($errors->all() as $e)<p>• {{ $e }}</p>@endforeach
</div>
@endif
<div class="bg-white border rounded-xl shadow-sm p-6">
<form method="POST" action="{{ route('widerruf.submit') }}" class="space-y-6">
@csrf
{{-- Bestelldaten --}}
<fieldset>
<legend class="text-xs font-semibold text-gray-400 uppercase tracking-wider mb-3">Bestellinformationen</legend>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium mb-1">Kundennummer</label>
<input type="text" name="customer_number" value="{{ old('customer_number') }}"
class="w-full border rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="z.B. KD-00123">
</div>
<div>
<label class="block text-sm font-medium mb-1">Bestell- / Rechnungsnummer</label>
<input type="text" name="order_number" value="{{ old('order_number') }}"
class="w-full border rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium mb-1">Bestelldatum</label>
<input type="date" name="order_date" value="{{ old('order_date') }}"
class="w-full border rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium mb-1">Datum des Warenerhalts *</label>
<input type="date" name="date" value="{{ old('date') }}" required
class="w-full border rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="col-span-2">
<label class="block text-sm font-medium mb-1">Artikelbezeichnung *</label>
<input type="text" name="product" value="{{ old('product') }}" required
class="w-full border rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Artikelname, Variante, Menge …">
</div>
</div>
</fieldset>
{{-- Kontaktdaten --}}
<fieldset>
<legend class="text-xs font-semibold text-gray-400 uppercase tracking-wider mb-3">Ihre Kontaktdaten</legend>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium mb-1">Vorname *</label>
<input type="text" name="first_name" value="{{ old('first_name') }}" required
class="w-full border rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium mb-1">Nachname *</label>
<input type="text" name="last_name" value="{{ old('last_name') }}" required
class="w-full border rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="col-span-2">
<label class="block text-sm font-medium mb-1">E-Mail-Adresse *</label>
<input type="email" name="email" value="{{ old('email') }}" required
class="w-full border rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="col-span-2">
<label class="block text-sm font-medium mb-1">Straße und Hausnummer *</label>
<input type="text" name="street" value="{{ old('street') }}" required
class="w-full border rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium mb-1">PLZ *</label>
<input type="text" name="zip" value="{{ old('zip') }}" required
class="w-full border rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium mb-1">Ort *</label>
<input type="text" name="city" value="{{ old('city') }}" required
class="w-full border rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
</div>
</fieldset>
{{-- Widerrufsgrund --}}
<fieldset>
<legend class="text-xs font-semibold text-gray-400 uppercase tracking-wider mb-3">Widerrufsgrund</legend>
<div class="space-y-3">
<div>
<label class="block text-sm font-medium mb-1">Grund (optional)</label>
<select name="reason_category" class="w-full border rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="">– Bitte wählen –</option>
<option value="falsch_bestellt" {{ old('reason_category') === 'falsch_bestellt' ? 'selected' : '' }}>Falsch bestellt</option>
<option value="nicht_gefallen" {{ old('reason_category') === 'nicht_gefallen' ? 'selected' : '' }}>Artikel gefällt mir nicht</option>
<option value="beschaedigt" {{ old('reason_category') === 'beschaedigt' ? 'selected' : '' }}>Ware beschädigt erhalten</option>
<option value="falsch_geliefert" {{ old('reason_category') === 'falsch_geliefert' ? 'selected' : '' }}>Falsche Ware geliefert</option>
<option value="zu_spaet" {{ old('reason_category') === 'zu_spaet' ? 'selected' : '' }}>Lieferung zu spät</option>
<option value="sonstiges" {{ old('reason_category') === 'sonstiges' ? 'selected' : '' }}>Sonstiges</option>
</select>
</div>
<div>
<label class="block text-sm font-medium mb-1">Anmerkungen (optional)</label>
<textarea name="reason" rows="3"
class="w-full border rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Weitere Details zu Ihrem Widerruf …">{{ old('reason') }}</textarea>
</div>
</div>
</fieldset>
{{-- Erstattungsart --}}
<fieldset>
<legend class="text-xs font-semibold text-gray-400 uppercase tracking-wider mb-3">Erstattung</legend>
<div class="space-y-2 text-sm">
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" name="refund_method" value="original" {{ old('refund_method', 'original') === 'original' ? 'checked' : '' }} class="accent-blue-600">
Ursprüngliche Zahlungsart
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" name="refund_method" value="bank" {{ old('refund_method') === 'bank' ? 'checked' : '' }} class="accent-blue-600">
Banküberweisung
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" name="refund_method" value="credit" {{ old('refund_method') === 'credit' ? 'checked' : '' }} class="accent-blue-600">
Gutschrift im Shop
</label>
</div>
</fieldset>
{{-- Pflicht-Checkboxen --}}
<fieldset class="border-t pt-4 space-y-3">
<label class="flex items-start gap-3 text-sm cursor-pointer">
<input type="checkbox" name="privacy_accepted" value="1" required
class="mt-0.5 accent-blue-600">
<span>Ich habe die <a href="{{ route('page', 'datenschutz') }}" class="text-blue-600 underline" target="_blank">Datenschutzerklärung</a> gelesen und stimme der Verarbeitung meiner Daten zur Bearbeitung des Widerrufs zu. *</span>
</label>
<label class="flex items-start gap-3 text-sm cursor-pointer">
<input type="checkbox" name="accuracy_confirmed" value="1" required
class="mt-0.5 accent-blue-600">
<span>Ich bestätige, dass die angegebenen Informationen korrekt und vollständig sind. *</span>
</label>
</fieldset>
<button type="submit"
class="w-full bg-blue-600 text-white py-3 rounded-lg font-medium hover:bg-blue-700 transition text-sm">
Widerruf verbindlich absenden
</button>
</form>
</div>
<div class="mt-6 text-sm text-gray-500 bg-gray-50 rounded-lg p-4 space-y-2">
<p><strong>Hinweis:</strong> Die 14-tägige Widerrufsfrist beginnt ab dem Tag des Erhalts der Ware. Nach Absenden dieses Formulars erhalten Sie eine Bestätigungs-E-Mail.</p>
<p>Bitte senden Sie die Ware zurück an:<br>
<strong class="text-gray-700">{{ \App\Models\Setting::get('shop_owner') }}</strong><br>
{!! nl2br(e(\App\Models\Setting::get('shop_address'))) !!}</p>
<p>Alternativ können Sie den Widerruf auch per E-Mail an <a href="mailto:{{ \App\Models\Setting::get('shop_email') }}" class="text-blue-600 underline">{{ \App\Models\Setting::get('shop_email') }}</a> erklären.</p>
</div>
</div>
@endsection