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:
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
|
||||
import Alpine from 'alpinejs';
|
||||
|
||||
window.Alpine = Alpine;
|
||||
|
||||
Alpine.start();
|
||||
@@ -0,0 +1,50 @@
|
||||
@extends('layouts.admin')
|
||||
@section('title', 'Attribute')
|
||||
@section('content')
|
||||
<div class="grid grid-cols-2 gap-6">
|
||||
{{-- Neues Attribut --}}
|
||||
<div class="bg-white rounded shadow p-6">
|
||||
<h3 class="font-semibold mb-4">Neues Attribut</h3>
|
||||
<form method="POST" action="{{ route('admin.attributes.store') }}" class="flex gap-2">
|
||||
@csrf
|
||||
<input type="text" name="name" placeholder="z.B. Größe, Farbe" class="flex-1 border rounded px-3 py-2 text-sm" required>
|
||||
<button class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 text-sm">Erstellen</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 grid grid-cols-2 gap-6">
|
||||
@foreach($attributes as $attr)
|
||||
<div class="bg-white rounded shadow p-5">
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<h3 class="font-semibold">{{ $attr->name }}</h3>
|
||||
<form method="POST" action="{{ route('admin.attributes.destroy', $attr) }}" onsubmit="return confirm('Attribut und alle Werte löschen?')">
|
||||
@csrf @method('DELETE')
|
||||
<button class="text-red-500 text-sm hover:underline">Löschen</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1 mb-4">
|
||||
@foreach($attr->values as $val)
|
||||
<div class="flex justify-between items-center text-sm py-1 border-b">
|
||||
<span>{{ $val->value }}</span>
|
||||
<form method="POST" action="{{ route('admin.attribute-values.destroy', $val) }}">
|
||||
@csrf @method('DELETE')
|
||||
<button class="text-red-400 hover:text-red-600 text-xs">✕</button>
|
||||
</form>
|
||||
</div>
|
||||
@endforeach
|
||||
@if($attr->values->isEmpty())
|
||||
<p class="text-xs text-gray-400">Keine Werte.</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ route('admin.attributes.values.store', $attr) }}" class="flex gap-2">
|
||||
@csrf
|
||||
<input type="text" name="value" placeholder="Neuer Wert" class="flex-1 border rounded px-2 py-1.5 text-sm" required>
|
||||
<button class="bg-gray-100 px-3 py-1.5 rounded text-sm hover:bg-gray-200">+ Hinzufügen</button>
|
||||
</form>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,29 @@
|
||||
@extends('layouts.admin')
|
||||
@section('title', isset($banner) ? 'Banner bearbeiten' : 'Neuer Banner')
|
||||
@section('content')
|
||||
<div class="max-w-xl">
|
||||
<form method="POST" action="{{ isset($banner) ? route('admin.banners.update', $banner) : route('admin.banners.store') }}" enctype="multipart/form-data" class="bg-white rounded shadow p-6 space-y-4">
|
||||
@csrf @if(isset($banner)) @method('PUT') @endif
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Bild *</label>
|
||||
@if(isset($banner) && $banner->image)<img src="{{ asset('storage/'.$banner->image) }}" class="h-20 mb-2 rounded">@endif
|
||||
<input type="file" name="image" accept="image/*" {{ !isset($banner) ? 'required' : '' }} class="text-sm">
|
||||
</div>
|
||||
<div><label class="block text-sm font-medium mb-1">Überschrift</label><input type="text" name="headline" value="{{ old('headline', $banner->headline ?? '') }}" class="w-full border rounded px-3 py-2 text-sm"></div>
|
||||
<div><label class="block text-sm font-medium mb-1">Untertext</label><input type="text" name="subtext" value="{{ old('subtext', $banner->subtext ?? '') }}" class="w-full border rounded px-3 py-2 text-sm"></div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div><label class="block text-sm font-medium mb-1">Button-Text</label><input type="text" name="button_label" value="{{ old('button_label', $banner->button_label ?? '') }}" class="w-full border rounded px-3 py-2 text-sm"></div>
|
||||
<div><label class="block text-sm font-medium mb-1">Button-Link</label><input type="text" name="button_link" value="{{ old('button_link', $banner->button_link ?? '') }}" class="w-full border rounded px-3 py-2 text-sm"></div>
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<div><label class="block text-sm font-medium mb-1">Reihenfolge</label><input type="number" name="sort_order" value="{{ old('sort_order', $banner->sort_order ?? 0) }}" class="w-24 border rounded px-3 py-2 text-sm"></div>
|
||||
<div class="flex items-end pb-2"><label class="flex items-center gap-2 text-sm"><input type="checkbox" name="active" value="1" {{ old('active', $banner->active ?? true) ? 'checked' : '' }}> Aktiv</label></div>
|
||||
</div>
|
||||
<div class="flex gap-3 pt-2">
|
||||
<button type="submit" class="bg-blue-600 text-white px-5 py-2 rounded hover:bg-blue-700 text-sm">Speichern</button>
|
||||
<a href="{{ route('admin.banners.index') }}" class="px-5 py-2 rounded border text-sm hover:bg-gray-50">Abbrechen</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,27 @@
|
||||
@extends('layouts.admin')
|
||||
@section('title', 'Banner')
|
||||
@section('content')
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-lg font-semibold">Startseiten-Banner</h2>
|
||||
<a href="{{ route('admin.banners.create') }}" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 text-sm">+ Neuer Banner</a>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
@forelse($banners as $banner)
|
||||
<div class="bg-white rounded shadow overflow-hidden">
|
||||
@if($banner->image)<img src="{{ asset('storage/'.$banner->image) }}" class="w-full h-32 object-cover">@endif
|
||||
<div class="p-4">
|
||||
<p class="font-medium text-sm">{{ $banner->headline ?: '(kein Titel)' }}</p>
|
||||
<p class="text-xs text-gray-400">Reihenfolge: {{ $banner->sort_order }}</p>
|
||||
<div class="flex gap-2 mt-3">
|
||||
<a href="{{ route('admin.banners.edit', $banner) }}" class="text-blue-600 text-sm hover:underline">Bearbeiten</a>
|
||||
<form method="POST" action="{{ route('admin.banners.destroy', $banner) }}" onsubmit="return confirm('Löschen?')">
|
||||
@csrf @method('DELETE')<button class="text-red-600 text-sm hover:underline">Löschen</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<p class="col-span-3 text-gray-400">Keine Banner.</p>
|
||||
@endforelse
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,54 @@
|
||||
@extends('layouts.admin')
|
||||
@section('title', isset($category) ? 'Kategorie bearbeiten' : 'Neue Kategorie')
|
||||
@section('content')
|
||||
<div class="max-w-xl">
|
||||
<form method="POST" action="{{ isset($category) ? route('admin.categories.update', $category) : route('admin.categories.store') }}" enctype="multipart/form-data" class="bg-white rounded shadow p-6 space-y-4">
|
||||
@csrf
|
||||
@if(isset($category)) @method('PUT') @endif
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Name *</label>
|
||||
<input type="text" name="name" value="{{ old('name', $category->name ?? '') }}" class="w-full border rounded px-3 py-2 text-sm" required>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Übergeordnete Kategorie</label>
|
||||
<select name="parent_id" class="w-full border rounded px-3 py-2 text-sm">
|
||||
<option value="">— Keine —</option>
|
||||
@foreach($parents as $p)
|
||||
<option value="{{ $p->id }}" {{ old('parent_id', $category->parent_id ?? '') == $p->id ? 'selected' : '' }}>{{ $p->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Beschreibung</label>
|
||||
<textarea name="description" rows="3" class="w-full border rounded px-3 py-2 text-sm">{{ old('description', $category->description ?? '') }}</textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Bild</label>
|
||||
@if(isset($category) && $category->image)
|
||||
<img src="{{ asset('storage/'.$category->image) }}" class="h-16 mb-2 rounded">
|
||||
@endif
|
||||
<input type="file" name="image" accept="image/*" class="text-sm">
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<div class="flex-1">
|
||||
<label class="block text-sm font-medium mb-1">Reihenfolge</label>
|
||||
<input type="number" name="sort_order" value="{{ old('sort_order', $category->sort_order ?? 0) }}" class="w-full border rounded px-3 py-2 text-sm">
|
||||
</div>
|
||||
<div class="flex items-end gap-2 pb-2">
|
||||
<input type="checkbox" name="active" id="active" value="1" {{ old('active', $category->active ?? true) ? 'checked' : '' }}>
|
||||
<label for="active" class="text-sm">Aktiv</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3 pt-2">
|
||||
<button type="submit" class="bg-blue-600 text-white px-5 py-2 rounded hover:bg-blue-700 text-sm">Speichern</button>
|
||||
<a href="{{ route('admin.categories.index') }}" class="px-5 py-2 rounded border text-sm hover:bg-gray-50">Abbrechen</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,45 @@
|
||||
@extends('layouts.admin')
|
||||
@section('title', 'Kategorien')
|
||||
@section('content')
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-lg font-semibold">Kategorien</h2>
|
||||
<a href="{{ route('admin.categories.create') }}" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 text-sm">+ Neue Kategorie</a>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded shadow overflow-hidden">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50 text-left">
|
||||
<tr>
|
||||
<th class="px-4 py-3">Name</th>
|
||||
<th class="px-4 py-3">Übergeordnet</th>
|
||||
<th class="px-4 py-3">Reihenfolge</th>
|
||||
<th class="px-4 py-3">Aktiv</th>
|
||||
<th class="px-4 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y">
|
||||
@forelse($categories as $cat)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-3 font-medium">{{ $cat->name }}</td>
|
||||
<td class="px-4 py-3 text-gray-500">{{ $cat->parent?->name ?? '—' }}</td>
|
||||
<td class="px-4 py-3">{{ $cat->sort_order }}</td>
|
||||
<td class="px-4 py-3">
|
||||
<span class="px-2 py-0.5 rounded text-xs {{ $cat->active ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-500' }}">
|
||||
{{ $cat->active ? 'Ja' : 'Nein' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right space-x-2">
|
||||
<a href="{{ route('admin.categories.edit', $cat) }}" class="text-blue-600 hover:underline">Bearbeiten</a>
|
||||
<form method="POST" action="{{ route('admin.categories.destroy', $cat) }}" class="inline" onsubmit="return confirm('Kategorie löschen?')">
|
||||
@csrf @method('DELETE')
|
||||
<button class="text-red-600 hover:underline">Löschen</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="5" class="px-4 py-8 text-center text-gray-400">Keine Kategorien vorhanden.</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,104 @@
|
||||
@extends('layouts.admin')
|
||||
@section('title', 'Kunden')
|
||||
@section('content')
|
||||
|
||||
@php $pendingCount = \App\Models\User::where('role','customer')->where('approved', false)->count(); @endphp
|
||||
@if($pendingCount > 0)
|
||||
<div class="mb-4 bg-amber-50 border border-amber-200 rounded-lg px-4 py-3 flex items-center gap-3 text-sm text-amber-800">
|
||||
<svg class="w-5 h-5 shrink-0 text-amber-500" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>
|
||||
<span><strong>{{ $pendingCount }} Kunde{{ $pendingCount > 1 ? 'n' : '' }}</strong> warte{{ $pendingCount > 1 ? 'n' : 't' }} auf Freischaltung.</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="flex items-center gap-4 mb-6">
|
||||
<h2 class="text-lg font-semibold">Kunden</h2>
|
||||
<form method="GET" class="flex gap-2 ml-2">
|
||||
<input type="text" name="q" value="{{ request('q') }}"
|
||||
placeholder="Name oder E-Mail …"
|
||||
class="border rounded px-3 py-1.5 text-sm w-64">
|
||||
<button class="bg-gray-200 px-3 py-1.5 rounded text-sm hover:bg-gray-300">Suchen</button>
|
||||
@if(request('q'))
|
||||
<a href="{{ route('admin.customers.index') }}" class="px-3 py-1.5 rounded text-sm text-gray-500 hover:bg-gray-100">✕ Zurücksetzen</a>
|
||||
@endif
|
||||
</form>
|
||||
<span class="ml-auto text-sm text-gray-400">{{ $customers->total() }} Kunden gesamt</span>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded shadow overflow-hidden">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50 text-left border-b">
|
||||
<tr>
|
||||
<th class="px-4 py-3">Name</th>
|
||||
<th class="px-4 py-3">E-Mail</th>
|
||||
<th class="px-4 py-3">Registriert</th>
|
||||
<th class="px-4 py-3 text-center">Bestellungen</th>
|
||||
<th class="px-4 py-3">Verifiziert</th>
|
||||
<th class="px-4 py-3 text-center">Status</th>
|
||||
<th class="px-4 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y">
|
||||
@forelse($customers as $customer)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-3 font-medium">
|
||||
<a href="{{ route('admin.customers.show', $customer) }}" class="hover:text-blue-600">
|
||||
{{ $customer->name }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-gray-600">{{ $customer->email }}</td>
|
||||
<td class="px-4 py-3 text-gray-400">{{ $customer->created_at->format('d.m.Y') }}</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
@if($customer->orders_count > 0)
|
||||
<a href="{{ route('admin.customers.show', $customer) }}"
|
||||
class="inline-flex items-center justify-center w-7 h-7 rounded-full bg-blue-100 text-blue-700 font-semibold text-xs hover:bg-blue-200">
|
||||
{{ $customer->orders_count }}
|
||||
</a>
|
||||
@else
|
||||
<span class="text-gray-300">—</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
@if($customer->email_verified_at)
|
||||
<span class="text-green-600 text-xs font-medium">✓ Ja</span>
|
||||
@else
|
||||
<span class="text-orange-500 text-xs">Ausstehend</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
<form method="POST" action="{{ route('admin.customers.toggle-approval', $customer) }}">
|
||||
@csrf
|
||||
<button type="submit"
|
||||
class="text-xs px-2.5 py-1 rounded-full font-medium transition
|
||||
{{ $customer->approved
|
||||
? 'bg-green-100 text-green-700 hover:bg-green-200'
|
||||
: 'bg-red-100 text-red-700 hover:bg-red-200' }}">
|
||||
{{ $customer->approved ? '✓ Aktiv' : '✗ Gesperrt' }}
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right space-x-3">
|
||||
<a href="{{ route('admin.customers.show', $customer) }}"
|
||||
class="text-blue-600 hover:underline text-xs">Details</a>
|
||||
<form method="POST" action="{{ route('admin.customers.destroy', $customer) }}"
|
||||
class="inline" onsubmit="return confirm('Kunde und alle Daten löschen?')">
|
||||
@csrf @method('DELETE')
|
||||
<button class="text-red-500 hover:underline text-xs">Löschen</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="px-4 py-12 text-center text-gray-400">
|
||||
@if(request('q'))
|
||||
Keine Kunden für „{{ request('q') }}" gefunden.
|
||||
@else
|
||||
Noch keine registrierten Kunden.
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="px-4 py-3 border-t">{{ $customers->withQueryString()->links() }}</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,175 @@
|
||||
@extends('layouts.admin')
|
||||
@section('title', $customer->name)
|
||||
@section('content')
|
||||
|
||||
<div class="mb-5">
|
||||
<a href="{{ route('admin.customers.index') }}" class="text-sm text-gray-500 hover:text-blue-600">← Alle Kunden</a>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-6">
|
||||
|
||||
{{-- Linke Spalte: Kundendaten --}}
|
||||
<div class="space-y-4">
|
||||
|
||||
{{-- Stammdaten --}}
|
||||
<div class="bg-white rounded shadow p-5">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="w-12 h-12 rounded-full bg-blue-600 flex items-center justify-center text-white font-bold text-lg">
|
||||
{{ mb_substr($customer->name, 0, 1) }}
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="font-semibold">{{ $customer->name }}</h2>
|
||||
<p class="text-xs text-gray-400">Kunde seit {{ $customer->created_at->format('d.m.Y') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<dl class="space-y-2 text-sm">
|
||||
<div>
|
||||
<dt class="text-xs text-gray-400">E-Mail</dt>
|
||||
<dd><a href="mailto:{{ $customer->email }}" class="text-blue-600 hover:underline">{{ $customer->email }}</a></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs text-gray-400">E-Mail verifiziert</dt>
|
||||
<dd>
|
||||
@if($customer->email_verified_at)
|
||||
<span class="text-green-600">✓ {{ $customer->email_verified_at->format('d.m.Y') }}</span>
|
||||
@else
|
||||
<span class="text-orange-500">Noch nicht verifiziert</span>
|
||||
@endif
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs text-gray-400">Konto-Status</dt>
|
||||
<dd>
|
||||
@if($customer->approved)
|
||||
<span class="text-green-600 font-medium">✓ Freigeschaltet</span>
|
||||
@else
|
||||
<span class="text-red-500 font-medium">✗ Gesperrt / Ausstehend</span>
|
||||
@endif
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div class="mt-4 pt-4 border-t space-y-2">
|
||||
<form method="POST" action="{{ route('admin.customers.toggle-approval', $customer) }}">
|
||||
@csrf
|
||||
<button type="submit"
|
||||
class="w-full py-2 rounded text-sm font-medium transition
|
||||
{{ $customer->approved
|
||||
? 'bg-red-50 text-red-600 hover:bg-red-100'
|
||||
: 'bg-green-600 text-white hover:bg-green-700' }}">
|
||||
{{ $customer->approved ? 'Konto sperren' : 'Konto freischalten' }}
|
||||
</button>
|
||||
</form>
|
||||
<form method="POST" action="{{ route('admin.customers.destroy', $customer) }}"
|
||||
onsubmit="return confirm('Kunde und alle Daten unwiderruflich löschen?')">
|
||||
@csrf @method('DELETE')
|
||||
<button class="text-red-500 text-xs hover:underline w-full text-left">Kunde löschen</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Kennzahlen --}}
|
||||
<div class="bg-white rounded shadow p-5">
|
||||
<h3 class="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-3">Kennzahlen</h3>
|
||||
<dl class="space-y-3 text-sm">
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-gray-500">Bestellungen gesamt</dt>
|
||||
<dd class="font-semibold">{{ $stats['total_orders'] }}</dd>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-gray-500">Umsatz gesamt</dt>
|
||||
<dd class="font-semibold text-green-700">{{ number_format($stats['total_revenue'], 2, ',', '.') }} €</dd>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-gray-500">Offene Bestellungen</dt>
|
||||
<dd class="{{ $stats['open_orders'] > 0 ? 'font-semibold text-orange-500' : 'text-gray-400' }}">
|
||||
{{ $stats['open_orders'] }}
|
||||
</dd>
|
||||
</div>
|
||||
@if($stats['last_order'])
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-gray-500">Letzte Bestellung</dt>
|
||||
<dd class="text-gray-600">{{ $stats['last_order']->created_at->format('d.m.Y') }}</dd>
|
||||
</div>
|
||||
@endif
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
{{-- Adressen --}}
|
||||
@if($customer->addresses->count())
|
||||
<div class="bg-white rounded shadow p-5">
|
||||
<h3 class="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-3">Gespeicherte Adressen</h3>
|
||||
<div class="space-y-3">
|
||||
@foreach($customer->addresses as $addr)
|
||||
<div class="text-sm border-b pb-3 last:border-0 last:pb-0">
|
||||
@if($addr->is_default)
|
||||
<span class="text-xs bg-blue-100 text-blue-700 px-1.5 py-0.5 rounded mb-1 inline-block">Standard</span>
|
||||
@endif
|
||||
<p class="font-medium">{{ $addr->first_name }} {{ $addr->last_name }}</p>
|
||||
@if($addr->company)<p class="text-gray-500 text-xs">{{ $addr->company }}</p>@endif
|
||||
<p class="text-gray-600">{{ $addr->street }}, {{ $addr->zip }} {{ $addr->city }}</p>
|
||||
@if($addr->phone)<p class="text-gray-400 text-xs">{{ $addr->phone }}</p>@endif
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
|
||||
{{-- Rechte Spalte: Bestellungen --}}
|
||||
<div class="col-span-2">
|
||||
<h3 class="font-semibold mb-3">Bestellhistorie</h3>
|
||||
|
||||
@if($orders->count())
|
||||
<div class="bg-white rounded shadow 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('admin.orders.show', $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 rounded shadow p-10 text-center text-gray-400">
|
||||
Dieser Kunde hat noch keine Bestellungen aufgegeben.
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,80 @@
|
||||
@extends('layouts.admin')
|
||||
@section('title', 'Dashboard')
|
||||
@section('content')
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
|
||||
<div class="bg-white rounded-lg shadow p-5">
|
||||
<p class="text-xs text-gray-400 mb-1">Umsatz heute</p>
|
||||
<p class="text-2xl font-bold text-green-600">{{ number_format($todayRevenue, 2, ',', '.') }} €</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow p-5">
|
||||
<p class="text-xs text-gray-400 mb-1">Umsatz diesen Monat</p>
|
||||
<p class="text-2xl font-bold text-blue-600">{{ number_format($monthRevenue, 2, ',', '.') }} €</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow p-5">
|
||||
<p class="text-xs text-gray-400 mb-1">Offene Bestellungen</p>
|
||||
<p class="text-2xl font-bold {{ $openOrders > 0 ? 'text-orange-500' : 'text-gray-400' }}">{{ $openOrders }}</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow p-5">
|
||||
<p class="text-xs text-gray-400 mb-1">Lagerwarnung</p>
|
||||
<p class="text-2xl font-bold {{ $lowStock->count() > 0 ? 'text-red-500' : 'text-gray-400' }}">{{ $lowStock->count() }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($lowStock->count())
|
||||
<div class="bg-red-50 border border-red-200 rounded-lg p-4 mb-8">
|
||||
<h3 class="font-semibold text-red-700 mb-2">Lagerbestand niedrig</h3>
|
||||
<ul class="text-sm space-y-1">
|
||||
@foreach($lowStock as $v)
|
||||
<li class="flex justify-between">
|
||||
<span>{{ $v->product->name }} @if($v->label)({{ $v->label }})@endif</span>
|
||||
<span class="font-bold text-red-600">{{ $v->stock }} Stück</span>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="bg-white rounded-lg shadow overflow-hidden">
|
||||
<div class="px-5 py-3 border-b font-semibold">Letzte Bestellungen</div>
|
||||
<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">Kunde</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 text-left">Datum</th>
|
||||
<th class="px-4 py-2"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y">
|
||||
@foreach($recentOrders as $o)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-2 font-mono font-medium">{{ $o->order_number }}</td>
|
||||
<td class="px-4 py-2">{{ $o->billingAddress()?->full_name ?? $o->guest_email }}</td>
|
||||
<td class="px-4 py-2">
|
||||
<span class="px-2 py-0.5 rounded text-xs
|
||||
{{ match($o->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',
|
||||
} }}">
|
||||
{{ $o->status_label }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-2 text-right">{{ number_format($o->total, 2, ',', '.') }} €</td>
|
||||
<td class="px-4 py-2 text-gray-400">{{ $o->created_at->format('d.m.Y H:i') }}</td>
|
||||
<td class="px-4 py-2"><a href="{{ route('admin.orders.show', $o) }}" class="text-blue-600 hover:underline text-xs">Details</a></td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="px-4 py-3 border-t text-right">
|
||||
<a href="{{ route('admin.orders.index') }}" class="text-sm text-blue-600 hover:underline">Alle Bestellungen →</a>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,47 @@
|
||||
@extends('layouts.admin')
|
||||
@section('title', 'FAQ')
|
||||
@section('content')
|
||||
<div class="grid grid-cols-3 gap-6">
|
||||
<div>
|
||||
<div class="bg-white rounded shadow p-5 mb-4">
|
||||
<h3 class="font-semibold mb-3">Neue Gruppe</h3>
|
||||
<form method="POST" action="{{ route('admin.faqs.groups.store') }}" class="space-y-2">
|
||||
@csrf
|
||||
<input type="text" name="name" placeholder="Gruppenname" class="w-full border rounded px-3 py-2 text-sm" required>
|
||||
<button class="w-full bg-blue-600 text-white py-2 rounded text-sm hover:bg-blue-700">Erstellen</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="bg-white rounded shadow p-5">
|
||||
<h3 class="font-semibold mb-3">Neue Frage</h3>
|
||||
<form method="POST" action="{{ route('admin.faqs.store') }}" class="space-y-2">
|
||||
@csrf
|
||||
<select name="faq_group_id" class="w-full border rounded px-3 py-2 text-sm">
|
||||
<option value="">Ohne Gruppe</option>
|
||||
@foreach($groups as $g)<option value="{{ $g->id }}">{{ $g->name }}</option>@endforeach
|
||||
</select>
|
||||
<input type="text" name="question" placeholder="Frage" class="w-full border rounded px-3 py-2 text-sm" required>
|
||||
<textarea name="answer" placeholder="Antwort" rows="3" class="w-full border rounded px-3 py-2 text-sm" required></textarea>
|
||||
<button class="w-full bg-green-600 text-white py-2 rounded text-sm hover:bg-green-700">Hinzufügen</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-2 space-y-4">
|
||||
@foreach($groups as $group)
|
||||
<div class="bg-white rounded shadow p-5">
|
||||
<h3 class="font-semibold mb-3 text-blue-700">{{ $group->name }}</h3>
|
||||
@foreach($group->faqs as $faq)
|
||||
<div class="border-b pb-3 mb-3 last:border-0">
|
||||
<p class="font-medium text-sm">{{ $faq->question }}</p>
|
||||
<p class="text-sm text-gray-500 mt-1">{{ Str::limit($faq->answer, 100) }}</p>
|
||||
<form method="POST" action="{{ route('admin.faqs.destroy', $faq) }}" class="mt-1" onsubmit="return confirm('Löschen?')">
|
||||
@csrf @method('DELETE')
|
||||
<button class="text-red-400 text-xs hover:text-red-600">Löschen</button>
|
||||
</form>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,69 @@
|
||||
@extends('layouts.admin')
|
||||
@section('title', 'Bestellungen')
|
||||
@section('content')
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<h2 class="text-lg font-semibold">Bestellungen</h2>
|
||||
<form method="GET" class="flex gap-2 ml-4">
|
||||
<input type="text" name="q" value="{{ request('q') }}" placeholder="Bestellnr." class="border rounded px-3 py-1.5 text-sm w-36">
|
||||
<select name="status" class="border rounded px-2 py-1.5 text-sm">
|
||||
<option value="">Alle Status</option>
|
||||
@foreach(\App\Models\Order::$statusLabels as $key => $label)
|
||||
<option value="{{ $key }}" {{ request('status') === $key ? 'selected' : '' }}>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="payment" class="border rounded px-2 py-1.5 text-sm">
|
||||
<option value="">Alle Zahlungsarten</option>
|
||||
@foreach(\App\Models\Order::$paymentLabels as $key => $label)
|
||||
<option value="{{ $key }}" {{ request('payment') === $key ? 'selected' : '' }}>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<button class="bg-gray-200 px-3 py-1.5 rounded text-sm hover:bg-gray-300">Filtern</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded shadow overflow-hidden">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50 text-left">
|
||||
<tr>
|
||||
<th class="px-4 py-3">Bestellnr.</th>
|
||||
<th class="px-4 py-3">Kunde</th>
|
||||
<th class="px-4 py-3">Zahlungsart</th>
|
||||
<th class="px-4 py-3">Status</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">
|
||||
@forelse($orders as $o)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-3 font-mono font-medium">{{ $o->order_number }}</td>
|
||||
<td class="px-4 py-3">{{ $o->billingAddress()?->full_name ?? $o->guest_email }}</td>
|
||||
<td class="px-4 py-3">{{ $o->payment_label }}</td>
|
||||
<td class="px-4 py-3">
|
||||
<span class="px-2 py-0.5 rounded text-xs
|
||||
{{ match($o->status) {
|
||||
'new' => 'bg-yellow-100 text-yellow-700',
|
||||
'payment_received' => 'bg-blue-100 text-blue-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',
|
||||
} }}">
|
||||
{{ $o->status_label }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right">{{ number_format($o->total, 2, ',', '.') }} €</td>
|
||||
<td class="px-4 py-3 text-gray-400">{{ $o->created_at->format('d.m.Y') }}</td>
|
||||
<td class="px-4 py-3 text-right">
|
||||
<a href="{{ route('admin.orders.show', $o) }}" class="text-blue-600 hover:underline">Details</a>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="7" class="px-4 py-8 text-center text-gray-400">Keine Bestellungen gefunden.</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="px-4 py-3 border-t">{{ $orders->withQueryString()->links() }}</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,113 @@
|
||||
@extends('layouts.admin')
|
||||
@section('title', 'Bestellung ' . $order->order_number)
|
||||
@section('content')
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<a href="{{ route('admin.orders.index') }}" class="text-sm text-gray-500 hover:text-blue-600">← Alle Bestellungen</a>
|
||||
<h2 class="text-xl font-bold mt-1">{{ $order->order_number }}</h2>
|
||||
<p class="text-sm text-gray-400">{{ $order->created_at->format('d.m.Y H:i') }} Uhr</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<a href="{{ route('admin.orders.pdf', $order) }}" class="bg-gray-100 px-4 py-2 rounded text-sm hover:bg-gray-200">PDF-Rechnung herunterladen</a>
|
||||
<form method="POST" action="{{ route('admin.orders.invoice', $order) }}">
|
||||
@csrf
|
||||
<button class="bg-blue-100 text-blue-700 px-4 py-2 rounded text-sm hover:bg-blue-200">Rechnung erneut mailen</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-6">
|
||||
<div class="col-span-2 space-y-6">
|
||||
{{-- Artikel --}}
|
||||
<div class="bg-white rounded shadow overflow-hidden">
|
||||
<div class="px-5 py-3 border-b font-semibold">Bestellpositionen</div>
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50"><tr><th class="px-4 py-2 text-left">Artikel</th><th class="px-4 py-2 text-center">Menge</th><th class="px-4 py-2 text-right">Einzelpreis</th><th class="px-4 py-2 text-right">MwSt.</th><th class="px-4 py-2 text-right">Gesamt</th></tr></thead>
|
||||
<tbody class="divide-y">
|
||||
@foreach($order->items as $item)
|
||||
<tr>
|
||||
<td class="px-4 py-2">{{ $item->product_name }}@if($item->variant_label) <span class="text-gray-400">({{ $item->variant_label }})</span>@endif</td>
|
||||
<td class="px-4 py-2 text-center">{{ $item->quantity }}</td>
|
||||
<td class="px-4 py-2 text-right">{{ number_format($item->unit_price, 2, ',', '.') }} €</td>
|
||||
<td class="px-4 py-2 text-right">{{ number_format($item->tax_rate, 0) }} %</td>
|
||||
<td class="px-4 py-2 text-right font-medium">{{ number_format($item->line_total, 2, ',', '.') }} €</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
<tfoot class="border-t">
|
||||
<tr><td colspan="4" class="px-4 py-2 text-right text-gray-500">Zwischensumme</td><td class="px-4 py-2 text-right">{{ number_format($order->subtotal, 2, ',', '.') }} €</td></tr>
|
||||
<tr><td colspan="4" class="px-4 py-2 text-right text-gray-500">MwSt. gesamt</td><td class="px-4 py-2 text-right">{{ number_format($order->tax_total, 2, ',', '.') }} €</td></tr>
|
||||
<tr><td colspan="4" class="px-4 py-2 text-right text-gray-500">Versand</td><td class="px-4 py-2 text-right">{{ number_format($order->shipping_cost, 2, ',', '.') }} €</td></tr>
|
||||
<tr class="font-bold"><td colspan="4" class="px-4 py-2 text-right">Gesamt</td><td class="px-4 py-2 text-right text-lg">{{ number_format($order->total, 2, ',', '.') }} €</td></tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- Adressen --}}
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
@foreach(['billing' => 'Rechnungsadresse', 'shipping' => 'Lieferadresse'] as $type => $label)
|
||||
@php $addr = $order->addresses->where('type', $type)->first(); @endphp
|
||||
@if($addr)
|
||||
<div class="bg-white rounded shadow p-5">
|
||||
<h3 class="font-semibold mb-2">{{ $label }}</h3>
|
||||
<p class="text-sm">
|
||||
{{ $addr->full_name }}<br>
|
||||
@if($addr->company){{ $addr->company }}<br>@endif
|
||||
{{ $addr->street }}<br>
|
||||
{{ $addr->zip }} {{ $addr->city }}<br>
|
||||
{{ $addr->country }}
|
||||
@if($addr->phone)<br>{{ $addr->phone }}@endif
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Status & Aktionen --}}
|
||||
<div class="space-y-4">
|
||||
<div class="bg-white rounded shadow p-5">
|
||||
<h3 class="font-semibold mb-3">Bestelldetails</h3>
|
||||
<dl class="space-y-2 text-sm">
|
||||
<div class="flex justify-between"><dt class="text-gray-500">Zahlungsart</dt><dd>{{ $order->payment_label }}</dd></div>
|
||||
<div class="flex justify-between"><dt class="text-gray-500">Status</dt>
|
||||
<dd><span class="px-2 py-0.5 rounded text-xs bg-blue-100 text-blue-700">{{ $order->status_label }}</span></dd>
|
||||
</div>
|
||||
<div class="flex justify-between"><dt class="text-gray-500">Zahlung</dt><dd>{{ $order->paid_at?->format('d.m.Y') ?? '—' }}</dd></div>
|
||||
@if($order->tracking_code)<div class="flex justify-between"><dt class="text-gray-500">Tracking</dt><dd class="font-mono">{{ $order->tracking_code }}</dd></div>@endif
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded shadow p-5">
|
||||
<h3 class="font-semibold mb-3">Status ändern</h3>
|
||||
<form method="POST" action="{{ route('admin.orders.status', $order) }}">
|
||||
@csrf @method('PUT')
|
||||
<div class="mb-3">
|
||||
<select name="status" class="w-full border rounded px-3 py-2 text-sm">
|
||||
@foreach(\App\Models\Order::$statusLabels as $key => $label)
|
||||
<option value="{{ $key }}" {{ $order->status === $key ? 'selected' : '' }}>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="block text-xs text-gray-500 mb-1">Trackingcode (optional)</label>
|
||||
<input type="text" name="tracking_code" value="{{ $order->tracking_code }}" class="w-full border rounded px-3 py-2 text-sm">
|
||||
</div>
|
||||
<button class="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700 text-sm">Status speichern</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@if($order->user)
|
||||
<div class="bg-white rounded shadow p-5">
|
||||
<h3 class="font-semibold mb-2">Kunde</h3>
|
||||
<p class="text-sm">{{ $order->user->name }}<br>{{ $order->user->email }}</p>
|
||||
</div>
|
||||
@elseif($order->guest_email)
|
||||
<div class="bg-white rounded shadow p-5">
|
||||
<h3 class="font-semibold mb-2">Gast</h3>
|
||||
<p class="text-sm">{{ $order->guest_email }}</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,252 @@
|
||||
@extends('layouts.admin')
|
||||
@section('title', isset($page) ? 'Seite bearbeiten' : 'Neue Seite')
|
||||
|
||||
@push('head')
|
||||
{{-- Quill WYSIWYG --}}
|
||||
<link href="https://cdn.quilljs.com/1.3.7/quill.snow.css" rel="stylesheet">
|
||||
<style>
|
||||
.ql-container { font-size: 14px; min-height: 320px; border-bottom-left-radius: 6px; border-bottom-right-radius: 6px; }
|
||||
.ql-toolbar { border-top-left-radius: 6px; border-top-right-radius: 6px; background: #f9fafb; }
|
||||
.ql-editor { min-height: 320px; line-height: 1.7; }
|
||||
.ql-editor h1 { font-size: 1.8em; font-weight: 700; margin-bottom: .5em; }
|
||||
.ql-editor h2 { font-size: 1.4em; font-weight: 600; margin-bottom: .4em; }
|
||||
.ql-editor h3 { font-size: 1.15em; font-weight: 600; margin-bottom: .3em; }
|
||||
.ql-editor a { color: #2563eb; text-decoration: underline; }
|
||||
.ql-editor blockquote { border-left: 4px solid #d1d5db; padding-left: 1em; color: #6b7280; font-style: italic; }
|
||||
.ql-editor pre { background: #1e293b; color: #e2e8f0; padding: 1em; border-radius: 6px; font-family: monospace; overflow-x: auto; }
|
||||
.ql-editor ul, .ql-editor ol { padding-left: 1.5em; }
|
||||
.ql-editor img { max-width: 100%; border-radius: 4px; }
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-4xl">
|
||||
<form id="page-form"
|
||||
method="POST"
|
||||
action="{{ isset($page) ? route('admin.pages.update', $page) : route('admin.pages.store') }}"
|
||||
enctype="multipart/form-data"
|
||||
class="space-y-4">
|
||||
@csrf @if(isset($page)) @method('PUT') @endif
|
||||
|
||||
{{-- Metadaten --}}
|
||||
<div class="bg-white rounded shadow p-6 space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Titel *</label>
|
||||
<input type="text" name="title" id="title-input"
|
||||
value="{{ old('title', $page->title ?? '') }}"
|
||||
class="w-full border rounded px-3 py-2 text-sm" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">
|
||||
Slug (URL)
|
||||
<span class="text-xs text-gray-400 font-normal">– automatisch aus Titel</span>
|
||||
</label>
|
||||
<div class="flex items-center border rounded overflow-hidden">
|
||||
<span class="px-2 py-2 bg-gray-50 text-gray-400 text-xs border-r select-none">/seite/</span>
|
||||
<input type="text" name="slug" id="slug-input"
|
||||
value="{{ old('slug', $page->slug ?? '') }}"
|
||||
class="flex-1 px-3 py-2 text-sm font-mono focus:outline-none"
|
||||
placeholder="wird-automatisch-generiert">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Typ</label>
|
||||
<select name="type" class="w-full border rounded px-3 py-2 text-sm">
|
||||
<option value="static" {{ old('type', $page->type ?? 'static') === 'static' ? 'selected' : '' }}>Statische Seite (Impressum, AGB, …)</option>
|
||||
<option value="blog" {{ old('type', $page->type ?? '') === 'blog' ? 'selected' : '' }}>Blog-Beitrag</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Titelbild</label>
|
||||
@if(isset($page) && $page->image)
|
||||
<img src="{{ asset('storage/'.$page->image) }}" class="h-10 mb-1 rounded">
|
||||
@endif
|
||||
<input type="file" name="image" accept="image/*" class="text-sm">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- WYSIWYG Editor --}}
|
||||
<div class="bg-white rounded shadow overflow-hidden">
|
||||
<div class="px-5 py-3 border-b flex items-center justify-between">
|
||||
<span class="text-sm font-semibold text-gray-700">Inhalt</span>
|
||||
<button type="button" id="toggle-source"
|
||||
class="text-xs px-3 py-1 border rounded hover:bg-gray-50 text-gray-500">
|
||||
HTML-Quellcode
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{-- Quill Editor --}}
|
||||
<div id="quill-editor" class="border-0">
|
||||
{!! old('content', $page->content ?? '') !!}
|
||||
</div>
|
||||
|
||||
{{-- Roher HTML-Quellcode (Toggle) --}}
|
||||
<textarea id="source-editor" name="_content_source" rows="16"
|
||||
class="hidden w-full px-4 py-3 text-sm font-mono border-0 focus:outline-none resize-none"
|
||||
style="min-height: 320px">{{ old('content', $page->content ?? '') }}</textarea>
|
||||
|
||||
{{-- Verstecktes Feld das beim Submit gefüllt wird --}}
|
||||
<input type="hidden" name="content" id="content-hidden"
|
||||
value="{{ old('content', $page->content ?? '') }}">
|
||||
</div>
|
||||
|
||||
{{-- SEO & Optionen --}}
|
||||
<div class="bg-white rounded shadow p-6 space-y-4">
|
||||
<h3 class="text-sm font-semibold text-gray-700">SEO & Einstellungen</h3>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Meta-Titel <span class="text-xs text-gray-400 font-normal">(Browser-Tab)</span></label>
|
||||
<input type="text" name="meta_title"
|
||||
value="{{ old('meta_title', $page->meta_title ?? '') }}"
|
||||
class="w-full border rounded px-3 py-2 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Meta-Beschreibung <span class="text-xs text-gray-400 font-normal">(Suchergebnisse)</span></label>
|
||||
<input type="text" name="meta_description"
|
||||
value="{{ old('meta_description', $page->meta_description ?? '') }}"
|
||||
class="w-full border rounded px-3 py-2 text-sm"
|
||||
maxlength="160">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Veröffentlichungsdatum</label>
|
||||
<input type="date" name="published_at"
|
||||
value="{{ old('published_at', isset($page->published_at) ? $page->published_at->format('Y-m-d') : '') }}"
|
||||
class="w-full border rounded px-3 py-2 text-sm">
|
||||
</div>
|
||||
<div class="flex items-end pb-2">
|
||||
<label class="flex items-center gap-2 text-sm cursor-pointer">
|
||||
<input type="checkbox" name="published" value="1"
|
||||
{{ old('published', $page->published ?? true) ? 'checked' : '' }}
|
||||
class="w-4 h-4 accent-blue-600">
|
||||
<span>Veröffentlicht</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-100">
|
||||
<h3 class="text-sm font-semibold text-gray-700">Navigation</h3>
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<label class="flex items-start gap-2 text-sm cursor-pointer p-3 border rounded-lg hover:bg-gray-50">
|
||||
<input type="checkbox" name="show_in_footer" value="1"
|
||||
{{ old('show_in_footer', $page->show_in_footer ?? true) ? 'checked' : '' }}
|
||||
class="mt-0.5 accent-blue-600">
|
||||
<div>
|
||||
<span class="font-medium">Footer</span>
|
||||
<p class="text-xs text-gray-400 mt-0.5">Link in der Fußzeile des Shops</p>
|
||||
</div>
|
||||
</label>
|
||||
<label class="flex items-start gap-2 text-sm cursor-pointer p-3 border rounded-lg hover:bg-gray-50">
|
||||
<input type="checkbox" name="show_in_nav" value="1"
|
||||
{{ old('show_in_nav', $page->show_in_nav ?? false) ? 'checked' : '' }}
|
||||
class="mt-0.5 accent-blue-600">
|
||||
<div>
|
||||
<span class="font-medium">Hauptmenü</span>
|
||||
<p class="text-xs text-gray-400 mt-0.5">Link in der Top-Navigation</p>
|
||||
</div>
|
||||
</label>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Reihenfolge im Menü</label>
|
||||
<input type="number" name="nav_sort" value="{{ old('nav_sort', $page->nav_sort ?? 0) }}"
|
||||
class="w-full border rounded px-3 py-2 text-sm" min="0">
|
||||
<p class="text-xs text-gray-400 mt-1">Niedrigere Zahl = weiter links</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($errors->any())
|
||||
<div class="bg-red-50 border border-red-200 rounded px-4 py-3 text-sm text-red-700">
|
||||
@foreach($errors->all() as $e)<p>• {{ $e }}</p>@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="flex gap-3 pb-8">
|
||||
<button type="submit" class="bg-blue-600 text-white px-6 py-2.5 rounded-lg hover:bg-blue-700 text-sm font-medium">
|
||||
Speichern
|
||||
</button>
|
||||
<a href="{{ route('admin.pages.index') }}"
|
||||
class="px-6 py-2.5 rounded-lg border text-sm hover:bg-gray-50">
|
||||
Abbrechen
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.quilljs.com/1.3.7/quill.js"></script>
|
||||
<script>
|
||||
// ── Quill initialisieren ────────────────────────────────────────────────────
|
||||
const quill = new Quill('#quill-editor', {
|
||||
theme: 'snow',
|
||||
modules: {
|
||||
toolbar: [
|
||||
[{ header: [1, 2, 3, false] }],
|
||||
['bold', 'italic', 'underline', 'strike'],
|
||||
[{ color: [] }, { background: [] }],
|
||||
[{ align: [] }],
|
||||
[{ list: 'ordered' }, { list: 'bullet' }],
|
||||
[{ indent: '-1' }, { indent: '+1' }],
|
||||
['blockquote', 'code-block'],
|
||||
['link', 'image'],
|
||||
['clean']
|
||||
]
|
||||
},
|
||||
placeholder: 'Seiteninhalt hier eingeben …',
|
||||
});
|
||||
|
||||
// ── Slug automatisch aus Titel generieren ───────────────────────────────────
|
||||
const titleInput = document.getElementById('title-input');
|
||||
const slugInput = document.getElementById('slug-input');
|
||||
let slugManuallyEdited = slugInput.value.length > 0;
|
||||
|
||||
titleInput.addEventListener('input', () => {
|
||||
if (slugManuallyEdited) return;
|
||||
slugInput.value = titleInput.value
|
||||
.toLowerCase()
|
||||
.replace(/ä/g,'ae').replace(/ö/g,'oe').replace(/ü/g,'ue').replace(/ß/g,'ss')
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
});
|
||||
slugInput.addEventListener('input', () => { slugManuallyEdited = true; });
|
||||
|
||||
// ── HTML-Quellcode-Toggle ───────────────────────────────────────────────────
|
||||
const toggleBtn = document.getElementById('toggle-source');
|
||||
const sourceEditor = document.getElementById('source-editor');
|
||||
const quillWrapper = document.getElementById('quill-editor');
|
||||
let sourceMode = false;
|
||||
|
||||
toggleBtn.addEventListener('click', () => {
|
||||
sourceMode = !sourceMode;
|
||||
if (sourceMode) {
|
||||
// Quill → HTML
|
||||
sourceEditor.value = quill.root.innerHTML === '<p><br></p>' ? '' : quill.root.innerHTML;
|
||||
quillWrapper.style.display = 'none';
|
||||
sourceEditor.classList.remove('hidden');
|
||||
toggleBtn.textContent = 'WYSIWYG';
|
||||
toggleBtn.classList.add('bg-yellow-50', 'text-yellow-700', 'border-yellow-300');
|
||||
} else {
|
||||
// HTML → Quill
|
||||
quill.root.innerHTML = sourceEditor.value;
|
||||
sourceEditor.classList.add('hidden');
|
||||
quillWrapper.style.display = '';
|
||||
toggleBtn.textContent = 'HTML-Quellcode';
|
||||
toggleBtn.classList.remove('bg-yellow-50', 'text-yellow-700', 'border-yellow-300');
|
||||
}
|
||||
});
|
||||
|
||||
// ── Beim Absenden: Quill-HTML in hidden field schreiben ─────────────────────
|
||||
document.getElementById('page-form').addEventListener('submit', () => {
|
||||
const html = sourceMode
|
||||
? sourceEditor.value
|
||||
: (quill.root.innerHTML === '<p><br></p>' ? '' : quill.root.innerHTML);
|
||||
document.getElementById('content-hidden').value = html;
|
||||
});
|
||||
</script>
|
||||
|
||||
@push('styles')
|
||||
<style>
|
||||
/* Quill-Bilder klickbar im Editor */
|
||||
.ql-editor img { cursor: pointer; }
|
||||
</style>
|
||||
@endpush
|
||||
@endsection
|
||||
@@ -0,0 +1,29 @@
|
||||
@extends('layouts.admin')
|
||||
@section('title', 'Seiten')
|
||||
@section('content')
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-lg font-semibold">Seiten</h2>
|
||||
<a href="{{ route('admin.pages.create') }}" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 text-sm">+ Neue Seite</a>
|
||||
</div>
|
||||
<div class="bg-white rounded shadow overflow-hidden">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50 text-left"><tr><th class="px-4 py-3">Titel</th><th class="px-4 py-3">Slug</th><th class="px-4 py-3">Typ</th><th class="px-4 py-3">Veröffentlicht</th><th class="px-4 py-3"></th></tr></thead>
|
||||
<tbody class="divide-y">
|
||||
@forelse($pages as $page)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-3 font-medium">{{ $page->title }}</td>
|
||||
<td class="px-4 py-3 font-mono text-gray-500">/seite/{{ $page->slug }}</td>
|
||||
<td class="px-4 py-3"><span class="px-2 py-0.5 rounded text-xs bg-gray-100">{{ $page->type }}</span></td>
|
||||
<td class="px-4 py-3"><span class="px-2 py-0.5 rounded text-xs {{ $page->published ? 'bg-green-100 text-green-700' : 'bg-gray-100' }}">{{ $page->published ? 'Ja' : 'Nein' }}</span></td>
|
||||
<td class="px-4 py-3 text-right space-x-2">
|
||||
<a href="{{ route('admin.pages.edit', $page) }}" class="text-blue-600 hover:underline">Bearbeiten</a>
|
||||
<form method="POST" action="{{ route('admin.pages.destroy', $page) }}" class="inline" onsubmit="return confirm('Löschen?')">@csrf @method('DELETE')<button class="text-red-600 hover:underline">Löschen</button></form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="5" class="px-4 py-8 text-center text-gray-400">Keine Seiten.</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,190 @@
|
||||
@extends('layouts.admin')
|
||||
@section('title', isset($product) ? 'Artikel bearbeiten' : 'Neuer Artikel')
|
||||
@section('content')
|
||||
<form method="POST" action="{{ isset($product) ? route('admin.products.update', $product) : route('admin.products.store') }}" enctype="multipart/form-data" x-data="productForm()">
|
||||
@csrf
|
||||
@if(isset($product)) @method('PUT') @endif
|
||||
|
||||
<div class="grid grid-cols-3 gap-6">
|
||||
{{-- Linke Spalte: Basisdaten --}}
|
||||
<div class="col-span-2 space-y-4">
|
||||
<div class="bg-white rounded shadow p-6 space-y-4">
|
||||
<h3 class="font-semibold text-gray-700">Basisdaten</h3>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Name *</label>
|
||||
<input type="text" name="name" value="{{ old('name', $product->name ?? '') }}" class="w-full border rounded px-3 py-2 text-sm" required>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Beschreibung</label>
|
||||
<textarea name="description" rows="6" class="w-full border rounded px-3 py-2 text-sm">{{ old('description', $product->description ?? '') }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Bruttopreis (€) *</label>
|
||||
<input type="number" name="base_price" step="0.01" min="0" value="{{ old('base_price', $product->base_price ?? '') }}" class="w-full border rounded px-3 py-2 text-sm" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">MwSt.-Satz *</label>
|
||||
<select name="tax_rate" class="w-full border rounded px-3 py-2 text-sm">
|
||||
<option value="19" {{ old('tax_rate', $product->tax_rate ?? 19) == 19 ? 'selected' : '' }}>19 %</option>
|
||||
<option value="7" {{ old('tax_rate', $product->tax_rate ?? 19) == 7 ? 'selected' : '' }}>7 %</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Gewicht (kg)</label>
|
||||
<input type="number" name="weight" step="0.001" min="0" value="{{ old('weight', $product->weight ?? '') }}" class="w-full border rounded px-3 py-2 text-sm">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Varianten --}}
|
||||
<div class="bg-white rounded shadow p-6">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="font-semibold text-gray-700">Varianten</h3>
|
||||
<button type="button" @click="addVariant()" class="text-sm bg-gray-100 px-3 py-1.5 rounded hover:bg-gray-200">+ Variante hinzufügen</button>
|
||||
</div>
|
||||
|
||||
<template x-if="variants.length === 0">
|
||||
<p class="text-sm text-gray-400">Keine Varianten — der Artikel wird mit dem Basispreis verkauft.</p>
|
||||
</template>
|
||||
|
||||
<div class="space-y-4">
|
||||
<template x-for="(v, i) in variants" :key="i">
|
||||
<div class="border rounded p-4 space-y-3">
|
||||
<input type="hidden" :name="'variants[' + i + '][id]'" :value="v.id">
|
||||
<div class="grid grid-cols-4 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium mb-1">SKU *</label>
|
||||
<input type="text" :name="'variants[' + i + '][sku]'" x-model="v.sku" class="w-full border rounded px-2 py-1.5 text-sm" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium mb-1">Preis (€, leer = Basispreis)</label>
|
||||
<input type="number" step="0.01" :name="'variants[' + i + '][price_override]'" x-model="v.price_override" class="w-full border rounded px-2 py-1.5 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium mb-1">Bestand</label>
|
||||
<input type="number" :name="'variants[' + i + '][stock]'" x-model="v.stock" class="w-full border rounded px-2 py-1.5 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium mb-1">Warnung ab</label>
|
||||
<input type="number" :name="'variants[' + i + '][stock_warning]'" x-model="v.stock_warning" class="w-full border rounded px-2 py-1.5 text-sm">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Attributwerte --}}
|
||||
<div>
|
||||
<label class="block text-xs font-medium mb-2">Attributwerte</label>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
@foreach($attributes as $attr)
|
||||
<div>
|
||||
<div class="text-xs text-gray-500 mb-1">{{ $attr->name }}</div>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
@foreach($attr->values as $val)
|
||||
<label class="flex items-center gap-1 text-xs cursor-pointer">
|
||||
<input type="checkbox"
|
||||
:name="'variants[' + i + '][attribute_value_ids][]'"
|
||||
value="{{ $val->id }}"
|
||||
:checked="v.attribute_value_ids.includes({{ $val->id }})"
|
||||
@change="toggleAttrValue(v, {{ $val->id }})">
|
||||
{{ $val->value }}
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="flex items-center gap-2 text-sm">
|
||||
<input type="checkbox" :name="'variants[' + i + '][active]'" :checked="v.active" @change="v.active = !v.active">
|
||||
Aktiv
|
||||
</label>
|
||||
<button type="button" @click="removeVariant(i)" class="text-red-600 text-sm hover:underline">Entfernen</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Bilder --}}
|
||||
<div class="bg-white rounded shadow p-6 space-y-3">
|
||||
<h3 class="font-semibold text-gray-700">Bilder</h3>
|
||||
@if(isset($product) && $product->images->count())
|
||||
<div class="flex flex-wrap gap-3">
|
||||
@foreach($product->images as $img)
|
||||
<div class="relative">
|
||||
<img src="{{ asset('storage/'.$img->path) }}" class="h-20 w-20 object-cover rounded border">
|
||||
<label class="absolute -top-1 -right-1 bg-red-600 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center cursor-pointer" title="Löschen">
|
||||
<input type="checkbox" name="delete_images[]" value="{{ $img->id }}" class="sr-only">✕
|
||||
</label>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
<input type="file" name="images[]" multiple accept="image/*" class="text-sm">
|
||||
<p class="text-xs text-gray-400">Mehrere Dateien auswählbar. Das erste Bild wird als Hauptbild verwendet.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Rechte Spalte: Meta --}}
|
||||
<div class="space-y-4">
|
||||
<div class="bg-white rounded shadow p-6 space-y-4">
|
||||
<h3 class="font-semibold text-gray-700">Organisation</h3>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Kategorie</label>
|
||||
<select name="category_id" class="w-full border rounded px-3 py-2 text-sm">
|
||||
<option value="">— Keine —</option>
|
||||
@foreach($categories as $cat)
|
||||
<option value="{{ $cat->id }}" {{ old('category_id', $product->category_id ?? '') == $cat->id ? 'selected' : '' }}>{{ $cat->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Reihenfolge</label>
|
||||
<input type="number" name="sort_order" value="{{ old('sort_order', $product->sort_order ?? 0) }}" class="w-full border rounded px-3 py-2 text-sm">
|
||||
</div>
|
||||
<label class="flex items-center gap-2 text-sm">
|
||||
<input type="checkbox" name="active" value="1" {{ old('active', $product->active ?? true) ? 'checked' : '' }}>
|
||||
Aktiv (im Shop sichtbar)
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-sm">
|
||||
<input type="checkbox" name="featured" value="1" {{ old('featured', $product->featured ?? false) ? 'checked' : '' }} class="accent-yellow-500">
|
||||
<span>⭐ Top Produkt <span class="text-gray-400 font-normal">(Startseite)</span></span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-sm">
|
||||
<input type="checkbox" name="track_stock" value="1" {{ old('track_stock', $product->track_stock ?? true) ? 'checked' : '' }} class="accent-blue-600">
|
||||
<span>Lagerbestand verwalten <span class="text-gray-400 font-normal">(deaktiviert = immer lieferbar)</span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button type="submit" class="flex-1 bg-blue-600 text-white py-2 rounded hover:bg-blue-700 text-sm">Speichern</button>
|
||||
<a href="{{ route('admin.products.index') }}" class="flex-1 text-center py-2 rounded border text-sm hover:bg-gray-50">Abbrechen</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function productForm() {
|
||||
return {
|
||||
variants: {!! $variantsJson !!},
|
||||
addVariant() {
|
||||
this.variants.push({ id: null, sku: '', price_override: '', stock: 0, stock_warning: 5, active: true, attribute_value_ids: [] });
|
||||
},
|
||||
removeVariant(i) {
|
||||
this.variants.splice(i, 1);
|
||||
},
|
||||
toggleAttrValue(v, id) {
|
||||
const idx = v.attribute_value_ids.indexOf(id);
|
||||
if (idx === -1) v.attribute_value_ids.push(id);
|
||||
else v.attribute_value_ids.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
@@ -0,0 +1,57 @@
|
||||
@extends('layouts.admin')
|
||||
@section('title', 'Artikel')
|
||||
@section('content')
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<h2 class="text-lg font-semibold">Artikel</h2>
|
||||
<form method="GET" class="flex gap-2">
|
||||
<input type="text" name="q" value="{{ request('q') }}" placeholder="Suchen…" class="border rounded px-3 py-1.5 text-sm">
|
||||
<button class="bg-gray-200 px-3 py-1.5 rounded text-sm hover:bg-gray-300">Suchen</button>
|
||||
</form>
|
||||
</div>
|
||||
<a href="{{ route('admin.products.create') }}" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 text-sm">+ Neuer Artikel</a>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded shadow overflow-hidden">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50 text-left">
|
||||
<tr>
|
||||
<th class="px-4 py-3">Name</th>
|
||||
<th class="px-4 py-3">Kategorie</th>
|
||||
<th class="px-4 py-3">Preis (Brutto)</th>
|
||||
<th class="px-4 py-3">MwSt.</th>
|
||||
<th class="px-4 py-3">Aktiv</th>
|
||||
<th class="px-4 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y">
|
||||
@forelse($products as $p)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-3 font-medium">
|
||||
{{ $p->name }}
|
||||
@if($p->featured)<span class="ml-1 text-yellow-500 text-xs" title="Top Produkt">⭐</span>@endif
|
||||
</td>
|
||||
<td class="px-4 py-3 text-gray-500">{{ $p->category?->name ?? '—' }}</td>
|
||||
<td class="px-4 py-3">{{ number_format($p->base_price, 2, ',', '.') }} €</td>
|
||||
<td class="px-4 py-3">{{ number_format($p->tax_rate, 0) }} %</td>
|
||||
<td class="px-4 py-3">
|
||||
<span class="px-2 py-0.5 rounded text-xs {{ $p->active ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-500' }}">
|
||||
{{ $p->active ? 'Ja' : 'Nein' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right space-x-2">
|
||||
<a href="{{ route('admin.products.edit', $p) }}" class="text-blue-600 hover:underline">Bearbeiten</a>
|
||||
<form method="POST" action="{{ route('admin.products.destroy', $p) }}" class="inline" onsubmit="return confirm('Artikel löschen?')">
|
||||
@csrf @method('DELETE')
|
||||
<button class="text-red-600 hover:underline">Löschen</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="6" class="px-4 py-8 text-center text-gray-400">Keine Artikel gefunden.</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="px-4 py-3 border-t">{{ $products->withQueryString()->links() }}</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,114 @@
|
||||
@extends('layouts.admin')
|
||||
@section('title', 'Einstellungen')
|
||||
@section('content')
|
||||
<form method="POST" action="{{ route('admin.settings.update') }}" class="max-w-3xl" enctype="multipart/form-data">
|
||||
@csrf @method('PUT')
|
||||
|
||||
<div class="space-y-6">
|
||||
{{-- Shop-Daten --}}
|
||||
<div class="bg-white rounded shadow p-6">
|
||||
<h3 class="font-semibold mb-4">Shop-Informationen</h3>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
|
||||
{{-- Logo --}}
|
||||
<div class="col-span-2">
|
||||
<label class="block text-sm font-medium mb-2">Shop-Logo</label>
|
||||
@if($settings['shop_logo'])
|
||||
<div class="flex items-center gap-4 mb-3">
|
||||
<img src="{{ asset('storage/'.$settings['shop_logo']) }}"
|
||||
alt="Logo" class="h-14 object-contain border rounded p-1 bg-gray-50">
|
||||
<label class="flex items-center gap-2 text-sm text-red-600 cursor-pointer">
|
||||
<input type="checkbox" name="delete_logo" value="1">
|
||||
Logo löschen
|
||||
</label>
|
||||
</div>
|
||||
@endif
|
||||
<input type="file" name="shop_logo" accept="image/*" class="text-sm">
|
||||
<p class="text-xs text-gray-400 mt-1">PNG oder SVG empfohlen, max. 2 MB. Wird im Header und Footer angezeigt.</p>
|
||||
</div>
|
||||
|
||||
@foreach(['shop_name' => 'Shop-Name', 'shop_email' => 'Shop-E-Mail', 'shop_phone' => 'Telefon', 'shop_owner' => 'Inhaber / Firma', 'shop_vat_id' => 'USt-IdNr.'] as $key => $label)
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">{{ $label }}</label>
|
||||
<input type="text" name="{{ $key }}" value="{{ $settings[$key] }}" class="w-full border rounded px-3 py-2 text-sm">
|
||||
</div>
|
||||
@endforeach
|
||||
<div class="col-span-2">
|
||||
<label class="block text-sm font-medium mb-1">Adresse (für Impressum & Rechnung)</label>
|
||||
<textarea name="shop_address" rows="3" class="w-full border rounded px-3 py-2 text-sm">{{ $settings['shop_address'] }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Bankdaten --}}
|
||||
<div class="bg-white rounded shadow p-6">
|
||||
<h3 class="font-semibold mb-4">Bankverbindung (Vorkasse)</h3>
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
@foreach(['bank_name' => 'Bank', 'bank_iban' => 'IBAN', 'bank_bic' => 'BIC'] as $key => $label)
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">{{ $label }}</label>
|
||||
<input type="text" name="{{ $key }}" value="{{ $settings[$key] }}" class="w-full border rounded px-3 py-2 text-sm font-mono">
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
<div class="mt-4 max-w-xs">
|
||||
<label class="block text-sm font-medium mb-1">Zahlungsziel Rechnung (Tage)</label>
|
||||
<input type="number" name="invoice_payment_days" value="{{ $settings['invoice_payment_days'] }}" class="w-full border rounded px-3 py-2 text-sm">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- SMTP --}}
|
||||
<div class="bg-white rounded shadow p-6">
|
||||
<h3 class="font-semibold mb-4">E-Mail / SMTP</h3>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">SMTP Host</label>
|
||||
<input type="text" name="smtp_host" value="{{ $settings['smtp_host'] }}" class="w-full border rounded px-3 py-2 text-sm font-mono">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">SMTP Port</label>
|
||||
<input type="number" name="smtp_port" value="{{ $settings['smtp_port'] }}" class="w-full border rounded px-3 py-2 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">SMTP Benutzername</label>
|
||||
<input type="text" name="smtp_user" value="{{ $settings['smtp_user'] }}" class="w-full border rounded px-3 py-2 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">SMTP Passwort</label>
|
||||
<input type="password" name="smtp_password" value="{{ $settings['smtp_password'] }}" class="w-full border rounded px-3 py-2 text-sm" autocomplete="new-password">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Absendername</label>
|
||||
<input type="text" name="smtp_from_name" value="{{ $settings['smtp_from_name'] }}" class="w-full border rounded px-3 py-2 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Absender-E-Mail</label>
|
||||
<input type="email" name="smtp_from_email" value="{{ $settings['smtp_from_email'] }}" class="w-full border rounded px-3 py-2 text-sm">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Sonstiges --}}
|
||||
<div class="bg-white rounded shadow p-6">
|
||||
<h3 class="font-semibold mb-4">Sonstiges</h3>
|
||||
<label class="flex items-center gap-2 text-sm mb-3">
|
||||
<input type="checkbox" name="cookie_consent" value="1" {{ $settings['cookie_consent'] ? 'checked' : '' }}>
|
||||
Cookie-Consent-Banner anzeigen
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-sm mb-3">
|
||||
<input type="checkbox" name="require_customer_approval" value="1" {{ ($settings['require_customer_approval'] ?? false) ? 'checked' : '' }}>
|
||||
<span>Neue Kunden müssen manuell freigeschaltet werden <span class="text-gray-400">(nach Registrierung gesperrt bis Admin-Freigabe)</span></span>
|
||||
</label>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Google Analytics Tracking-ID (leer = deaktiviert)</label>
|
||||
<input type="text" name="google_analytics" value="{{ $settings['google_analytics'] }}" class="w-full border rounded px-3 py-2 text-sm font-mono max-w-xs" placeholder="G-XXXXXXXXXX">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<button type="submit" class="bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700">Einstellungen speichern</button>
|
||||
<a href="{{ route('admin.shipping.index') }}" class="border px-6 py-2 rounded hover:bg-gray-50 text-sm">Versandkosten verwalten →</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@endsection
|
||||
@@ -0,0 +1,42 @@
|
||||
@extends('layouts.admin')
|
||||
@section('title', 'Versandkosten')
|
||||
@section('content')
|
||||
<div class="max-w-2xl">
|
||||
<div class="bg-white rounded shadow p-6 mb-6">
|
||||
<h3 class="font-semibold mb-4">Neue Versandzone</h3>
|
||||
<form method="POST" action="{{ route('admin.shipping.store') }}" class="grid grid-cols-2 gap-3">
|
||||
@csrf
|
||||
<div><label class="block text-xs mb-1">Name</label><input type="text" name="name" class="w-full border rounded px-3 py-2 text-sm" required></div>
|
||||
<div><label class="block text-xs mb-1">Zone (z.B. DE, AT)</label><input type="text" name="zone" value="DE" class="w-full border rounded px-3 py-2 text-sm" required></div>
|
||||
<div><label class="block text-xs mb-1">Preis (€)</label><input type="number" name="price" step="0.01" class="w-full border rounded px-3 py-2 text-sm" required></div>
|
||||
<div><label class="block text-xs mb-1">Kostenlos ab (€, leer = nie)</label><input type="number" name="free_from" step="0.01" class="w-full border rounded px-3 py-2 text-sm"></div>
|
||||
<div class="col-span-2 flex items-center gap-3">
|
||||
<label class="flex items-center gap-2 text-sm"><input type="checkbox" name="active" value="1" checked> Aktiv</label>
|
||||
<button class="bg-blue-600 text-white px-4 py-2 rounded text-sm hover:bg-blue-700">Erstellen</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@foreach($rates as $rate)
|
||||
<div class="bg-white rounded shadow p-5 mb-4">
|
||||
<form method="POST" action="{{ route('admin.shipping.update', $rate) }}" class="grid grid-cols-2 gap-3">
|
||||
@csrf @method('PUT')
|
||||
<div><label class="block text-xs mb-1">Name</label><input type="text" name="name" value="{{ $rate->name }}" class="w-full border rounded px-3 py-2 text-sm"></div>
|
||||
<div><label class="block text-xs mb-1">Zone</label><input type="text" name="zone" value="{{ $rate->zone }}" class="w-full border rounded px-3 py-2 text-sm"></div>
|
||||
<div><label class="block text-xs mb-1">Preis (€)</label><input type="number" name="price" step="0.01" value="{{ $rate->price }}" class="w-full border rounded px-3 py-2 text-sm"></div>
|
||||
<div><label class="block text-xs mb-1">Kostenlos ab (€)</label><input type="number" name="free_from" step="0.01" value="{{ $rate->free_from }}" class="w-full border rounded px-3 py-2 text-sm"></div>
|
||||
<div class="col-span-2 flex items-center justify-between">
|
||||
<label class="flex items-center gap-2 text-sm"><input type="checkbox" name="active" value="1" {{ $rate->active ? 'checked' : '' }}> Aktiv</label>
|
||||
<div class="flex gap-2">
|
||||
<button class="bg-green-600 text-white px-4 py-2 rounded text-sm hover:bg-green-700">Speichern</button>
|
||||
<form method="POST" action="{{ route('admin.shipping.destroy', $rate) }}" onsubmit="return confirm('Löschen?')">
|
||||
@csrf @method('DELETE')
|
||||
<button class="bg-red-100 text-red-600 px-4 py-2 rounded text-sm hover:bg-red-200">Löschen</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,27 @@
|
||||
<x-guest-layout>
|
||||
<div class="mb-4 text-sm text-gray-600">
|
||||
{{ __('This is a secure area of the application. Please confirm your password before continuing.') }}
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ route('password.confirm') }}">
|
||||
@csrf
|
||||
|
||||
<!-- Password -->
|
||||
<div>
|
||||
<x-input-label for="password" :value="__('Password')" />
|
||||
|
||||
<x-text-input id="password" class="block mt-1 w-full"
|
||||
type="password"
|
||||
name="password"
|
||||
required autocomplete="current-password" />
|
||||
|
||||
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end mt-4">
|
||||
<x-primary-button>
|
||||
{{ __('Confirm') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-guest-layout>
|
||||
@@ -0,0 +1,25 @@
|
||||
<x-guest-layout>
|
||||
<div class="mb-4 text-sm text-gray-600">
|
||||
{{ __('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.') }}
|
||||
</div>
|
||||
|
||||
<!-- Session Status -->
|
||||
<x-auth-session-status class="mb-4" :status="session('status')" />
|
||||
|
||||
<form method="POST" action="{{ route('password.email') }}">
|
||||
@csrf
|
||||
|
||||
<!-- Email Address -->
|
||||
<div>
|
||||
<x-input-label for="email" :value="__('Email')" />
|
||||
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autofocus />
|
||||
<x-input-error :messages="$errors->get('email')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
<x-primary-button>
|
||||
{{ __('Email Password Reset Link') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-guest-layout>
|
||||
@@ -0,0 +1,47 @@
|
||||
<x-guest-layout>
|
||||
<!-- Session Status -->
|
||||
<x-auth-session-status class="mb-4" :status="session('status')" />
|
||||
|
||||
<form method="POST" action="{{ route('login') }}">
|
||||
@csrf
|
||||
|
||||
<!-- Email Address -->
|
||||
<div>
|
||||
<x-input-label for="email" :value="__('Email')" />
|
||||
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autofocus autocomplete="username" />
|
||||
<x-input-error :messages="$errors->get('email')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="password" :value="__('Password')" />
|
||||
|
||||
<x-text-input id="password" class="block mt-1 w-full"
|
||||
type="password"
|
||||
name="password"
|
||||
required autocomplete="current-password" />
|
||||
|
||||
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Remember Me -->
|
||||
<div class="block mt-4">
|
||||
<label for="remember_me" class="inline-flex items-center">
|
||||
<input id="remember_me" type="checkbox" class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500" name="remember">
|
||||
<span class="ms-2 text-sm text-gray-600">{{ __('Remember me') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
@if (Route::has('password.request'))
|
||||
<a class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" href="{{ route('password.request') }}">
|
||||
{{ __('Forgot your password?') }}
|
||||
</a>
|
||||
@endif
|
||||
|
||||
<x-primary-button class="ms-3">
|
||||
{{ __('Log in') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-guest-layout>
|
||||
@@ -0,0 +1,52 @@
|
||||
<x-guest-layout>
|
||||
<form method="POST" action="{{ route('register') }}">
|
||||
@csrf
|
||||
|
||||
<!-- Name -->
|
||||
<div>
|
||||
<x-input-label for="name" :value="__('Name')" />
|
||||
<x-text-input id="name" class="block mt-1 w-full" type="text" name="name" :value="old('name')" required autofocus autocomplete="name" />
|
||||
<x-input-error :messages="$errors->get('name')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Email Address -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="email" :value="__('Email')" />
|
||||
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autocomplete="username" />
|
||||
<x-input-error :messages="$errors->get('email')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="password" :value="__('Password')" />
|
||||
|
||||
<x-text-input id="password" class="block mt-1 w-full"
|
||||
type="password"
|
||||
name="password"
|
||||
required autocomplete="new-password" />
|
||||
|
||||
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Confirm Password -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="password_confirmation" :value="__('Confirm Password')" />
|
||||
|
||||
<x-text-input id="password_confirmation" class="block mt-1 w-full"
|
||||
type="password"
|
||||
name="password_confirmation" required autocomplete="new-password" />
|
||||
|
||||
<x-input-error :messages="$errors->get('password_confirmation')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
<a class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" href="{{ route('login') }}">
|
||||
{{ __('Already registered?') }}
|
||||
</a>
|
||||
|
||||
<x-primary-button class="ms-4">
|
||||
{{ __('Register') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-guest-layout>
|
||||
@@ -0,0 +1,39 @@
|
||||
<x-guest-layout>
|
||||
<form method="POST" action="{{ route('password.store') }}">
|
||||
@csrf
|
||||
|
||||
<!-- Password Reset Token -->
|
||||
<input type="hidden" name="token" value="{{ $request->route('token') }}">
|
||||
|
||||
<!-- Email Address -->
|
||||
<div>
|
||||
<x-input-label for="email" :value="__('Email')" />
|
||||
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email', $request->email)" required autofocus autocomplete="username" />
|
||||
<x-input-error :messages="$errors->get('email')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="password" :value="__('Password')" />
|
||||
<x-text-input id="password" class="block mt-1 w-full" type="password" name="password" required autocomplete="new-password" />
|
||||
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Confirm Password -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="password_confirmation" :value="__('Confirm Password')" />
|
||||
|
||||
<x-text-input id="password_confirmation" class="block mt-1 w-full"
|
||||
type="password"
|
||||
name="password_confirmation" required autocomplete="new-password" />
|
||||
|
||||
<x-input-error :messages="$errors->get('password_confirmation')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
<x-primary-button>
|
||||
{{ __('Reset Password') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-guest-layout>
|
||||
@@ -0,0 +1,31 @@
|
||||
<x-guest-layout>
|
||||
<div class="mb-4 text-sm text-gray-600">
|
||||
{{ __('Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn\'t receive the email, we will gladly send you another.') }}
|
||||
</div>
|
||||
|
||||
@if (session('status') == 'verification-link-sent')
|
||||
<div class="mb-4 font-medium text-sm text-green-600">
|
||||
{{ __('A new verification link has been sent to the email address you provided during registration.') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<form method="POST" action="{{ route('verification.send') }}">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<x-primary-button>
|
||||
{{ __('Resend Verification Email') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
|
||||
<button type="submit" class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
{{ __('Log Out') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</x-guest-layout>
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 316 316" xmlns="http://www.w3.org/2000/svg" {{ $attributes }}>
|
||||
<path d="M305.8 81.125C305.77 80.995 305.69 80.885 305.65 80.755C305.56 80.525 305.49 80.285 305.37 80.075C305.29 79.935 305.17 79.815 305.07 79.685C304.94 79.515 304.83 79.325 304.68 79.175C304.55 79.045 304.39 78.955 304.25 78.845C304.09 78.715 303.95 78.575 303.77 78.475L251.32 48.275C249.97 47.495 248.31 47.495 246.96 48.275L194.51 78.475C194.33 78.575 194.19 78.725 194.03 78.845C193.89 78.955 193.73 79.045 193.6 79.175C193.45 79.325 193.34 79.515 193.21 79.685C193.11 79.815 192.99 79.935 192.91 80.075C192.79 80.285 192.71 80.525 192.63 80.755C192.58 80.875 192.51 80.995 192.48 81.125C192.38 81.495 192.33 81.875 192.33 82.265V139.625L148.62 164.795V52.575C148.62 52.185 148.57 51.805 148.47 51.435C148.44 51.305 148.36 51.195 148.32 51.065C148.23 50.835 148.16 50.595 148.04 50.385C147.96 50.245 147.84 50.125 147.74 49.995C147.61 49.825 147.5 49.635 147.35 49.485C147.22 49.355 147.06 49.265 146.92 49.155C146.76 49.025 146.62 48.885 146.44 48.785L93.99 18.585C92.64 17.805 90.98 17.805 89.63 18.585L37.18 48.785C37 48.885 36.86 49.035 36.7 49.155C36.56 49.265 36.4 49.355 36.27 49.485C36.12 49.635 36.01 49.825 35.88 49.995C35.78 50.125 35.66 50.245 35.58 50.385C35.46 50.595 35.38 50.835 35.3 51.065C35.25 51.185 35.18 51.305 35.15 51.435C35.05 51.805 35 52.185 35 52.575V232.235C35 233.795 35.84 235.245 37.19 236.025L142.1 296.425C142.33 296.555 142.58 296.635 142.82 296.725C142.93 296.765 143.04 296.835 143.16 296.865C143.53 296.965 143.9 297.015 144.28 297.015C144.66 297.015 145.03 296.965 145.4 296.865C145.5 296.835 145.59 296.775 145.69 296.745C145.95 296.655 146.21 296.565 146.45 296.435L251.36 236.035C252.72 235.255 253.55 233.815 253.55 232.245V174.885L303.81 145.945C305.17 145.165 306 143.725 306 142.155V82.265C305.95 81.875 305.89 81.495 305.8 81.125ZM144.2 227.205L100.57 202.515L146.39 176.135L196.66 147.195L240.33 172.335L208.29 190.625L144.2 227.205ZM244.75 114.995V164.795L226.39 154.225L201.03 139.625V89.825L219.39 100.395L244.75 114.995ZM249.12 57.105L292.81 82.265L249.12 107.425L205.43 82.265L249.12 57.105ZM114.49 184.425L96.13 194.995V85.305L121.49 70.705L139.85 60.135V169.815L114.49 184.425ZM91.76 27.425L135.45 52.585L91.76 77.745L48.07 52.585L91.76 27.425ZM43.67 60.135L62.03 70.705L87.39 85.305V202.545V202.555V202.565C87.39 202.735 87.44 202.895 87.46 203.055C87.49 203.265 87.49 203.485 87.55 203.695V203.705C87.6 203.875 87.69 204.035 87.76 204.195C87.84 204.375 87.89 204.575 87.99 204.745C87.99 204.745 87.99 204.755 88 204.755C88.09 204.905 88.22 205.035 88.33 205.175C88.45 205.335 88.55 205.495 88.69 205.635L88.7 205.645C88.82 205.765 88.98 205.855 89.12 205.965C89.28 206.085 89.42 206.225 89.59 206.325C89.6 206.325 89.6 206.325 89.61 206.335C89.62 206.335 89.62 206.345 89.63 206.345L139.87 234.775V285.065L43.67 229.705V60.135ZM244.75 229.705L148.58 285.075V234.775L219.8 194.115L244.75 179.875V229.705ZM297.2 139.625L253.49 164.795V114.995L278.85 100.395L297.21 89.825V139.625H297.2Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,7 @@
|
||||
@props(['status'])
|
||||
|
||||
@if ($status)
|
||||
<div {{ $attributes->merge(['class' => 'font-medium text-sm text-green-600']) }}>
|
||||
{{ $status }}
|
||||
</div>
|
||||
@endif
|
||||
@@ -0,0 +1,3 @@
|
||||
<button {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-red-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-red-500 active:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition ease-in-out duration-150']) }}>
|
||||
{{ $slot }}
|
||||
</button>
|
||||
@@ -0,0 +1 @@
|
||||
<a {{ $attributes->merge(['class' => 'block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out']) }}>{{ $slot }}</a>
|
||||
@@ -0,0 +1,35 @@
|
||||
@props(['align' => 'right', 'width' => '48', 'contentClasses' => 'py-1 bg-white'])
|
||||
|
||||
@php
|
||||
$alignmentClasses = match ($align) {
|
||||
'left' => 'ltr:origin-top-left rtl:origin-top-right start-0',
|
||||
'top' => 'origin-top',
|
||||
default => 'ltr:origin-top-right rtl:origin-top-left end-0',
|
||||
};
|
||||
|
||||
$width = match ($width) {
|
||||
'48' => 'w-48',
|
||||
default => $width,
|
||||
};
|
||||
@endphp
|
||||
|
||||
<div class="relative" x-data="{ open: false }" @click.outside="open = false" @close.stop="open = false">
|
||||
<div @click="open = ! open">
|
||||
{{ $trigger }}
|
||||
</div>
|
||||
|
||||
<div x-show="open"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-75"
|
||||
x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-95"
|
||||
class="absolute z-50 mt-2 {{ $width }} rounded-md shadow-lg {{ $alignmentClasses }}"
|
||||
style="display: none;"
|
||||
@click="open = false">
|
||||
<div class="rounded-md ring-1 ring-black ring-opacity-5 {{ $contentClasses }}">
|
||||
{{ $content }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
@props(['messages'])
|
||||
|
||||
@if ($messages)
|
||||
<ul {{ $attributes->merge(['class' => 'text-sm text-red-600 space-y-1']) }}>
|
||||
@foreach ((array) $messages as $message)
|
||||
<li>{{ $message }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@endif
|
||||
@@ -0,0 +1,5 @@
|
||||
@props(['value'])
|
||||
|
||||
<label {{ $attributes->merge(['class' => 'block font-medium text-sm text-gray-700']) }}>
|
||||
{{ $value ?? $slot }}
|
||||
</label>
|
||||
@@ -0,0 +1,78 @@
|
||||
@props([
|
||||
'name',
|
||||
'show' => false,
|
||||
'maxWidth' => '2xl'
|
||||
])
|
||||
|
||||
@php
|
||||
$maxWidth = [
|
||||
'sm' => 'sm:max-w-sm',
|
||||
'md' => 'sm:max-w-md',
|
||||
'lg' => 'sm:max-w-lg',
|
||||
'xl' => 'sm:max-w-xl',
|
||||
'2xl' => 'sm:max-w-2xl',
|
||||
][$maxWidth];
|
||||
@endphp
|
||||
|
||||
<div
|
||||
x-data="{
|
||||
show: @js($show),
|
||||
focusables() {
|
||||
// All focusable element types...
|
||||
let selector = 'a, button, input:not([type=\'hidden\']), textarea, select, details, [tabindex]:not([tabindex=\'-1\'])'
|
||||
return [...$el.querySelectorAll(selector)]
|
||||
// All non-disabled elements...
|
||||
.filter(el => ! el.hasAttribute('disabled'))
|
||||
},
|
||||
firstFocusable() { return this.focusables()[0] },
|
||||
lastFocusable() { return this.focusables().slice(-1)[0] },
|
||||
nextFocusable() { return this.focusables()[this.nextFocusableIndex()] || this.firstFocusable() },
|
||||
prevFocusable() { return this.focusables()[this.prevFocusableIndex()] || this.lastFocusable() },
|
||||
nextFocusableIndex() { return (this.focusables().indexOf(document.activeElement) + 1) % (this.focusables().length + 1) },
|
||||
prevFocusableIndex() { return Math.max(0, this.focusables().indexOf(document.activeElement)) -1 },
|
||||
}"
|
||||
x-init="$watch('show', value => {
|
||||
if (value) {
|
||||
document.body.classList.add('overflow-y-hidden');
|
||||
{{ $attributes->has('focusable') ? 'setTimeout(() => firstFocusable().focus(), 100)' : '' }}
|
||||
} else {
|
||||
document.body.classList.remove('overflow-y-hidden');
|
||||
}
|
||||
})"
|
||||
x-on:open-modal.window="$event.detail == '{{ $name }}' ? show = true : null"
|
||||
x-on:close-modal.window="$event.detail == '{{ $name }}' ? show = false : null"
|
||||
x-on:close.stop="show = false"
|
||||
x-on:keydown.escape.window="show = false"
|
||||
x-on:keydown.tab.prevent="$event.shiftKey || nextFocusable().focus()"
|
||||
x-on:keydown.shift.tab.prevent="prevFocusable().focus()"
|
||||
x-show="show"
|
||||
class="fixed inset-0 overflow-y-auto px-4 py-6 sm:px-0 z-50"
|
||||
style="display: {{ $show ? 'block' : 'none' }};"
|
||||
>
|
||||
<div
|
||||
x-show="show"
|
||||
class="fixed inset-0 transform transition-all"
|
||||
x-on:click="show = false"
|
||||
x-transition:enter="ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
>
|
||||
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
x-show="show"
|
||||
class="mb-6 bg-white rounded-lg overflow-hidden shadow-xl transform transition-all sm:w-full {{ $maxWidth }} sm:mx-auto"
|
||||
x-transition:enter="ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave="ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
@props(['active'])
|
||||
|
||||
@php
|
||||
$classes = ($active ?? false)
|
||||
? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out'
|
||||
: 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out';
|
||||
@endphp
|
||||
|
||||
<a {{ $attributes->merge(['class' => $classes]) }}>
|
||||
{{ $slot }}
|
||||
</a>
|
||||
@@ -0,0 +1,3 @@
|
||||
<button {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150']) }}>
|
||||
{{ $slot }}
|
||||
</button>
|
||||
@@ -0,0 +1,11 @@
|
||||
@props(['active'])
|
||||
|
||||
@php
|
||||
$classes = ($active ?? false)
|
||||
? 'block w-full ps-3 pe-4 py-2 border-l-4 border-indigo-400 text-start text-base font-medium text-indigo-700 bg-indigo-50 focus:outline-none focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700 transition duration-150 ease-in-out'
|
||||
: 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out';
|
||||
@endphp
|
||||
|
||||
<a {{ $attributes->merge(['class' => $classes]) }}>
|
||||
{{ $slot }}
|
||||
</a>
|
||||
@@ -0,0 +1,3 @@
|
||||
<button {{ $attributes->merge(['type' => 'button', 'class' => 'inline-flex items-center px-4 py-2 bg-white border border-gray-300 rounded-md font-semibold text-xs text-gray-700 uppercase tracking-widest shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-25 transition ease-in-out duration-150']) }}>
|
||||
{{ $slot }}
|
||||
</button>
|
||||
@@ -0,0 +1,3 @@
|
||||
@props(['disabled' => false])
|
||||
|
||||
<input @disabled($disabled) {{ $attributes->merge(['class' => 'border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm']) }}>
|
||||
@@ -0,0 +1,17 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('Dashboard') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900">
|
||||
{{ __("You're logged in!") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
@@ -0,0 +1,65 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head><meta charset="UTF-8"><style>
|
||||
body { font-family: Arial, sans-serif; color: #333; max-width: 600px; margin: 0 auto; }
|
||||
h1 { color: #1d4ed8; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { padding: 8px; border-bottom: 1px solid #eee; text-align: left; }
|
||||
th { background: #f8fafc; }
|
||||
.total { font-weight: bold; font-size: 1.1em; }
|
||||
.box { background: #f8fafc; padding: 16px; border-radius: 8px; margin: 16px 0; }
|
||||
</style></head>
|
||||
<body>
|
||||
<h1>Vielen Dank für Ihre Bestellung!</h1>
|
||||
<p>Guten Tag {{ $order->billingAddress()?->full_name ?? $order->guest_email }},</p>
|
||||
<p>wir haben Ihre Bestellung <strong>{{ $order->order_number }}</strong> erhalten und bestätigen sie hiermit.</p>
|
||||
|
||||
@if($order->payment_method === 'prepayment')
|
||||
<div class="box">
|
||||
<strong>Bitte überweisen Sie den Betrag auf folgendes Konto:</strong><br>
|
||||
Empfänger: {{ \App\Models\Setting::get('shop_owner') }}<br>
|
||||
Bank: {{ \App\Models\Setting::get('bank_name') }}<br>
|
||||
IBAN: {{ \App\Models\Setting::get('bank_iban') }}<br>
|
||||
BIC: {{ \App\Models\Setting::get('bank_bic') }}<br>
|
||||
Verwendungszweck: <strong>{{ $order->order_number }}</strong><br>
|
||||
Betrag: <strong>{{ number_format($order->total, 2, ',', '.') }} €</strong>
|
||||
</div>
|
||||
@else
|
||||
<div class="box">
|
||||
Sie erhalten Ihre Rechnung separat per E-Mail sobald Ihre Bestellung bearbeitet wird.<br>
|
||||
Zahlungsziel: {{ \App\Models\Setting::get('invoice_payment_days', 14) }} Tage nach Rechnungserhalt.
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<h2>Bestellübersicht</h2>
|
||||
<table>
|
||||
<thead><tr><th>Artikel</th><th>Menge</th><th>Einzelpreis</th><th>Gesamt</th></tr></thead>
|
||||
<tbody>
|
||||
@foreach($order->items as $item)
|
||||
<tr>
|
||||
<td>{{ $item->product_name }}@if($item->variant_label) <small>({{ $item->variant_label }})</small>@endif</td>
|
||||
<td>{{ $item->quantity }}</td>
|
||||
<td>{{ number_format($item->unit_price, 2, ',', '.') }} €</td>
|
||||
<td>{{ number_format($item->line_total, 2, ',', '.') }} €</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr><td colspan="3">Zwischensumme</td><td>{{ number_format($order->subtotal, 2, ',', '.') }} €</td></tr>
|
||||
<tr><td colspan="3">Versand</td><td>{{ number_format($order->shipping_cost, 2, ',', '.') }} €</td></tr>
|
||||
<tr class="total"><td colspan="3">Gesamt (inkl. MwSt.)</td><td>{{ number_format($order->total, 2, ',', '.') }} €</td></tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
@if($addr = $order->billingAddress())
|
||||
<h3>Rechnungsadresse</h3>
|
||||
<p>{{ $addr->full_name }}<br>{{ $addr->street }}<br>{{ $addr->zip }} {{ $addr->city }}</p>
|
||||
@endif
|
||||
|
||||
<hr>
|
||||
<p style="font-size:0.85em;color:#666;">
|
||||
{{ \App\Models\Setting::get('shop_name') }} | {{ \App\Models\Setting::get('shop_address') }}<br>
|
||||
Bei Fragen: <a href="mailto:{{ \App\Models\Setting::get('shop_email') }}">{{ \App\Models\Setting::get('shop_email') }}</a>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head><meta charset="UTF-8"><style>
|
||||
body { font-family: Arial, sans-serif; color: #333; max-width: 600px; margin: 0 auto; }
|
||||
h1 { color: #1d4ed8; }
|
||||
.box { background: #f0fdf4; padding: 16px; border-radius: 8px; border: 1px solid #86efac; }
|
||||
</style></head>
|
||||
<body>
|
||||
<h1>Ihre Bestellung ist unterwegs! 📦</h1>
|
||||
<p>Ihre Bestellung <strong>{{ $order->order_number }}</strong> wurde versendet.</p>
|
||||
|
||||
@if($order->tracking_code)
|
||||
<div class="box">
|
||||
<strong>Sendungsverfolgung:</strong><br>
|
||||
Tracking-Nummer: <strong>{{ $order->tracking_code }}</strong>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<p>Bei Fragen zu Ihrer Lieferung können Sie uns jederzeit kontaktieren.</p>
|
||||
<hr>
|
||||
<p style="font-size:0.85em;color:#666;">{{ \App\Models\Setting::get('shop_name') }} | {{ \App\Models\Setting::get('shop_email') }}</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head><meta charset="UTF-8"><style>
|
||||
body { font-family: Arial, sans-serif; color: #333; max-width: 600px; margin: 0 auto; }
|
||||
h1 { color: #16a34a; }
|
||||
</style></head>
|
||||
<body>
|
||||
<h1>Zahlung eingegangen</h1>
|
||||
<p>Guten Tag,</p>
|
||||
<p>Ihre Zahlung für Bestellung <strong>{{ $order->order_number }}</strong> ({{ number_format($order->total, 2, ',', '.') }} €) ist eingegangen.</p>
|
||||
<p>Im Anhang finden Sie Ihre Rechnung als PDF.</p>
|
||||
<p>Wir werden Ihre Bestellung schnellstmöglich bearbeiten und versenden.</p>
|
||||
<hr>
|
||||
<p style="font-size:0.85em;color:#666;">{{ \App\Models\Setting::get('shop_name') }} | {{ \App\Models\Setting::get('shop_email') }}</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head><meta charset="UTF-8"></head>
|
||||
<body style="font-family: Arial, sans-serif; color: #333;">
|
||||
@if($isConfirmation)
|
||||
<h2>Bestätigung Ihres Widerrufs</h2>
|
||||
<p>Guten Tag {{ $data['name'] }},</p>
|
||||
<p>Ihr Widerruf ist bei uns eingegangen. Wir werden ihn schnellstmöglich bearbeiten.</p>
|
||||
@else
|
||||
<h2>Neues Widerrufsformular</h2>
|
||||
@endif
|
||||
|
||||
<table style="border-collapse:collapse;width:100%">
|
||||
<tr><td style="padding:6px;font-weight:bold">Name</td><td style="padding:6px">{{ $data['name'] }}</td></tr>
|
||||
<tr><td style="padding:6px;font-weight:bold">E-Mail</td><td style="padding:6px">{{ $data['email'] }}</td></tr>
|
||||
<tr><td style="padding:6px;font-weight:bold">Bestellnummer</td><td style="padding:6px">{{ $data['order_number'] ?? '—' }}</td></tr>
|
||||
<tr><td style="padding:6px;font-weight:bold">Produkt</td><td style="padding:6px">{{ $data['product'] }}</td></tr>
|
||||
<tr><td style="padding:6px;font-weight:bold">Datum</td><td style="padding:6px">{{ $data['date'] }}</td></tr>
|
||||
<tr><td style="padding:6px;font-weight:bold">Begründung</td><td style="padding:6px">{{ $data['reason'] ?? '—' }}</td></tr>
|
||||
</table>
|
||||
|
||||
<p style="font-size:0.85em;color:#666;">{{ \App\Models\Setting::get('shop_name') }}</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>@yield('title', 'Admin') — {{ \App\Models\Setting::get('shop_name', 'Shop') }}</title>
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
@stack('head')
|
||||
</head>
|
||||
<body class="bg-gray-100 text-gray-800 min-h-screen flex">
|
||||
|
||||
{{-- Sidebar --}}
|
||||
<aside class="w-64 bg-gray-900 text-white flex flex-col shrink-0">
|
||||
<div class="p-4 text-lg font-bold border-b border-gray-700">
|
||||
🛒 Admin
|
||||
</div>
|
||||
<nav class="flex-1 p-4 space-y-1 text-sm">
|
||||
<a href="{{ route('admin.dashboard') }}" class="block px-3 py-2 rounded hover:bg-gray-700 {{ request()->routeIs('admin.dashboard') ? 'bg-gray-700' : '' }}">Dashboard</a>
|
||||
<div class="pt-2 pb-1 text-xs text-gray-400 uppercase tracking-wide">Sortiment</div>
|
||||
<a href="{{ route('admin.categories.index') }}" class="block px-3 py-2 rounded hover:bg-gray-700 {{ request()->routeIs('admin.categories.*') ? 'bg-gray-700' : '' }}">Kategorien</a>
|
||||
<a href="{{ route('admin.products.index') }}" class="block px-3 py-2 rounded hover:bg-gray-700 {{ request()->routeIs('admin.products.*') ? 'bg-gray-700' : '' }}">Artikel</a>
|
||||
<a href="{{ route('admin.attributes.index') }}" class="block px-3 py-2 rounded hover:bg-gray-700 {{ request()->routeIs('admin.attributes.*') ? 'bg-gray-700' : '' }}">Attribute</a>
|
||||
<div class="pt-2 pb-1 text-xs text-gray-400 uppercase tracking-wide">Kunden & Bestellungen</div>
|
||||
<a href="{{ route('admin.customers.index') }}" class="block px-3 py-2 rounded hover:bg-gray-700 {{ request()->routeIs('admin.customers.*') ? 'bg-gray-700' : '' }}">Kunden</a>
|
||||
<a href="{{ route('admin.orders.index') }}" class="block px-3 py-2 rounded hover:bg-gray-700 {{ request()->routeIs('admin.orders.*') ? 'bg-gray-700' : '' }}">Bestellungen</a>
|
||||
<div class="pt-2 pb-1 text-xs text-gray-400 uppercase tracking-wide">CMS</div>
|
||||
<a href="{{ route('admin.pages.index') }}" class="block px-3 py-2 rounded hover:bg-gray-700 {{ request()->routeIs('admin.pages.*') ? 'bg-gray-700' : '' }}">Seiten</a>
|
||||
<a href="{{ route('admin.banners.index') }}" class="block px-3 py-2 rounded hover:bg-gray-700 {{ request()->routeIs('admin.banners.*') ? 'bg-gray-700' : '' }}">Banner</a>
|
||||
<a href="{{ route('admin.faqs.index') }}" class="block px-3 py-2 rounded hover:bg-gray-700 {{ request()->routeIs('admin.faqs.*') ? 'bg-gray-700' : '' }}">FAQ</a>
|
||||
<div class="pt-2 pb-1 text-xs text-gray-400 uppercase tracking-wide">System</div>
|
||||
<a href="{{ route('admin.settings.index') }}" class="block px-3 py-2 rounded hover:bg-gray-700 {{ request()->routeIs('admin.settings.*') ? 'bg-gray-700' : '' }}">Einstellungen</a>
|
||||
</nav>
|
||||
<div class="p-4 border-t border-gray-700 text-xs text-gray-400">
|
||||
{{ auth()->user()->name }}<br>
|
||||
<form method="POST" action="{{ route('logout') }}">@csrf
|
||||
<button class="text-gray-400 hover:text-white mt-1">Abmelden</button>
|
||||
</form>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{{-- Main --}}
|
||||
<div class="flex-1 flex flex-col overflow-hidden">
|
||||
<header class="bg-white shadow px-6 py-3 flex items-center justify-between">
|
||||
<h1 class="text-xl font-semibold">@yield('title', 'Admin')</h1>
|
||||
<a href="{{ url('/') }}" class="text-sm text-blue-600 hover:underline" target="_blank">Shop ansehen</a>
|
||||
</header>
|
||||
|
||||
<main class="flex-1 overflow-y-auto p-6">
|
||||
@if(session('success'))
|
||||
<div class="mb-4 bg-green-100 text-green-800 px-4 py-3 rounded">{{ session('success') }}</div>
|
||||
@endif
|
||||
@if(session('error'))
|
||||
<div class="mb-4 bg-red-100 text-red-800 px-4 py-3 rounded">{{ session('error') }}</div>
|
||||
@endif
|
||||
|
||||
@yield('content')
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
|
||||
<title>{{ config('app.name', 'Laravel') }}</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
||||
|
||||
<!-- Scripts -->
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
</head>
|
||||
<body class="font-sans antialiased">
|
||||
<div class="min-h-screen bg-gray-100">
|
||||
@include('layouts.navigation')
|
||||
|
||||
<!-- Page Heading -->
|
||||
@isset($header)
|
||||
<header class="bg-white shadow">
|
||||
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
||||
{{ $header }}
|
||||
</div>
|
||||
</header>
|
||||
@endisset
|
||||
|
||||
<!-- Page Content -->
|
||||
<main>
|
||||
{{ $slot }}
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
|
||||
<title>{{ config('app.name', 'Laravel') }}</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
||||
|
||||
<!-- Scripts -->
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
</head>
|
||||
<body class="font-sans text-gray-900 antialiased">
|
||||
<div class="min-h-screen flex flex-col sm:justify-center items-center pt-6 sm:pt-0 bg-gray-100">
|
||||
<div>
|
||||
<a href="/">
|
||||
<x-application-logo class="w-20 h-20 fill-current text-gray-500" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="w-full sm:max-w-md mt-6 px-6 py-4 bg-white shadow-md overflow-hidden sm:rounded-lg">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,100 @@
|
||||
<nav x-data="{ open: false }" class="bg-white border-b border-gray-100">
|
||||
<!-- Primary Navigation Menu -->
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<div class="flex">
|
||||
<!-- Logo -->
|
||||
<div class="shrink-0 flex items-center">
|
||||
<a href="{{ route('dashboard') }}">
|
||||
<x-application-logo class="block h-9 w-auto fill-current text-gray-800" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Navigation Links -->
|
||||
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
|
||||
<x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
|
||||
{{ __('Dashboard') }}
|
||||
</x-nav-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Settings Dropdown -->
|
||||
<div class="hidden sm:flex sm:items-center sm:ms-6">
|
||||
<x-dropdown align="right" width="48">
|
||||
<x-slot name="trigger">
|
||||
<button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition ease-in-out duration-150">
|
||||
<div>{{ Auth::user()->name }}</div>
|
||||
|
||||
<div class="ms-1">
|
||||
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="content">
|
||||
<x-dropdown-link :href="route('profile.edit')">
|
||||
{{ __('Profile') }}
|
||||
</x-dropdown-link>
|
||||
|
||||
<!-- Authentication -->
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
|
||||
<x-dropdown-link :href="route('logout')"
|
||||
onclick="event.preventDefault();
|
||||
this.closest('form').submit();">
|
||||
{{ __('Log Out') }}
|
||||
</x-dropdown-link>
|
||||
</form>
|
||||
</x-slot>
|
||||
</x-dropdown>
|
||||
</div>
|
||||
|
||||
<!-- Hamburger -->
|
||||
<div class="-me-2 flex items-center sm:hidden">
|
||||
<button @click="open = ! open" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out">
|
||||
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
|
||||
<path :class="{'hidden': open, 'inline-flex': ! open }" class="inline-flex" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
<path :class="{'hidden': ! open, 'inline-flex': open }" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Responsive Navigation Menu -->
|
||||
<div :class="{'block': open, 'hidden': ! open}" class="hidden sm:hidden">
|
||||
<div class="pt-2 pb-3 space-y-1">
|
||||
<x-responsive-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
|
||||
{{ __('Dashboard') }}
|
||||
</x-responsive-nav-link>
|
||||
</div>
|
||||
|
||||
<!-- Responsive Settings Options -->
|
||||
<div class="pt-4 pb-1 border-t border-gray-200">
|
||||
<div class="px-4">
|
||||
<div class="font-medium text-base text-gray-800">{{ Auth::user()->name }}</div>
|
||||
<div class="font-medium text-sm text-gray-500">{{ Auth::user()->email }}</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 space-y-1">
|
||||
<x-responsive-nav-link :href="route('profile.edit')">
|
||||
{{ __('Profile') }}
|
||||
</x-responsive-nav-link>
|
||||
|
||||
<!-- Authentication -->
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
|
||||
<x-responsive-nav-link :href="route('logout')"
|
||||
onclick="event.preventDefault();
|
||||
this.closest('form').submit();">
|
||||
{{ __('Log Out') }}
|
||||
</x-responsive-nav-link>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -0,0 +1,215 @@
|
||||
<!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>
|
||||
@@ -0,0 +1,134 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: DejaVu Sans, Arial, sans-serif; font-size: 11px; color: #222; }
|
||||
.header { display: flex; justify-content: space-between; margin-bottom: 30px; }
|
||||
.logo { font-size: 20px; font-weight: bold; color: #1d4ed8; }
|
||||
.shop-info { text-align: right; font-size: 10px; color: #555; }
|
||||
.address-block { margin-bottom: 30px; }
|
||||
.address-block .to { font-size: 9px; color: #888; margin-bottom: 4px; }
|
||||
.meta { float: right; width: 200px; }
|
||||
.meta table { width: 100%; }
|
||||
.meta td { padding: 3px 6px; }
|
||||
.meta td:first-child { color: #555; }
|
||||
.meta td:last-child { text-align: right; font-weight: bold; }
|
||||
h2 { font-size: 16px; margin-bottom: 20px; clear: both; }
|
||||
table.items { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
|
||||
table.items th { background: #f1f5f9; padding: 7px 8px; text-align: left; font-size: 10px; border-bottom: 2px solid #cbd5e1; }
|
||||
table.items td { padding: 6px 8px; border-bottom: 1px solid #e2e8f0; }
|
||||
.text-right { text-align: right; }
|
||||
.totals { width: 260px; margin-left: auto; }
|
||||
.totals td { padding: 4px 8px; }
|
||||
.totals td:last-child { text-align: right; }
|
||||
.totals .grand-total td { font-weight: bold; font-size: 13px; border-top: 2px solid #1d4ed8; padding-top: 6px; }
|
||||
.footer { margin-top: 40px; padding-top: 12px; border-top: 1px solid #e2e8f0; font-size: 9px; color: #888; }
|
||||
.payment-box { background: #f0fdf4; border: 1px solid #86efac; padding: 12px; margin: 20px 0; border-radius: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@php
|
||||
$shop = [
|
||||
'name' => \App\Models\Setting::get('shop_name'),
|
||||
'owner' => \App\Models\Setting::get('shop_owner'),
|
||||
'address' => \App\Models\Setting::get('shop_address'),
|
||||
'email' => \App\Models\Setting::get('shop_email'),
|
||||
'phone' => \App\Models\Setting::get('shop_phone'),
|
||||
'vat_id' => \App\Models\Setting::get('shop_vat_id'),
|
||||
'bank' => \App\Models\Setting::get('bank_name'),
|
||||
'iban' => \App\Models\Setting::get('bank_iban'),
|
||||
'bic' => \App\Models\Setting::get('bank_bic'),
|
||||
'days' => \App\Models\Setting::get('invoice_payment_days', 14),
|
||||
];
|
||||
$billing = $order->billingAddress();
|
||||
$due = $order->paid_at?->addDays((int)$shop['days']);
|
||||
@endphp
|
||||
|
||||
<div class="header">
|
||||
<div class="logo">{{ $shop['name'] }}</div>
|
||||
<div class="shop-info">
|
||||
{{ $shop['owner'] }}<br>
|
||||
{!! nl2br(e($shop['address'])) !!}<br>
|
||||
{{ $shop['email'] }}<br>
|
||||
@if($shop['vat_id']) USt-IdNr.: {{ $shop['vat_id'] }} @endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="address-block">
|
||||
<div class="meta">
|
||||
<table>
|
||||
<tr><td>Rechnungsnr.</td><td>{{ $order->order_number }}</td></tr>
|
||||
<tr><td>Datum</td><td>{{ ($order->paid_at ?? $order->created_at)->format('d.m.Y') }}</td></tr>
|
||||
@if($due)<tr><td>Zahlungsziel</td><td>{{ $due->format('d.m.Y') }}</td></tr>@endif
|
||||
<tr><td>Zahlungsart</td><td>{{ $order->payment_label }}</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="to">Rechnungsadresse</div>
|
||||
@if($billing)
|
||||
{{ $billing->full_name }}<br>
|
||||
@if($billing->company) {{ $billing->company }}<br> @endif
|
||||
{{ $billing->street }}<br>
|
||||
@if($billing->street2) {{ $billing->street2 }}<br> @endif
|
||||
{{ $billing->zip }} {{ $billing->city }}<br>
|
||||
{{ $billing->country }}
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<h2>Rechnung {{ $order->order_number }}</h2>
|
||||
|
||||
<table class="items">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Artikel</th>
|
||||
<th class="text-right">Menge</th>
|
||||
<th class="text-right">Einzelpreis</th>
|
||||
<th class="text-right">MwSt.</th>
|
||||
<th class="text-right">Gesamt</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($order->items as $item)
|
||||
<tr>
|
||||
<td>
|
||||
{{ $item->product_name }}
|
||||
@if($item->variant_label)<br><span style="color:#666;font-size:9px">{{ $item->variant_label }}</span>@endif
|
||||
</td>
|
||||
<td class="text-right">{{ $item->quantity }}</td>
|
||||
<td class="text-right">{{ number_format($item->unit_price, 2, ',', '.') }} €</td>
|
||||
<td class="text-right">{{ number_format($item->tax_rate, 0) }} %</td>
|
||||
<td class="text-right">{{ number_format($item->line_total, 2, ',', '.') }} €</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@php
|
||||
$taxGroups = $order->items->groupBy('tax_rate')->map(fn($g) => $g->sum('tax_amount'));
|
||||
@endphp
|
||||
|
||||
<table class="totals">
|
||||
<tr><td>Nettobetrag</td><td>{{ number_format($order->subtotal - $order->tax_total, 2, ',', '.') }} €</td></tr>
|
||||
@foreach($taxGroups as $rate => $amount)
|
||||
<tr><td>MwSt. {{ number_format($rate, 0) }} %</td><td>{{ number_format($amount, 2, ',', '.') }} €</td></tr>
|
||||
@endforeach
|
||||
<tr><td>Versand</td><td>{{ number_format($order->shipping_cost, 2, ',', '.') }} €</td></tr>
|
||||
<tr class="grand-total"><td>Gesamtbetrag</td><td>{{ number_format($order->total, 2, ',', '.') }} €</td></tr>
|
||||
</table>
|
||||
|
||||
@if($order->payment_method === 'invoice')
|
||||
<div class="payment-box">
|
||||
<strong>Bitte überweisen Sie den Betrag bis {{ $due?->format('d.m.Y') ?? 'baldmöglichst' }} auf:</strong><br>
|
||||
Bank: {{ $shop['bank'] }} | IBAN: {{ $shop['iban'] }} | BIC: {{ $shop['bic'] }}<br>
|
||||
Verwendungszweck: {{ $order->order_number }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="footer">
|
||||
{{ $shop['name'] }} · {{ $shop['owner'] }} · {{ $shop['address'] }} · {{ $shop['email'] }}
|
||||
@if($shop['vat_id']) · USt-IdNr.: {{ $shop['vat_id'] }} @endif
|
||||
@if($shop['iban']) · IBAN: {{ $shop['iban'] }} @endif
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,29 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('Profile') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
|
||||
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
|
||||
<div class="max-w-xl">
|
||||
@include('profile.partials.update-profile-information-form')
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
|
||||
<div class="max-w-xl">
|
||||
@include('profile.partials.update-password-form')
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
|
||||
<div class="max-w-xl">
|
||||
@include('profile.partials.delete-user-form')
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
@@ -0,0 +1,55 @@
|
||||
<section class="space-y-6">
|
||||
<header>
|
||||
<h2 class="text-lg font-medium text-gray-900">
|
||||
{{ __('Delete Account') }}
|
||||
</h2>
|
||||
|
||||
<p class="mt-1 text-sm text-gray-600">
|
||||
{{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.') }}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<x-danger-button
|
||||
x-data=""
|
||||
x-on:click.prevent="$dispatch('open-modal', 'confirm-user-deletion')"
|
||||
>{{ __('Delete Account') }}</x-danger-button>
|
||||
|
||||
<x-modal name="confirm-user-deletion" :show="$errors->userDeletion->isNotEmpty()" focusable>
|
||||
<form method="post" action="{{ route('profile.destroy') }}" class="p-6">
|
||||
@csrf
|
||||
@method('delete')
|
||||
|
||||
<h2 class="text-lg font-medium text-gray-900">
|
||||
{{ __('Are you sure you want to delete your account?') }}
|
||||
</h2>
|
||||
|
||||
<p class="mt-1 text-sm text-gray-600">
|
||||
{{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.') }}
|
||||
</p>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-input-label for="password" value="{{ __('Password') }}" class="sr-only" />
|
||||
|
||||
<x-text-input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
class="mt-1 block w-3/4"
|
||||
placeholder="{{ __('Password') }}"
|
||||
/>
|
||||
|
||||
<x-input-error :messages="$errors->userDeletion->get('password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
<x-secondary-button x-on:click="$dispatch('close')">
|
||||
{{ __('Cancel') }}
|
||||
</x-secondary-button>
|
||||
|
||||
<x-danger-button class="ms-3">
|
||||
{{ __('Delete Account') }}
|
||||
</x-danger-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-modal>
|
||||
</section>
|
||||
@@ -0,0 +1,48 @@
|
||||
<section>
|
||||
<header>
|
||||
<h2 class="text-lg font-medium text-gray-900">
|
||||
{{ __('Update Password') }}
|
||||
</h2>
|
||||
|
||||
<p class="mt-1 text-sm text-gray-600">
|
||||
{{ __('Ensure your account is using a long, random password to stay secure.') }}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<form method="post" action="{{ route('password.update') }}" class="mt-6 space-y-6">
|
||||
@csrf
|
||||
@method('put')
|
||||
|
||||
<div>
|
||||
<x-input-label for="update_password_current_password" :value="__('Current Password')" />
|
||||
<x-text-input id="update_password_current_password" name="current_password" type="password" class="mt-1 block w-full" autocomplete="current-password" />
|
||||
<x-input-error :messages="$errors->updatePassword->get('current_password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-input-label for="update_password_password" :value="__('New Password')" />
|
||||
<x-text-input id="update_password_password" name="password" type="password" class="mt-1 block w-full" autocomplete="new-password" />
|
||||
<x-input-error :messages="$errors->updatePassword->get('password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-input-label for="update_password_password_confirmation" :value="__('Confirm Password')" />
|
||||
<x-text-input id="update_password_password_confirmation" name="password_confirmation" type="password" class="mt-1 block w-full" autocomplete="new-password" />
|
||||
<x-input-error :messages="$errors->updatePassword->get('password_confirmation')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<x-primary-button>{{ __('Save') }}</x-primary-button>
|
||||
|
||||
@if (session('status') === 'password-updated')
|
||||
<p
|
||||
x-data="{ show: true }"
|
||||
x-show="show"
|
||||
x-transition
|
||||
x-init="setTimeout(() => show = false, 2000)"
|
||||
class="text-sm text-gray-600"
|
||||
>{{ __('Saved.') }}</p>
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -0,0 +1,64 @@
|
||||
<section>
|
||||
<header>
|
||||
<h2 class="text-lg font-medium text-gray-900">
|
||||
{{ __('Profile Information') }}
|
||||
</h2>
|
||||
|
||||
<p class="mt-1 text-sm text-gray-600">
|
||||
{{ __("Update your account's profile information and email address.") }}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<form id="send-verification" method="post" action="{{ route('verification.send') }}">
|
||||
@csrf
|
||||
</form>
|
||||
|
||||
<form method="post" action="{{ route('profile.update') }}" class="mt-6 space-y-6">
|
||||
@csrf
|
||||
@method('patch')
|
||||
|
||||
<div>
|
||||
<x-input-label for="name" :value="__('Name')" />
|
||||
<x-text-input id="name" name="name" type="text" class="mt-1 block w-full" :value="old('name', $user->name)" required autofocus autocomplete="name" />
|
||||
<x-input-error class="mt-2" :messages="$errors->get('name')" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-input-label for="email" :value="__('Email')" />
|
||||
<x-text-input id="email" name="email" type="email" class="mt-1 block w-full" :value="old('email', $user->email)" required autocomplete="username" />
|
||||
<x-input-error class="mt-2" :messages="$errors->get('email')" />
|
||||
|
||||
@if ($user instanceof \Illuminate\Contracts\Auth\MustVerifyEmail && ! $user->hasVerifiedEmail())
|
||||
<div>
|
||||
<p class="text-sm mt-2 text-gray-800">
|
||||
{{ __('Your email address is unverified.') }}
|
||||
|
||||
<button form="send-verification" class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
{{ __('Click here to re-send the verification email.') }}
|
||||
</button>
|
||||
</p>
|
||||
|
||||
@if (session('status') === 'verification-link-sent')
|
||||
<p class="mt-2 font-medium text-sm text-green-600">
|
||||
{{ __('A new verification link has been sent to your email address.') }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<x-primary-button>{{ __('Save') }}</x-primary-button>
|
||||
|
||||
@if (session('status') === 'profile-updated')
|
||||
<p
|
||||
x-data="{ show: true }"
|
||||
x-show="show"
|
||||
x-transition
|
||||
x-init="setTimeout(() => show = false, 2000)"
|
||||
class="text-sm text-gray-600"
|
||||
>{{ __('Saved.') }}</p>
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user