Enhance checkout flow with new components and validation
- Added AddressForm and CheckoutForm components for user input during checkout. - Implemented validation using Zod and VeeValidate for billing address fields. - Created OrderSummary and MockPayPalButton components for order confirmation and payment simulation. - Updated CartSheet and CartSidebar to navigate to the new checkout page at '/kasse'. - Introduced new API endpoints for validating checkout data and creating orders. - Enhanced user experience with responsive design and error handling. These changes complete the checkout functionality, allowing users to enter billing information, simulate payment, and confirm orders.
This commit is contained in:
219
app/pages/bestellung/bestaetigen/[orderId].vue
Normal file
219
app/pages/bestellung/bestaetigen/[orderId].vue
Normal file
@@ -0,0 +1,219 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* Order Confirmation Page (/bestellung/bestaetigen/[orderId])
|
||||
*
|
||||
* Features:
|
||||
* - Requires authentication (middleware: auth)
|
||||
* - Fetches order details from /api/orders/[orderId]
|
||||
* - Validates order belongs to user (server-side)
|
||||
* - Validates order status is 'pending'
|
||||
* - Shows OrderSummary component
|
||||
* - Shows billing address
|
||||
* - Warning text before final confirmation
|
||||
* - "Jetzt verbindlich bestellen" button
|
||||
* - Redirects to success page after confirmation
|
||||
*/
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'auth',
|
||||
layout: 'default',
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const orderId = computed(() => route.params.orderId as string)
|
||||
|
||||
// Order data
|
||||
const order = ref<any>(null)
|
||||
const isLoading = ref(false)
|
||||
const isConfirming = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
// Fetch order details
|
||||
async function fetchOrder() {
|
||||
if (!orderId.value) return
|
||||
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
order.value = await $fetch(`/api/orders/${orderId.value}`)
|
||||
|
||||
// Check order status
|
||||
if (order.value.status === 'completed') {
|
||||
// Order already completed - redirect to success page
|
||||
navigateTo(`/bestellung/erfolg/${orderId.value}`)
|
||||
return
|
||||
}
|
||||
|
||||
if (order.value.status !== 'pending') {
|
||||
error.value = `Bestellung kann nicht bestätigt werden. Status: ${order.value.status}`
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('Failed to fetch order:', err)
|
||||
|
||||
if (err.statusCode === 404) {
|
||||
error.value = 'Bestellung nicht gefunden'
|
||||
} else if (err.statusCode === 403) {
|
||||
error.value = 'Du hast keine Berechtigung, diese Bestellung zu sehen'
|
||||
} else {
|
||||
error.value = 'Fehler beim Laden der Bestellung'
|
||||
}
|
||||
|
||||
// Redirect to cart after 3 seconds
|
||||
setTimeout(() => {
|
||||
navigateTo('/warenkorb')
|
||||
}, 3000)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Confirm order
|
||||
async function confirmOrder() {
|
||||
if (!orderId.value) return
|
||||
|
||||
isConfirming.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const response = await $fetch(`/api/orders/confirm/${orderId.value}`, {
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
if (response.success) {
|
||||
// Redirect to success page
|
||||
navigateTo(`/bestellung/erfolg/${orderId.value}`)
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('Failed to confirm order:', err)
|
||||
error.value =
|
||||
err.data?.message ||
|
||||
'Fehler beim Bestätigen der Bestellung. Bitte versuche es erneut.'
|
||||
} finally {
|
||||
isConfirming.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch order on mount
|
||||
onMounted(() => {
|
||||
fetchOrder()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<CommonHeader />
|
||||
|
||||
<div class="container mx-auto px-4 py-8 max-w-4xl">
|
||||
<!-- Page Header -->
|
||||
<div class="mb-8 text-center">
|
||||
<h1 class="text-4xl font-bold text-white mb-2">Bestellung bestätigen</h1>
|
||||
<p class="text-white/70">Bitte überprüfe deine Bestellung vor der finalen Bestätigung</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 Bestellung...</p>
|
||||
</div>
|
||||
|
||||
<!-- Order Content -->
|
||||
<div v-else-if="order" class="space-y-6">
|
||||
<!-- Order Summary Card -->
|
||||
<Card class="p-6">
|
||||
<OrderSummary :order="order" :show-address="true" />
|
||||
</Card>
|
||||
|
||||
<!-- Warning Notice -->
|
||||
<Alert class="border-yellow-500/50 bg-yellow-500/10">
|
||||
<div class="flex items-start gap-3">
|
||||
<svg
|
||||
class="w-5 h-5 text-yellow-500 mt-0.5 flex-shrink-0"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
></path>
|
||||
</svg>
|
||||
<div>
|
||||
<AlertTitle class="text-yellow-500">Wichtiger Hinweis</AlertTitle>
|
||||
<AlertDescription class="text-yellow-100/90">
|
||||
Bitte überprüfe alle Angaben sorgfältig. Nach der Bestätigung ist die
|
||||
Bestellung verbindlich und kann nicht mehr geändert werden.
|
||||
</AlertDescription>
|
||||
</div>
|
||||
</div>
|
||||
</Alert>
|
||||
|
||||
<!-- Confirmation Button -->
|
||||
<Card class="p-6">
|
||||
<div class="space-y-4">
|
||||
<Button
|
||||
@click="confirmOrder"
|
||||
:disabled="isConfirming"
|
||||
class="w-full bg-gradient-button bg-size-300 bg-left hover:bg-right transition-all duration-300 font-bold text-white shadow-lg hover:shadow-2xl"
|
||||
size="lg"
|
||||
>
|
||||
<span v-if="!isConfirming" class="flex items-center justify-center gap-2">
|
||||
<svg
|
||||
class="w-5 h-5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
></path>
|
||||
</svg>
|
||||
<span>Jetzt verbindlich bestellen</span>
|
||||
</span>
|
||||
<span v-else class="flex items-center gap-2">
|
||||
<div
|
||||
class="animate-spin rounded-full h-4 w-4 border-2 border-white/20 border-t-white"
|
||||
/>
|
||||
Bestätigung läuft...
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
<p class="text-xs text-white/60 text-center">
|
||||
Mit dem Klick auf "Jetzt verbindlich bestellen" akzeptierst du unsere
|
||||
<NuxtLink to="/agb" class="text-experimenta-accent hover:underline">
|
||||
AGB
|
||||
</NuxtLink>
|
||||
und
|
||||
<NuxtLink to="/datenschutz" class="text-experimenta-accent hover:underline">
|
||||
Datenschutzerklärung
|
||||
</NuxtLink>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- Back Link -->
|
||||
<div class="text-center pt-4">
|
||||
<NuxtLink to="/warenkorb" class="text-sm text-experimenta-accent hover:underline">
|
||||
← Zurück zum Warenkorb
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user