- 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>
239 lines
14 KiB
PHP
239 lines
14 KiB
PHP
@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
|