feat: add new default store page with product filtering and sorting functionality

This commit is contained in:
seaznCode 2026-01-13 16:12:06 +01:00
parent 94bced580a
commit 123dd9e85c
2 changed files with 720 additions and 93 deletions

View File

@ -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
View 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>
)
}