Enhance market module with new chat and events features
- 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.
This commit is contained in:
parent
519a9003d4
commit
e40ac91417
46 changed files with 6305 additions and 3264 deletions
151
src/modules/market/components/ProductCard.vue
Normal file
151
src/modules/market/components/ProductCard.vue
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
<template>
|
||||
<Card class="overflow-hidden hover:shadow-lg transition-shadow duration-200">
|
||||
<!-- Product Image -->
|
||||
<div class="relative">
|
||||
<img
|
||||
:src="product.images?.[0] || '/placeholder-product.png'"
|
||||
:alt="product.name"
|
||||
class="w-full h-48 object-cover"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
|
||||
<!-- Add to Cart Button -->
|
||||
<Button
|
||||
@click="addToCart"
|
||||
:disabled="product.quantity < 1"
|
||||
size="sm"
|
||||
class="absolute top-2 right-2 bg-blue-600 hover:bg-blue-700 text-white"
|
||||
>
|
||||
<ShoppingCart class="w-4 h-4" />
|
||||
</Button>
|
||||
|
||||
<!-- Out of Stock Badge -->
|
||||
<Badge
|
||||
v-if="product.quantity < 1"
|
||||
variant="destructive"
|
||||
class="absolute top-2 left-2"
|
||||
>
|
||||
Out of Stock
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<CardContent class="p-4">
|
||||
<!-- Product Name -->
|
||||
<CardTitle class="text-lg font-semibold mb-2 line-clamp-2">
|
||||
{{ product.name }}
|
||||
</CardTitle>
|
||||
|
||||
<!-- Product Description -->
|
||||
<p v-if="product.description" class="text-gray-600 text-sm mb-3 line-clamp-2">
|
||||
{{ product.description }}
|
||||
</p>
|
||||
|
||||
<!-- Price and Quantity -->
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<span class="text-xl font-bold text-green-600">
|
||||
{{ formatPrice(product.price, product.currency) }}
|
||||
</span>
|
||||
<span class="text-sm text-gray-500">
|
||||
{{ product.quantity }} left
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Categories -->
|
||||
<div v-if="product.categories && product.categories.length > 0" class="mb-3">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<Badge
|
||||
v-for="category in product.categories.slice(0, 3)"
|
||||
:key="category"
|
||||
variant="secondary"
|
||||
class="text-xs"
|
||||
>
|
||||
{{ category }}
|
||||
</Badge>
|
||||
<Badge
|
||||
v-if="product.categories.length > 3"
|
||||
variant="outline"
|
||||
class="text-xs"
|
||||
>
|
||||
+{{ product.categories.length - 3 }} more
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stall Name -->
|
||||
<div class="text-sm text-gray-500 mb-3">
|
||||
{{ product.stallName }}
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
<CardFooter class="p-4 pt-0">
|
||||
<div class="flex w-full space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="flex-1"
|
||||
@click="$emit('view-stall', product.stall_id)"
|
||||
>
|
||||
Visit Stall
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
class="flex-1"
|
||||
@click="$emit('view-details', product)"
|
||||
>
|
||||
View Details
|
||||
</Button>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useMarketStore } from '@/stores/market'
|
||||
import { Card, CardContent, CardFooter, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { ShoppingCart } from 'lucide-vue-next'
|
||||
import type { Product } from '@/stores/market'
|
||||
|
||||
interface Props {
|
||||
product: Product
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
// const emit = defineEmits<{
|
||||
// 'view-details': [product: Product]
|
||||
// 'view-stall': [stallId: string]
|
||||
// }>()
|
||||
|
||||
const marketStore = useMarketStore()
|
||||
const imageError = ref(false)
|
||||
|
||||
const addToCart = () => {
|
||||
marketStore.addToStallCart(props.product, 1)
|
||||
}
|
||||
|
||||
const handleImageError = () => {
|
||||
imageError.value = true
|
||||
}
|
||||
|
||||
const formatPrice = (price: number, currency: string) => {
|
||||
if (currency === 'sat' || currency === 'sats') {
|
||||
return `${price.toLocaleString('en-US')} sats`
|
||||
}
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: currency.toUpperCase()
|
||||
}).format(price)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue