- Update the price formatting in ProductCard.vue to use `toLocaleString` to add thousand mark separators.
146 lines
No EOL
3.8 KiB
Vue
146 lines
No EOL
3.8 KiB
Vue
<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="$emit('add-to-cart', product)"
|
|
: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 { 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
|
|
}
|
|
|
|
defineProps<Props>()
|
|
|
|
defineEmits<{
|
|
'add-to-cart': [product: Product]
|
|
'view-details': [product: Product]
|
|
'view-stall': [stallId: string]
|
|
}>()
|
|
|
|
const imageError = ref(false)
|
|
|
|
const handleImageError = () => {
|
|
imageError.value = true
|
|
}
|
|
|
|
const formatPrice = (price: number, currency: string) => {
|
|
if (currency === 'sat') {
|
|
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> |