dressed_for_succes_store/pages/collections/[slug].tsx

200 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 минут
}
}