more stuff

This commit is contained in:
2025-06-29 09:20:25 -04:00
parent 5cd7db4339
commit 6aa87ea11d
7 changed files with 1614 additions and 752 deletions

View File

@@ -0,0 +1,334 @@
'use client';
import { useState } from 'react';
import { useParams } from 'next/navigation';
import { mockProducts } from '@/mock/product';
import RestrictionAlert from '@/components/RestrictionAlert';
import { StarIcon } from '@heroicons/react/20/solid';
import Image from 'next/image';
export default function ProductDetailsPage() {
const params = useParams();
const productId = params.id as string;
const product = mockProducts.find(p => p.id === productId);
const [selectedImageIndex, setSelectedImageIndex] = useState(0);
const [selectedOffer, setSelectedOffer] = useState(0);
if (!product) {
return (
<div className="container mx-auto px-4 py-8">
<div className="alert alert-error">
<span>Product not found</span>
</div>
</div>
);
}
// Use images array if present, otherwise fallback to image_url
const allImages = product.images && product.images.length > 0
? product.images
: [product.image_url];
const lowestPrice = Math.min(...product.offers.map(o => o.price));
const highestPrice = Math.max(...product.offers.map(o => o.price));
const averageRating = product.reviews
? product.reviews.reduce((acc, review) => acc + review.rating, 0) / product.reviews.length
: 0;
return (
<div className="container mx-auto px-4 py-8">
{/* Breadcrumb */}
<div className="text-sm breadcrumbs mb-6">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href={`/products?category=${product.category.id}`}>{product.category.name}</a></li>
<li>{product.name}</li>
</ul>
</div>
{/* Restriction Alert */}
{(product.restrictions?.nfa || product.restrictions?.sbr || product.restrictions?.suppressor) && (
<RestrictionAlert
type="warning"
title="Restricted Item"
message="This item may have federal or state restrictions. Please ensure compliance with all applicable laws and regulations."
icon="🔒"
/>
)}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Product Images */}
<div className="space-y-4">
<div className="aspect-square bg-neutral-100 dark:bg-neutral-800 rounded-lg overflow-hidden">
<Image
src={allImages[selectedImageIndex]}
alt={product.name}
width={600}
height={600}
className="w-full h-full object-cover"
/>
</div>
{allImages.length > 1 && (
<div className="flex gap-2 overflow-x-auto">
{allImages.map((image, index) => (
<button
key={index}
onClick={() => setSelectedImageIndex(index)}
className={`flex-shrink-0 w-20 h-20 rounded-lg overflow-hidden border-2 ${
selectedImageIndex === index
? 'border-primary-500'
: 'border-neutral-200 dark:border-neutral-700'
}`}
>
<Image
src={image}
alt={`${product.name} view ${index + 1}`}
width={80}
height={80}
className="w-full h-full object-cover"
/>
</button>
))}
</div>
)}
</div>
{/* Product Info */}
<div className="space-y-6">
{/* Brand & Category */}
<div className="flex items-center gap-4">
{product.brand.logo && (
<Image
src={product.brand.logo}
alt={product.brand.name}
width={100}
height={50}
className="h-8 w-auto"
/>
)}
<div>
<div className="text-sm text-neutral-600 dark:text-neutral-400">
{product.brand.name}
</div>
<div className="flex items-center gap-2">
<span className="text-2xl">{product.category.icon}</span>
<span className="text-sm text-neutral-600 dark:text-neutral-400">
{product.category.name}
</span>
</div>
</div>
</div>
{/* Product Name */}
<h1 className="text-3xl font-bold text-neutral-900 dark:text-white">
{product.name}
</h1>
{/* Price Range */}
<div className="flex items-center gap-4">
<div className="text-3xl font-bold text-primary-600">
${lowestPrice.toFixed(2)}
</div>
{lowestPrice !== highestPrice && (
<div className="text-lg text-neutral-600 dark:text-neutral-400">
- ${highestPrice.toFixed(2)}
</div>
)}
<div className="text-sm text-neutral-500">
from {product.offers.length} vendor{product.offers.length > 1 ? 's' : ''}
</div>
</div>
{/* Reviews */}
{product.reviews && product.reviews.length > 0 && (
<div className="flex items-center gap-2">
<div className="flex items-center">
{[1, 2, 3, 4, 5].map((star) => (
<StarIcon
key={star}
className={`h-5 w-5 ${
star <= averageRating
? 'text-yellow-400'
: 'text-neutral-300 dark:text-neutral-600'
}`}
/>
))}
</div>
<span className="text-sm text-neutral-600 dark:text-neutral-400">
{averageRating.toFixed(1)} ({product.reviews.length} reviews)
</span>
</div>
)}
{/* Description */}
<div>
<h3 className="text-lg font-semibold mb-2">Description</h3>
<p className="text-neutral-700 dark:text-neutral-300">
{product.longDescription || product.description}
</p>
</div>
{/* Add to Build Button */}
<div className="flex gap-4">
<button className="btn btn-primary flex-1">
Add to Current Build
</button>
<button className="btn btn-outline">
Save for Later
</button>
</div>
</div>
</div>
{/* Specifications */}
{product.specifications && (
<div className="mt-12">
<h2 className="text-2xl font-bold mb-6">Specifications</h2>
<div className="card">
<div className="card-body">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{product.specifications.weight && (
<div>
<span className="font-semibold">Weight:</span> {product.specifications.weight}
</div>
)}
{product.specifications.length && (
<div>
<span className="font-semibold">Length:</span> {product.specifications.length}
</div>
)}
{product.specifications.material && (
<div>
<span className="font-semibold">Material:</span> {product.specifications.material}
</div>
)}
{product.specifications.finish && (
<div>
<span className="font-semibold">Finish:</span> {product.specifications.finish}
</div>
)}
{product.specifications.caliber && (
<div>
<span className="font-semibold">Caliber:</span> {product.specifications.caliber}
</div>
)}
{product.specifications.compatibility && (
<div className="md:col-span-2">
<span className="font-semibold">Compatibility:</span>
<div className="flex flex-wrap gap-2 mt-1">
{product.specifications.compatibility.map((comp, index) => (
<span key={index} className="badge badge-outline">{comp}</span>
))}
</div>
</div>
)}
</div>
</div>
</div>
</div>
)}
{/* Vendor Offers */}
<div className="mt-12">
<h2 className="text-2xl font-bold mb-6">Where to Buy</h2>
<div className="space-y-4">
{product.offers.map((offer, index) => (
<div key={index} className="card">
<div className="card-body">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
{offer.vendor.logo && (
<Image
src={offer.vendor.logo}
alt={offer.vendor.name}
width={80}
height={40}
className="h-8 w-auto"
/>
)}
<div>
<div className="font-semibold">{offer.vendor.name}</div>
{offer.shipping && (
<div className="text-sm text-neutral-600 dark:text-neutral-400">
{offer.shipping}
</div>
)}
</div>
</div>
<div className="flex items-center gap-4">
<div className="text-right">
<div className="text-2xl font-bold text-primary-600">
${offer.price.toFixed(2)}
</div>
{offer.inStock !== undefined && (
<div className={`text-sm ${offer.inStock ? 'text-success' : 'text-error'}`}>
{offer.inStock ? 'In Stock' : 'Out of Stock'}
</div>
)}
</div>
<a
href={offer.url}
target="_blank"
rel="noopener noreferrer"
className="btn btn-primary"
>
View Deal
</a>
</div>
</div>
</div>
</div>
))}
</div>
</div>
{/* Reviews */}
{product.reviews && product.reviews.length > 0 && (
<div className="mt-12">
<h2 className="text-2xl font-bold mb-6">Customer Reviews</h2>
<div className="space-y-4">
{product.reviews.map((review) => (
<div key={review.id} className="card">
<div className="card-body">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
{[1, 2, 3, 4, 5].map((star) => (
<StarIcon
key={star}
className={`h-4 w-4 ${
star <= review.rating
? 'text-yellow-400'
: 'text-neutral-300 dark:text-neutral-600'
}`}
/>
))}
</div>
<div className="text-sm text-neutral-600 dark:text-neutral-400">
{new Date(review.date).toLocaleDateString()}
</div>
</div>
<div className="font-semibold mb-1">{review.user}</div>
<p className="text-neutral-700 dark:text-neutral-300">{review.comment}</p>
</div>
</div>
))}
</div>
</div>
)}
{/* Compatibility */}
{product.compatibility && product.compatibility.length > 0 && (
<div className="mt-12">
<h2 className="text-2xl font-bold mb-6">Compatible Parts</h2>
<div className="flex flex-wrap gap-2">
{product.compatibility.map((part, index) => (
<span key={index} className="badge badge-primary">{part}</span>
))}
</div>
</div>
)}
</div>
);
}