Implement shopping cart functionality with UI components and API integration
- Added CartItem, CartSummary, CartEmpty, CartSidebar, and CartSheet components for managing cart display and interactions. - Integrated useCart and useCartUI composables for cart state management and UI control. - Implemented API endpoints for cart operations, including fetching, adding, updating, and removing items. - Enhanced user experience with loading states and notifications using vue-sonner for cart actions. - Configured session management for guest and authenticated users, ensuring cart persistence across sessions. This commit completes the shopping cart feature, enabling users to add items, view their cart, and proceed to checkout. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
116
app/components/Cart/CartSummary.vue
Normal file
116
app/components/Cart/CartSummary.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '~/lib/utils'
|
||||
import type { CartItemWithProduct } from '~/types/cart'
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* Cart items for summary calculation
|
||||
*/
|
||||
items: CartItemWithProduct[]
|
||||
/**
|
||||
* Total amount in EUR
|
||||
*/
|
||||
total: number
|
||||
/**
|
||||
* Loading state (e.g., during checkout)
|
||||
*/
|
||||
loading?: boolean
|
||||
/**
|
||||
* Additional CSS classes
|
||||
*/
|
||||
class?: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
// Format currency in EUR
|
||||
const formatCurrency = (amount: number) => {
|
||||
return new Intl.NumberFormat('de-DE', {
|
||||
style: 'currency',
|
||||
currency: 'EUR',
|
||||
}).format(amount)
|
||||
}
|
||||
|
||||
// Calculate item count
|
||||
const itemCount = computed(() => {
|
||||
return props.items.reduce((sum, item) => sum + item.quantity, 0)
|
||||
})
|
||||
|
||||
// Calculate VAT (7% already included in total)
|
||||
// Formula: VAT = total * (VAT_rate / (1 + VAT_rate))
|
||||
const vatAmount = computed(() => {
|
||||
return props.total * (0.07 / 1.07)
|
||||
})
|
||||
|
||||
// Format values
|
||||
const formattedSubtotal = computed(() => formatCurrency(props.total))
|
||||
const formattedVat = computed(() => formatCurrency(vatAmount.value))
|
||||
const formattedTotal = computed(() => formatCurrency(props.total))
|
||||
|
||||
// Item count text (singular/plural)
|
||||
const itemCountText = computed(() => {
|
||||
return itemCount.value === 1 ? '1 Artikel' : `${itemCount.value} Artikel`
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card :class="cn('sticky top-4', props.class)">
|
||||
<div class="p-6 space-y-4">
|
||||
<!-- Header -->
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-white">Zusammenfassung</h2>
|
||||
<p class="text-sm text-white/60 mt-1">{{ itemCountText }}</p>
|
||||
</div>
|
||||
|
||||
<Separator class="bg-white/20" />
|
||||
|
||||
<!-- Price Breakdown -->
|
||||
<div class="space-y-3">
|
||||
<!-- Subtotal -->
|
||||
<div class="flex items-center justify-between text-white/80">
|
||||
<span class="text-sm">Zwischensumme</span>
|
||||
<span class="font-medium">{{ formattedSubtotal }}</span>
|
||||
</div>
|
||||
|
||||
<!-- VAT (included) -->
|
||||
<div class="flex items-center justify-between text-white/60 text-sm">
|
||||
<span>inkl. MwSt. (7%)</span>
|
||||
<span>{{ formattedVat }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator class="bg-white/20" />
|
||||
|
||||
<!-- Total -->
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-lg font-bold text-white">Gesamt</span>
|
||||
<span class="text-2xl font-bold text-experimenta-accent">
|
||||
{{ formattedTotal }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Checkout Button -->
|
||||
<Button
|
||||
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" :disabled="loading || items.length === 0" @click="$emit('checkout')">
|
||||
<span v-if="!loading">Zur Kasse</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" />
|
||||
Wird verarbeitet...
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
<!-- Additional Info -->
|
||||
<!-- <div class="pt-2 space-y-2 text-xs text-white/60">
|
||||
<p class="flex items-start gap-2">
|
||||
<span class="text-experimenta-accent">✓</span>
|
||||
<span>Sichere Zahlung mit PayPal</span>
|
||||
</p>
|
||||
<p class="flex items-start gap-2">
|
||||
<span class="text-experimenta-accent">✓</span>
|
||||
<span>Versandkostenfrei</span>
|
||||
</p>
|
||||
</div> -->
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
Reference in New Issue
Block a user