feat: add new default store page with product filtering and sorting functionality
This commit is contained in:
parent
94bced580a
commit
123dd9e85c
@ -17,8 +17,6 @@ import {
|
||||
} from '@headlessui/react'
|
||||
import {
|
||||
Bars3Icon,
|
||||
ShoppingBagIcon,
|
||||
UsersIcon,
|
||||
UserCircleIcon,
|
||||
XMarkIcon,
|
||||
ArrowRightOnRectangleIcon,
|
||||
@ -29,12 +27,6 @@ import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||
import useAuthStore from '../../store/authStore';
|
||||
import { Avatar } from '../avatar';
|
||||
|
||||
// Replace current shopItems definition with detailed version (adds icon & description)
|
||||
const shopItems = [
|
||||
{ name: 'VIP', href: '/shop/vip', description: 'Exclusive VIP shop', icon: ShoppingBagIcon },
|
||||
{ name: 'Public', href: '/shop/public', description: 'Open catalog for everyone', icon: UsersIcon },
|
||||
];
|
||||
|
||||
const informationItems = [
|
||||
{ name: 'Affiliate-Links', href: '/affiliate-links', description: 'Browse our partner links' },
|
||||
{ name: 'Memberships', href: '/memberships', description: 'Explore membership options' },
|
||||
@ -42,12 +34,10 @@ const informationItems = [
|
||||
];
|
||||
|
||||
const navLinks = [
|
||||
{ name: 'Shop', href: '/shop' },
|
||||
{ name: 'News', href: '/news' },
|
||||
];
|
||||
|
||||
// Toggle visibility of Shop navigation across header (desktop + mobile)
|
||||
const showShop = false;
|
||||
|
||||
export default function Header() {
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
||||
const [isDark, setIsDark] = useState(false)
|
||||
@ -250,47 +240,6 @@ export default function Header() {
|
||||
</button>
|
||||
</div>
|
||||
<PopoverGroup className="hidden lg:flex lg:gap-x-12">
|
||||
{/* Shop dropdown stays first (hidden via flag) */}
|
||||
{showShop && (
|
||||
<Popover>
|
||||
<PopoverButton className="flex items-center gap-x-1 text-sm/6 font-semibold text-gray-900 dark:text-white">
|
||||
Shop
|
||||
<ChevronDownIcon aria-hidden="true" className="size-5 flex-none text-gray-500" />
|
||||
</PopoverButton>
|
||||
<PopoverPanel
|
||||
transition
|
||||
className="absolute left-0 right-0 top-full z-50 rounded-b-2xl shadow-xl shadow-black/40 ring-1 ring-white/10 dark:ring-white/15 overflow-hidden data-closed:-translate-y-1 data-closed:opacity-0 data-enter:duration-200 data-enter:ease-out data-leave:duration-150 data-leave:ease-in"
|
||||
style={{
|
||||
background: 'linear-gradient(150deg, rgba(26,46,84,0.95) 0%, rgba(18,37,70,0.92) 45%, rgba(30,56,104,0.88) 100%)',
|
||||
backdropFilter: 'blur(26px) saturate(175%)',
|
||||
WebkitBackdropFilter: 'blur(26px) saturate(175%)'
|
||||
}}
|
||||
>
|
||||
<div className="relative before:absolute before:inset-0 before:pointer-events-none before:bg-[radial-gradient(circle_at_18%_30%,rgba(56,124,255,0.30),transparent_62%),radial-gradient(circle_at_82%_40%,rgba(139,92,246,0.22),transparent_65%)]">
|
||||
<div className="mx-auto grid max-w-7xl grid-cols-2 md:grid-cols-3 gap-x-4 px-6 py-10 lg:px-8 xl:gap-x-8">
|
||||
{shopItems.map(item => (
|
||||
<div
|
||||
key={item.name}
|
||||
className="group relative rounded-lg p-6 text-sm/6 hover:bg-white/5 transition-colors"
|
||||
>
|
||||
<div className="flex size-11 items-center justify-center rounded-lg bg-white/10 backdrop-blur-md group-hover:bg-white/20 transition-colors">
|
||||
<item.icon aria-hidden="true" className="size-6 text-gray-300 group-hover:text-white" />
|
||||
</div>
|
||||
<button
|
||||
onClick={() => router.push(item.href)}
|
||||
className="mt-6 block font-semibold text-white"
|
||||
>
|
||||
{item.name}
|
||||
<span className="absolute inset-0" />
|
||||
</button>
|
||||
<p className="mt-1 text-gray-300">{item.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</PopoverPanel>
|
||||
</Popover>
|
||||
)}
|
||||
|
||||
{/* Navigation Links */}
|
||||
{navLinks.map((link) => (
|
||||
@ -675,28 +624,8 @@ export default function Header() {
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
</div>
|
||||
{/* Navigation / Shop after that */}
|
||||
{/* Navigation after that */}
|
||||
<div className="space-y-2 py-6">
|
||||
{showShop && (
|
||||
<Disclosure as="div">
|
||||
<DisclosureButton className="group flex w-full items-center justify-between rounded-lg py-2 px-3 text-base/7 font-semibold text-gray-900 dark:text-white hover:bg-white/5">
|
||||
Shop
|
||||
<ChevronDownIcon aria-hidden="true" className="size-5 flex-none group-data-open:rotate-180" />
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel className="mt-2 space-y-1">
|
||||
{shopItems.map(item => (
|
||||
<DisclosureButton
|
||||
key={item.name}
|
||||
as="button"
|
||||
onClick={() => { router.push(item.href); setMobileMenuOpen(false); }}
|
||||
className="block rounded-lg py-2 pl-6 pr-3 text-sm/7 font-semibold text-gray-900 dark:text-white hover:bg-white/5 w-full text-left"
|
||||
>
|
||||
{item.name}
|
||||
</DisclosureButton>
|
||||
))}
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
)}
|
||||
{/* Information disclosure */}
|
||||
<Disclosure as="div">
|
||||
<DisclosureButton className="group flex w-full items-center justify-between rounded-lg py-2 px-3 text-base/7 font-semibold text-gray-900 dark:text-white hover:bg-white/5">
|
||||
@ -753,26 +682,6 @@ export default function Header() {
|
||||
</>
|
||||
) : (
|
||||
<div className="py-6 space-y-4">
|
||||
{showShop && (
|
||||
<Disclosure as="div">
|
||||
<DisclosureButton className="group flex w-full items-center justify-between rounded-lg py-2 px-3 text-base/7 font-semibold text-gray-900 dark:text-white hover:bg-white/5">
|
||||
Shop
|
||||
<ChevronDownIcon aria-hidden="true" className="size-5 flex-none group-data-open:rotate-180" />
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel className="mt-2 space-y-1">
|
||||
{shopItems.map(item => (
|
||||
<DisclosureButton
|
||||
key={item.name}
|
||||
as="button"
|
||||
onClick={() => { router.push(item.href); setMobileMenuOpen(false); }}
|
||||
className="block rounded-lg py-2 pl-6 pr-3 text-sm/7 font-semibold text-gray-900 dark:text-white hover:bg-white/5 w-full text-left"
|
||||
>
|
||||
{item.name}
|
||||
</DisclosureButton>
|
||||
))}
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
)}
|
||||
{/* Information disclosure */}
|
||||
<Disclosure as="div">
|
||||
<DisclosureButton className="group flex w-full items-center justify-between rounded-lg py-2 px-3 text-base/7 font-semibold text-gray-900 dark:text-white hover:bg-white/5">
|
||||
|
||||
718
src/app/shop/page.tsx
Normal file
718
src/app/shop/page.tsx
Normal file
@ -0,0 +1,718 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { notFound } from 'next/navigation'
|
||||
import { MagnifyingGlassIcon, FunnelIcon, Squares2X2Icon, ListBulletIcon, HeartIcon, StarIcon, ShoppingCartIcon } from '@heroicons/react/20/solid'
|
||||
import { HeartIcon as HeartOutlineIcon } from '@heroicons/react/24/outline'
|
||||
import PageLayout from '../components/PageLayout'
|
||||
|
||||
const categories = [
|
||||
'Alle Kategorien',
|
||||
'Technik',
|
||||
'Beauty',
|
||||
'Kleidung',
|
||||
'Getränke',
|
||||
'Lifestyle',
|
||||
'Büro'
|
||||
]
|
||||
|
||||
const sortOptions = [
|
||||
{ name: 'Beliebteste', href: '#', current: true },
|
||||
{ name: 'Neueste', href: '#', current: false },
|
||||
{ name: 'Preis: Niedrig zu Hoch', href: '#', current: false },
|
||||
{ name: 'Preis: Hoch zu Niedrig', href: '#', current: false },
|
||||
]
|
||||
|
||||
// Sample Products Data - Profit Planet Theme
|
||||
const sampleProducts = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'EcoDesk Pro',
|
||||
category: 'Büro',
|
||||
price: 299.99,
|
||||
originalPrice: 399.99,
|
||||
rating: 4.8,
|
||||
reviews: 124,
|
||||
image: 'https://images.unsplash.com/photo-1586953208448-b95a79798f07?ixlib=rb-4.0.3&auto=format&fit=crop&w=500&q=80',
|
||||
badge: 'Bestseller',
|
||||
inStock: true,
|
||||
brand: 'EcoTech',
|
||||
description: 'Nachhaltiger Steh-Sitz-Schreibtisch aus recyceltem Bambus'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Smart Success Journal',
|
||||
category: 'Lifestyle',
|
||||
price: 49.99,
|
||||
rating: 3,
|
||||
reviews: 89,
|
||||
image: 'https://images.unsplash.com/photo-1544716278-ca5e3f4abd8c?ixlib=rb-4.0.3&auto=format&fit=crop&w=500&q=80',
|
||||
badge: 'Neu',
|
||||
inStock: true,
|
||||
brand: 'GreenLife',
|
||||
description: 'Digital verknüpftes Erfolgs-Tagebuch mit App-Integration'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Wireless Charging Pad Pro',
|
||||
category: 'Technik',
|
||||
price: 79.99,
|
||||
originalPrice: 99.99,
|
||||
rating: 4.6,
|
||||
reviews: 203,
|
||||
image: 'https://images.unsplash.com/photo-1544716278-ca5e3f4abd8c?ixlib=rb-4.0.3&auto=format&fit=crop&w=500&q=80',
|
||||
badge: 'Sale',
|
||||
inStock: true,
|
||||
brand: 'EcoTech',
|
||||
description: 'Schnelles kabelloses Laden mit Solarenergie-Option'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Organic Green Tea Set',
|
||||
category: 'Getränke',
|
||||
price: 34.99,
|
||||
rating: 3.1,
|
||||
reviews: 67,
|
||||
image: 'https://images.unsplash.com/photo-1556679343-c7306c1976bc?ixlib=rb-4.0.3&auto=format&fit=crop&w=500&q=80',
|
||||
inStock: true,
|
||||
brand: 'GreenLife',
|
||||
description: 'Premium Bio-Tee-Sammlung für mehr Fokus und Energie'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'Sustainable Business Shirt',
|
||||
category: 'Kleidung',
|
||||
price: 89.99,
|
||||
rating: 4.5,
|
||||
reviews: 156,
|
||||
image: 'https://images.unsplash.com/photo-1596755094514-f87e34085b2c?ixlib=rb-4.0.3&auto=format&fit=crop&w=500&q=80',
|
||||
inStock: false,
|
||||
brand: 'SustainableStyle',
|
||||
description: 'Elegantes Businesshemd aus nachhaltiger Bio-Baumwolle'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'Anti-Blue Light Glasses',
|
||||
category: 'Lifestyle',
|
||||
price: 59.99,
|
||||
rating: 4.4,
|
||||
reviews: 91,
|
||||
image: 'https://images.unsplash.com/photo-1574258495973-f010dfbb5371?ixlib=rb-4.0.3&auto=format&fit=crop&w=500&q=80',
|
||||
inStock: true,
|
||||
brand: 'EcoTech',
|
||||
description: 'Stilvolle Blaulichtfilter-Brille für digitale Professionals'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: 'Glow Serum Vitamin C',
|
||||
category: 'Beauty',
|
||||
price: 24.99,
|
||||
rating: 2.8,
|
||||
reviews: 234,
|
||||
image: 'https://images.unsplash.com/photo-1620916566398-39f1143ab7be?ixlib=rb-4.0.3&auto=format&fit=crop&w=500&q=80',
|
||||
badge: 'Bestseller',
|
||||
inStock: true,
|
||||
brand: 'GreenLife',
|
||||
description: 'Natürliches Vitamin C Serum für strahlende Haut'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: 'Productivity Planner 2025',
|
||||
category: 'Büro',
|
||||
price: 39.99,
|
||||
rating: 4.3,
|
||||
reviews: 445,
|
||||
image: 'https://images.unsplash.com/photo-1517842645767-c639042777db?ixlib=rb-4.0.3&auto=format&fit=crop&w=500&q=80',
|
||||
badge: 'Bestseller',
|
||||
inStock: true,
|
||||
brand: 'SustainableStyle',
|
||||
description: 'Der ultimative Planer für maximale Produktivität'
|
||||
}
|
||||
]
|
||||
|
||||
export default function StorePage() {
|
||||
const SHOW_SHOP = 'true'
|
||||
if (!SHOW_SHOP) notFound()
|
||||
const [selectedCategory, setSelectedCategory] = useState('Alle Kategorien')
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')
|
||||
const [showFilters, setShowFilters] = useState(false)
|
||||
const [favorites, setFavorites] = useState<number[]>([])
|
||||
|
||||
// Filter states
|
||||
const [priceRange, setPriceRange] = useState({ min: '', max: '' })
|
||||
const [selectedRatings, setSelectedRatings] = useState<number[]>([])
|
||||
const [selectedAvailability, setSelectedAvailability] = useState<string[]>([])
|
||||
const [selectedBrand, setSelectedBrand] = useState('Alle Marken')
|
||||
const [sortBy, setSortBy] = useState('Beliebteste')
|
||||
|
||||
// Filter products based on all criteria
|
||||
const filteredProducts = sampleProducts.filter(product => {
|
||||
// Search filter
|
||||
const matchesSearch = product.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
product.description.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
|
||||
// Category filter
|
||||
const matchesCategory = selectedCategory === 'Alle Kategorien' || product.category === selectedCategory
|
||||
|
||||
// Price filter
|
||||
const minPrice = priceRange.min ? parseFloat(priceRange.min) : 0
|
||||
const maxPrice = priceRange.max ? parseFloat(priceRange.max) : Infinity
|
||||
const matchesPrice = product.price >= minPrice && product.price <= maxPrice
|
||||
|
||||
// Rating filter
|
||||
const matchesRating = selectedRatings.length === 0 || selectedRatings.some(rating => {
|
||||
if (rating === 5) return product.rating >= 4.8
|
||||
if (rating === 4) return product.rating >= 4.0
|
||||
if (rating === 3) return product.rating >= 3.0
|
||||
return false
|
||||
})
|
||||
|
||||
// Availability filter
|
||||
const matchesAvailability = selectedAvailability.length === 0 || selectedAvailability.some(availability => {
|
||||
if (availability === 'inStock') return product.inStock
|
||||
if (availability === 'preOrder') return !product.inStock
|
||||
return false
|
||||
})
|
||||
|
||||
// Brand filter
|
||||
const matchesBrand = selectedBrand === 'Alle Marken' || product.brand === selectedBrand
|
||||
|
||||
return matchesSearch && matchesCategory && matchesPrice && matchesRating && matchesAvailability && matchesBrand
|
||||
})
|
||||
|
||||
// Sort products
|
||||
const sortedProducts = [...filteredProducts].sort((a, b) => {
|
||||
switch (sortBy) {
|
||||
case 'Neueste':
|
||||
return b.id - a.id // Assuming higher ID means newer
|
||||
case 'Preis: Niedrig zu Hoch':
|
||||
return a.price - b.price
|
||||
case 'Preis: Hoch zu Niedrig':
|
||||
return b.price - a.price
|
||||
case 'Beliebteste':
|
||||
default:
|
||||
return b.rating - a.rating // Higher rating first
|
||||
}
|
||||
})
|
||||
|
||||
// Reset all filters
|
||||
const resetFilters = () => {
|
||||
setSelectedCategory('Alle Kategorien')
|
||||
setSearchQuery('')
|
||||
setPriceRange({ min: '', max: '' })
|
||||
setSelectedRatings([])
|
||||
setSelectedAvailability([])
|
||||
setSelectedBrand('Alle Marken')
|
||||
setSortBy('Beliebteste')
|
||||
}
|
||||
|
||||
// Toggle favorite status
|
||||
const toggleFavorite = (productId: number) => {
|
||||
setFavorites(prev =>
|
||||
prev.includes(productId)
|
||||
? prev.filter(id => id !== productId)
|
||||
: [...prev, productId]
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
<div className="bg-white">
|
||||
{/* Modern Compact Header with Background Image */}
|
||||
<div className="relative bg-gray-900">
|
||||
{/* Background Image with Overlay */}
|
||||
<div
|
||||
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||||
style={{
|
||||
backgroundImage: 'url(https://images.unsplash.com/photo-1441986300917-64674bd600d8?ixlib=rb-4.0.3&auto=format&fit=crop&w=2070&q=80)',
|
||||
}}
|
||||
>
|
||||
<div className="absolute inset-0 bg-gray-900/70 backdrop-blur-sm" />
|
||||
</div>
|
||||
|
||||
{/* Header Content */}
|
||||
<div className="relative z-10 px-4 py-12 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-7xl">
|
||||
{/* Store Title */}
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-3xl font-bold tracking-tight text-white sm:text-4xl">
|
||||
Profit Planet Store
|
||||
</h1>
|
||||
<p className="mt-2 text-lg text-gray-300">
|
||||
Nachhaltige Produkte für deinen Erfolg
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Search & Navigation Section */}
|
||||
<div className="space-y-6">
|
||||
{/* Search Bar - Tailwind UI Plus Style */}
|
||||
<div className="mx-auto max-w-2xl">
|
||||
<div className="relative">
|
||||
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<MagnifyingGlassIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
||||
</div>
|
||||
<input
|
||||
type="search"
|
||||
name="search"
|
||||
id="search"
|
||||
className="block w-full rounded-lg border-0 bg-white/90 backdrop-blur-sm py-3 pl-10 pr-3 text-gray-900 placeholder-gray-500 shadow-lg ring-1 ring-inset ring-white/10 focus:ring-2 focus:ring-inset focus:ring-[#8D6B1D] sm:text-sm sm:leading-6"
|
||||
placeholder="Produkte durchsuchen..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Category Navigation - Horizontal Tabs Style */}
|
||||
<div className="flex justify-center">
|
||||
<div className="inline-flex rounded-lg bg-white/10 backdrop-blur-sm p-1">
|
||||
<nav className="flex space-x-1" aria-label="Kategorien">
|
||||
{categories.map((category) => (
|
||||
<button
|
||||
key={category}
|
||||
onClick={() => setSelectedCategory(category)}
|
||||
className={`
|
||||
${selectedCategory === category
|
||||
? 'bg-white text-gray-900 shadow-sm'
|
||||
: 'text-white hover:bg-white/20'
|
||||
}
|
||||
rounded-md px-3 py-2 text-sm font-medium transition-all duration-200
|
||||
`}
|
||||
>
|
||||
{category}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Filters & View Options Bar */}
|
||||
<div className="border-b border-gray-200 bg-white">
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex h-16 items-center justify-between">
|
||||
{/* Left: Results Count & Filters */}
|
||||
<div className="flex items-center space-x-4">
|
||||
<p className="text-sm text-gray-700">
|
||||
<span className="font-medium">{sortedProducts.length}</span> Produkte gefunden
|
||||
</p>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowFilters(!showFilters)}
|
||||
className="inline-flex items-center gap-x-1.5 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
|
||||
>
|
||||
<FunnelIcon className="-ml-0.5 h-4 w-4" />
|
||||
Filter
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Right: Sort & View Options */}
|
||||
<div className="flex items-center space-x-4">
|
||||
{/* Sort Dropdown */}
|
||||
<div className="relative">
|
||||
<select
|
||||
value={sortBy}
|
||||
onChange={(e) => setSortBy(e.target.value)}
|
||||
className="rounded-md border-0 bg-white py-1.5 pl-3 pr-8 text-sm text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-[#8D6B1D]"
|
||||
>
|
||||
{sortOptions.map((option) => (
|
||||
<option key={option.name} value={option.name}>
|
||||
{option.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* View Mode Toggle */}
|
||||
<div className="flex rounded-md shadow-sm">
|
||||
<button
|
||||
onClick={() => setViewMode('grid')}
|
||||
className={`
|
||||
${viewMode === 'grid'
|
||||
? 'bg-[#8D6B1D] text-white'
|
||||
: 'bg-white text-gray-400 hover:text-gray-500'
|
||||
}
|
||||
relative inline-flex items-center rounded-l-md px-3 py-2 text-sm font-medium ring-1 ring-inset ring-gray-300 focus:z-10 focus:ring-2 focus:ring-[#8D6B1D]
|
||||
`}
|
||||
>
|
||||
<Squares2X2Icon className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode('list')}
|
||||
className={`
|
||||
${viewMode === 'list'
|
||||
? 'bg-[#8D6B1D] text-white'
|
||||
: 'bg-white text-gray-400 hover:text-gray-500'
|
||||
}
|
||||
relative -ml-px inline-flex items-center rounded-r-md px-3 py-2 text-sm font-medium ring-1 ring-inset ring-gray-300 focus:z-10 focus:ring-2 focus:ring-[#8D6B1D]
|
||||
`}
|
||||
>
|
||||
<ListBulletIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Expandable Filters Section */}
|
||||
{showFilters && (
|
||||
<div className="border-b border-gray-200 bg-gray-50">
|
||||
<div className="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{/* Price Range */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-900">Preis</h3>
|
||||
<div className="mt-2 space-y-3">
|
||||
<div className="flex space-x-3">
|
||||
<input
|
||||
type="number"
|
||||
placeholder="Min €"
|
||||
value={priceRange.min}
|
||||
onChange={(e) => setPriceRange({...priceRange, min: e.target.value})}
|
||||
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-[#8D6B1D] sm:text-sm sm:leading-6"
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
placeholder="Max €"
|
||||
value={priceRange.max}
|
||||
onChange={(e) => setPriceRange({...priceRange, max: e.target.value})}
|
||||
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-[#8D6B1D] sm:text-sm sm:leading-6"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Rating Filter */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-900">Bewertung</h3>
|
||||
<div className="mt-2 space-y-2">
|
||||
{[
|
||||
{ label: '5 Sterne', value: 5 },
|
||||
{ label: '4+ Sterne', value: 4 },
|
||||
{ label: '3+ Sterne', value: 3 }
|
||||
].map((rating) => (
|
||||
<label key={rating.value} className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedRatings.includes(rating.value)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setSelectedRatings([...selectedRatings, rating.value])
|
||||
} else {
|
||||
setSelectedRatings(selectedRatings.filter(r => r !== rating.value))
|
||||
}
|
||||
}}
|
||||
className="h-4 w-4 rounded border-gray-300 text-[#8D6B1D] focus:ring-[#8D6B1D]"
|
||||
/>
|
||||
<span className="ml-2 text-sm text-gray-600">{rating.label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Availability */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-900">Verfügbarkeit</h3>
|
||||
<div className="mt-2 space-y-2">
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedAvailability.includes('inStock')}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setSelectedAvailability([...selectedAvailability, 'inStock'])
|
||||
} else {
|
||||
setSelectedAvailability(selectedAvailability.filter(a => a !== 'inStock'))
|
||||
}
|
||||
}}
|
||||
className="h-4 w-4 rounded border-gray-300 text-[#8D6B1D] focus:ring-[#8D6B1D]"
|
||||
/>
|
||||
<span className="ml-2 text-sm text-gray-600">Auf Lager</span>
|
||||
</label>
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedAvailability.includes('preOrder')}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setSelectedAvailability([...selectedAvailability, 'preOrder'])
|
||||
} else {
|
||||
setSelectedAvailability(selectedAvailability.filter(a => a !== 'preOrder'))
|
||||
}
|
||||
}}
|
||||
className="h-4 w-4 rounded border-gray-300 text-[#8D6B1D] focus:ring-[#8D6B1D]"
|
||||
/>
|
||||
<span className="ml-2 text-sm text-gray-600">Vorbestellung</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Brand Filter */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-900">Marke</h3>
|
||||
<div className="mt-2">
|
||||
<select
|
||||
value={selectedBrand}
|
||||
onChange={(e) => setSelectedBrand(e.target.value)}
|
||||
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-[#8D6B1D] sm:text-sm sm:leading-6"
|
||||
>
|
||||
<option>Alle Marken</option>
|
||||
<option>EcoTech</option>
|
||||
<option>GreenLife</option>
|
||||
<option>SustainableStyle</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Filter Actions */}
|
||||
<div className="mt-6 flex justify-start">
|
||||
<button
|
||||
type="button"
|
||||
onClick={resetFilters}
|
||||
className="rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 transition-colors duration-200"
|
||||
>
|
||||
Filter zurücksetzen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Products Grid/List */}
|
||||
<div className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
|
||||
{sortedProducts.length === 0 ? (
|
||||
<div className="text-center py-16">
|
||||
<p className="text-lg text-gray-500">Keine Produkte gefunden</p>
|
||||
<p className="mt-2 text-sm text-gray-400">
|
||||
Versuche andere Suchbegriffe oder Filter
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* Grid View */}
|
||||
{viewMode === 'grid' && (
|
||||
<div className="grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
|
||||
{sortedProducts.map((product) => (
|
||||
<div key={product.id} className="group relative flex flex-col h-full bg-white border border-gray-200 rounded-lg p-4 hover:shadow-md transition-shadow duration-200">
|
||||
{/* Product Image - Clickable area for product page */}
|
||||
<div className="aspect-h-1 aspect-w-1 w-full overflow-hidden rounded-md bg-gray-200 xl:aspect-h-8 xl:aspect-w-7 relative">
|
||||
<a href="#" className="block h-full w-full">
|
||||
<img
|
||||
src={product.image}
|
||||
alt={product.name}
|
||||
className="h-full w-full object-cover object-center group-hover:opacity-75 transition-opacity duration-200"
|
||||
/>
|
||||
</a>
|
||||
|
||||
{/* Badge */}
|
||||
{product.badge && (
|
||||
<div className="absolute top-2 left-2 z-10">
|
||||
<span className={`
|
||||
inline-flex items-center rounded-full px-2 py-1 text-xs font-medium
|
||||
${product.badge === 'Bestseller' ? 'bg-yellow-100 text-yellow-800' : ''}
|
||||
${product.badge === 'Neu' ? 'bg-green-100 text-green-800' : ''}
|
||||
${product.badge === 'Sale' ? 'bg-red-100 text-red-800' : ''}
|
||||
`}>
|
||||
{product.badge}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Favorite Button */}
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
toggleFavorite(product.id);
|
||||
}}
|
||||
className="absolute top-2 right-2 z-10 rounded-full bg-white/90 p-2 text-gray-400 hover:text-red-500 focus:outline-none focus:ring-2 focus:ring-[#8D6B1D] focus:ring-offset-2 transition-colors duration-200"
|
||||
>
|
||||
{favorites.includes(product.id) ? (
|
||||
<HeartIcon className="h-5 w-5 text-red-500" />
|
||||
) : (
|
||||
<HeartOutlineIcon className="h-5 w-5" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Stock Status */}
|
||||
{!product.inStock && (
|
||||
<div className="absolute inset-0 z-5 flex items-center justify-center bg-gray-900/50">
|
||||
<span className="rounded-md bg-white px-3 py-1 text-sm font-medium text-gray-900">
|
||||
Ausverkauft
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Product Info - Non-clickable */}
|
||||
<div className="mt-3 flex-1 flex flex-col justify-between">
|
||||
<div className="flex-1">
|
||||
<h3 className="text-sm text-gray-700">
|
||||
<a href="#" className="hover:text-gray-900 transition-colors duration-200">
|
||||
{product.name}
|
||||
</a>
|
||||
</h3>
|
||||
<p className="mt-1 text-xs text-gray-500">{product.brand}</p>
|
||||
|
||||
{/* Rating */}
|
||||
<div className="mt-2 flex items-center">
|
||||
<div className="flex items-center">
|
||||
{[0, 1, 2, 3, 4].map((rating) => (
|
||||
<StarIcon
|
||||
key={rating}
|
||||
className={`
|
||||
${product.rating > rating ? 'text-yellow-400' : 'text-gray-300'}
|
||||
h-3 w-3 flex-shrink-0
|
||||
`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<p className="ml-1 text-xs text-gray-600">({product.reviews})</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Price and Cart Button - Non-clickable for product page */}
|
||||
<div className="mt-4 flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<p className="text-lg font-medium text-gray-900">€{product.price}</p>
|
||||
{product.originalPrice && (
|
||||
<p className="text-sm text-gray-500 line-through">€{product.originalPrice}</p>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// Add to cart logic here
|
||||
console.log('Added to cart:', product.name);
|
||||
}}
|
||||
disabled={!product.inStock}
|
||||
className={`
|
||||
inline-flex items-center rounded-md px-3 py-2 text-sm font-semibold shadow-sm focus-visible:outline focus-visible:outline-offset-2 transition-colors duration-200
|
||||
${product.inStock
|
||||
? 'bg-[#8D6B1D] text-white hover:bg-[#7A5E1A] focus-visible:outline-[#8D6B1D]'
|
||||
: 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
||||
}
|
||||
`}
|
||||
>
|
||||
<ShoppingCartIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* List View */}
|
||||
{viewMode === 'list' && (
|
||||
<div className="space-y-4">
|
||||
{sortedProducts.map((product) => (
|
||||
<div key={product.id} className="flex items-center space-x-4 rounded-lg border border-gray-200 bg-white p-4 shadow-sm hover:shadow-md transition-shadow duration-200">
|
||||
{/* Product Image */}
|
||||
<div className="relative h-24 w-24 flex-shrink-0 overflow-hidden rounded-md bg-gray-200">
|
||||
<img
|
||||
src={product.image}
|
||||
alt={product.name}
|
||||
className="h-full w-full object-cover object-center"
|
||||
/>
|
||||
{product.badge && (
|
||||
<div className="absolute -top-1 -left-1">
|
||||
<span className={`
|
||||
inline-flex items-center rounded-full px-2 py-1 text-xs font-medium
|
||||
${product.badge === 'Bestseller' ? 'bg-yellow-100 text-yellow-800' : ''}
|
||||
${product.badge === 'Neu' ? 'bg-green-100 text-green-800' : ''}
|
||||
${product.badge === 'Sale' ? 'bg-red-100 text-red-800' : ''}
|
||||
`}>
|
||||
{product.badge}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Product Details */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<h3 className="text-base font-medium text-gray-900">{product.name}</h3>
|
||||
<p className="text-sm text-gray-500">{product.brand}</p>
|
||||
<p className="mt-1 text-sm text-gray-600">{product.description}</p>
|
||||
|
||||
{/* Rating */}
|
||||
<div className="mt-2 flex items-center">
|
||||
<div className="flex items-center">
|
||||
{[0, 1, 2, 3, 4].map((rating) => (
|
||||
<StarIcon
|
||||
key={rating}
|
||||
className={`
|
||||
${product.rating > rating ? 'text-yellow-400' : 'text-gray-300'}
|
||||
h-4 w-4 flex-shrink-0
|
||||
`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<p className="ml-2 text-sm text-gray-600">({product.reviews} Bewertungen)</p>
|
||||
{!product.inStock && (
|
||||
<span className="ml-4 inline-flex items-center rounded-full bg-red-100 px-2 py-1 text-xs font-medium text-red-800">
|
||||
Ausverkauft
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Price and Actions */}
|
||||
<div className="ml-6 flex flex-col items-end space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<p className="text-xl font-medium text-gray-900">€{product.price}</p>
|
||||
{product.originalPrice && (
|
||||
<p className="text-sm text-gray-500 line-through">€{product.originalPrice}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<button
|
||||
onClick={() => toggleFavorite(product.id)}
|
||||
className="rounded-full bg-gray-50 p-2 text-gray-400 hover:text-red-500 focus:outline-none focus:ring-2 focus:ring-[#8D6B1D] focus:ring-offset-2"
|
||||
>
|
||||
{favorites.includes(product.id) ? (
|
||||
<HeartIcon className="h-5 w-5 text-red-500" />
|
||||
) : (
|
||||
<HeartOutlineIcon className="h-5 w-5" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
disabled={!product.inStock}
|
||||
className={`
|
||||
inline-flex items-center rounded-md px-4 py-2 text-sm font-semibold shadow-sm focus-visible:outline focus-visible:outline-offset-2
|
||||
${product.inStock
|
||||
? 'bg-[#8D6B1D] text-white hover:bg-[#7A5E1A] focus-visible:outline-[#8D6B1D]'
|
||||
: 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
||||
}
|
||||
`}
|
||||
>
|
||||
<ShoppingCartIcon className="mr-2 h-4 w-4" />
|
||||
{product.inStock ? 'In Warenkorb' : 'Ausverkauft'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user