Files
gunbuilder-next-tailwind/src/components/ProductCard.tsx
T
2025-06-30 06:36:03 -04:00

141 lines
4.5 KiB
TypeScript

'use client';
import { useState } from 'react';
import Link from 'next/link';
import { Product } from '@/mock/product';
interface ProductCardProps {
product: Product;
onAdd?: () => void;
added?: boolean;
}
function getRestrictionFlags(restrictions?: Product['restrictions']): string[] {
const flags: string[] = [];
if (restrictions?.nfa) flags.push('NFA');
if (restrictions?.sbr) flags.push('SBR');
if (restrictions?.suppressor) flags.push('SUPPRESSOR');
if (restrictions?.stateRestrictions && restrictions.stateRestrictions.length > 0) flags.push('STATE_RESTRICTIONS');
return flags;
}
export default function ProductCard({ product, onAdd, added }: ProductCardProps) {
const [imageError, setImageError] = useState(false);
const lowestPrice = Math.min(...product.offers.map(offer => offer.price));
// Restriction badge component
const RestrictionBadge = ({ restriction }: { restriction: string }) => {
const restrictionConfig = {
NFA: {
label: 'NFA',
color: 'badge-error',
icon: '🔒',
tooltip: 'National Firearms Act - Requires special registration'
},
SBR: {
label: 'SBR',
color: 'badge-warning',
icon: '📏',
tooltip: 'Short Barrel Rifle - Requires NFA registration'
},
SUPPRESSOR: {
label: 'Suppressor',
color: 'badge-secondary',
icon: '🔇',
tooltip: 'Sound Suppressor - Requires NFA registration'
},
FFL_REQUIRED: {
label: 'FFL',
color: 'badge-info',
icon: '🏪',
tooltip: 'Federal Firearms License required for purchase'
},
STATE_RESTRICTIONS: {
label: 'State',
color: 'badge-warning',
icon: '🗺️',
tooltip: 'State-specific restrictions may apply'
},
HIGH_CAPACITY: {
label: 'High Cap',
color: 'badge-accent',
icon: '🥁',
tooltip: 'High capacity magazine - check local laws'
},
SILENCERSHOP_PARTNER: {
label: 'SilencerShop',
color: 'badge-success',
icon: '🤝',
tooltip: 'Available through SilencerShop partnership'
}
};
const config = restrictionConfig[restriction as keyof typeof restrictionConfig];
if (!config) return null;
return (
<div
className={`badge ${config.color} gap-1 cursor-help`}
title={config.tooltip}
>
<span>{config.icon}</span>
<span>{config.label}</span>
</div>
);
};
return (
<div className="card bg-base-100 shadow-lg hover:shadow-xl transition-shadow duration-300 border border-base-300">
<figure className="relative">
<img
src={imageError ? '/window.svg' : product.image_url}
alt={product.name}
className="w-full h-48 object-cover"
onError={() => setImageError(true)}
/>
{getRestrictionFlags(product.restrictions).length > 0 && (
<div className="absolute top-2 left-2 flex flex-wrap gap-1">
{getRestrictionFlags(product.restrictions).map((restriction) => (
<RestrictionBadge key={restriction} restriction={restriction} />
))}
</div>
)}
</figure>
<div className="card-body">
<h3 className="card-title text-base-content line-clamp-2">{product.name}</h3>
<p className="text-base-content/70 text-sm line-clamp-2">{product.description}</p>
<div className="flex items-center justify-between mb-3">
<span className="text-sm text-base-content/60">{product.brand.name}</span>
<span className="text-lg font-bold text-primary">${lowestPrice.toFixed(2)}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-xs text-base-content/50">{product.category.name}</span>
<Link href={`/products/${product.id}`} legacyBehavior>
<a className="btn btn-primary btn-sm">
View Details
</a>
</Link>
{onAdd && (
<button
className="btn btn-neutral btn-sm ml-2 flex items-center gap-1"
onClick={onAdd}
disabled={added}
>
{added ? (
'Added!'
) : (
<>
<span className="text-lg leading-none">+</span>
<span className="text-xs font-normal">to build</span>
</>
)}
</button>
)}
</div>
</div>
</div>
);
}