From ccc6e41724c49e41b574ea6d44554066c465c1dd Mon Sep 17 00:00:00 2001 From: Sean S Date: Sun, 29 Jun 2025 15:58:03 -0400 Subject: [PATCH] add state management and other stuff --- next.config.ts | 9 +- package-lock.json | 36 +- package.json | 3 +- src/app/build/page.tsx | 408 +++++++--- src/app/page.tsx | 4 +- src/app/parts/page.tsx | 303 ++++++-- src/app/products/[id]/page.tsx | 56 +- src/components/Navbar.tsx | 2 +- src/components/ProductCard.tsx | 15 +- src/mock/product.ts | 1301 +++++++++++++------------------- src/store/useBuildStore.ts | 53 ++ 11 files changed, 1221 insertions(+), 969 deletions(-) create mode 100644 src/store/useBuildStore.ts diff --git a/next.config.ts b/next.config.ts index 0904350..862135b 100644 --- a/next.config.ts +++ b/next.config.ts @@ -2,14 +2,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { images: { - remotePatterns: [ - { - protocol: 'https', - hostname: 'placehold.co', - port: '', - pathname: '/**', - }, - ], + remotePatterns: [], }, }; diff --git a/package-lock.json b/package-lock.json index 3c23630..d5d9c8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,8 @@ "daisyui": "^4.7.3", "next": "15.3.4", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "zustand": "^5.0.6" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -1258,7 +1259,7 @@ "version": "19.1.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -2545,7 +2546,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/culori": { @@ -6826,6 +6827,35 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.6.tgz", + "integrity": "sha512-ihAqNeUVhe0MAD+X8M5UzqyZ9k3FFZLBTtqo6JLPwV53cbRB/mJwBI0PxcIgqhBBHlEs8G45OTDTMq3gNcLq3A==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 8e01a43..027521e 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "daisyui": "^4.7.3", "next": "15.3.4", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "zustand": "^5.0.6" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/src/app/build/page.tsx b/src/app/build/page.tsx index 9ee7ee4..a3aca2f 100644 --- a/src/app/build/page.tsx +++ b/src/app/build/page.tsx @@ -4,6 +4,10 @@ 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 = [ @@ -215,12 +219,60 @@ 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'; +}; + +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) { @@ -278,36 +330,66 @@ export default function BuildPage() { const getStatusColor = (status: string) => { switch (status) { - case 'completed': - return 'bg-green-100 text-green-800'; - case 'pending': - return 'bg-yellow-100 text-yellow-800'; - case 'in-progress': - return 'bg-blue-100 text-blue-800'; - default: - return 'bg-gray-100 text-gray-800'; + 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 => component.status === 'completed').length; + 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 */}
-

AR-15 Build Checklist

-

Track your build progress and find required components

+

Plan Your Build

{/* Build Summary */}
-
+
{allComponents.length}
Total Components
@@ -320,26 +402,138 @@ export default function BuildPage() {
{allComponents.length - completedCount}
Remaining
-
-
${totalEstimatedCost}
-
Estimated Cost
+
+
+
${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 */} -
+
+ handleSort(e.target.value as SortField)} - className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white text-gray-900" + className="w-full px-3 py-1.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white text-gray-900 text-sm" > @@ -376,8 +570,8 @@ export default function BuildPage() {
{/* Clear Filters */} -
-
@@ -387,10 +581,10 @@ export default function BuildPage() { {/* Build Components Table */}
-
-
+
+
- + - @@ -429,7 +620,7 @@ export default function BuildPage() { Notes @@ -446,78 +637,103 @@ export default function BuildPage() { return ( {/* Group Header */} - - + - {/* Group Components */} - {groupComponents.map((component) => ( - - - - - - - - - - ))} + {groupComponents.map((component) => { + const selected = selectedParts[component.id]; + return ( + + + + + + + + + ); + })} ); }) @@ -547,7 +763,7 @@ export default function BuildPage() { )}
- Total Value: ${sortedComponents.reduce((sum, component) => sum + component.estimatedPrice, 0).toFixed(2)} + Total Value: ${actualTotalCost.toFixed(2)}
diff --git a/src/app/page.tsx b/src/app/page.tsx index 2ada719..088b87f 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -34,7 +34,7 @@ export default function LandingPage() {

Get Building @@ -46,7 +46,7 @@ export default function LandingPage() { AR-15 Lower Receiver
diff --git a/src/app/parts/page.tsx b/src/app/parts/page.tsx index 8246ec1..22530a5 100644 --- a/src/app/parts/page.tsx +++ b/src/app/parts/page.tsx @@ -1,9 +1,9 @@ 'use client'; import { useState, useEffect } from 'react'; -import { useSearchParams } from 'next/navigation'; +import { useSearchParams, useRouter } from 'next/navigation'; import { Listbox, Transition } from '@headlessui/react'; -import { ChevronUpDownIcon, CheckIcon, XMarkIcon, TableCellsIcon, Squares2X2Icon } from '@heroicons/react/20/solid'; +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'; @@ -12,6 +12,8 @@ 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)))]; @@ -20,11 +22,11 @@ const vendors = ['All', ...Array.from(new Set(mockProducts.flatMap(part => part. // Restrictions for filter dropdown const restrictionOptions = [ - '', + 'All', 'NFA', 'SBR', - 'SUPPRESSOR', - 'STATE_RESTRICTIONS', + 'Suppressor', + 'State Restrictions', ]; type SortField = 'name' | 'category' | 'price'; @@ -112,13 +114,13 @@ const Dropdown = ({ {label} - + {value || placeholder} @@ -128,7 +130,7 @@ const Dropdown = ({ leave="transition ease-in duration-100" leaveFrom="opacity-100" leaveTo="opacity-0" - className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white dark:bg-neutral-800 py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm" + className="absolute z-20 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white dark:bg-neutral-800 py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm" > {options.map((option, optionIdx) => ( @@ -148,7 +150,7 @@ const Dropdown = ({ {selected ? ( - ) : null} @@ -163,8 +165,78 @@ const Dropdown = ({ ); }; +// 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'); @@ -174,6 +246,11 @@ export default function Home() { 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(() => { @@ -198,8 +275,8 @@ export default function Home() { 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 if (selectedRestriction === 'Suppressor') matchesRestriction = !!part.restrictions?.suppressor; + else if (selectedRestriction === 'State Restrictions') matchesRestriction = !!(part.restrictions?.stateRestrictions && part.restrictions.stateRestrictions.length > 0); else matchesRestriction = false; } @@ -280,11 +357,16 @@ export default function Home() { 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'); + 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 */} @@ -309,56 +391,86 @@ export default function Home() { {/* 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' : @@ -368,7 +480,7 @@ export default function Home() { @@ -378,7 +490,7 @@ export default function Home() { leave="transition ease-in duration-100" leaveFrom="opacity-100" leaveTo="opacity-0" - className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white dark:bg-neutral-800 py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm" + className="absolute z-20 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white dark:bg-neutral-800 py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm" > {[ @@ -404,7 +516,7 @@ export default function Home() { {selected ? ( - ) : null} @@ -418,26 +530,28 @@ export default function Home() {
{/* Restriction Filter */} - +
+ +
{/* Clear Filters */} -
+
@@ -480,24 +594,12 @@ export default function Home() {
- {/* Restriction Alert Example */} - {sortedParts.some(part => part.restrictions?.nfa) && ( -
- -
- )} - {/* Table View */} {viewMode === 'table' && (
-
+
Status @@ -413,15 +607,12 @@ export default function BuildPage() { {getSortIcon('category')} - Description - handleSort('estimatedPrice')} >
- Est. Price + Price {getSortIcon('estimatedPrice')}
- Actions + Selected Product
+
-
-
- - {group.name === 'Upper Parts' ? 'πŸ”«' : - group.name === 'Lower Parts' ? 'πŸ”§' : 'πŸ“¦'} - -
-
-
-

{group.name}

-

{group.description}

+
+

{group.name}

-
+
{groupComponents.length} components
- - {component.status} - - -
- {component.name} -
-
- {component.required ? 'Required' : 'Optional'} -
-
- - {component.category} - - -
- {component.description} -
-
-
- ${component.estimatedPrice} -
-
-
- {component.notes} -
-
-
- - Find Parts - -
-
+ {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)} +
+ ) : ( +
+ β€” +
+ )} +
+
+ {component.notes} +
+
+ {selected ? ( + + ) : ( + + Find Parts + + )} +
- + ))} @@ -577,7 +720,7 @@ export default function Home() { {viewMode === 'cards' && (
{sortedParts.map((part) => ( - + handleAdd(part)} added={addedPartIds.includes(part.id)} /> ))}
)} diff --git a/src/app/products/[id]/page.tsx b/src/app/products/[id]/page.tsx index 86aae04..039ef1d 100644 --- a/src/app/products/[id]/page.tsx +++ b/src/app/products/[id]/page.tsx @@ -6,6 +6,7 @@ import { mockProducts } from '@/mock/product'; import RestrictionAlert from '@/components/RestrictionAlert'; import { StarIcon } from '@heroicons/react/20/solid'; import Image from 'next/image'; +import { useBuildStore } from '@/store/useBuildStore'; export default function ProductDetailsPage() { const params = useParams(); @@ -14,6 +15,8 @@ export default function ProductDetailsPage() { const product = mockProducts.find(p => p.id === productId); const [selectedImageIndex, setSelectedImageIndex] = useState(0); const [selectedOffer, setSelectedOffer] = useState(0); + const [addSuccess, setAddSuccess] = useState(false); + const selectPartForComponent = useBuildStore((state) => state.selectPartForComponent); if (!product) { return ( @@ -35,14 +38,51 @@ export default function ProductDetailsPage() { ? product.reviews.reduce((acc, review) => acc + review.rating, 0) / product.reviews.length : 0; + const handleAddToBuild = () => { + // Map category to component ID + const categoryToComponentMap: Record = { + 'Barrel': 'barrel', + 'Upper Receiver': 'upper', + 'Suppressor': 'suppressor', + 'BCG': 'bcg', + 'Charging Handle': 'charging-handle', + 'Handguard': 'handguard', + 'Gas Block': 'gas-block', + 'Gas Tube': 'gas-tube', + 'Muzzle Device': 'muzzle-device', + 'Lower Receiver': 'lower', + 'Trigger': 'trigger', + 'Pistol Grip': 'pistol-grip', + 'Buffer Tube': 'buffer-tube', + 'Buffer': 'buffer', + 'Buffer Spring': 'buffer-spring', + 'Stock': 'stock', + 'Magazine': 'magazine', + 'Sights': 'sights' + }; + + const componentId = categoryToComponentMap[product.category.name] || product.category.id; + + selectPartForComponent(componentId, { + id: product.id, + name: product.name, + image_url: product.image_url, + brand: product.brand, + category: product.category, + offers: product.offers + }); + setAddSuccess(true); + setTimeout(() => setAddSuccess(false), 1500); + }; + return (
{/* Breadcrumb */} @@ -112,11 +152,8 @@ export default function ProductDetailsPage() {
{product.brand.name}
-
- {product.category.icon} - - {product.category.name} - +
+ {product.category.name}
@@ -172,13 +209,16 @@ export default function ProductDetailsPage() { {/* Add to Build Button */}
-
+ {addSuccess && ( +
Added to build!
+ )} diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 8bc8901..2608dec 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -18,7 +18,7 @@ export default function Navbar() { <> {/* Top Bar */}
- Pew Builder + Pew Builder
diff --git a/src/components/ProductCard.tsx b/src/components/ProductCard.tsx index 1b380a8..85f7284 100644 --- a/src/components/ProductCard.tsx +++ b/src/components/ProductCard.tsx @@ -6,6 +6,8 @@ import { Product } from '@/mock/product'; interface ProductCardProps { product: Product; + onAdd?: () => void; + added?: boolean; } function getRestrictionFlags(restrictions?: Product['restrictions']): string[] { @@ -17,7 +19,7 @@ function getRestrictionFlags(restrictions?: Product['restrictions']): string[] { return flags; } -export default function ProductCard({ product }: ProductCardProps) { +export default function ProductCard({ product, onAdd, added }: ProductCardProps) { const [imageError, setImageError] = useState(false); const lowestPrice = Math.min(...product.offers.map(offer => offer.price)); @@ -86,7 +88,7 @@ export default function ProductCard({ product }: ProductCardProps) {
{product.name} setImageError(true)} @@ -116,6 +118,15 @@ export default function ProductCard({ product }: ProductCardProps) { View Details + {onAdd && ( + + )}
diff --git a/src/mock/product.ts b/src/mock/product.ts index 3d0305b..fac6dfd 100644 --- a/src/mock/product.ts +++ b/src/mock/product.ts @@ -13,7 +13,6 @@ export interface Product { category: { id: string; name: string; - icon?: string; }; specifications?: { weight?: string; @@ -21,6 +20,7 @@ export interface Product { material?: string; finish?: string; caliber?: string; + capacity?: string; compatibility?: string[]; }; restrictions?: { @@ -56,21 +56,20 @@ export const mockProducts: Product[] = [ name: 'Faxon 16" Gunner Barrel - 5.56 NATO', description: 'Lightweight, high-performance AR-15 barrel.', longDescription: 'The Faxon 16" Gunner Profile barrel offers the perfect balance of weight and performance. The Gunner profile reduces weight while maintaining accuracy and heat dissipation. Features a 1:8 twist rate for optimal bullet stabilization with a wide range of ammunition.', - image_url: 'https://placehold.co/300x200/1f2937/ffffff?text=Barrel', + image_url: '/window.svg', images: [ - 'https://placehold.co/600x400/1f2937/ffffff?text=Barrel+Front', - 'https://placehold.co/600x400/1f2937/ffffff?text=Barrel+Side', - 'https://placehold.co/600x400/1f2937/ffffff?text=Barrel+Chamber' + '/window.svg', + '/window.svg', + '/window.svg' ], brand: { id: 'b1', name: 'Faxon Firearms', - logo: 'https://placehold.co/100x50/1f2937/ffffff?text=Faxon' + logo: '/window.svg' }, category: { id: 'c1', - name: 'Barrel', - icon: 'πŸ”«' + name: 'Barrel' }, specifications: { weight: '1.5 lbs', @@ -92,7 +91,7 @@ export const mockProducts: Product[] = [ url: 'https://primaryarms.com/faxon-16-gunner-barrel', vendor: { name: 'Primary Arms', - logo: 'https://placehold.co/80x40/1f2937/ffffff?text=PA' + logo: '/window.svg' }, inStock: true, shipping: 'Free shipping on orders over $150' @@ -102,7 +101,7 @@ export const mockProducts: Product[] = [ url: 'https://brownells.com/faxon-16-gunner-barrel', vendor: { name: 'Brownells', - logo: 'https://placehold.co/80x40/1f2937/ffffff?text=Brownells' + logo: '/window.svg' }, inStock: true, shipping: '$9.99 flat rate' @@ -132,21 +131,20 @@ export const mockProducts: Product[] = [ name: 'BCM M4 Upper Receiver', description: 'Forged upper with M4 feed ramps.', longDescription: 'The BCM M4 Upper Receiver is forged from 7075-T6 aluminum and features M4 feed ramps for reliable feeding. Includes forward assist and dust cover. Mil-spec design ensures compatibility with standard AR-15 parts.', - image_url: 'https://placehold.co/300x200/1f2937/ffffff?text=Upper', + image_url: '/window.svg', images: [ - 'https://placehold.co/600x400/1f2937/ffffff?text=Upper+Left', - 'https://placehold.co/600x400/1f2937/ffffff?text=Upper+Right', - 'https://placehold.co/600x400/1f2937/ffffff?text=Upper+Inside' + '/window.svg', + '/window.svg', + '/window.svg' ], brand: { id: 'b2', name: 'BCM', - logo: 'https://placehold.co/100x50/1f2937/ffffff?text=BCM' + logo: '/window.svg' }, category: { id: 'c2', - name: 'Upper Receiver', - icon: 'πŸ”§' + name: 'Upper Receiver' }, specifications: { weight: '0.8 lbs', @@ -166,7 +164,7 @@ export const mockProducts: Product[] = [ url: 'https://rainierarms.com/bcm-m4-upper', vendor: { name: 'Rainier Arms', - logo: 'https://placehold.co/80x40/1f2937/ffffff?text=Rainier' + logo: '/window.svg' }, inStock: true, shipping: 'Free shipping' @@ -189,280 +187,207 @@ export const mockProducts: Product[] = [ name: 'SilencerCo Omega 300 Suppressor', description: 'Multi-caliber rifle suppressor with excellent sound reduction.', longDescription: 'The SilencerCo Omega 300 is a versatile, lightweight suppressor designed for multiple calibers including .308, .300 Win Mag, and .223. Features a titanium tube with stainless steel baffles for durability and performance.', - image_url: 'https://placehold.co/300x200/1f2937/ffffff?text=Suppressor', + image_url: '/window.svg', images: [ - 'https://placehold.co/600x400/1f2937/ffffff?text=Suppressor+Front', - 'https://placehold.co/600x400/1f2937/ffffff?text=Suppressor+Side' + '/window.svg', + '/window.svg' ], brand: { id: 'b3', name: 'SilencerCo', - logo: 'https://placehold.co/100x50/1f2937/ffffff?text=SilencerCo' + logo: '/window.svg' }, category: { id: 'c3', - name: 'Suppressor', - icon: 'πŸ”‡' + name: 'Suppressor' }, specifications: { - weight: '14.4 oz', + weight: '14 oz', length: '7.2 inches', material: 'Titanium / Stainless Steel', finish: 'Cerakote', - caliber: '.308, .300 Win Mag, .223, 6.5 Creedmoor' + caliber: '.308, .300 Win Mag, .223', + compatibility: ['AR-15', 'AR-10', 'Bolt Actions'] }, restrictions: { nfa: true, sbr: false, suppressor: true, - stateRestrictions: ['CA', 'NY', 'IL', 'HI'] + stateRestrictions: ['CA', 'NY', 'IL'] }, offers: [ { price: 899.99, url: 'https://silencershop.com/omega-300', vendor: { - name: 'Silencer Shop', - logo: 'https://placehold.co/80x40/1f2937/ffffff?text=SS' + name: 'SilencerShop', + logo: '/window.svg' }, inStock: true, - shipping: 'Free shipping + tax stamp included' + shipping: 'Free shipping' } ], reviews: [ { id: 'r4', rating: 5, - comment: 'Amazing sound reduction and build quality. Worth the wait for the tax stamp!', - user: 'SuppressedLife', - date: '2024-01-05' + comment: 'Excellent sound reduction and build quality. Worth the wait for the stamp.', + user: 'SuppressorGuy', + date: '2024-01-18' } ], - relatedProducts: ['7', '8'], - compatibility: ['Barrel', 'Muzzle Device', 'Mounting System'] + relatedProducts: ['1', '2'], + compatibility: ['Muzzle Device', 'Barrel'] }, { id: '4', - name: 'Geissele SSA-E Trigger', - description: 'Two-stage trigger with 3.5lb total pull weight and enhanced reliability.', - longDescription: 'The Geissele SSA-E trigger is designed for precision and reliability, offering a crisp break and short reset. Ideal for both competition and duty use.', - image_url: 'https://placehold.co/300x200/1f2937/ffffff?text=Trigger', + name: 'Toolcraft BCG - Nitride', + description: 'Mil-spec bolt carrier group with nitride finish.', + longDescription: 'The Toolcraft BCG features a properly staked gas key, shot peened bolt, and nitride finish for durability. Built to mil-spec standards for reliable performance in any AR-15.', + image_url: '/window.svg', images: [ - 'https://placehold.co/600x400/1f2937/ffffff?text=Trigger+Front', - 'https://placehold.co/600x400/1f2937/ffffff?text=Trigger+Side' + '/window.svg', + '/window.svg' ], brand: { id: 'b4', - name: 'Geissele', - logo: 'https://placehold.co/100x50/1f2937/ffffff?text=Geissele' + name: 'Toolcraft', + logo: '/window.svg' }, category: { id: 'c4', - name: 'Trigger', - icon: 'πŸ”«' + name: 'BCG' }, specifications: { - weight: '2.6 oz', - material: 'Steel', - finish: 'Nickel Boron', - compatibility: ['AR-15', 'M4'] + weight: '11.6 oz', + material: '8620 Steel', + finish: 'Nitride', + compatibility: ['AR-15', 'M4', 'M16'] + }, + restrictions: { + nfa: false, + sbr: false, + suppressor: false, + stateRestrictions: [] }, - restrictions: {}, offers: [ { - price: 249.99, - url: 'https://geissele.com/ssa-e', + price: 89.99, + url: 'https://primaryarms.com/toolcraft-bcg', vendor: { - name: 'Geissele', - logo: 'https://placehold.co/80x40/1f2937/ffffff?text=Geissele' + name: 'Primary Arms', + logo: '/window.svg' }, inStock: true, - shipping: 'Free shipping' + shipping: 'Free shipping on orders over $150' } ], reviews: [ { id: 'r5', rating: 5, - comment: 'Best trigger I have ever used!', - user: 'TriggerFan', - date: '2024-02-01' + comment: 'Great BCG for the price. Properly staked and finished.', + user: 'AR_Enthusiast', + date: '2024-01-12' } ], - relatedProducts: ['1', '2'], - compatibility: ['Lower Receiver'] + relatedProducts: ['2', '5'], + compatibility: ['Upper Receiver', 'Charging Handle'] }, { id: '5', - name: 'Magpul PMAG 30-Round Magazine', - description: '30-round polymer magazine with anti-tilt follower and dust cover.', - longDescription: 'The Magpul PMAG is the industry standard for AR-15 magazines, offering reliability and durability in all conditions.', - image_url: 'https://placehold.co/300x200/1f2937/ffffff?text=Magazine', + name: 'Radian Raptor Charging Handle', + description: 'Ambidextrous charging handle with enhanced ergonomics.', + longDescription: 'The Radian Raptor features oversized latches for easy manipulation and a unique design that reduces gas blowback. Made from 7075-T6 aluminum with a hardcoat anodized finish.', + image_url: '/window.svg', images: [ - 'https://placehold.co/600x400/1f2937/ffffff?text=Magazine+Front', - 'https://placehold.co/600x400/1f2937/ffffff?text=Magazine+Side' + '/window.svg', + '/window.svg' ], brand: { id: 'b5', - name: 'Magpul', - logo: 'https://placehold.co/100x50/1f2937/ffffff?text=Magpul' + name: 'Radian Weapons', + logo: '/window.svg' }, category: { id: 'c5', - name: 'Magazine', - icon: 'πŸ₯' + name: 'Charging Handle' }, specifications: { - weight: '4.5 oz', - material: 'Polymer', - compatibility: ['AR-15'] + weight: '1.2 oz', + material: '7075-T6 Aluminum', + finish: 'Hardcoat Anodized', + compatibility: ['AR-15', 'M4', 'M16'] }, restrictions: { - stateRestrictions: ['CA', 'NY', 'NJ', 'MA'] + nfa: false, + sbr: false, + suppressor: false, + stateRestrictions: [] }, offers: [ { - price: 14.99, - url: 'https://magpul.com/pmag', + price: 79.99, + url: 'https://primaryarms.com/radian-raptor', vendor: { - name: 'Magpul', - logo: 'https://placehold.co/80x40/1f2937/ffffff?text=Magpul' + name: 'Primary Arms', + logo: '/window.svg' }, inStock: true, - shipping: 'Flat rate $5' + shipping: 'Free shipping on orders over $150' } ], reviews: [ { id: 'r6', rating: 5, - comment: 'Runs flawlessly in my AR.', - user: 'MagFan', - date: '2024-02-10' + comment: 'Best charging handle I\'ve used. Smooth operation and great ergonomics.', + user: 'TacticalUser', + date: '2024-01-14' } ], - relatedProducts: ['6'], - compatibility: ['Lower Receiver'] + relatedProducts: ['2', '4'], + compatibility: ['Upper Receiver', 'BCG'] }, { id: '6', - name: 'BCM Gunfighter Stock', - description: 'Lightweight, durable stock with QD sling mount.', - longDescription: 'The BCM Gunfighter Stock is designed for comfort and durability, featuring a snag-free design and multiple sling attachment points.', - image_url: 'https://placehold.co/300x200/1f2937/ffffff?text=Stock', + name: 'BCM MCMR Handguard - 13"', + description: 'Free-float M-LOK handguard with excellent heat dissipation.', + longDescription: 'The BCM MCMR features a unique design that provides excellent heat dissipation while maintaining a slim profile. Includes M-LOK slots for accessory mounting and a proprietary barrel nut.', + image_url: '/window.svg', images: [ - 'https://placehold.co/600x400/1f2937/ffffff?text=Stock+Front', - 'https://placehold.co/600x400/1f2937/ffffff?text=Stock+Side' + '/window.svg', + '/window.svg' ], brand: { id: 'b2', name: 'BCM', - logo: 'https://placehold.co/100x50/1f2937/ffffff?text=BCM' + logo: '/window.svg' }, category: { id: 'c6', - name: 'Stock', - icon: 'πŸͺ‘' + name: 'Handguard' }, specifications: { - weight: '7.5 oz', - material: 'Polymer', - compatibility: ['AR-15', 'M4'] + weight: '7.2 oz', + length: '13 inches', + material: '6061-T6 Aluminum', + finish: 'Type III Hardcoat Anodized', + compatibility: ['AR-15', 'M4', 'M16'] + }, + restrictions: { + nfa: false, + sbr: false, + suppressor: false, + stateRestrictions: [] }, - restrictions: {}, offers: [ { - price: 59.99, - url: 'https://bravocompanyusa.com/gunfighter-stock', + price: 199.99, + url: 'https://bravocompanyusa.com/mcmr-handguard', vendor: { name: 'BCM', - logo: 'https://placehold.co/80x40/1f2937/ffffff?text=BCM' - }, - inStock: true, - shipping: 'Free shipping on orders over $100' - } - ], - reviews: [], - relatedProducts: ['5'], - compatibility: ['Buffer Tube'] - }, - { - id: '7', - name: 'Aero Precision Lower Receiver', - description: 'Mil-spec forged lower receiver with trigger guard and threaded bolt catch.', - longDescription: 'Aero Precision lowers are known for their tight tolerances and high quality. Perfect for any AR-15 build.', - image_url: 'https://placehold.co/300x200/1f2937/ffffff?text=Lower+Receiver', - images: [ - 'https://placehold.co/600x400/1f2937/ffffff?text=Lower+Receiver+Front', - 'https://placehold.co/600x400/1f2937/ffffff?text=Lower+Receiver+Side' - ], - brand: { - id: 'b3', - name: 'Aero Precision', - logo: 'https://placehold.co/100x50/1f2937/ffffff?text=Aero' - }, - category: { - id: 'c7', - name: 'Lower Receiver', - icon: '🧩' - }, - specifications: { - weight: '8.5 oz', - material: '7075-T6 Aluminum', - finish: 'Type III Hardcoat Anodized', - compatibility: ['AR-15'] - }, - restrictions: {}, - offers: [ - { - price: 89.99, - url: 'https://aeroprecisionusa.com/lower', - vendor: { - name: 'Aero Precision', - logo: 'https://placehold.co/80x40.png?text=Aero' - }, - inStock: true, - shipping: 'Free shipping' - } - ], - reviews: [], - relatedProducts: ['1', '2', '3'], - compatibility: ['Trigger', 'Magazine'] - }, - { - id: '8', - name: 'Radian Raptor Charging Handle', - description: 'Ambidextrous charging handle with oversized latches and smooth operation.', - longDescription: 'The Radian Raptor is a premium ambidextrous charging handle designed for fast, fluid operation from either side of the rifle.', - image_url: 'https://placehold.co/300x200.png?text=Charging+Handle', - images: [ - 'https://placehold.co/600x400.png?text=Charging+Handle+Front', - 'https://placehold.co/600x400.png?text=Charging+Handle+Side' - ], - brand: { - id: 'b6', - name: 'Radian Weapons', - logo: 'https://placehold.co/100x50.png?text=Radian' - }, - category: { - id: 'c8', - name: 'Charging Handle', - icon: 'πŸ”§' - }, - specifications: { - weight: '1.3 oz', - material: '7075-T6 Aluminum', - finish: 'Type III Hardcoat Anodized', - compatibility: ['AR-15', 'M4'] - }, - restrictions: {}, - offers: [ - { - price: 89.99, - url: 'https://radianweapons.com/raptor', - vendor: { - name: 'Radian Weapons', - logo: 'https://placehold.co/80x40.png?text=Radian' + logo: '/window.svg' }, inStock: true, shipping: 'Free shipping' @@ -472,119 +397,32 @@ export const mockProducts: Product[] = [ { id: 'r7', rating: 5, - comment: 'Smoothest charging handle I have used.', - user: 'LeftyShooter', - date: '2024-02-15' + comment: 'Excellent handguard. Great heat dissipation and solid mounting.', + user: 'BCM_Fan', + date: '2024-01-16' } ], - relatedProducts: ['2', '7'], - compatibility: ['Upper Receiver'] + relatedProducts: ['1', '2'], + compatibility: ['Upper Receiver', 'Barrel', 'Gas Block'] }, { - id: '9', - name: 'SureFire WarComp Flash Hider', - description: 'Compensator/flash hider hybrid with suppressor mount capability.', - longDescription: 'The SureFire WarComp provides excellent flash reduction and muzzle control, and is compatible with SureFire suppressors.', - image_url: 'https://placehold.co/300x200.png?text=Muzzle+Device', - images: [ - 'https://placehold.co/600x400.png?text=Muzzle+Device+Front', - 'https://placehold.co/600x400.png?text=Muzzle+Device+Side' - ], - brand: { - id: 'b7', - name: 'SureFire', - logo: 'https://placehold.co/100x50.png?text=SureFire' - }, - category: { - id: 'c9', - name: 'Muzzle Device', - icon: 'πŸ’₯' - }, - specifications: { - weight: '2.7 oz', - material: 'Stainless Steel', - finish: 'Black Oxide', - compatibility: ['AR-15', 'M4'] - }, - restrictions: {}, - offers: [ - { - price: 159.99, - url: 'https://surefire.com/warcomp', - vendor: { - name: 'SureFire', - logo: 'https://placehold.co/80x40.png?text=SureFire' - }, - inStock: true, - shipping: 'Free shipping' - } - ], - reviews: [], - relatedProducts: ['3'], - compatibility: ['Barrel', 'Suppressor'] - }, - { - id: '10', - name: 'Toolcraft BCG - Nitride', - description: 'Mil-spec bolt carrier group with Carpenter 158 bolt and nitride finish.', - longDescription: 'Toolcraft BCGs are trusted by professionals for their reliability and durability. Nitride finish for easy cleaning and corrosion resistance.', - image_url: 'https://placehold.co/300x200.png?text=BCG', - images: [ - 'https://placehold.co/600x400.png?text=BCG+Front', - 'https://placehold.co/600x400.png?text=BCG+Side' - ], - brand: { - id: 'b8', - name: 'Toolcraft', - logo: 'https://placehold.co/100x50.png?text=Toolcraft' - }, - category: { - id: 'c10', - name: 'BCG', - icon: 'πŸ”©' - }, - specifications: { - weight: '11.5 oz', - material: '8620 Steel', - finish: 'Nitride', - compatibility: ['AR-15', 'M4'] - }, - restrictions: {}, - offers: [ - { - price: 149.99, - url: 'https://wctoolcraft.com/bcg-nitride', - vendor: { - name: 'WC Toolcraft', - logo: 'https://placehold.co/80x40.png?text=Toolcraft' - }, - inStock: true, - shipping: 'Free shipping' - } - ], - reviews: [], - relatedProducts: ['2', '7'], - compatibility: ['Upper Receiver', 'Barrel'] - }, - { - id: '11', + id: '7', name: 'Aero Precision Gas Block - Low Profile', description: 'Low-profile adjustable gas block for free-float handguards.', - longDescription: 'Aero Precision gas blocks are machined to tight tolerances and are perfect for custom AR builds.', - image_url: 'https://placehold.co/300x200.png?text=Gas+Block', + longDescription: 'Aero Precision gas blocks are machined to tight tolerances and are perfect for custom AR builds. Features an adjustment screw for fine-tuning gas flow.', + image_url: '/window.svg', images: [ - 'https://placehold.co/600x400.png?text=Gas+Block+Front', - 'https://placehold.co/600x400.png?text=Gas+Block+Side' + '/window.svg', + '/window.svg' ], brand: { id: 'b3', name: 'Aero Precision', - logo: 'https://placehold.co/100x50.png?text=Aero' + logo: '/window.svg' }, category: { - id: 'c11', - name: 'Gas Block', - icon: 'πŸ›‘' + id: 'c7', + name: 'Gas Block' }, specifications: { weight: '1.1 oz', @@ -592,14 +430,19 @@ export const mockProducts: Product[] = [ finish: 'Phosphate', compatibility: ['AR-15', 'M4'] }, - restrictions: {}, + restrictions: { + nfa: false, + sbr: false, + suppressor: false, + stateRestrictions: [] + }, offers: [ { price: 49.99, url: 'https://aeroprecisionusa.com/gas-block', vendor: { name: 'Aero Precision', - logo: 'https://placehold.co/80x40.png?text=Aero' + logo: '/window.svg' }, inStock: true, shipping: 'Flat rate $7' @@ -610,598 +453,520 @@ export const mockProducts: Product[] = [ compatibility: ['Barrel', 'Handguard'] }, { - id: '12', + id: '8', name: 'BCM Gas Tube - Mid Length', description: 'Stainless steel gas tube for mid-length gas systems.', - longDescription: 'BCM gas tubes are made from high-quality stainless steel and are compatible with most AR-15 barrels.', - image_url: 'https://placehold.co/300x200.png?text=Gas+Tube', + longDescription: 'BCM gas tubes are made from high-quality stainless steel and are compatible with most AR-15 barrels. Features proper crimping and heat treatment.', + image_url: '/window.svg', images: [ - 'https://placehold.co/600x400.png?text=Gas+Tube+Front', - 'https://placehold.co/600x400.png?text=Gas+Tube+Side' + '/window.svg', + '/window.svg' ], brand: { id: 'b2', name: 'BCM', - logo: 'https://placehold.co/100x50.png?text=BCM' + logo: '/window.svg' }, category: { - id: 'c12', - name: 'Gas Tube', - icon: 'πŸ§ͺ' + id: 'c8', + name: 'Gas Tube' }, specifications: { weight: '0.7 oz', material: 'Stainless Steel', compatibility: ['AR-15', 'M4'] }, - restrictions: {}, + restrictions: { + nfa: false, + sbr: false, + suppressor: false, + stateRestrictions: [] + }, offers: [ { price: 19.99, url: 'https://bravocompanyusa.com/gas-tube', vendor: { name: 'BCM', - logo: 'https://placehold.co/80x40.png?text=BCM' + logo: '/window.svg' }, inStock: true, shipping: 'Flat rate $5' } ], reviews: [], - relatedProducts: ['11'], + relatedProducts: ['7'], compatibility: ['Gas Block'] }, { - id: '13', - name: 'Aero Precision Gas Block - High Profile', - description: 'High-profile adjustable gas block for free-float handguards.', - longDescription: 'Aero Precision gas blocks are machined to tight tolerances and are perfect for custom AR builds.', - image_url: 'https://placehold.co/300x200.png?text=Gas+Block', + id: '9', + name: 'A2 Flash Hider', + description: 'Standard A2 flash hider for AR-15 rifles.', + longDescription: 'The A2 flash hider is the standard muzzle device for AR-15 rifles. Provides effective flash suppression and is compatible with most AR-15 barrels.', + image_url: '/window.svg', images: [ - 'https://placehold.co/600x400.png?text=Gas+Block+Front', - 'https://placehold.co/600x400.png?text=Gas+Block+Side' + '/window.svg', + '/window.svg' + ], + brand: { + id: 'b6', + name: 'Mil-Spec', + logo: '/window.svg' + }, + category: { + id: 'c9', + name: 'Muzzle Device' + }, + specifications: { + weight: '2.1 oz', + material: 'Steel', + finish: 'Phosphate', + compatibility: ['AR-15', 'M4', 'M16'] + }, + restrictions: { + nfa: false, + sbr: false, + suppressor: false, + stateRestrictions: [] + }, + offers: [ + { + price: 15.99, + url: 'https://primaryarms.com/a2-flash-hider', + vendor: { + name: 'Primary Arms', + logo: '/window.svg' + }, + inStock: true, + shipping: 'Free shipping on orders over $150' + } + ], + reviews: [], + relatedProducts: ['1'], + compatibility: ['Barrel'] + }, + { + id: '10', + name: 'Aero Precision Lower Receiver', + description: 'Forged lower receiver with enhanced features.', + longDescription: 'The Aero Precision lower receiver features enhanced trigger guard, threaded bolt catch roll pin, and tension screw. Made from 7075-T6 aluminum with Type III hardcoat anodizing.', + image_url: '/window.svg', + images: [ + '/window.svg', + '/window.svg' ], brand: { id: 'b3', name: 'Aero Precision', - logo: 'https://placehold.co/100x50.png?text=Aero' + logo: '/window.svg' }, category: { - id: 'c13', - name: 'Gas Block', - icon: 'πŸ›‘' + id: 'c10', + name: 'Lower Receiver' }, specifications: { - weight: '1.2 oz', - material: 'Steel', - finish: 'Phosphate', - compatibility: ['AR-15', 'M4'] + weight: '0.5 lbs', + material: '7075-T6 Aluminum', + finish: 'Type III Hardcoat Anodized', + compatibility: ['AR-15', 'M4', 'M16'] + }, + restrictions: { + nfa: false, + sbr: false, + suppressor: false, + stateRestrictions: [] }, - restrictions: {}, offers: [ { - price: 59.99, - url: 'https://aeroprecisionusa.com/gas-block', + price: 89.99, + url: 'https://aeroprecisionusa.com/lower-receiver', vendor: { name: 'Aero Precision', - logo: 'https://placehold.co/80x40.png?text=Aero' + logo: '/window.svg' }, inStock: true, shipping: 'Flat rate $7' } ], reviews: [], - relatedProducts: ['1', '2'], - compatibility: ['Barrel', 'Handguard'] + relatedProducts: ['11', '12', '13'], + compatibility: ['Trigger', 'Pistol Grip', 'Stock', 'Buffer Tube'] }, { - id: '14', - name: 'BCM Gas Tube - Full Length', - description: 'Stainless steel gas tube for full-length gas systems.', - longDescription: 'BCM gas tubes are made from high-quality stainless steel and are compatible with most AR-15 barrels.', - image_url: 'https://placehold.co/300x200.png?text=Gas+Tube', + id: '11', + name: 'LaRue MBT-2S Trigger', + description: 'Two-stage trigger with excellent break and reset.', + longDescription: 'The LaRue MBT-2S features a crisp two-stage design with a 2.5 lb second stage. Made from tool steel and includes both curved and straight shoe options.', + image_url: '/window.svg', images: [ - 'https://placehold.co/600x400.png?text=Gas+Tube+Front', - 'https://placehold.co/600x400.png?text=Gas+Tube+Side' + '/window.svg', + '/window.svg' + ], + brand: { + id: 'b7', + name: 'LaRue Tactical', + logo: '/window.svg' + }, + category: { + id: 'c11', + name: 'Trigger' + }, + specifications: { + weight: '2.1 oz', + material: 'Tool Steel', + finish: 'Nitride', + compatibility: ['AR-15', 'M4', 'M16'] + }, + restrictions: { + nfa: false, + sbr: false, + suppressor: false, + stateRestrictions: [] + }, + offers: [ + { + price: 99.99, + url: 'https://larue.com/mbt-2s-trigger', + vendor: { + name: 'LaRue Tactical', + logo: '/window.svg' + }, + inStock: true, + shipping: 'Free shipping' + } + ], + reviews: [], + relatedProducts: ['10'], + compatibility: ['Lower Receiver'] + }, + { + id: '12', + name: 'BCM Gunfighter Pistol Grip', + description: 'Ergonomic pistol grip with enhanced texture.', + longDescription: 'The BCM Gunfighter pistol grip features an ergonomic design with enhanced texture for secure grip. Includes a storage compartment and is compatible with standard AR-15 lowers.', + image_url: '/window.svg', + images: [ + '/window.svg', + '/window.svg' ], brand: { id: 'b2', name: 'BCM', - logo: 'https://placehold.co/100x50.png?text=BCM' + logo: '/window.svg' + }, + category: { + id: 'c12', + name: 'Pistol Grip' + }, + specifications: { + weight: '2.8 oz', + material: 'Polymer', + finish: 'Molded', + compatibility: ['AR-15', 'M4', 'M16'] + }, + restrictions: { + nfa: false, + sbr: false, + suppressor: false, + stateRestrictions: [] + }, + offers: [ + { + price: 24.99, + url: 'https://bravocompanyusa.com/gunfighter-grip', + vendor: { + name: 'BCM', + logo: '/window.svg' + }, + inStock: true, + shipping: 'Free shipping' + } + ], + reviews: [], + relatedProducts: ['10'], + compatibility: ['Lower Receiver'] + }, + { + id: '13', + name: 'BCM Buffer Tube', + description: 'Mil-spec buffer tube for AR-15 rifles.', + longDescription: 'The BCM buffer tube is made to mil-spec dimensions and features proper staking for the castle nut. Compatible with all mil-spec stocks and buffer assemblies.', + image_url: '/window.svg', + images: [ + '/window.svg', + '/window.svg' + ], + brand: { + id: 'b2', + name: 'BCM', + logo: '/window.svg' + }, + category: { + id: 'c13', + name: 'Buffer Tube' + }, + specifications: { + weight: '3.2 oz', + material: '7075-T6 Aluminum', + finish: 'Type III Hardcoat Anodized', + compatibility: ['AR-15', 'M4', 'M16'] + }, + restrictions: { + nfa: false, + sbr: false, + suppressor: false, + stateRestrictions: [] + }, + offers: [ + { + price: 39.99, + url: 'https://bravocompanyusa.com/buffer-tube', + vendor: { + name: 'BCM', + logo: '/window.svg' + }, + inStock: true, + shipping: 'Free shipping' + } + ], + reviews: [], + relatedProducts: ['14', '15'], + compatibility: ['Lower Receiver', 'Stock', 'Buffer'] + }, + { + id: '14', + name: 'H2 Buffer', + description: 'Heavy buffer for reduced recoil and improved cycling.', + longDescription: 'The H2 buffer provides additional weight to reduce felt recoil and improve cycling reliability. Compatible with all mil-spec buffer tubes.', + image_url: '/window.svg', + images: [ + '/window.svg', + '/window.svg' + ], + brand: { + id: 'b8', + name: 'Mil-Spec', + logo: '/window.svg' }, category: { id: 'c14', - name: 'Gas Tube', - icon: 'πŸ§ͺ' + name: 'Buffer' + }, + specifications: { + weight: '4.6 oz', + material: 'Steel', + finish: 'Phosphate', + compatibility: ['AR-15', 'M4', 'M16'] + }, + restrictions: { + nfa: false, + sbr: false, + suppressor: false, + stateRestrictions: [] + }, + offers: [ + { + price: 19.99, + url: 'https://primaryarms.com/h2-buffer', + vendor: { + name: 'Primary Arms', + logo: '/window.svg' + }, + inStock: true, + shipping: 'Free shipping on orders over $150' + } + ], + reviews: [], + relatedProducts: ['13', '15'], + compatibility: ['Buffer Tube', 'Buffer Spring'] + }, + { + id: '15', + name: 'Buffer Spring', + description: 'Mil-spec buffer spring for AR-15 rifles.', + longDescription: 'The mil-spec buffer spring provides reliable cycling and is compatible with all standard buffer assemblies. Made from high-quality spring steel.', + image_url: '/window.svg', + images: [ + '/window.svg', + '/window.svg' + ], + brand: { + id: 'b8', + name: 'Mil-Spec', + logo: '/window.svg' + }, + category: { + id: 'c15', + name: 'Buffer Spring' }, specifications: { weight: '0.8 oz', - material: 'Stainless Steel', - compatibility: ['AR-15', 'M4'] + material: 'Spring Steel', + finish: 'Natural', + compatibility: ['AR-15', 'M4', 'M16'] + }, + restrictions: { + nfa: false, + sbr: false, + suppressor: false, + stateRestrictions: [] }, - restrictions: {}, offers: [ { - price: 29.99, - url: 'https://bravocompanyusa.com/gas-tube', + price: 9.99, + url: 'https://primaryarms.com/buffer-spring', vendor: { - name: 'BCM', - logo: 'https://placehold.co/80x40.png?text=BCM' + name: 'Primary Arms', + logo: '/window.svg' }, inStock: true, - shipping: 'Flat rate $5' + shipping: 'Free shipping on orders over $150' + } + ], + reviews: [], + relatedProducts: ['13', '14'], + compatibility: ['Buffer Tube', 'Buffer'] + }, + { + id: '16', + name: 'BCM Gunfighter Stock', + description: 'Lightweight collapsible stock with enhanced ergonomics.', + longDescription: 'The BCM Gunfighter stock features a lightweight design with enhanced cheek weld and secure locking mechanism. Compatible with all mil-spec buffer tubes.', + image_url: '/window.svg', + images: [ + '/window.svg', + '/window.svg' + ], + brand: { + id: 'b2', + name: 'BCM', + logo: '/window.svg' + }, + category: { + id: 'c16', + name: 'Stock' + }, + specifications: { + weight: '6.8 oz', + material: 'Polymer / Aluminum', + finish: 'Molded / Anodized', + compatibility: ['AR-15', 'M4', 'M16'] + }, + restrictions: { + nfa: false, + sbr: false, + suppressor: false, + stateRestrictions: [] + }, + offers: [ + { + price: 59.99, + url: 'https://bravocompanyusa.com/gunfighter-stock', + vendor: { + name: 'BCM', + logo: '/window.svg' + }, + inStock: true, + shipping: 'Free shipping' } ], reviews: [], relatedProducts: ['13'], - compatibility: ['Gas Block'] - }, - { - id: '15', - name: 'Aero Precision Gas Block - Low Profile', - description: 'Low-profile adjustable gas block for free-float handguards.', - longDescription: 'Aero Precision gas blocks are machined to tight tolerances and are perfect for custom AR builds.', - image_url: 'https://placehold.co/300x200.png?text=Gas+Block', - images: [ - 'https://placehold.co/600x400.png?text=Gas+Block+Front', - 'https://placehold.co/600x400.png?text=Gas+Block+Side' - ], - brand: { - id: 'b3', - name: 'Aero Precision', - logo: 'https://placehold.co/100x50.png?text=Aero' - }, - category: { - id: 'c15', - name: 'Gas Block', - icon: 'πŸ›‘' - }, - specifications: { - weight: '1.1 oz', - material: 'Steel', - finish: 'Phosphate', - compatibility: ['AR-15', 'M4'] - }, - restrictions: {}, - offers: [ - { - price: 49.99, - url: 'https://aeroprecisionusa.com/gas-block', - vendor: { - name: 'Aero Precision', - logo: 'https://placehold.co/80x40.png?text=Aero' - }, - inStock: true, - shipping: 'Flat rate $7' - } - ], - reviews: [], - relatedProducts: ['1', '2'], - compatibility: ['Barrel', 'Handguard'] - }, - { - id: '16', - name: 'BCM Gas Tube - Mid Length', - description: 'Stainless steel gas tube for mid-length gas systems.', - longDescription: 'BCM gas tubes are made from high-quality stainless steel and are compatible with most AR-15 barrels.', - image_url: 'https://placehold.co/300x200.png?text=Gas+Tube', - images: [ - 'https://placehold.co/600x400.png?text=Gas+Tube+Front', - 'https://placehold.co/600x400.png?text=Gas+Tube+Side' - ], - brand: { - id: 'b2', - name: 'BCM', - logo: 'https://placehold.co/100x50.png?text=BCM' - }, - category: { - id: 'c16', - name: 'Gas Tube', - icon: 'πŸ§ͺ' - }, - specifications: { - weight: '0.7 oz', - material: 'Stainless Steel', - compatibility: ['AR-15', 'M4'] - }, - restrictions: {}, - offers: [ - { - price: 19.99, - url: 'https://bravocompanyusa.com/gas-tube', - vendor: { - name: 'BCM', - logo: 'https://placehold.co/80x40.png?text=BCM' - }, - inStock: true, - shipping: 'Flat rate $5' - } - ], - reviews: [], - relatedProducts: ['15'], - compatibility: ['Gas Block'] + compatibility: ['Buffer Tube'] }, { id: '17', - name: 'Aero Precision Gas Block - High Profile', - description: 'High-profile adjustable gas block for free-float handguards.', - longDescription: 'Aero Precision gas blocks are machined to tight tolerances and are perfect for custom AR builds.', - image_url: 'https://placehold.co/300x200.png?text=Gas+Block', + name: 'Magpul PMAG 30 Gen 3', + description: '30-round polymer magazine with enhanced reliability.', + longDescription: 'The Magpul PMAG Gen 3 features enhanced feed lips, improved follower design, and over-insertion protection. Made from high-strength polymer with steel reinforcement.', + image_url: '/window.svg', images: [ - 'https://placehold.co/600x400.png?text=Gas+Block+Front', - 'https://placehold.co/600x400.png?text=Gas+Block+Side' + '/window.svg', + '/window.svg' ], brand: { - id: 'b3', - name: 'Aero Precision', - logo: 'https://placehold.co/100x50.png?text=Aero' + id: 'b9', + name: 'Magpul', + logo: '/window.svg' }, category: { id: 'c17', - name: 'Gas Block', - icon: 'πŸ›‘' + name: 'Magazine' }, specifications: { - weight: '1.2 oz', - material: 'Steel', - finish: 'Phosphate', - compatibility: ['AR-15', 'M4'] + weight: '4.2 oz', + capacity: '30 rounds', + material: 'Polymer', + finish: 'Molded', + compatibility: ['AR-15', 'M4', 'M16'] + }, + restrictions: { + nfa: false, + sbr: false, + suppressor: false, + stateRestrictions: ['CA', 'CO', 'CT', 'HI', 'MD', 'MA', 'NJ', 'NY', 'WA'] }, - restrictions: {}, offers: [ { - price: 59.99, - url: 'https://aeroprecisionusa.com/gas-block', + price: 14.99, + url: 'https://primaryarms.com/pmag-gen3', vendor: { - name: 'Aero Precision', - logo: 'https://placehold.co/80x40.png?text=Aero' + name: 'Primary Arms', + logo: '/window.svg' }, inStock: true, - shipping: 'Flat rate $7' + shipping: 'Free shipping on orders over $150' } ], reviews: [], - relatedProducts: ['1', '2'], - compatibility: ['Barrel', 'Handguard'] + relatedProducts: ['10'], + compatibility: ['Lower Receiver'] }, { id: '18', - name: 'BCM Gas Tube - Full Length', - description: 'Stainless steel gas tube for full-length gas systems.', - longDescription: 'BCM gas tubes are made from high-quality stainless steel and are compatible with most AR-15 barrels.', - image_url: 'https://placehold.co/300x200.png?text=Gas+Tube', + name: 'Magpul MBUS Pro Sights', + description: 'Low-profile flip-up backup sights.', + longDescription: 'The Magpul MBUS Pro sights feature a low-profile design that flips up when needed. Made from steel with a durable finish for long-term reliability.', + image_url: '/window.svg', images: [ - 'https://placehold.co/600x400.png?text=Gas+Tube+Front', - 'https://placehold.co/600x400.png?text=Gas+Tube+Side' + '/window.svg', + '/window.svg' ], brand: { - id: 'b2', - name: 'BCM', - logo: 'https://placehold.co/100x50.png?text=BCM' + id: 'b9', + name: 'Magpul', + logo: '/window.svg' }, category: { id: 'c18', - name: 'Gas Tube', - icon: 'πŸ§ͺ' + name: 'Sights' }, specifications: { - weight: '0.8 oz', - material: 'Stainless Steel', - compatibility: ['AR-15', 'M4'] - }, - restrictions: {}, - offers: [ - { - price: 29.99, - url: 'https://bravocompanyusa.com/gas-tube', - vendor: { - name: 'BCM', - logo: 'https://placehold.co/80x40.png?text=BCM' - }, - inStock: true, - shipping: 'Flat rate $5' - } - ], - reviews: [], - relatedProducts: ['17'], - compatibility: ['Gas Block'] - }, - { - id: '19', - name: 'Aero Precision Gas Block - Low Profile', - description: 'Low-profile adjustable gas block for free-float handguards.', - longDescription: 'Aero Precision gas blocks are machined to tight tolerances and are perfect for custom AR builds.', - image_url: 'https://placehold.co/300x200.png?text=Gas+Block', - images: [ - 'https://placehold.co/600x400.png?text=Gas+Block+Front', - 'https://placehold.co/600x400.png?text=Gas+Block+Side' - ], - brand: { - id: 'b3', - name: 'Aero Precision', - logo: 'https://placehold.co/100x50.png?text=Aero' - }, - category: { - id: 'c19', - name: 'Gas Block', - icon: 'πŸ›‘' - }, - specifications: { - weight: '1.1 oz', + weight: '2.1 oz (front), 2.3 oz (rear)', material: 'Steel', - finish: 'Phosphate', - compatibility: ['AR-15', 'M4'] + finish: 'Nitride', + compatibility: ['AR-15', 'M4', 'M16'] + }, + restrictions: { + nfa: false, + sbr: false, + suppressor: false, + stateRestrictions: [] }, - restrictions: {}, offers: [ { - price: 49.99, - url: 'https://aeroprecisionusa.com/gas-block', + price: 149.99, + url: 'https://primaryarms.com/mbus-pro-sights', vendor: { - name: 'Aero Precision', - logo: 'https://placehold.co/80x40.png?text=Aero' + name: 'Primary Arms', + logo: '/window.svg' }, inStock: true, - shipping: 'Flat rate $7' + shipping: 'Free shipping on orders over $150' } ], reviews: [], - relatedProducts: ['1', '2'], - compatibility: ['Barrel', 'Handguard'] - }, - { - id: '20', - name: 'BCM Gas Tube - Mid Length', - description: 'Stainless steel gas tube for mid-length gas systems.', - longDescription: 'BCM gas tubes are made from high-quality stainless steel and are compatible with most AR-15 barrels.', - image_url: 'https://placehold.co/300x200.png?text=Gas+Tube', - images: [ - 'https://placehold.co/600x400.png?text=Gas+Tube+Front', - 'https://placehold.co/600x400.png?text=Gas+Tube+Side' - ], - brand: { - id: 'b2', - name: 'BCM', - logo: 'https://placehold.co/100x50.png?text=BCM' - }, - category: { - id: 'c20', - name: 'Gas Tube', - icon: 'πŸ§ͺ' - }, - specifications: { - weight: '0.7 oz', - material: 'Stainless Steel', - compatibility: ['AR-15', 'M4'] - }, - restrictions: {}, - offers: [ - { - price: 19.99, - url: 'https://bravocompanyusa.com/gas-tube', - vendor: { - name: 'BCM', - logo: 'https://placehold.co/80x40.png?text=BCM' - }, - inStock: true, - shipping: 'Flat rate $5' - } - ], - reviews: [], - relatedProducts: ['19'], - compatibility: ['Gas Block'] - }, - { - id: '21', - name: 'Aero Precision Gas Block - High Profile', - description: 'High-profile adjustable gas block for free-float handguards.', - longDescription: 'Aero Precision gas blocks are machined to tight tolerances and are perfect for custom AR builds.', - image_url: 'https://placehold.co/300x200.png?text=Gas+Block', - images: [ - 'https://placehold.co/600x400.png?text=Gas+Block+Front', - 'https://placehold.co/600x400.png?text=Gas+Block+Side' - ], - brand: { - id: 'b3', - name: 'Aero Precision', - logo: 'https://placehold.co/100x50.png?text=Aero' - }, - category: { - id: 'c21', - name: 'Gas Block', - icon: 'πŸ›‘' - }, - specifications: { - weight: '1.2 oz', - material: 'Steel', - finish: 'Phosphate', - compatibility: ['AR-15', 'M4'] - }, - restrictions: {}, - offers: [ - { - price: 59.99, - url: 'https://aeroprecisionusa.com/gas-block', - vendor: { - name: 'Aero Precision', - logo: 'https://placehold.co/80x40.png?text=Aero' - }, - inStock: true, - shipping: 'Flat rate $7' - } - ], - reviews: [], - relatedProducts: ['1', '2'], - compatibility: ['Barrel', 'Handguard'] - }, - { - id: '22', - name: 'BCM Gas Tube - Full Length', - description: 'Stainless steel gas tube for full-length gas systems.', - longDescription: 'BCM gas tubes are made from high-quality stainless steel and are compatible with most AR-15 barrels.', - image_url: 'https://placehold.co/300x200.png?text=Gas+Tube', - images: [ - 'https://placehold.co/600x400.png?text=Gas+Tube+Front', - 'https://placehold.co/600x400.png?text=Gas+Tube+Side' - ], - brand: { - id: 'b2', - name: 'BCM', - logo: 'https://placehold.co/100x50.png?text=BCM' - }, - category: { - id: 'c22', - name: 'Gas Tube', - icon: 'πŸ§ͺ' - }, - specifications: { - weight: '0.8 oz', - material: 'Stainless Steel', - compatibility: ['AR-15', 'M4'] - }, - restrictions: {}, - offers: [ - { - price: 29.99, - url: 'https://bravocompanyusa.com/gas-tube', - vendor: { - name: 'BCM', - logo: 'https://placehold.co/80x40.png?text=BCM' - }, - inStock: true, - shipping: 'Flat rate $5' - } - ], - reviews: [], - relatedProducts: ['21'], - compatibility: ['Gas Block'] - }, - { - id: '23', - name: 'Aero Precision Gas Block - Low Profile', - description: 'Low-profile adjustable gas block for free-float handguards.', - longDescription: 'Aero Precision gas blocks are machined to tight tolerances and are perfect for custom AR builds.', - image_url: 'https://placehold.co/300x200.png?text=Gas+Block', - images: [ - 'https://placehold.co/600x400.png?text=Gas+Block+Front', - 'https://placehold.co/600x400.png?text=Gas+Block+Side' - ], - brand: { - id: 'b3', - name: 'Aero Precision', - logo: 'https://placehold.co/100x50.png?text=Aero' - }, - category: { - id: 'c23', - name: 'Gas Block', - icon: 'πŸ›‘' - }, - specifications: { - weight: '1.1 oz', - material: 'Steel', - finish: 'Phosphate', - compatibility: ['AR-15', 'M4'] - }, - restrictions: {}, - offers: [ - { - price: 49.99, - url: 'https://aeroprecisionusa.com/gas-block', - vendor: { - name: 'Aero Precision', - logo: 'https://placehold.co/80x40.png?text=Aero' - }, - inStock: true, - shipping: 'Flat rate $7' - } - ], - reviews: [], - relatedProducts: ['1', '2'], - compatibility: ['Barrel', 'Handguard'] - }, - { - id: '24', - name: 'BCM Gas Tube - Mid Length', - description: 'Stainless steel gas tube for mid-length gas systems.', - longDescription: 'BCM gas tubes are made from high-quality stainless steel and are compatible with most AR-15 barrels.', - image_url: 'https://placehold.co/300x200.png?text=Gas+Tube', - images: [ - 'https://placehold.co/600x400.png?text=Gas+Tube+Front', - 'https://placehold.co/600x400.png?text=Gas+Tube+Side' - ], - brand: { - id: 'b2', - name: 'BCM', - logo: 'https://placehold.co/100x50.png?text=BCM' - }, - category: { - id: 'c24', - name: 'Gas Tube', - icon: 'πŸ§ͺ' - }, - specifications: { - weight: '0.7 oz', - material: 'Stainless Steel', - compatibility: ['AR-15', 'M4'] - }, - restrictions: {}, - offers: [ - { - price: 19.99, - url: 'https://bravocompanyusa.com/gas-tube', - vendor: { - name: 'BCM', - logo: 'https://placehold.co/80x40.png?text=BCM' - }, - inStock: true, - shipping: 'Flat rate $5' - } - ], - reviews: [], - relatedProducts: ['23'], - compatibility: ['Gas Block'] - }, - { - id: '25', - name: 'Aero Precision Gas Block - High Profile', - description: 'High-profile adjustable gas block for free-float handguards.', - longDescription: 'Aero Precision gas blocks are machined to tight tolerances and are perfect for custom AR builds.', - image_url: 'https://placehold.co/300x200.png?text=Gas+Block', - images: [ - 'https://placehold.co/600x400.png?text=Gas+Block+Front', - 'https://placehold.co/600x400.png?text=Gas+Block+Side' - ], - brand: { - id: 'b3', - name: 'Aero Precision', - logo: 'https://placehold.co/100x50.png?text=Aero' - }, - category: { - id: 'c25', - name: 'Gas Block', - icon: 'πŸ›‘' - }, - specifications: { - weight: '1.2 oz', - material: 'Steel', - finish: 'Phosphate', - compatibility: ['AR-15', 'M4'] - }, - restrictions: {}, - offers: [ - { - price: 59.99, - url: 'https://aeroprecisionusa.com/gas-block', - vendor: { - name: 'Aero Precision', - logo: 'https://placehold.co/80x40.png?text=Aero' - }, - inStock: true, - shipping: 'Flat rate $7' - } - ], - reviews: [], - relatedProducts: ['1', '2'], - compatibility: ['Barrel', 'Handguard'] + relatedProducts: ['6'], + compatibility: ['Handguard', 'Upper Receiver'] } -]; \ No newline at end of file +]; \ No newline at end of file diff --git a/src/store/useBuildStore.ts b/src/store/useBuildStore.ts new file mode 100644 index 0000000..6cbe72a --- /dev/null +++ b/src/store/useBuildStore.ts @@ -0,0 +1,53 @@ +import { create, StateCreator } from 'zustand'; +import { persist } from 'zustand/middleware'; + +export interface BuildPart { + id: string; + name: string; + image_url: string; + brand: { + id: string; + name: string; + logo?: string; + }; + category: { + id: string; + name: string; + }; + offers: Array<{ + price: number; + url: string; + vendor: { + name: string; + logo?: string; + }; + inStock?: boolean; + shipping?: string; + }>; +} + +export interface BuildState { + selectedParts: Record; // key: checklist component id + selectPartForComponent: (componentId: string, part: BuildPart) => void; + removePartForComponent: (componentId: string) => void; + clearBuild: () => void; +} + +const buildStoreCreator: StateCreator = (set) => ({ + selectedParts: {}, + selectPartForComponent: (componentId, part) => set((state) => ({ + selectedParts: { ...state.selectedParts, [componentId]: part }, + })), + removePartForComponent: (componentId) => set((state) => { + const updated = { ...state.selectedParts }; + delete updated[componentId]; + return { selectedParts: updated }; + }), + clearBuild: () => set({ selectedParts: {} }), +}); + +export const useBuildStore = create()( + persist(buildStoreCreator, { + name: 'current-build-storage', + }) +); \ No newline at end of file
Product @@ -527,7 +629,9 @@ export default function Home() { 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.name} +
{part.brand.name}
@@ -542,11 +646,50 @@ export default function Home() {
- - - Add - - + {(() => { + // 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 + ); + } + })()}