'use client'; import { useState, useEffect } from 'react'; import { useSearchParams, useRouter } from 'next/navigation'; import { Listbox, Transition } from '@headlessui/react'; import { ChevronUpDownIcon, CheckIcon, XMarkIcon, TableCellsIcon, Squares2X2Icon, MagnifyingGlassIcon } from '@heroicons/react/20/solid'; import SearchInput from '@/components/SearchInput'; import ProductCard from '@/components/ProductCard'; import RestrictionAlert from '@/components/RestrictionAlert'; import Tooltip from '@/components/Tooltip'; import Link from 'next/link'; import { mockProducts } from '@/mock/product'; import type { Product } from '@/mock/product'; import Image from 'next/image'; import { useBuildStore } from '@/store/useBuildStore'; import { buildGroups } from '../build/page'; // Extract unique values for dropdowns const categories = ['All', ...Array.from(new Set(mockProducts.map(part => part.category.name)))]; const brands = ['All', ...Array.from(new Set(mockProducts.map(part => part.brand.name)))]; const vendors = ['All', ...Array.from(new Set(mockProducts.flatMap(part => part.offers.map(offer => offer.vendor.name))))]; // Restrictions for filter dropdown const restrictionOptions = [ 'All', 'NFA', 'SBR', 'Suppressor', 'State Restrictions', ]; type SortField = 'name' | 'category' | 'price'; type SortDirection = 'asc' | 'desc'; // Restriction indicator component const RestrictionBadge = ({ restriction }: { restriction: string }) => { const restrictionConfig = { NFA: { label: 'NFA', color: 'bg-red-600 text-white', icon: '🔒', tooltip: 'National Firearms Act - Requires special registration' }, SBR: { label: 'SBR', color: 'bg-orange-600 text-white', icon: '📏', tooltip: 'Short Barrel Rifle - Requires NFA registration' }, SUPPRESSOR: { label: 'Suppressor', color: 'bg-purple-600 text-white', icon: '🔇', tooltip: 'Sound Suppressor - Requires NFA registration' }, FFL_REQUIRED: { label: 'FFL', color: 'bg-blue-600 text-white', icon: '🏪', tooltip: 'Federal Firearms License required for purchase' }, STATE_RESTRICTIONS: { label: 'State', color: 'bg-yellow-600 text-black', icon: '🗺️', tooltip: 'State-specific restrictions may apply' }, HIGH_CAPACITY: { label: 'High Cap', color: 'bg-pink-600 text-white', icon: '🥁', tooltip: 'High capacity magazine - check local laws' }, SILENCERSHOP_PARTNER: { label: 'SilencerShop', color: 'bg-green-600 text-white', icon: '🤝', tooltip: 'Available through SilencerShop partnership' } }; const config = restrictionConfig[restriction as keyof typeof restrictionConfig]; if (!config) return null; return (
{config.icon} {config.label}
); }; // Tailwind UI Dropdown Component const Dropdown = ({ label, value, onChange, options, placeholder = "Select option" }: { label: string; value: string; onChange: (value: string) => void; options: string[]; placeholder?: string; }) => { return (
{label} {value || placeholder} {options.map((option, optionIdx) => ( `relative cursor-default select-none py-2 pl-10 pr-4 ${ active ? 'bg-primary-100 dark:bg-primary-900 text-primary-900 dark:text-primary-100' : 'text-neutral-900 dark:text-white' }` } value={option} > {({ selected }) => ( <> {option} {selected ? ( ) : null} )} ))}
); }; // Map product categories to checklist component categories const getComponentCategory = (productCategory: string): string => { const categoryMap: Record = { // Upper components 'Upper Receiver': 'Upper', 'Barrel': 'Upper', 'BCG': 'Upper', 'Bolt Carrier Group': 'Upper', 'Charging Handle': 'Upper', 'Gas Block': 'Upper', 'Gas Tube': 'Upper', 'Handguard': 'Upper', 'Muzzle Device': 'Upper', 'Suppressor': 'Upper', // Lower components 'Lower Receiver': 'Lower', 'Trigger': 'Lower', 'Trigger Guard': 'Lower', 'Pistol Grip': 'Lower', 'Buffer Tube': 'Lower', 'Buffer': 'Lower', 'Buffer Spring': 'Lower', 'Stock': 'Lower', // Accessories 'Magazine': 'Accessory', 'Sights': 'Accessory', 'Optic': 'Accessory', 'Scope': 'Accessory', 'Red Dot': 'Accessory', }; return categoryMap[productCategory] || 'Accessory'; // Default to Accessory if no match }; // Map product categories to specific checklist component names const getMatchingComponentName = (productCategory: string): string => { const componentMap: Record = { 'Upper Receiver': 'Upper Receiver', 'Barrel': 'Barrel', 'BCG': 'Bolt Carrier Group (BCG)', 'Bolt Carrier Group': 'Bolt Carrier Group (BCG)', 'Charging Handle': 'Charging Handle', 'Gas Block': 'Gas Block', 'Gas Tube': 'Gas Tube', 'Handguard': 'Handguard', 'Muzzle Device': 'Muzzle Device', 'Suppressor': 'Muzzle Device', // Suppressors go to Muzzle Device component 'Lower Receiver': 'Lower Receiver', 'Trigger': 'Trigger', 'Trigger Guard': 'Trigger Guard', 'Pistol Grip': 'Pistol Grip', 'Buffer Tube': 'Buffer Tube', 'Buffer': 'Buffer', 'Buffer Spring': 'Buffer Spring', 'Stock': 'Stock', 'Magazine': 'Magazine', 'Sights': 'Sights', 'Optic': 'Sights', 'Scope': 'Sights', 'Red Dot': 'Sights', }; return componentMap[productCategory] || ''; }; export default function Home() { const searchParams = useSearchParams(); const router = useRouter(); const [selectedCategory, setSelectedCategory] = useState('All'); const [selectedBrand, setSelectedBrand] = useState('All'); const [selectedVendor, setSelectedVendor] = useState('All'); const [priceRange, setPriceRange] = useState(''); const [searchTerm, setSearchTerm] = useState(''); const [selectedRestriction, setSelectedRestriction] = useState(''); const [sortField, setSortField] = useState('name'); const [sortDirection, setSortDirection] = useState('asc'); const [viewMode, setViewMode] = useState<'table' | 'cards'>('table'); const [addedPartIds, setAddedPartIds] = useState([]); const [isSearchExpanded, setIsSearchExpanded] = useState(false); const selectPartForComponent = useBuildStore((state) => state.selectPartForComponent); const selectedParts = useBuildStore((state) => state.selectedParts); const removePartForComponent = useBuildStore((state) => state.removePartForComponent); // Read category from URL parameter on page load useEffect(() => { const categoryParam = searchParams.get('category'); if (categoryParam && categories.includes(categoryParam)) { setSelectedCategory(categoryParam); } }, [searchParams]); // Filter parts based on selected criteria const filteredParts = mockProducts.filter(part => { const matchesCategory = selectedCategory === 'All' || part.category.name === selectedCategory; const matchesBrand = selectedBrand === 'All' || part.brand.name === selectedBrand; const matchesVendor = selectedVendor === 'All' || part.offers.some(offer => offer.vendor.name === selectedVendor); const matchesSearch = !searchTerm || part.name.toLowerCase().includes(searchTerm.toLowerCase()) || part.description.toLowerCase().includes(searchTerm.toLowerCase()) || part.brand.name.toLowerCase().includes(searchTerm.toLowerCase()); // Restriction filter logic let matchesRestriction = true; if (selectedRestriction) { if (selectedRestriction === 'NFA') matchesRestriction = !!part.restrictions?.nfa; else if (selectedRestriction === 'SBR') matchesRestriction = !!part.restrictions?.sbr; else if (selectedRestriction === 'Suppressor') matchesRestriction = !!part.restrictions?.suppressor; else if (selectedRestriction === 'State Restrictions') matchesRestriction = !!(part.restrictions?.stateRestrictions && part.restrictions.stateRestrictions.length > 0); else matchesRestriction = false; } // Price range filtering let matchesPrice = true; if (priceRange) { const lowestPrice = Math.min(...part.offers.map(offer => offer.price)); switch (priceRange) { case 'under-100': matchesPrice = lowestPrice < 100; break; case '100-300': matchesPrice = lowestPrice >= 100 && lowestPrice <= 300; break; case '300-500': matchesPrice = lowestPrice > 300 && lowestPrice <= 500; break; case 'over-500': matchesPrice = lowestPrice > 500; break; } } return matchesCategory && matchesBrand && matchesVendor && matchesSearch && matchesPrice && matchesRestriction; }); // Sort parts const sortedParts = [...filteredParts].sort((a, b) => { let aValue: any, bValue: any; if (sortField === 'price') { aValue = Math.min(...a.offers.map(offer => offer.price)); bValue = Math.min(...b.offers.map(offer => offer.price)); } else if (sortField === 'category') { aValue = a.category.name.toLowerCase(); bValue = b.category.name.toLowerCase(); } else { aValue = a.name.toLowerCase(); bValue = b.name.toLowerCase(); } if (sortDirection === 'asc') { return aValue > bValue ? 1 : -1; } else { return aValue < bValue ? 1 : -1; } }); const handleSort = (field: SortField) => { if (sortField === field) { setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc'); } else { setSortField(field); setSortDirection('asc'); } }; const getSortIcon = (field: SortField) => { if (sortField !== field) { return '↕️'; } return sortDirection === 'asc' ? '↑' : '↓'; }; const clearFilters = () => { setSelectedCategory('All'); setSelectedBrand('All'); setSelectedVendor('All'); setSearchTerm(''); setPriceRange(''); setSelectedRestriction(''); }; const hasActiveFilters = selectedCategory !== 'All' || selectedBrand !== 'All' || selectedVendor !== 'All' || searchTerm || priceRange || selectedRestriction; // RestrictionBadge for table view (show NFA/SBR/Suppressor/State) const getRestrictionFlags = (restrictions?: Product['restrictions']) => { 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; }; const handleAdd = (part: Product) => { setAddedPartIds((prev) => [...prev, part.id]); setTimeout(() => setAddedPartIds((prev) => prev.filter((id) => id !== part.id)), 1500); }; return (
{/* Page Title */}

