200 lines
7.5 KiB
TypeScript
200 lines
7.5 KiB
TypeScript
import { GetStaticPaths, GetStaticProps } from "next"
|
||
import Head from "next/head"
|
||
import Image from "next/image"
|
||
import Link from "next/link"
|
||
import { useState } from "react"
|
||
import { Heart } from "lucide-react"
|
||
import Header from "../../components/Header"
|
||
import Footer from "../../components/Footer"
|
||
import { collections, getCollectionBySlug } from "../../data/collections"
|
||
import { Product, formatPrice, getProductsByCollection } from "../../data/products"
|
||
import { useRouter } from "next/router"
|
||
import { motion } from "framer-motion"
|
||
|
||
interface CollectionPageProps {
|
||
collection: {
|
||
id: number
|
||
name: string
|
||
image: string
|
||
description: string
|
||
url: string
|
||
slug: string
|
||
}
|
||
collectionProducts: Product[]
|
||
}
|
||
|
||
export default function CollectionPage({ collection, collectionProducts }: CollectionPageProps) {
|
||
const router = useRouter()
|
||
const [hoveredProduct, setHoveredProduct] = useState<number | null>(null)
|
||
const [favorites, setFavorites] = useState<number[]>([])
|
||
|
||
// Если страница еще загружается, показываем заглушку
|
||
if (router.isFallback) {
|
||
return (
|
||
<div className="min-h-screen flex items-center justify-center">
|
||
<div className="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-gray-900"></div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// Функция для добавления/удаления товара из избранного
|
||
const toggleFavorite = (id: number, e: React.MouseEvent) => {
|
||
e.stopPropagation()
|
||
e.preventDefault()
|
||
setFavorites((prev) => (prev.includes(id) ? prev.filter((itemId) => itemId !== id) : [...prev, id]))
|
||
}
|
||
|
||
return (
|
||
<div className="min-h-screen bg-white font-['Arimo']">
|
||
<Head>
|
||
<title>{collection.name} | Brand Store</title>
|
||
<link rel="icon" href="/favicon.ico" />
|
||
</Head>
|
||
|
||
<Header />
|
||
|
||
<main>
|
||
{/* Баннер коллекции */}
|
||
<div className="relative h-[50vh] md:h-[60vh]">
|
||
<Image
|
||
src={collection.image}
|
||
alt={collection.name}
|
||
fill
|
||
className="object-cover"
|
||
priority
|
||
quality={95}
|
||
/>
|
||
<div className="absolute inset-0 bg-black bg-opacity-40 flex items-center justify-center">
|
||
<div className="text-center text-white px-4">
|
||
<motion.h1
|
||
initial={{ opacity: 0, y: -20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ duration: 0.5 }}
|
||
className="text-3xl md:text-5xl font-bold mb-4 font-['Playfair_Display']"
|
||
>
|
||
{collection.name}
|
||
</motion.h1>
|
||
<motion.p
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ duration: 0.5, delay: 0.2 }}
|
||
className="max-w-2xl mx-auto text-lg"
|
||
>
|
||
{collection.description}
|
||
</motion.p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Список товаров */}
|
||
<section className="py-12 px-4 md:px-8 max-w-7xl mx-auto">
|
||
<div className="mb-8">
|
||
<Link href="/collections" className="text-gray-600 hover:text-black transition-colors">
|
||
← Все коллекции
|
||
</Link>
|
||
<h2 className="text-2xl md:text-3xl font-bold mt-4 font-['Playfair_Display']">
|
||
Товары из коллекции
|
||
</h2>
|
||
</div>
|
||
|
||
{collectionProducts.length > 0 ? (
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8">
|
||
{collectionProducts.map((product) => (
|
||
<motion.div
|
||
key={product.id}
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ duration: 0.5, delay: product.id * 0.05 }}
|
||
whileHover={{ y: -5, transition: { duration: 0.2 } }}
|
||
>
|
||
<Link
|
||
href={`/product/${product.slug}`}
|
||
className="block h-full"
|
||
onMouseEnter={() => setHoveredProduct(product.id)}
|
||
onMouseLeave={() => setHoveredProduct(null)}
|
||
>
|
||
<div className="relative overflow-hidden rounded-xl">
|
||
<div className="aspect-[3/4] relative overflow-hidden rounded-xl">
|
||
<Image
|
||
src={
|
||
hoveredProduct === product.id && product.images.length > 1
|
||
? product.images[1]
|
||
: product.images[0]
|
||
}
|
||
alt={product.name}
|
||
fill
|
||
sizes="(max-width: 640px) 100vw, (max-width: 768px) 50vw, (max-width: 1024px) 33vw, 25vw"
|
||
className="object-cover transition-all duration-500 group-hover:scale-105"
|
||
/>
|
||
{product.isNew && (
|
||
<span className="absolute top-4 left-4 bg-black text-white text-sm py-1 px-3 rounded">
|
||
Новинка
|
||
</span>
|
||
)}
|
||
<button
|
||
onClick={(e) => toggleFavorite(product.id, e)}
|
||
className="absolute top-4 right-4 bg-white/80 hover:bg-white rounded-full p-2 transition-all"
|
||
aria-label={favorites.includes(product.id) ? "Удалить из избранного" : "Добавить в избранное"}
|
||
>
|
||
<Heart
|
||
className={`w-5 h-5 ${favorites.includes(product.id) ? "fill-red-500 text-red-500" : "text-gray-700"}`}
|
||
/>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div className="mt-4">
|
||
<h3 className="text-lg font-medium">{product.name}</h3>
|
||
<p className="mt-1 text-lg font-bold">{formatPrice(product.price)} ₽</p>
|
||
</div>
|
||
</Link>
|
||
</motion.div>
|
||
))}
|
||
</div>
|
||
) : (
|
||
<div className="text-center py-12">
|
||
<p className="text-xl text-gray-600">В этой коллекции пока нет товаров</p>
|
||
<Link href="/" className="mt-4 inline-block bg-black text-white px-6 py-2 rounded-md hover:bg-gray-800 transition-colors">
|
||
Вернуться на главную
|
||
</Link>
|
||
</div>
|
||
)}
|
||
</section>
|
||
</main>
|
||
|
||
<Footer />
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export const getStaticPaths: GetStaticPaths = async () => {
|
||
const paths = collections.map((collection) => ({
|
||
params: { slug: collection.slug },
|
||
}))
|
||
|
||
return {
|
||
paths,
|
||
fallback: 'blocking',
|
||
}
|
||
}
|
||
|
||
export const getStaticProps: GetStaticProps = async ({ params }) => {
|
||
const slug = params?.slug as string
|
||
const collection = getCollectionBySlug(slug)
|
||
|
||
if (!collection) {
|
||
return {
|
||
notFound: true,
|
||
}
|
||
}
|
||
|
||
// Получаем товары, принадлежащие к данной коллекции
|
||
const collectionProducts = getProductsByCollection(collection.id)
|
||
|
||
return {
|
||
props: {
|
||
collection,
|
||
collectionProducts,
|
||
},
|
||
revalidate: 600, // Перегенерация страницы каждые 10 минут
|
||
}
|
||
}
|