'use client'; import { useState } from 'react'; import Link from 'next/link'; import React from 'react'; import SearchInput from '@/components/SearchInput'; import RestrictionAlert from '@/components/RestrictionAlert'; import { useBuildStore } from '@/store/useBuildStore'; import { mockProducts } from '@/mock/product'; import { Dialog } from '@headlessui/react'; // AR-15 Build Requirements grouped by main categories const buildGroups = [ { name: 'Upper Parts', description: 'Components that make up the upper receiver assembly', components: [ { id: 'upper-receiver', name: 'Upper Receiver', category: 'Upper', description: 'The upper receiver houses the barrel, bolt carrier group, and charging handle', required: true, status: 'pending', estimatedPrice: 150, notes: 'Can be purchased as complete upper or stripped' }, { id: 'barrel', name: 'Barrel', category: 'Upper', description: 'The barrel determines accuracy and caliber compatibility', required: true, status: 'pending', estimatedPrice: 200, notes: 'Common lengths: 16", 18", 20"' }, { id: 'bolt-carrier-group', name: 'Bolt Carrier Group (BCG)', category: 'Upper', description: 'Handles the firing, extraction, and ejection of rounds', required: true, status: 'pending', estimatedPrice: 150, notes: 'Mil-spec or enhanced options available' }, { id: 'charging-handle', name: 'Charging Handle', category: 'Upper', description: 'Allows manual operation of the bolt carrier group', required: true, status: 'pending', estimatedPrice: 50, notes: 'Standard or ambidextrous options' }, { id: 'gas-block', name: 'Gas Block', category: 'Upper', description: 'Controls gas flow from barrel to BCG', required: true, status: 'pending', estimatedPrice: 30, notes: 'Low-profile for free-float handguards' }, { id: 'gas-tube', name: 'Gas Tube', category: 'Upper', description: 'Transfers gas from barrel to BCG', required: true, status: 'pending', estimatedPrice: 15, notes: 'Carbine, mid-length, or rifle length' }, { id: 'handguard', name: 'Handguard', category: 'Upper', description: 'Provides grip and mounting points for accessories', required: true, status: 'pending', estimatedPrice: 100, notes: 'Free-float or drop-in options' }, { id: 'muzzle-device', name: 'Muzzle Device', category: 'Upper', description: 'Flash hider, compensator, or suppressor mount', required: true, status: 'pending', estimatedPrice: 80, notes: 'A2 flash hider is standard' } ] }, { name: 'Lower Parts', description: 'Components that make up the lower receiver assembly', components: [ { id: 'lower-receiver', name: 'Lower Receiver', category: 'Lower', description: 'The lower receiver contains the trigger group and magazine well', required: true, status: 'pending', estimatedPrice: 100, notes: 'Must be purchased through FFL dealer' }, { id: 'trigger', name: 'Trigger', category: 'Lower', description: 'Controls firing mechanism', required: true, status: 'pending', estimatedPrice: 60, notes: 'Mil-spec or enhanced triggers available' }, { id: 'trigger-guard', name: 'Trigger Guard', category: 'Lower', description: 'Protects trigger from accidental discharge', required: true, status: 'pending', estimatedPrice: 10, notes: 'Often included with lower receiver' }, { id: 'pistol-grip', name: 'Pistol Grip', category: 'Lower', description: 'Provides grip for firing hand', required: true, status: 'pending', estimatedPrice: 25, notes: 'Various ergonomic options available' }, { id: 'buffer-tube', name: 'Buffer Tube', category: 'Lower', description: 'Houses buffer and spring for recoil management', required: true, status: 'pending', estimatedPrice: 40, notes: 'Carbine, A5, or rifle length' }, { id: 'buffer', name: 'Buffer', category: 'Lower', description: 'Absorbs recoil energy', required: true, status: 'pending', estimatedPrice: 20, notes: 'H1, H2, H3 weights available' }, { id: 'buffer-spring', name: 'Buffer Spring', category: 'Lower', description: 'Returns BCG to battery position', required: true, status: 'pending', estimatedPrice: 15, notes: 'Standard or enhanced springs' }, { id: 'stock', name: 'Stock', category: 'Lower', description: 'Provides shoulder support and cheek weld', required: true, status: 'pending', estimatedPrice: 60, notes: 'Fixed or adjustable options' } ] }, { name: 'Accessories', description: 'Additional components needed for a complete build', components: [ { id: 'magazine', name: 'Magazine', category: 'Accessory', description: 'Holds and feeds ammunition', required: true, status: 'pending', estimatedPrice: 15, notes: '30-round capacity is standard' }, { id: 'sights', name: 'Sights', category: 'Accessory', description: 'Iron sights or optic for aiming', required: true, status: 'pending', estimatedPrice: 100, notes: 'Backup iron sights recommended' } ] } ]; // Flatten all components for filtering and sorting const allComponents = buildGroups.flatMap(group => group.components); const categories = ["All", "Upper", "Lower", "Accessory"]; type SortField = 'name' | 'category' | 'estimatedPrice' | 'status'; type SortDirection = 'asc' | 'desc'; // Map checklist component categories to product categories for filtering const getProductCategory = (componentCategory: string): string => { const categoryMap: Record = { 'Upper': 'Upper Receiver', // Default to Upper Receiver for Upper category 'Lower': 'Lower Receiver', // Default to Lower Receiver for Lower category 'Accessory': 'Magazine', // Default to Magazine for Accessory category }; return categoryMap[componentCategory] || 'Magazine'; }; // Map specific checklist components to product categories const getProductCategoryForComponent = (componentName: string): string => { const componentMap: Record = { // Upper components 'Upper Receiver': 'Upper Receiver', 'Barrel': 'Barrel', 'Bolt Carrier Group (BCG)': 'BCG', 'Charging Handle': 'Charging Handle', 'Gas Block': 'Gas Block', 'Gas Tube': 'Gas Tube', 'Handguard': 'Handguard', 'Muzzle Device': 'Muzzle Device', // Lower components 'Lower Receiver': 'Lower Receiver', 'Trigger': 'Trigger', 'Trigger Guard': 'Lower Receiver', 'Pistol Grip': 'Lower Receiver', 'Buffer Tube': 'Lower Receiver', 'Buffer': 'Lower Receiver', 'Buffer Spring': 'Lower Receiver', 'Stock': 'Stock', // Accessories 'Magazine': 'Magazine', 'Sights': 'Magazine', }; return componentMap[componentName] || 'Lower Receiver'; }; // Add a slugify helper at the top of the file const slugify = (str: string) => str?.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)+/g, ''); export { buildGroups }; export default function BuildPage() { const [sortField, setSortField] = useState('name'); const [sortDirection, setSortDirection] = useState('asc'); const [selectedCategory, setSelectedCategory] = useState('All'); const [searchTerm, setSearchTerm] = useState(''); const selectedParts = useBuildStore((state) => state.selectedParts); const removePartForComponent = useBuildStore((state) => state.removePartForComponent); const clearBuild = useBuildStore((state) => state.clearBuild); const [showClearModal, setShowClearModal] = useState(false); // Filter components const filteredComponents = allComponents.filter(component => { if (selectedCategory !== 'All' && component.category !== selectedCategory) { return false; } if (searchTerm && !component.name.toLowerCase().includes(searchTerm.toLowerCase()) && !component.description.toLowerCase().includes(searchTerm.toLowerCase())) { return false; } return true; }); // Sort components const sortedComponents = [...filteredComponents].sort((a, b) => { let aValue: any, bValue: any; if (sortField === 'estimatedPrice') { aValue = a.estimatedPrice; bValue = b.estimatedPrice; } else if (sortField === 'category') { aValue = a.category.toLowerCase(); bValue = b.category.toLowerCase(); } else if (sortField === 'status') { aValue = a.status.toLowerCase(); bValue = b.status.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 getStatusColor = (status: string) => { switch (status) { case 'completed': return 'bg-green-100 text-green-800'; case 'in-progress': return 'bg-yellow-100 text-yellow-800'; case 'pending': return 'bg-gray-100 text-gray-800'; default: return 'bg-gray-100 text-gray-800'; } }; const totalEstimatedCost = sortedComponents.reduce((sum, component) => sum + component.estimatedPrice, 0); const completedCount = sortedComponents.filter(component => selectedParts[component.id]).length; const actualTotalCost = sortedComponents.reduce((sum, component) => { const selected = selectedParts[component.id]; if (selected && selected.offers) { return sum + Math.min(...selected.offers.map(offer => offer.price)); } return sum; }, 0); const hasActiveFilters = selectedCategory !== 'All' || searchTerm; // Check for restricted parts in the build const getRestrictedParts = () => { const restrictedParts: Array<{ part: any; restriction: string }> = []; Object.values(selectedParts).forEach(selectedPart => { if (selectedPart) { const product = mockProducts.find(p => p.id === selectedPart.id); if (product?.restrictions) { const restrictions = product.restrictions; if (restrictions.nfa) restrictedParts.push({ part: product, restriction: 'NFA' }); if (restrictions.sbr) restrictedParts.push({ part: product, restriction: 'SBR' }); if (restrictions.suppressor) restrictedParts.push({ part: product, restriction: 'Suppressor' }); if (restrictions.stateRestrictions && restrictions.stateRestrictions.length > 0) { restrictedParts.push({ part: product, restriction: 'State Restrictions' }); } } } }); return restrictedParts; }; const restrictedParts = getRestrictedParts(); const hasNFAItems = restrictedParts.some(rp => rp.restriction === 'NFA'); const hasSuppressors = restrictedParts.some(rp => rp.restriction === 'Suppressor'); const hasStateRestrictions = restrictedParts.some(rp => rp.restriction === 'State Restrictions'); const [showRestrictionAlerts, setShowRestrictionAlerts] = useState(true); return (
{/* Page Title */}

