Add comprehensive plant trading marketplace with: - Prisma schema with marketplace models (Listing, Offer, SellerProfile, WishlistItem) - Service layer for listings, offers, search, and matching - API endpoints for CRUD operations, search, and recommendations - Marketplace pages: home, listing detail, create, my-listings, my-offers - Reusable UI components: ListingCard, ListingGrid, OfferForm, SearchFilters, etc. Features: - Browse and search listings by category, price, tags - Create and manage listings (draft, active, sold, cancelled) - Make and manage offers on listings - Seller and buyer views with statistics - Featured and recommended listings - In-memory store (ready for database migration via Agent 2)
96 lines
2.1 KiB
TypeScript
96 lines
2.1 KiB
TypeScript
interface PriceDisplayProps {
|
|
price: number;
|
|
currency?: string;
|
|
originalPrice?: number;
|
|
size?: 'sm' | 'md' | 'lg' | 'xl';
|
|
showCurrency?: boolean;
|
|
}
|
|
|
|
const sizeClasses = {
|
|
sm: 'text-sm',
|
|
md: 'text-lg',
|
|
lg: 'text-2xl',
|
|
xl: 'text-4xl',
|
|
};
|
|
|
|
export function PriceDisplay({
|
|
price,
|
|
currency = 'USD',
|
|
originalPrice,
|
|
size = 'md',
|
|
showCurrency = false,
|
|
}: PriceDisplayProps) {
|
|
const hasDiscount = originalPrice && originalPrice > price;
|
|
const discountPercentage = hasDiscount
|
|
? Math.round((1 - price / originalPrice) * 100)
|
|
: 0;
|
|
|
|
const formatPrice = (value: number) => {
|
|
return new Intl.NumberFormat('en-US', {
|
|
style: 'currency',
|
|
currency: currency,
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2,
|
|
}).format(value);
|
|
};
|
|
|
|
return (
|
|
<div className="flex items-baseline gap-2">
|
|
<span className={`font-bold text-green-600 ${sizeClasses[size]}`}>
|
|
{formatPrice(price)}
|
|
</span>
|
|
|
|
{showCurrency && (
|
|
<span className="text-gray-500 text-sm">{currency}</span>
|
|
)}
|
|
|
|
{hasDiscount && (
|
|
<>
|
|
<span className="text-gray-400 line-through text-sm">
|
|
{formatPrice(originalPrice)}
|
|
</span>
|
|
<span className="px-2 py-0.5 bg-red-100 text-red-700 text-xs rounded-full font-medium">
|
|
{discountPercentage}% OFF
|
|
</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface PriceRangeDisplayProps {
|
|
minPrice: number;
|
|
maxPrice: number;
|
|
currency?: string;
|
|
}
|
|
|
|
export function PriceRangeDisplay({
|
|
minPrice,
|
|
maxPrice,
|
|
currency = 'USD',
|
|
}: PriceRangeDisplayProps) {
|
|
const formatPrice = (value: number) => {
|
|
return new Intl.NumberFormat('en-US', {
|
|
style: 'currency',
|
|
currency: currency,
|
|
minimumFractionDigits: 0,
|
|
maximumFractionDigits: 0,
|
|
}).format(value);
|
|
};
|
|
|
|
if (minPrice === maxPrice) {
|
|
return (
|
|
<span className="font-semibold text-green-600">
|
|
{formatPrice(minPrice)}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<span className="font-semibold text-green-600">
|
|
{formatPrice(minPrice)} - {formatPrice(maxPrice)}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
export default PriceDisplay;
|