- Schema.org JSON-LD templates (product, event, local-business, faq) - Brand, UI, SEO, and decision guide rules - Working code snippets (vendor-card, schema-inject, deploy-webhook) - JSON schemas for project config validation - Client presets (slc-bride, default) - Self-update protocol with changelog tracking Made-with: Cursor
92 lines
2.7 KiB
TypeScript
92 lines
2.7 KiB
TypeScript
import Image from 'next/image'
|
|
import Link from 'next/link'
|
|
import { Card, CardContent } from '@/components/ui/card'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { Star, MapPin } from 'lucide-react'
|
|
|
|
interface Vendor {
|
|
id: string
|
|
name: string
|
|
slug: string
|
|
category: string
|
|
description: string
|
|
image_url: string
|
|
rating: number
|
|
review_count: number
|
|
location: string
|
|
price_range?: string
|
|
}
|
|
|
|
interface VendorCardProps {
|
|
vendor: Vendor
|
|
className?: string
|
|
}
|
|
|
|
export function VendorCard({ vendor, className }: VendorCardProps) {
|
|
return (
|
|
<Link href={`/vendors/${vendor.slug}`}>
|
|
<Card className={cn(
|
|
'group overflow-hidden transition-all duration-300 hover:shadow-lg',
|
|
className
|
|
)}>
|
|
{/* Image Container */}
|
|
<div className="relative aspect-[4/3] overflow-hidden">
|
|
<Image
|
|
src={vendor.image_url}
|
|
alt={`${vendor.name} - ${vendor.category} in ${vendor.location}`}
|
|
fill
|
|
className="object-cover transition-transform duration-300 group-hover:scale-105"
|
|
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
|
/>
|
|
{vendor.price_range && (
|
|
<Badge className="absolute top-3 right-3" variant="secondary">
|
|
{vendor.price_range}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<CardContent className="p-4">
|
|
{/* Category Badge */}
|
|
<Badge variant="outline" className="mb-2">
|
|
{vendor.category}
|
|
</Badge>
|
|
|
|
{/* Name */}
|
|
<h3 className="font-semibold text-lg mb-1 group-hover:text-primary transition-colors">
|
|
{vendor.name}
|
|
</h3>
|
|
|
|
{/* Rating */}
|
|
<div className="flex items-center gap-1 mb-2">
|
|
<Star className="h-4 w-4 fill-yellow-400 text-yellow-400" />
|
|
<span className="font-medium">{vendor.rating.toFixed(1)}</span>
|
|
<span className="text-muted-foreground text-sm">
|
|
({vendor.review_count} reviews)
|
|
</span>
|
|
</div>
|
|
|
|
{/* Location */}
|
|
<div className="flex items-center gap-1 text-muted-foreground text-sm">
|
|
<MapPin className="h-3 w-3" />
|
|
<span>{vendor.location}</span>
|
|
</div>
|
|
|
|
{/* Description - truncated */}
|
|
<p className="mt-2 text-sm text-muted-foreground line-clamp-2">
|
|
{vendor.description}
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</Link>
|
|
)
|
|
}
|
|
|
|
// Usage:
|
|
// import { VendorCard } from '@/components/vendor-card'
|
|
//
|
|
// <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
// {vendors.map((vendor) => (
|
|
// <VendorCard key={vendor.id} vendor={vendor} />
|
|
// ))}
|
|
// </div>
|