Plan Your Build

{/* Build Summary */}
{allComponents.length}
Total Components
{completedCount}
Completed
{allComponents.length - completedCount}
Remaining
${actualTotalCost.toFixed(2)}
Total Cost
{/* Clear Build Modal */} setShowClearModal(false)} className="fixed z-50 inset-0 overflow-y-auto">
{/* Restriction Alerts */} {restrictedParts.length > 0 && (
{restrictedParts.length} restriction{restrictedParts.length > 1 ? 's' : ''} detected
{showRestrictionAlerts && (
{hasNFAItems && (
πŸ”’
NFA Items in Your Build
Your build contains items that require National Firearms Act registration.
)} {hasSuppressors && (
πŸ”‡
Suppressor in Your Build
Sound suppressor requires NFA registration. Processing times: 6-12 months.
)} {hasStateRestrictions && (
πŸ—ΊοΈ
State Restrictions Apply
Some items may be restricted in certain states. Verify local laws.
)}
)}
)} {/* Search and Filters */}
{/* Filters Row */}
{/* Category Dropdown */}
{/* Status Filter */}
{/* Sort by */}
{/* Clear Filters */}
{/* Build Components Table */}
{sortedComponents.length > 0 ? ( buildGroups.map((group) => { // Filter components in this group that match current filters const groupComponents = group.components.filter(component => sortedComponents.some(sorted => sorted.id === component.id) ); if (groupComponents.length === 0) return null; return ( {/* Group Header */} {/* Group Components */} {groupComponents.map((component) => { const selected = selectedParts[component.id]; return ( ); })} ); }) ) : ( )}
Status handleSort('name')} >
Component {getSortIcon('name')}
handleSort('category')} >
Category {getSortIcon('category')}
handleSort('estimatedPrice')} >
Price {getSortIcon('estimatedPrice')}
Selected Product

{group.name}

{groupComponents.length} components
{selected ? ( Selected ) : ( {component.status} )} {selected ? (
{selected.name}
{selected.brand.name} · {component.required ? 'Required' : 'Optional'}
) : (
{component.name}
{component.required ? 'Required' : 'Optional'}
)}
{getProductCategoryForComponent(component.name)} {selected ? (
${Math.min(...selected.offers?.map(offer => offer.price) || [0]).toFixed(2)}
) : (
β€”
)}
{selected ? ( ) : ( Find Parts )}
No components found
Try adjusting your filters or search terms
{/* Table Footer */}
Showing {sortedComponents.length} of {allComponents.length} components {hasActiveFilters && ( (filtered) )}
Total Value: ${actualTotalCost.toFixed(2)}
); }