Parts Catalog {selectedCategory !== 'All' && ( - {selectedCategory} )}

{selectedCategory !== 'All' ? `Showing ${selectedCategory} parts for your build` : 'Browse and filter firearm parts for your build' }

{/* Search and Filters */}
{/* Search Row */}
{isSearchExpanded ? (
) : ( )}
{/* Filters Row */}
{/* Category Dropdown */}
{/* Brand Dropdown */}
{/* Vendor Dropdown */}
{/* Price Range */}
Price Range {priceRange === '' ? 'Select price range' : priceRange === 'under-100' ? 'Under $100' : priceRange === '100-300' ? '$100 - $300' : priceRange === '300-500' ? '$300 - $500' : priceRange === 'over-500' ? '$500+' : priceRange} {[ { value: '', label: 'Select price range' }, { value: 'under-100', label: 'Under $100' }, { value: '100-300', label: '$100 - $300' }, { value: '300-500', label: '$300 - $500' }, { value: 'over-500', label: '$500+' } ].map((option, optionIdx) => ( `relative cursor-default select-none py-2 pl-10 pr-4 ${ active ? 'bg-primary-100 dark:bg-primary-900 text-primary-900 dark:text-primary-100' : 'text-neutral-900 dark:text-white' }` } value={option.value} > {({ selected }) => ( <> {option.label} {selected ? ( ) : null} )} ))}
{/* Restriction Filter */}
{/* Clear Filters */}
{/* Parts Display */}
{/* View Toggle and Results Count */}
Showing {sortedParts.length} of {mockProducts.length} parts {hasActiveFilters && ( (filtered) )}
{/* View Toggle */}
View:
{/* Table View */} {viewMode === 'table' && (
{sortedParts.map((part) => ( ))}
Product Category handleSort('price')} >
Price {getSortIcon('price')}
Actions
0 ? (part.images as string[])[0] : '/window.svg'} alt={part.name} width={48} height={48} className="object-contain w-12 h-12" />
{part.name}
{part.brand.name}
{part.category.name}
${Math.min(...part.offers.map(offer => offer.price)).toFixed(2)}
{(() => { // Find if this part is already selected for any component const selectedComponentId = Object.entries(selectedParts).find(([_, selectedPart]) => selectedPart?.id === part.id)?.[0]; // Find the appropriate component based on category match const matchingComponentName = getMatchingComponentName(part.category.name); const matchingComponent = (buildGroups as {components: any[]}[]).flatMap((group) => group.components).find((component: any) => component.name === matchingComponentName && !selectedParts[component.id] ); if (selectedComponentId) { return ( ); } else if (matchingComponent && !selectedParts[matchingComponent.id]) { return ( ); } else { return ( Part Selected ); } })()}
{/* Table Footer */}
Showing {sortedParts.length} of {mockProducts.length} parts {hasActiveFilters && ( (filtered) )}
Total Value: ${sortedParts.reduce((sum, part) => sum + Math.min(...part.offers.map(offer => offer.price)), 0).toFixed(2)}
)} {/* Card View */} {viewMode === 'cards' && (
{sortedParts.map((part) => ( handleAdd(part)} added={addedPartIds.includes(part.id)} /> ))}
)}
{/* Compact Restriction Legend */}
Restrictions:
🔒NFA
National Firearms Act
📏SBR
Short Barrel Rifle
🔇Suppressor
Sound Suppressor
🏪FFL
FFL Required
🗺️State
State Restrictions
🥁High Cap
High Capacity
🤝SilencerShop
SilencerShop Partner
); }