You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

250 lines
8.5 KiB

<script setup lang="ts">
/**
* Checkout Page (/checkout)
*
* Features:
* - Requires authentication (middleware: auth)
* - Shows CheckoutForm with billing address
* - Shows CartSummary in right sidebar (desktop) / top (mobile)
* - Redirects to homepage if cart is empty
* - "Warenkorb bearbeiten" button opens cart sidebar
* - Creates order on form submit
* - Redirects to /payment?orderId={orderId} after order creation
*/
// Type for checkout data (matches server schema)
type CheckoutData = {
salutation: 'male' | 'female' | 'other'
firstName: string
lastName: string
dateOfBirth: string
street: string
postCode: string
city: string
countryCode: string
saveAddress?: boolean
}
definePageMeta({
middleware: 'auth',
layout: 'default',
})
const { items, total, itemCount, fetchCart } = useCart()
const { open: openCart } = useCartUI()
// Loading states
const isLoading = ref(true) // Start as true to prevent premature redirect
const isCreatingOrder = ref(false)
const cartLoaded = ref(false) // Track if initial cart fetch completed
// Error state
const error = ref<string | null>(null)
// Fetch cart on mount
onMounted(async () => {
isLoading.value = true
try {
await fetchCart()
cartLoaded.value = true // Mark cart as loaded
} catch (err) {
console.error('Failed to fetch cart:', err)
error.value = 'Fehler beim Laden des Warenkorbs'
cartLoaded.value = true // Mark as loaded even on error to allow redirect
} finally {
isLoading.value = false
}
})
// Redirect to homepage if cart is empty (only after cart is loaded)
watchEffect(() => {
if (cartLoaded.value && !isLoading.value && itemCount.value === 0) {
navigateTo('/')
}
})
// Handle checkout form submission
async function handleCheckout(checkoutData: CheckoutData) {
isCreatingOrder.value = true
error.value = null
try {
// Create order via API
const response = await $fetch<{
success: boolean
orderId: string
orderNumber: string
message: string
}>('/api/orders/create', {
method: 'POST',
body: checkoutData,
})
if (response.success) {
// Redirect to payment page with order ID
navigateTo(`/payment?orderId=${response.orderId}`)
}
} catch (err: any) {
console.error('Order creation failed:', err)
error.value =
err.data?.message || 'Fehler beim Erstellen der Bestellung. Bitte versuche es erneut.'
} finally {
isCreatingOrder.value = false
}
}
</script>
<template>
<div>
<CommonHeader />
<div class="container mx-auto px-4 py-8 max-w-7xl">
<!-- Page Header -->
<div class="mb-8">
<h1 class="text-4xl font-bold text-white mb-2">Zur Kasse</h1>
<p class="text-white/70">
Bitte gib deine Rechnungsadresse ein, um die Bestellung abzuschließen.
</p>
</div>
<!-- Error Alert -->
<Alert v-if="error" variant="destructive" class="mb-6">
<AlertTitle>Fehler</AlertTitle>
<AlertDescription>{{ error }}</AlertDescription>
</Alert>
<!-- Loading State -->
<div v-if="isLoading" class="text-center py-12">
<div class="animate-spin rounded-full h-12 w-12 border-4 border-white/20 border-t-white mx-auto mb-4" />
<p class="text-white/60">Lade Warenkorb...</p>
</div>
<!-- Checkout Layout -->
<div v-else class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Left Column: Checkout Form (2/3 width on desktop) -->
<div class="lg:col-span-2">
<Card class="p-6">
<h2 class="text-2xl font-bold text-white mb-6">Rechnungsadresse</h2>
<CheckoutForm :loading="isCreatingOrder" @submit="handleCheckout" />
</Card>
</div>
<!-- Right Column: Cart Summary (1/3 width on desktop, sticky) -->
<div class="lg:col-span-1">
<div class="lg:sticky lg:top-4">
<!-- Mobile: Show summary at top, Desktop: Show in sidebar -->
<div class="lg:hidden mb-8">
<CartSummary :items="items" :total="total" :loading="isCreatingOrder" @checkout="() => { }"
class="hidden" />
<!-- Simplified summary for mobile -->
<Card class="p-4 bg-white/5 border-white/10">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-white/60">
{{ itemCount }} {{ itemCount === 1 ? 'Artikel' : 'Artikel' }}
</p>
<p class="text-2xl font-bold text-experimenta-accent">
{{
new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR',
}).format(total)
}}
</p>
</div>
<button
@click="openCart"
class="text-sm text-experimenta-accent hover:underline"
>
Bearbeiten
</button>
</div>
</Card>
</div>
<!-- Desktop: Full cart summary -->
<div class="hidden lg:block">
<Card class="p-6">
<h2 class="text-xl font-bold text-white mb-4">Deine Bestellung</h2>
<!-- Items List -->
<div class="space-y-3 mb-4 divide-y divide-white/10">
<div v-for="item in items" :key="item.id"
class="flex items-start justify-between pt-4 py-2 border-white/10">
<div class="flex-1">
<p class="text-sm font-medium text-white">{{ item.product.name }}</p>
<p class="text-xs text-white/60">
{{ item.quantity }}x
{{
new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR',
}).format(Number.parseFloat(item.product.price))
}}
</p>
</div>
<p class="text-sm font-semibold text-white">
{{
new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR',
}).format(item.subtotal)
}}
</p>
</div>
</div>
<!-- Totals -->
<div class="space-y-2 pt-4 border-t border-white/20">
<div class="flex items-center justify-between text-white/80">
<span class="text-sm">Zwischensumme</span>
<span class="font-medium">
{{
new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR',
}).format(total)
}}
</span>
</div>
<div class="flex items-center justify-between text-white/60 text-sm">
<span>inkl. MwSt. (7%)</span>
<span>
{{
new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR',
}).format(total * (0.07 / 1.07))
}}
</span>
</div>
<div class="flex items-center justify-between pt-3 border-t border-white/20">
<span class="text-lg font-bold text-white">Gesamt</span>
<span class="text-2xl font-bold text-experimenta-accent">
{{
new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR',
}).format(total)
}}
</span>
</div>
</div>
<!-- Edit Cart Link -->
<div class="mt-6 pt-6 border-t border-white/20">
<button
@click="openCart"
class="text-sm text-experimenta-accent hover:underline"
>
Warenkorb bearbeiten
</button>
</div>
</Card>
</div>
</div>
</div>
</div>
</div>
</div>
</template>