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:
@@ -66,11 +66,11 @@ function navigateToArea(area: ProductArea) {
|
||||
<div class="w-full">
|
||||
<!-- Desktop: Tabs -->
|
||||
<Tabs :model-value="currentArea" class="hidden md:block">
|
||||
<TabsList class="h-auto p-1 bg-muted/50">
|
||||
<TabsList class="h-auto p-1.5 bg-white/5">
|
||||
<TabsTrigger v-for="area in areas.filter(area => area.visible)" :key="area.id" :value="area.id"
|
||||
:disabled="!area.enabled" :class="[
|
||||
'gap-2 data-[state=active]:bg-white dark:data-[state=active]:bg-zinc-900',
|
||||
!area.enabled && 'opacity-60 cursor-not-allowed',
|
||||
'gap-2 data-[state=active]:bg-accent data-[state=active]:text-white data-[state=active]:shadow-md',
|
||||
!area.enabled && 'opacity-50 cursor-not-allowed',
|
||||
]" @click="navigateToArea(area)">
|
||||
<component :is="area.icon" class="h-4 w-4" />
|
||||
<span>{{ area.label }}</span>
|
||||
@@ -81,20 +81,19 @@ function navigateToArea(area: ProductArea) {
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
<!-- Mobile: Horizontal scroll with cards -->
|
||||
<!-- Mobile: Horizontal scroll with cards (matching desktop styling) -->
|
||||
<div class="md:hidden overflow-x-auto scrollbar-hide">
|
||||
<div class="flex gap-2 p-1 min-w-max">
|
||||
<div class="inline-flex h-auto items-center justify-center rounded-[35px] bg-white/5 p-1.5 min-w-max">
|
||||
<button v-for="area in areas.filter(area => area.visible)" :key="area.id" :disabled="!area.enabled" :class="[
|
||||
'flex items-center gap-2 px-4 py-2 rounded-lg border-2 transition-all whitespace-nowrap',
|
||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-[25px] px-4 py-[10px] text-lg font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-0',
|
||||
currentArea === area.id
|
||||
? 'bg-purple-600 text-white border-purple-600'
|
||||
: 'bg-white dark:bg-zinc-900 border-border hover:border-purple-300',
|
||||
!area.enabled && 'opacity-60 cursor-not-allowed',
|
||||
? 'bg-accent text-white shadow-md'
|
||||
: 'text-white/70 hover:text-white',
|
||||
!area.enabled && 'opacity-50 cursor-not-allowed',
|
||||
]" @click="navigateToArea(area)">
|
||||
<component :is="area.icon" :class="['h-4 w-4', currentArea === area.id ? 'text-white' : '']" />
|
||||
<span class="font-medium">{{ area.label }}</span>
|
||||
<Badge v-if="area.badge" :variant="currentArea === area.id ? 'secondary' : 'outline'"
|
||||
class="text-[10px] px-1.5 py-0">
|
||||
<component :is="area.icon" class="h-4 w-4" />
|
||||
<span>{{ area.label }}</span>
|
||||
<Badge v-if="area.badge" variant="secondary" class="ml-1 text-[10px] px-1.5 py-0">
|
||||
{{ area.badge }}
|
||||
</Badge>
|
||||
</button>
|
||||
|
||||
@@ -26,12 +26,12 @@ function handleClick(e: Event) {
|
||||
<!-- Desktop cart button (visible only on lg and up) -->
|
||||
<button
|
||||
@click="handleClick"
|
||||
class="hidden lg:flex items-center gap-2 rounded-lg px-4 py-2 transition-all hover:bg-white/10 focus:outline-none focus:ring-2 focus:ring-experimenta-accent focus:ring-offset-2 focus:ring-offset-transparent"
|
||||
class="relative hidden lg:flex items-center gap-5 rounded-[25px] px-[30px] py-[10px] transition-all hover:bg-white/10 focus:outline-none focus:ring-2 focus:ring-experimenta-accent focus:ring-offset-2 focus:ring-offset-transparent"
|
||||
aria-label="Warenkorb öffnen"
|
||||
>
|
||||
<!-- Cart icon with item count badge -->
|
||||
<div class="relative inline-flex">
|
||||
<ShoppingCart class="h-6 w-6 text-white" />
|
||||
<div class="relative inline-flex items-center justify-center">
|
||||
<ShoppingCart class="h-6 w-6 text-white" strokeWidth="2" />
|
||||
|
||||
<!-- Item count badge -->
|
||||
<Transition
|
||||
@@ -44,7 +44,7 @@ function handleClick(e: Event) {
|
||||
>
|
||||
<Badge
|
||||
v-if="hasItems"
|
||||
class="absolute -top-2 -right-2 h-5 min-w-[20px] px-1 flex items-center justify-center bg-red-500 text-white text-xs font-bold border-2 border-white"
|
||||
class="absolute -top-2.5 -right-3.5 h-5.5 min-w-[22px] px-1.5 flex items-center justify-center bg-experimenta-accent text-white text-xs font-bold border-2 border-purple-darkest shadow-lg"
|
||||
>
|
||||
{{ itemCount > 99 ? '99+' : itemCount }}
|
||||
</Badge>
|
||||
@@ -52,14 +52,13 @@ function handleClick(e: Event) {
|
||||
</div>
|
||||
|
||||
<!-- Total price (desktop only) -->
|
||||
<span class="text-sm font-semibold text-white">
|
||||
<span class="text-base font-bold text-white tabular-nums">
|
||||
{{ formattedTotal }}
|
||||
</span>
|
||||
|
||||
<!-- Pulse animation when items are added -->
|
||||
<!-- Static background -->
|
||||
<span
|
||||
v-if="hasItems"
|
||||
class="absolute inset-0 rounded-lg bg-experimenta-accent/20 animate-pulse opacity-50"
|
||||
class="absolute inset-0 rounded-[25px] bg-white/10"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user