mirror of
https://gitea.gofwd.group/sean/gunbuilder-next-tailwind.git
synced 2025-12-05 18:46:45 -05:00
Remove DaisyUI and replace with standard Tailwind classes - Replace btn-primary with bg-blue-600 hover:bg-blue-700 - Replace badge classes with custom Tailwind badge styling - Replace checkbox-primary with standard checkbox styling - Replace text-primary with text-blue-600 - Replace card classes with custom styling - Update ProductCard component styling
This commit is contained in:
@@ -17,7 +17,7 @@ export default function ForgotPasswordPage() {
|
|||||||
<h1 className="text-2xl font-bold mb-4 text-gray-900 dark:text-white">Forgot your password?</h1>
|
<h1 className="text-2xl font-bold mb-4 text-gray-900 dark:text-white">Forgot your password?</h1>
|
||||||
<p className="mb-6 text-gray-600 dark:text-gray-300 text-sm">
|
<p className="mb-6 text-gray-600 dark:text-gray-300 text-sm">
|
||||||
Enter your email address and we'll send you a link to reset your password.<br/>
|
Enter your email address and we'll send you a link to reset your password.<br/>
|
||||||
<span className="text-primary font-semibold">(This feature is not yet implemented.)</span>
|
<span className="text-blue-600 font-semibold">(This feature is not yet implemented.)</span>
|
||||||
</p>
|
</p>
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
<input
|
<input
|
||||||
@@ -31,14 +31,14 @@ export default function ForgotPasswordPage() {
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="w-full btn btn-primary"
|
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium text-sm py-2 px-4 rounded-md transition-colors"
|
||||||
disabled={submitted}
|
disabled={submitted}
|
||||||
>
|
>
|
||||||
{submitted ? 'Check your email' : 'Send reset link'}
|
{submitted ? 'Check your email' : 'Send reset link'}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<div className="mt-6 text-center">
|
<div className="mt-6 text-center">
|
||||||
<Link href="/account/login" className="text-primary-600 hover:underline text-sm">Back to login</Link>
|
<Link href="/account/login" className="text-blue-600 hover:underline text-sm">Back to login</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export default function LoginPage() {
|
|||||||
<h2 className="mt-6 text-3xl font-extrabold text-gray-900 dark:text-white">Sign in to your account</h2>
|
<h2 className="mt-6 text-3xl font-extrabold text-gray-900 dark:text-white">Sign in to your account</h2>
|
||||||
<p className="mt-2 text-sm text-gray-600 dark:text-gray-300">
|
<p className="mt-2 text-sm text-gray-600 dark:text-gray-300">
|
||||||
Or{' '}
|
Or{' '}
|
||||||
<Link href="/account/register" className="font-medium text-primary-600 hover:text-primary-500">
|
<Link href="/account/register" className="font-medium text-blue-600 hover:text-blue-500">
|
||||||
Sign Up For Free
|
Sign Up For Free
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
@@ -109,7 +109,7 @@ export default function LoginPage() {
|
|||||||
id="remember-me"
|
id="remember-me"
|
||||||
name="remember-me"
|
name="remember-me"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="checkbox checkbox-primary"
|
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
/>
|
/>
|
||||||
<label htmlFor="remember-me" className="ml-2 block text-sm text-gray-900 dark:text-gray-300">
|
<label htmlFor="remember-me" className="ml-2 block text-sm text-gray-900 dark:text-gray-300">
|
||||||
@@ -118,7 +118,7 @@ export default function LoginPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
<Link href="/account/forgot-password" className="font-medium text-primary-600 hover:text-primary-500">
|
<Link href="/account/forgot-password" className="font-medium text-blue-600 hover:text-blue-500">
|
||||||
Forgot your password?
|
Forgot your password?
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@@ -127,7 +127,7 @@ export default function LoginPage() {
|
|||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="w-full btn btn-primary text-white font-medium text-sm py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
|
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium text-sm py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
{loading ? 'Signing in...' : 'Sign in'}
|
{loading ? 'Signing in...' : 'Sign in'}
|
||||||
|
|||||||
@@ -79,14 +79,14 @@ export default function RegisterPage() {
|
|||||||
{error && <div className="text-red-600 text-sm">{error}</div>}
|
{error && <div className="text-red-600 text-sm">{error}</div>}
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="w-full btn btn-primary text-white font-medium text-sm py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
|
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium text-sm py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
{loading ? 'Creating account...' : 'Create Account'}
|
{loading ? 'Creating account...' : 'Create Account'}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<div className="mt-6 text-center">
|
<div className="mt-6 text-center">
|
||||||
<Link href="/account/login" className="text-primary-600 hover:underline text-sm">Already have an account? Sign in</Link>
|
<Link href="/account/login" className="text-blue-600 hover:underline text-sm">Already have an account? Sign in</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -725,7 +725,7 @@ export default function BuildPage() {
|
|||||||
) : (
|
) : (
|
||||||
<Link
|
<Link
|
||||||
href={`/parts?category=${encodeURIComponent(getProductCategoryForComponent(component.name))}`}
|
href={`/parts?category=${encodeURIComponent(getProductCategoryForComponent(component.name))}`}
|
||||||
className="btn btn-primary btn-sm"
|
className="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded-md text-sm font-medium transition-colors"
|
||||||
>
|
>
|
||||||
Find Parts
|
Find Parts
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default function LandingPage() {
|
|||||||
<div className="mt-10 flex items-top gap-x-6">
|
<div className="mt-10 flex items-top gap-x-6">
|
||||||
<Link
|
<Link
|
||||||
href="/build"
|
href="/build"
|
||||||
className="btn btn-primary text-base font-semibold px-6"
|
className="bg-blue-600 hover:bg-blue-700 text-white text-base font-semibold px-6 py-3 rounded-md transition-colors"
|
||||||
>
|
>
|
||||||
Get Building
|
Get Building
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -227,6 +227,123 @@ const getMatchingComponentName = (productCategory: string): string => {
|
|||||||
return categoryToComponentType[productCategory] || '';
|
return categoryToComponentType[productCategory] || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Pagination Component
|
||||||
|
const Pagination = ({
|
||||||
|
currentPage,
|
||||||
|
totalPages,
|
||||||
|
onPageChange,
|
||||||
|
itemsPerPage,
|
||||||
|
onItemsPerPageChange,
|
||||||
|
totalItems,
|
||||||
|
startIndex,
|
||||||
|
endIndex
|
||||||
|
}: {
|
||||||
|
currentPage: number;
|
||||||
|
totalPages: number;
|
||||||
|
onPageChange: (page: number) => void;
|
||||||
|
itemsPerPage: number;
|
||||||
|
onItemsPerPageChange: (items: number) => void;
|
||||||
|
totalItems: number;
|
||||||
|
startIndex: number;
|
||||||
|
endIndex: number;
|
||||||
|
}) => {
|
||||||
|
const getPageNumbers = () => {
|
||||||
|
const pages = [];
|
||||||
|
const maxVisiblePages = 5;
|
||||||
|
|
||||||
|
if (totalPages <= maxVisiblePages) {
|
||||||
|
for (let i = 1; i <= totalPages; i++) {
|
||||||
|
pages.push(i);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (currentPage <= 3) {
|
||||||
|
for (let i = 1; i <= 4; i++) {
|
||||||
|
pages.push(i);
|
||||||
|
}
|
||||||
|
pages.push('...');
|
||||||
|
pages.push(totalPages);
|
||||||
|
} else if (currentPage >= totalPages - 2) {
|
||||||
|
pages.push(1);
|
||||||
|
pages.push('...');
|
||||||
|
for (let i = totalPages - 3; i <= totalPages; i++) {
|
||||||
|
pages.push(i);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pages.push(1);
|
||||||
|
pages.push('...');
|
||||||
|
for (let i = currentPage - 1; i <= currentPage + 1; i++) {
|
||||||
|
pages.push(i);
|
||||||
|
}
|
||||||
|
pages.push('...');
|
||||||
|
pages.push(totalPages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col sm:flex-row justify-between items-center gap-4 py-4 px-6 bg-white border-t border-zinc-200">
|
||||||
|
{/* Items per page selector */}
|
||||||
|
<div className="flex items-center gap-2 text-sm text-zinc-600">
|
||||||
|
<span>Show:</span>
|
||||||
|
<select
|
||||||
|
value={itemsPerPage}
|
||||||
|
onChange={(e) => onItemsPerPageChange(Number(e.target.value))}
|
||||||
|
className="border border-zinc-300 rounded px-2 py-1 text-sm"
|
||||||
|
>
|
||||||
|
<option value={10}>10</option>
|
||||||
|
<option value={20}>20</option>
|
||||||
|
<option value={50}>50</option>
|
||||||
|
<option value={100}>100</option>
|
||||||
|
</select>
|
||||||
|
<span>per page</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Page info */}
|
||||||
|
<div className="text-sm text-zinc-600">
|
||||||
|
Showing {startIndex + 1} to {Math.min(endIndex, totalItems)} of {totalItems} results
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Pagination controls */}
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<button
|
||||||
|
onClick={() => onPageChange(currentPage - 1)}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
className="px-3 py-1 text-sm border border-zinc-300 rounded hover:bg-zinc-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
Previous
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{getPageNumbers().map((page, index) => (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
onClick={() => typeof page === 'number' ? onPageChange(page) : null}
|
||||||
|
disabled={page === '...'}
|
||||||
|
className={`px-3 py-1 text-sm border rounded ${
|
||||||
|
page === currentPage
|
||||||
|
? 'bg-primary-600 text-white border-primary-600'
|
||||||
|
: page === '...'
|
||||||
|
? 'border-zinc-300 text-zinc-400 cursor-default'
|
||||||
|
: 'border-zinc-300 hover:bg-zinc-50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{page}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => onPageChange(currentPage + 1)}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
className="px-3 py-1 text-sm border border-zinc-300 rounded hover:bg-zinc-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -247,6 +364,10 @@ export default function Home() {
|
|||||||
const [viewMode, setViewMode] = useState<'table' | 'cards'>('table');
|
const [viewMode, setViewMode] = useState<'table' | 'cards'>('table');
|
||||||
const [addedPartIds, setAddedPartIds] = useState<string[]>([]);
|
const [addedPartIds, setAddedPartIds] = useState<string[]>([]);
|
||||||
const [isSearchExpanded, setIsSearchExpanded] = useState(false);
|
const [isSearchExpanded, setIsSearchExpanded] = useState(false);
|
||||||
|
|
||||||
|
// Pagination state
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [itemsPerPage, setItemsPerPage] = useState(20);
|
||||||
const selectPartForComponent = useBuildStore((state) => state.selectPartForComponent);
|
const selectPartForComponent = useBuildStore((state) => state.selectPartForComponent);
|
||||||
const selectedParts = useBuildStore((state) => state.selectedParts);
|
const selectedParts = useBuildStore((state) => state.selectedParts);
|
||||||
const removePartForComponent = useBuildStore((state) => state.removePartForComponent);
|
const removePartForComponent = useBuildStore((state) => state.removePartForComponent);
|
||||||
@@ -386,6 +507,17 @@ export default function Home() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Pagination logic
|
||||||
|
const totalPages = Math.ceil(sortedParts.length / itemsPerPage);
|
||||||
|
const startIndex = (currentPage - 1) * itemsPerPage;
|
||||||
|
const endIndex = startIndex + itemsPerPage;
|
||||||
|
const paginatedParts = sortedParts.slice(startIndex, endIndex);
|
||||||
|
|
||||||
|
// Reset to first page when filters change
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}, [selectedCategoryId, selectedSubcategoryId, selectedBrand, selectedVendor, searchTerm, priceRange, selectedRestriction, sortField, sortDirection]);
|
||||||
|
|
||||||
const handleSort = (field: SortField) => {
|
const handleSort = (field: SortField) => {
|
||||||
if (sortField === field) {
|
if (sortField === field) {
|
||||||
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
|
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
|
||||||
@@ -648,7 +780,7 @@ export default function Home() {
|
|||||||
{/* View Toggle and Results Count */}
|
{/* View Toggle and Results Count */}
|
||||||
<div className="flex justify-between items-center mb-6">
|
<div className="flex justify-between items-center mb-6">
|
||||||
<div className="text-sm text-zinc-700">
|
<div className="text-sm text-zinc-700">
|
||||||
{loading ? 'Loading...' : `Showing ${sortedParts.length} of ${products.length} parts`}
|
{loading ? 'Loading...' : `Showing ${startIndex + 1}-${Math.min(endIndex, sortedParts.length)} of ${sortedParts.length} parts`}
|
||||||
{hasActiveFilters && !loading && (
|
{hasActiveFilters && !loading && (
|
||||||
<span className="ml-2 text-primary-600">
|
<span className="ml-2 text-primary-600">
|
||||||
(filtered)
|
(filtered)
|
||||||
@@ -707,7 +839,7 @@ export default function Home() {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="bg-white divide-y divide-zinc-200">
|
<tbody className="bg-white divide-y divide-zinc-200">
|
||||||
{sortedParts.map((part) => (
|
{paginatedParts.map((part) => (
|
||||||
<tr key={part.id} className="hover:bg-zinc-50 transition-colors">
|
<tr key={part.id} className="hover:bg-zinc-50 transition-colors">
|
||||||
<td className="px-0 py-2 flex items-center gap-2 align-top">
|
<td className="px-0 py-2 flex items-center gap-2 align-top">
|
||||||
<div className="w-12 h-12 flex-shrink-0 rounded bg-zinc-100 overflow-hidden flex items-center justify-center border border-zinc-200">
|
<div className="w-12 h-12 flex-shrink-0 rounded bg-zinc-100 overflow-hidden flex items-center justify-center border border-zinc-200">
|
||||||
@@ -782,26 +914,30 @@ export default function Home() {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Table Footer */}
|
|
||||||
<div className="bg-zinc-50 px-6 py-3 border-t border-zinc-200">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="text-sm text-zinc-700">
|
|
||||||
Showing {sortedParts.length} of {products.length} parts
|
|
||||||
{hasActiveFilters && (
|
|
||||||
<span className="ml-2 text-primary-600">
|
|
||||||
(filtered)
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
|
{totalPages > 1 && (
|
||||||
|
<Pagination
|
||||||
|
currentPage={currentPage}
|
||||||
|
totalPages={totalPages}
|
||||||
|
onPageChange={setCurrentPage}
|
||||||
|
itemsPerPage={itemsPerPage}
|
||||||
|
onItemsPerPageChange={(items) => {
|
||||||
|
setItemsPerPage(items);
|
||||||
|
setCurrentPage(1); // Reset to first page when changing items per page
|
||||||
|
}}
|
||||||
|
totalItems={sortedParts.length}
|
||||||
|
startIndex={startIndex}
|
||||||
|
endIndex={endIndex}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Card View */}
|
{/* Card View */}
|
||||||
{viewMode === 'cards' && (
|
{viewMode === 'cards' && (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||||
{sortedParts.map((part) => (
|
{paginatedParts.map((part) => (
|
||||||
<ProductCard key={part.id} product={part} onAdd={() => handleAdd(part)} added={addedPartIds.includes(part.id)} />
|
<ProductCard key={part.id} product={part} onAdd={() => handleAdd(part)} added={addedPartIds.includes(part.id)} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -120,8 +120,10 @@ export default function Navbar() {
|
|||||||
{session.user.email}
|
{session.user.email}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{menuItems.map((item, idx) =>
|
{menuItems.map((item, idx) => {
|
||||||
item.custom ? (
|
if (!item) return null;
|
||||||
|
|
||||||
|
return item.custom ? (
|
||||||
<div key={idx} className="px-4 py-2 flex items-center justify-between">
|
<div key={idx} className="px-4 py-2 flex items-center justify-between">
|
||||||
<span className="text-sm text-neutral-700 dark:text-neutral-200">Theme</span>
|
<span className="text-sm text-neutral-700 dark:text-neutral-200">Theme</span>
|
||||||
{item.custom}
|
{item.custom}
|
||||||
@@ -132,7 +134,7 @@ export default function Navbar() {
|
|||||||
href={item.href}
|
href={item.href}
|
||||||
className={`block px-4 py-2 text-sm rounded transition-colors ${
|
className={`block px-4 py-2 text-sm rounded transition-colors ${
|
||||||
item.active
|
item.active
|
||||||
? 'bg-primary/10 text-primary font-semibold'
|
? 'bg-blue-100 text-blue-700 font-semibold'
|
||||||
: 'text-neutral-700 dark:text-neutral-200 hover:bg-neutral-100 dark:hover:bg-neutral-700'
|
: 'text-neutral-700 dark:text-neutral-200 hover:bg-neutral-100 dark:hover:bg-neutral-700'
|
||||||
}`}
|
}`}
|
||||||
onClick={item.onClick}
|
onClick={item.onClick}
|
||||||
@@ -147,15 +149,15 @@ export default function Navbar() {
|
|||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
</button>
|
</button>
|
||||||
)
|
);
|
||||||
)}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-primary font-semibold px-4 py-1"
|
className="bg-blue-600 hover:bg-blue-700 text-white font-semibold px-4 py-1 rounded-md text-sm transition-colors"
|
||||||
onClick={() => signIn()}
|
onClick={() => signIn()}
|
||||||
>
|
>
|
||||||
Sign In
|
Sign In
|
||||||
@@ -175,8 +177,8 @@ export default function Navbar() {
|
|||||||
href={item.href}
|
href={item.href}
|
||||||
className={`px-2 py-1 rounded-md text-sm font-medium transition-colors ${
|
className={`px-2 py-1 rounded-md text-sm font-medium transition-colors ${
|
||||||
pathname === item.href
|
pathname === item.href
|
||||||
? 'text-primary font-semibold underline underline-offset-4'
|
? 'text-blue-600 font-semibold underline underline-offset-4'
|
||||||
: 'text-neutral-700 dark:text-neutral-200 hover:text-primary'
|
: 'text-neutral-700 dark:text-neutral-200 hover:text-blue-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
|
|||||||
@@ -28,43 +28,43 @@ export default function ProductCard({ product, onAdd, added }: ProductCardProps)
|
|||||||
const restrictionConfig = {
|
const restrictionConfig = {
|
||||||
NFA: {
|
NFA: {
|
||||||
label: 'NFA',
|
label: 'NFA',
|
||||||
color: 'badge-error',
|
color: 'bg-red-100 text-red-800',
|
||||||
icon: '🔒',
|
icon: '🔒',
|
||||||
tooltip: 'National Firearms Act - Requires special registration'
|
tooltip: 'National Firearms Act - Requires special registration'
|
||||||
},
|
},
|
||||||
SBR: {
|
SBR: {
|
||||||
label: 'SBR',
|
label: 'SBR',
|
||||||
color: 'badge-warning',
|
color: 'bg-yellow-100 text-yellow-800',
|
||||||
icon: '📏',
|
icon: '📏',
|
||||||
tooltip: 'Short Barrel Rifle - Requires NFA registration'
|
tooltip: 'Short Barrel Rifle - Requires NFA registration'
|
||||||
},
|
},
|
||||||
SUPPRESSOR: {
|
SUPPRESSOR: {
|
||||||
label: 'Suppressor',
|
label: 'Suppressor',
|
||||||
color: 'badge-secondary',
|
color: 'bg-gray-100 text-gray-800',
|
||||||
icon: '🔇',
|
icon: '🔇',
|
||||||
tooltip: 'Sound Suppressor - Requires NFA registration'
|
tooltip: 'Sound Suppressor - Requires NFA registration'
|
||||||
},
|
},
|
||||||
FFL_REQUIRED: {
|
FFL_REQUIRED: {
|
||||||
label: 'FFL',
|
label: 'FFL',
|
||||||
color: 'badge-info',
|
color: 'bg-blue-100 text-blue-800',
|
||||||
icon: '🏪',
|
icon: '🏪',
|
||||||
tooltip: 'Federal Firearms License required for purchase'
|
tooltip: 'Federal Firearms License required for purchase'
|
||||||
},
|
},
|
||||||
STATE_RESTRICTIONS: {
|
STATE_RESTRICTIONS: {
|
||||||
label: 'State',
|
label: 'State',
|
||||||
color: 'badge-warning',
|
color: 'bg-yellow-100 text-yellow-800',
|
||||||
icon: '🗺️',
|
icon: '🗺️',
|
||||||
tooltip: 'State-specific restrictions may apply'
|
tooltip: 'State-specific restrictions may apply'
|
||||||
},
|
},
|
||||||
HIGH_CAPACITY: {
|
HIGH_CAPACITY: {
|
||||||
label: 'High Cap',
|
label: 'High Cap',
|
||||||
color: 'badge-accent',
|
color: 'bg-purple-100 text-purple-800',
|
||||||
icon: '🥁',
|
icon: '🥁',
|
||||||
tooltip: 'High capacity magazine - check local laws'
|
tooltip: 'High capacity magazine - check local laws'
|
||||||
},
|
},
|
||||||
SILENCERSHOP_PARTNER: {
|
SILENCERSHOP_PARTNER: {
|
||||||
label: 'SilencerShop',
|
label: 'SilencerShop',
|
||||||
color: 'badge-success',
|
color: 'bg-green-100 text-green-800',
|
||||||
icon: '🤝',
|
icon: '🤝',
|
||||||
tooltip: 'Available through SilencerShop partnership'
|
tooltip: 'Available through SilencerShop partnership'
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ export default function ProductCard({ product, onAdd, added }: ProductCardProps)
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`badge ${config.color} gap-1 cursor-help`}
|
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${config.color} gap-1 cursor-help`}
|
||||||
title={config.tooltip}
|
title={config.tooltip}
|
||||||
>
|
>
|
||||||
<span>{config.icon}</span>
|
<span>{config.icon}</span>
|
||||||
@@ -85,7 +85,7 @@ export default function ProductCard({ product, onAdd, added }: ProductCardProps)
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card bg-base-100 shadow-lg hover:shadow-xl transition-shadow duration-300 border border-base-300">
|
<div className="bg-white dark:bg-gray-800 shadow-lg hover:shadow-xl transition-shadow duration-300 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
|
||||||
<figure className="relative">
|
<figure className="relative">
|
||||||
<img
|
<img
|
||||||
src={imageError ? '/window.svg' : product.image_url}
|
src={imageError ? '/window.svg' : product.image_url}
|
||||||
@@ -102,25 +102,25 @@ export default function ProductCard({ product, onAdd, added }: ProductCardProps)
|
|||||||
)}
|
)}
|
||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
<div className="card-body">
|
<div className="p-4">
|
||||||
<h3 className="card-title text-base-content line-clamp-2">{product.name}</h3>
|
<h3 className="font-semibold text-gray-900 dark:text-white line-clamp-2">{product.name}</h3>
|
||||||
<p className="text-base-content/70 text-sm line-clamp-2">{product.description}</p>
|
<p className="text-gray-600 dark:text-gray-300 text-sm line-clamp-2">{product.description}</p>
|
||||||
|
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<span className="text-sm text-base-content/60">{product.brand.name}</span>
|
<span className="text-sm text-gray-500 dark:text-gray-400">{product.brand.name}</span>
|
||||||
<span className="text-lg font-bold text-primary">${lowestPrice.toFixed(2)}</span>
|
<span className="text-lg font-bold text-blue-600 dark:text-blue-400">${lowestPrice.toFixed(2)}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-xs text-base-content/50">{product.category.name}</span>
|
<span className="text-xs text-gray-400 dark:text-gray-500">{product.category.name}</span>
|
||||||
<Link href={`/products/${product.id}`} legacyBehavior>
|
<Link href={`/products/${product.id}`} legacyBehavior>
|
||||||
<a className="btn btn-primary btn-sm">
|
<a className="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded-md text-sm font-medium transition-colors">
|
||||||
View Details
|
View Details
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
{onAdd && (
|
{onAdd && (
|
||||||
<button
|
<button
|
||||||
className="btn btn-neutral btn-sm ml-2 flex items-center gap-1"
|
className="bg-gray-600 hover:bg-gray-700 text-white px-3 py-1 rounded-md text-sm font-medium transition-colors ml-2 flex items-center gap-1"
|
||||||
onClick={onAdd}
|
onClick={onAdd}
|
||||||
disabled={added}
|
disabled={added}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user