From 14b25e7359a4d0727b6e467da3400bf59844097c Mon Sep 17 00:00:00 2001 From: Sean S Date: Sun, 29 Jun 2025 13:43:46 -0400 Subject: [PATCH] fixed theming and color --- src/app/globals.css | 9 +- src/app/layout.tsx | 2 +- src/app/page.tsx | 688 +++------------------------------- src/app/parts/page.tsx | 640 +++++++++++++++++++++++++++++++ src/components/BetaTester.tsx | 40 ++ src/components/Navbar.tsx | 77 ++-- tailwind.config.js | 70 +--- 7 files changed, 780 insertions(+), 746 deletions(-) create mode 100644 src/app/parts/page.tsx create mode 100644 src/components/BetaTester.tsx diff --git a/src/app/globals.css b/src/app/globals.css index c2f4d43..f559d12 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -32,7 +32,7 @@ /* Focus styles for better accessibility */ .focus-ring { - @apply focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:focus:ring-offset-neutral-900; + @apply focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 dark:focus:ring-offset-neutral-900; } /* Card styles */ @@ -41,16 +41,13 @@ } /* Button styles */ - .btn-primary { - @apply bg-primary-600 hover:bg-primary-700 dark:bg-primary-500 dark:hover:bg-primary-600 text-white font-medium py-2 px-4 rounded-lg transition-colors duration-200 focus-ring; - } - + /* Removed custom .btn-primary to avoid DaisyUI conflict */ .btn-secondary { @apply bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-700 dark:hover:bg-neutral-600 text-neutral-700 dark:text-neutral-300 font-medium py-2 px-4 rounded-lg transition-colors duration-200 focus-ring; } /* Input styles */ .input-field { - @apply w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg bg-white dark:bg-neutral-800 text-neutral-900 dark:text-white placeholder-neutral-500 dark:placeholder-neutral-400 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500 transition-colors duration-200; + @apply w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg bg-white dark:bg-neutral-800 text-neutral-900 dark:text-white placeholder-neutral-500 dark:placeholder-neutral-400 focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary transition-colors duration-200; } } \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index a5a869c..9d5d7e7 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -17,7 +17,7 @@ export default function RootLayout({ children: React.ReactNode; }) { return ( - +
diff --git a/src/app/page.tsx b/src/app/page.tsx index 57bb679..2ada719 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,640 +1,58 @@ -'use client'; - -import { useState, useEffect } from 'react'; -import { useSearchParams } from 'next/navigation'; -import { Listbox, Transition } from '@headlessui/react'; -import { ChevronUpDownIcon, CheckIcon, XMarkIcon } 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 BetaTester from "../components/BetaTester"; import Link from 'next/link'; -import { mockProducts } from '@/mock/product'; -import type { Product } from '@/mock/product'; - -// 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 = [ - '', - '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; +export default function LandingPage() { return ( -
- {config.icon} - {config.label} +
+ {/* SVG Grid Background */} +
+ +
+ {/* Left: Headline, Subheading, Button */} +
+

+ A better way to plan your next build +

+

+ Anim aute id magna aliqua ad ad non deserunt sunt. Qui irure qui lorem cupidatat commodo. Elit sunt amet + fugiat veniam occaecat fugiat aliqua. Anim aute id magna aliqua ad ad non deserunt sunt. +

+
+ + Get Building + +
+
+ {/* Right: Product Image */} +
+ AR-15 Lower Receiver +
+
+
+ {/* Beta Tester CTA */} +
); -}; - -// 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} - - )} - - ))} - - -
-
-
- ); -}; - -export default function Home() { - const searchParams = useSearchParams(); - 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'); - - // 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; - }; - - 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 */} -
-
- -
-
- - {/* 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: -
- - -
-
-
- - {/* Restriction Alert Example */} - {sortedParts.some(part => part.restrictions?.nfa) && ( -
- -
- )} - - {/* Table View */} - {viewMode === 'table' && ( -
-
- - - - - - - - - - - - - {sortedParts.map((part) => ( - - - - - - - - - ))} - -
- Category - handleSort('name')} - > -
- Name - {getSortIcon('name')} -
-
- Brand - - Description - handleSort('price')} - > -
- Price - {getSortIcon('price')} -
-
- Actions -
- - {part.category.name} - - -
- {part.name} -
-
- {part.brand.name} -
-
-
- {part.brand.name} -
-
-
- {part.description} -
-
-
- ${Math.min(...part.offers.map(offer => offer.price)).toFixed(2)} -
-
- - - View Details - - -
-
- - {/* 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) => ( - - ))} -
- )} -
- - {/* 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 -
-
-
-
- ); -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/app/parts/page.tsx b/src/app/parts/page.tsx new file mode 100644 index 0000000..57bb679 --- /dev/null +++ b/src/app/parts/page.tsx @@ -0,0 +1,640 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useSearchParams } from 'next/navigation'; +import { Listbox, Transition } from '@headlessui/react'; +import { ChevronUpDownIcon, CheckIcon, XMarkIcon } 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'; + +// 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 = [ + '', + '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} + + )} + + ))} + + +
+
+
+ ); +}; + +export default function Home() { + const searchParams = useSearchParams(); + 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'); + + // 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; + }; + + 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 */} +
+
+ +
+
+ + {/* 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: +
+ + +
+
+
+ + {/* Restriction Alert Example */} + {sortedParts.some(part => part.restrictions?.nfa) && ( +
+ +
+ )} + + {/* Table View */} + {viewMode === 'table' && ( +
+
+ + + + + + + + + + + + + {sortedParts.map((part) => ( + + + + + + + + + ))} + +
+ Category + handleSort('name')} + > +
+ Name + {getSortIcon('name')} +
+
+ Brand + + Description + handleSort('price')} + > +
+ Price + {getSortIcon('price')} +
+
+ Actions +
+ + {part.category.name} + + +
+ {part.name} +
+
+ {part.brand.name} +
+
+
+ {part.brand.name} +
+
+
+ {part.description} +
+
+
+ ${Math.min(...part.offers.map(offer => offer.price)).toFixed(2)} +
+
+ + + View Details + + +
+
+ + {/* 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) => ( + + ))} +
+ )} +
+ + {/* 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 +
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/BetaTester.tsx b/src/components/BetaTester.tsx new file mode 100644 index 0000000..6c97dac --- /dev/null +++ b/src/components/BetaTester.tsx @@ -0,0 +1,40 @@ +export default function BetaTester() { + return ( +
+
+

+ Interested in being a beta tester? Join the beta tester list. +

+
+
+ + + +
+

+ We care about your data. Read our{' '} + + privacy policy + + . +

+
+
+
+ ) + } \ No newline at end of file diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index a8bf62c..07e844d 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -3,42 +3,38 @@ import Link from 'next/link'; import { usePathname } from 'next/navigation'; import ThemeSwitcher from './ThemeSwitcher'; +import { MagnifyingGlassIcon, UserCircleIcon } from '@heroicons/react/24/outline'; export default function Navbar() { const pathname = usePathname(); const navItems = [ - { href: '/', label: 'Parts Catalog' }, + { href: '/parts', label: 'Parts Catalog' }, { href: '/build', label: 'Build Checklist' }, { href: '/builds', label: 'My Builds' }, ]; return ( - + + ); } \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js index 9ee844d..ecd71a6 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -10,20 +10,6 @@ module.exports = { theme: { extend: { colors: { - // Primary brand colors - primary: { - 50: '#f0f9ff', - 100: '#e0f2fe', - 200: '#bae6fd', - 300: '#7dd3fc', - 400: '#38bdf8', - 500: '#0ea5e9', - 600: '#0284c7', - 700: '#0369a1', - 800: '#075985', - 900: '#0c4a6e', - 950: '#082f49', - }, // Secondary accent colors accent: { 50: '#fef2f2', @@ -105,50 +91,30 @@ module.exports = { daisyui: { themes: [ { - light: { - "primary": "#0ea5e9", - "primary-content": "#ffffff", - "secondary": "#ef4444", - "secondary-content": "#ffffff", - "accent": "#f59e0b", - "accent-content": "#ffffff", - "neutral": "#737373", - "neutral-content": "#ffffff", - "base-100": "#ffffff", - "base-200": "#f5f5f5", - "base-300": "#e5e5e5", - "base-content": "#171717", - "info": "#0ea5e9", - "success": "#22c55e", - "warning": "#f59e0b", - "error": "#ef4444", - }, - dark: { - "primary": "#38bdf8", - "primary-content": "#ffffff", - "secondary": "#f87171", - "secondary-content": "#ffffff", - "accent": "#fbbf24", - "accent-content": "#ffffff", - "neutral": "#a3a3a3", - "neutral-content": "#ffffff", - "base-100": "#171717", - "base-200": "#262626", - "base-300": "#404040", - "base-content": "#ffffff", - "info": "#38bdf8", - "success": "#4ade80", - "warning": "#fbbf24", - "error": "#f87171", + pew: { + primary: '#4B6516', // Olive/army green + 'primary-content': '#fff', + accent: '#181C20', // Dark navy for CTA/footer + 'accent-content': '#fff', + neutral: '#222', + 'base-100': '#fff', + 'base-200': '#f5f6fa', + 'base-300': '#e5e7eb', + info: '#3ABFF8', + success: '#36D399', + warning: '#FBBD23', + error: '#F87272', }, }, + 'dark', ], darkTheme: "dark", base: true, styled: true, utils: true, - prefix: "", - logs: true, - themeRoot: ":root", + logs: false, + rtl: false, + prefix: '', + // 'pew' is the default theme }, }; \ No newline at end of file