- Introduce chat module with components, services, and composables for real-time messaging. - Implement events module with API service, components, and ticket purchasing functionality. - Update app configuration to include new modules and their respective settings. - Refactor existing components to integrate with the new chat and events features. - Enhance market store and services to support new functionalities and improve order management. - Update routing to accommodate new views for chat and events, ensuring seamless navigation.
250 lines
7.7 KiB
Vue
250 lines
7.7 KiB
Vue
<template>
|
|
<div class="space-y-6">
|
|
<!-- Cart Header -->
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h2 class="text-2xl font-bold text-foreground">Shopping Cart</h2>
|
|
<p class="text-muted-foreground">
|
|
{{ totalCartItems }} items across {{ allStallCarts.length }} stalls
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex items-center space-x-4">
|
|
<div class="text-right">
|
|
<p class="text-sm text-muted-foreground">Total Value</p>
|
|
<p class="text-xl font-bold text-green-600">
|
|
{{ formatPrice(totalCartValue, 'sats') }}
|
|
</p>
|
|
</div>
|
|
|
|
<Button
|
|
v-if="allStallCarts.length > 0"
|
|
@click="clearAllCarts"
|
|
variant="outline"
|
|
size="sm"
|
|
>
|
|
Clear All
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Empty Cart State -->
|
|
<div v-if="allStallCarts.length === 0" class="text-center py-12">
|
|
<ShoppingCart class="mx-auto h-12 w-12 text-muted-foreground/50 mb-4" />
|
|
<h3 class="text-lg font-medium text-foreground mb-2">Your cart is empty</h3>
|
|
<p class="text-muted-foreground mb-6">Start shopping to add items to your cart</p>
|
|
<Button @click="$router.push('/market')" variant="default">
|
|
Continue Shopping
|
|
</Button>
|
|
</div>
|
|
|
|
<!-- Stall Carts -->
|
|
<div v-else class="space-y-6">
|
|
<div
|
|
v-for="cart in allStallCarts"
|
|
:key="cart.id"
|
|
class="border border-border rounded-lg p-6 bg-card shadow-sm"
|
|
>
|
|
<!-- Stall Header -->
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="flex items-center space-x-3">
|
|
<div class="w-10 h-10 bg-primary/10 rounded-full flex items-center justify-center">
|
|
<Store class="w-5 h-5 text-primary" />
|
|
</div>
|
|
<div>
|
|
<h3 class="font-semibold text-foreground">
|
|
{{ getStallName(cart.id) }}
|
|
</h3>
|
|
<p class="text-sm text-muted-foreground">
|
|
{{ cart.products.length }} item{{ cart.products.length !== 1 ? 's' : '' }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="text-right">
|
|
<p class="text-sm text-muted-foreground">Stall Total</p>
|
|
<p class="text-lg font-semibold text-green-600">
|
|
{{ formatPrice(cart.subtotal, cart.currency) }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Cart Items -->
|
|
<div class="space-y-3 mb-4">
|
|
<CartItem
|
|
v-for="item in cart.products"
|
|
:key="item.product.id"
|
|
:item="item"
|
|
:stall-id="cart.id"
|
|
@update-quantity="updateQuantity"
|
|
@remove-item="removeItem"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Stall Cart Actions -->
|
|
<div class="pt-4 border-t border-border">
|
|
<!-- Desktop Layout -->
|
|
<div class="hidden md:flex items-center justify-between">
|
|
<div class="flex items-center space-x-4">
|
|
<Button
|
|
@click="clearStallCart(cart.id)"
|
|
variant="outline"
|
|
size="sm"
|
|
>
|
|
Clear Stall
|
|
</Button>
|
|
|
|
<Button
|
|
@click="viewStall(cart.id)"
|
|
variant="ghost"
|
|
size="sm"
|
|
>
|
|
View Stall
|
|
</Button>
|
|
</div>
|
|
|
|
<div class="flex items-center space-x-3">
|
|
<!-- Cart Summary for this stall -->
|
|
<div class="text-right mr-4">
|
|
<p class="text-sm text-muted-foreground">Total</p>
|
|
<p class="text-lg font-semibold text-green-600">
|
|
{{ formatPrice(cart.subtotal, cart.currency) }}
|
|
</p>
|
|
</div>
|
|
|
|
<Button
|
|
@click="proceedToCheckout(cart.id)"
|
|
:disabled="!canProceedToCheckout(cart.id)"
|
|
variant="default"
|
|
>
|
|
Checkout
|
|
<ArrowRight class="w-4 h-4 ml-2" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mobile Layout -->
|
|
<div class="md:hidden space-y-4">
|
|
<!-- Action Buttons Row -->
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center space-x-2">
|
|
<Button
|
|
@click="clearStallCart(cart.id)"
|
|
variant="outline"
|
|
size="sm"
|
|
>
|
|
Clear Stall
|
|
</Button>
|
|
|
|
<Button
|
|
@click="viewStall(cart.id)"
|
|
variant="ghost"
|
|
size="sm"
|
|
>
|
|
View Stall
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Total and Checkout Row -->
|
|
<div class="flex items-center justify-between">
|
|
<!-- Cart Summary for this stall -->
|
|
<div class="text-left">
|
|
<p class="text-sm text-muted-foreground">Total</p>
|
|
<p class="text-lg font-semibold text-green-600">
|
|
{{ formatPrice(cart.subtotal, cart.currency) }}
|
|
</p>
|
|
</div>
|
|
|
|
<Button
|
|
@click="proceedToCheckout(cart.id)"
|
|
:disabled="!canProceedToCheckout(cart.id)"
|
|
variant="default"
|
|
class="flex items-center"
|
|
>
|
|
Checkout
|
|
<ArrowRight class="w-4 h-4 ml-2" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Continue Shopping Button -->
|
|
<div v-if="allStallCarts.length > 0" class="text-center mt-8">
|
|
<Button @click="$router.push('/market')" variant="outline" size="lg">
|
|
Continue Shopping
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { useMarketStore } from '@/stores/market'
|
|
import { Button } from '@/components/ui/button'
|
|
import {
|
|
ShoppingCart,
|
|
Store,
|
|
ArrowRight
|
|
} from 'lucide-vue-next'
|
|
import CartItem from './CartItem.vue'
|
|
|
|
|
|
const router = useRouter()
|
|
const marketStore = useMarketStore()
|
|
|
|
// Computed properties
|
|
const allStallCarts = computed(() => marketStore.allStallCarts)
|
|
const totalCartItems = computed(() => marketStore.totalCartItems)
|
|
const totalCartValue = computed(() => marketStore.totalCartValue)
|
|
|
|
// Methods
|
|
const getStallName = (stallId: string) => {
|
|
const stall = marketStore.stalls.find(s => s.id === stallId)
|
|
return stall?.name || 'Unknown Stall'
|
|
}
|
|
|
|
const updateQuantity = (stallId: string, productId: string, quantity: number) => {
|
|
marketStore.updateStallCartQuantity(stallId, productId, quantity)
|
|
}
|
|
|
|
const removeItem = (stallId: string, productId: string) => {
|
|
marketStore.removeFromStallCart(stallId, productId)
|
|
}
|
|
|
|
const clearStallCart = (stallId: string) => {
|
|
marketStore.clearStallCart(stallId)
|
|
}
|
|
|
|
const clearAllCarts = () => {
|
|
marketStore.clearAllStallCarts()
|
|
}
|
|
|
|
const viewStall = (stallId: string) => {
|
|
// TODO: Navigate to stall page
|
|
console.log('View stall:', stallId)
|
|
}
|
|
|
|
const proceedToCheckout = (stallId: string) => {
|
|
marketStore.setCheckoutCart(stallId)
|
|
router.push(`/checkout/${stallId}`)
|
|
}
|
|
|
|
const canProceedToCheckout = (stallId: string) => {
|
|
const cart = marketStore.stallCarts[stallId]
|
|
return cart && cart.products.length > 0
|
|
}
|
|
|
|
const formatPrice = (price: number, currency: string) => {
|
|
if (currency === 'sats' || currency === 'sat') {
|
|
return `${price.toLocaleString()} sats`
|
|
}
|
|
return new Intl.NumberFormat('en-US', {
|
|
style: 'currency',
|
|
currency: currency.toUpperCase()
|
|
}).format(price)
|
|
}
|
|
</script>
|