104 lines
3.3 KiB
TypeScript
104 lines
3.3 KiB
TypeScript
import React from 'react';
|
|
import { Link } from 'react-router-dom';
|
|
import { Item } from '../types';
|
|
import { getImageUrl } from '../services/uploadService';
|
|
|
|
interface ItemCardProps {
|
|
item: Item;
|
|
variant?: 'compact' | 'standard';
|
|
}
|
|
|
|
const ItemCard: React.FC<ItemCardProps> = ({
|
|
item,
|
|
variant = 'standard'
|
|
}) => {
|
|
const isCompact = variant === 'compact';
|
|
|
|
const getPriceDisplay = () => {
|
|
// Collect all available pricing tiers
|
|
const pricingTiers: string[] = [];
|
|
|
|
if (item.pricePerHour && Number(item.pricePerHour) > 0) {
|
|
pricingTiers.push(`$${Math.floor(Number(item.pricePerHour))}/hr`);
|
|
}
|
|
if (item.pricePerDay && Number(item.pricePerDay) > 0) {
|
|
pricingTiers.push(`$${Math.floor(Number(item.pricePerDay))}/day`);
|
|
}
|
|
if (item.pricePerWeek && Number(item.pricePerWeek) > 0) {
|
|
pricingTiers.push(`$${Math.floor(Number(item.pricePerWeek))}/wk`);
|
|
}
|
|
if (item.pricePerMonth && Number(item.pricePerMonth) > 0) {
|
|
pricingTiers.push(`$${Math.floor(Number(item.pricePerMonth))}/mo`);
|
|
}
|
|
|
|
if (pricingTiers.length === 0) {
|
|
return "Free to Borrow";
|
|
}
|
|
|
|
// Display up to 2 pricing tiers separated by bullet point
|
|
return pricingTiers.slice(0, 2).join(" • ");
|
|
};
|
|
|
|
const getLocationDisplay = () => {
|
|
return item.city && item.state
|
|
? `${item.city}, ${item.state}`
|
|
: '';
|
|
};
|
|
|
|
return (
|
|
<Link to={`/items/${item.id}`} className="text-decoration-none">
|
|
<div className="card h-100" style={{ cursor: 'pointer' }}>
|
|
{item.imageFilenames && item.imageFilenames[0] ? (
|
|
<img
|
|
src={getImageUrl(item.imageFilenames[0], 'thumbnail')}
|
|
className="card-img-top"
|
|
alt={item.name}
|
|
loading="lazy"
|
|
onError={(e) => {
|
|
// Fallback to original for images uploaded before resizing was added
|
|
const target = e.currentTarget;
|
|
if (!target.dataset.fallback) {
|
|
target.dataset.fallback = 'true';
|
|
target.src = getImageUrl(item.imageFilenames[0], 'original');
|
|
}
|
|
}}
|
|
style={{
|
|
height: isCompact ? '150px' : '200px',
|
|
objectFit: 'contain',
|
|
backgroundColor: '#f8f9fa'
|
|
}}
|
|
/>
|
|
) : (
|
|
<div
|
|
className="card-img-top bg-light d-flex align-items-center justify-content-center"
|
|
style={{ height: isCompact ? '150px' : '200px' }}
|
|
>
|
|
<i className="bi bi-image text-muted" style={{ fontSize: '2rem' }}></i>
|
|
</div>
|
|
)}
|
|
|
|
<div className={`card-body ${isCompact ? 'p-2' : ''}`}>
|
|
{isCompact ? (
|
|
<h6 className="card-title text-truncate mb-1 text-dark">{item.name}</h6>
|
|
) : (
|
|
<h5 className="card-title text-dark">{item.name}</h5>
|
|
)}
|
|
|
|
<div className={isCompact ? "mb-1" : "mb-3"}>
|
|
<div className="text-primary">
|
|
<strong className={isCompact ? "small" : ""}>
|
|
{getPriceDisplay()}
|
|
</strong>
|
|
</div>
|
|
</div>
|
|
|
|
<div className={`text-muted small ${isCompact ? 'mb-1' : 'mb-2'}`}>
|
|
<i className="bi bi-geo-alt"></i> {getLocationDisplay()}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
);
|
|
};
|
|
|
|
export default ItemCard; |