Обновить футер и главную страницу с добавлением новых компонентов и улучшением навигации

This commit is contained in:
belikovme 2025-02-27 16:10:28 +07:00
parent 1e9a516f9f
commit d770f217b6
15 changed files with 530 additions and 95 deletions

View File

@ -0,0 +1,63 @@
import { motion } from 'framer-motion';
import Image from 'next/image';
import Link from 'next/link';
// Типы для свойств компонента
interface CollectionsProps {
collections: Collection[];
}
// Тип для коллекции
interface Collection {
id: number;
name: string;
image: string;
description: string;
url: string;
}
export default function Collections({ collections }: CollectionsProps) {
return (
<section className="py-8 bg-white">
<div className="container mx-auto px-6">
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
className="text-3xl text-center font-medium mb-12"
>
Коллекции
</motion.h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{collections.map((collection, index) => (
<motion.div
key={collection.id}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 }}
className="group relative overflow-hidden rounded-lg"
>
<Link href={collection.url}>
<div className="relative aspect-[4/3] w-full overflow-hidden">
<Image
src={collection.image}
alt={collection.name}
layout="fill"
objectFit="cover"
className="transition-all duration-500 group-hover:scale-105"
/>
<div className="absolute inset-0 bg-black bg-opacity-20 transition-opacity group-hover:bg-opacity-30" />
</div>
<div className="absolute bottom-0 left-0 right-0 p-6 text-white">
<h3 className="text-xl font-medium mb-2">{collection.name}</h3>
<p className="text-sm opacity-90">{collection.description}</p>
</div>
</Link>
</motion.div>
))}
</div>
</div>
</section>
);
}

View File

@ -1,37 +1,83 @@
export default function Footer() {
import Link from "next/link";
import { Facebook, Instagram, Twitter, Youtube } from "lucide-react";
export default function Footer() {
return (
<footer className="bg-primary text-white py-12">
<div className="container mx-auto px-6">
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<div>
<h4 className="text-xl mb-4">О нас</h4>
<p className="text-gray-400">
Мы создаем элегантную одежду для тех, кто ценит качество и стиль.
</p>
<footer className="bg-gray-100 text-gray-800 py-12">
<div className="container mx-auto px-6">
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
<div>
<h4 className="text-lg font-medium mb-4">Помощь</h4>
<ul className="space-y-2">
<li><Link href="/contact" className="text-sm hover:underline">Связаться с нами</Link></li>
<li><Link href="/faq" className="text-sm hover:underline">Часто задаваемые вопросы</Link></li>
<li><Link href="/shipping" className="text-sm hover:underline">Доставка и возврат</Link></li>
<li><Link href="/track-order" className="text-sm hover:underline">Отследить заказ</Link></li>
<li><Link href="/size-guide" className="text-sm hover:underline">Руководство по размерам</Link></li>
</ul>
</div>
<div>
<h4 className="text-lg font-medium mb-4">Магазин</h4>
<ul className="space-y-2">
<li><Link href="/women" className="text-sm hover:underline">Женщинам</Link></li>
<li><Link href="/men" className="text-sm hover:underline">Мужчинам</Link></li>
<li><Link href="/accessories" className="text-sm hover:underline">Аксессуары</Link></li>
<li><Link href="/collections" className="text-sm hover:underline">Коллекции</Link></li>
<li><Link href="/sale" className="text-sm hover:underline">Распродажа</Link></li>
</ul>
</div>
<div>
<h4 className="text-lg font-medium mb-4">О компании</h4>
<ul className="space-y-2">
<li><Link href="/about" className="text-sm hover:underline">О нас</Link></li>
<li><Link href="/careers" className="text-sm hover:underline">Карьера</Link></li>
<li><Link href="/sustainability" className="text-sm hover:underline">Устойчивое развитие</Link></li>
<li><Link href="/press" className="text-sm hover:underline">Пресса</Link></li>
<li><Link href="/affiliates" className="text-sm hover:underline">Партнерская программа</Link></li>
</ul>
</div>
<div>
<h4 className="text-lg font-medium mb-4">Подписаться</h4>
<p className="text-sm mb-4">Подпишитесь на нашу рассылку, чтобы получать новости о новых коллекциях и эксклюзивных предложениях.</p>
<div className="flex mb-6">
<input
type="email"
placeholder="Ваш email"
className="bg-white px-4 py-2 text-sm border border-gray-300 rounded-l focus:outline-none flex-grow"
/>
<button className="bg-black text-white px-4 py-2 text-sm rounded-r hover:bg-gray-800 transition-colors">
</button>
</div>
<div>
<h4 className="text-xl mb-4">Контакты</h4>
<p className="text-gray-400">
Email: info@brandstore.com<br />
Телефон: +7 (999) 123-45-67
</p>
</div>
<div>
<h4 className="text-xl mb-4">Подписаться</h4>
<div className="flex">
<input
type="email"
placeholder="Ваш email"
className="bg-white/10 px-4 py-2 rounded-l-full focus:outline-none"
/>
<button className="bg-accent px-6 rounded-r-full hover:bg-accent/90 transition-colors">
</button>
</div>
<div className="flex space-x-4">
<a href="#" className="text-gray-600 hover:text-black transition-colors">
<Facebook size={20} />
</a>
<a href="#" className="text-gray-600 hover:text-black transition-colors">
<Instagram size={20} />
</a>
<a href="#" className="text-gray-600 hover:text-black transition-colors">
<Twitter size={20} />
</a>
<a href="#" className="text-gray-600 hover:text-black transition-colors">
<Youtube size={20} />
</a>
</div>
</div>
</div>
</footer>
<div className="border-t border-gray-200 mt-8 pt-8 flex flex-col md:flex-row justify-between items-center">
<p className="text-xs text-gray-500 mb-4 md:mb-0">© {new Date().getFullYear()} Brand Store. Все права защищены.</p>
<div className="flex space-x-4">
<Link href="/privacy" className="text-xs text-gray-500 hover:underline">Политика конфиденциальности</Link>
<Link href="/terms" className="text-xs text-gray-500 hover:underline">Условия использования</Link>
<Link href="/cookies" className="text-xs text-gray-500 hover:underline">Политика использования файлов cookie</Link>
</div>
</div>
</div>
</footer>
);
}

103
components/NewArrivals.tsx Normal file
View File

@ -0,0 +1,103 @@
import { motion } from 'framer-motion';
import Image from 'next/image';
import { useState } from 'react';
import { Heart } from 'lucide-react';
// Типы для свойств компонента
interface NewArrivalsProps {
products: Product[];
}
// Тип для товара
interface Product {
id: number;
name: string;
price: number;
images: string[];
description: string;
isNew?: boolean;
}
export default function NewArrivals({ products }: NewArrivalsProps) {
// Состояние для отслеживания наведения на карточки товаров
const [hoveredProduct, setHoveredProduct] = useState<number | null>(null);
// Состояние для отслеживания избранных товаров
const [favorites, setFavorites] = useState<number[]>([]);
// Функция для добавления/удаления товара из избранного
const toggleFavorite = (id: number, e: React.MouseEvent) => {
e.stopPropagation();
setFavorites(prev =>
prev.includes(id)
? prev.filter(itemId => itemId !== id)
: [...prev, id]
);
};
return (
<section className="py-8 bg-white">
<div className="container mx-auto px-6">
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
className="text-3xl text-center font-medium mb-12"
>
Новинки
</motion.h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-x-6 gap-y-12">
{products.map((product, index) => (
<motion.div
key={product.id}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 }}
className="group relative"
onMouseEnter={() => setHoveredProduct(product.id)}
onMouseLeave={() => setHoveredProduct(null)}
>
{/* Метка "New" */}
{product.isNew && (
<div className="absolute top-4 left-4 z-10 bg-gray-100 text-gray-700 text-xs px-3 py-1 rounded-full">
New
</div>
)}
{/* Кнопка избранного */}
<button
onClick={(e) => toggleFavorite(product.id, e)}
className="absolute top-4 right-4 z-10 p-1"
>
<Heart
className={`w-6 h-6 transition-colors ${
favorites.includes(product.id)
? 'fill-black text-black'
: 'text-gray-400 hover:text-black'
}`}
/>
</button>
{/* Изображение товара */}
<div className="relative aspect-[3/4] w-full overflow-hidden mb-4">
<Image
src={hoveredProduct === product.id && product.images.length > 1 ? product.images[1] : product.images[0]}
alt={product.name}
layout="fill"
objectFit="cover"
className="transition-all duration-500 hover:scale-105"
/>
</div>
{/* Информация о товаре */}
<div className="px-1">
<h3 className="text-sm text-gray-700 mb-2 line-clamp-2">{product.name}</h3>
<p className="text-base font-medium">{product.price.toLocaleString()} </p>
</div>
</motion.div>
))}
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,58 @@
import { motion } from 'framer-motion';
import Image from 'next/image';
import Link from 'next/link';
// Типы для свойств компонента
interface PopularCategoriesProps {
categories: Category[];
}
// Тип для категории
interface Category {
id: number;
name: string;
image: string;
url: string;
}
export default function PopularCategories({ categories }: PopularCategoriesProps) {
return (
<section className="py-8 bg-white">
<div className="container mx-auto px-6">
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
className="text-3xl text-center font-medium mb-12"
>
Популярные категории
</motion.h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
{categories.map((category, index) => (
<motion.div
key={category.id}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 }}
className="group"
>
<Link href={category.url} className="block">
<div className="relative aspect-square w-full overflow-hidden mb-4 bg-gray-100">
<Image
src={category.image}
alt={category.name}
layout="fill"
objectFit="cover"
className="transition-all duration-500 group-hover:scale-105"
/>
</div>
<h3 className="text-center text-sm font-medium">{category.name}</h3>
</Link>
</motion.div>
))}
</div>
</div>
</section>
);
}

102
components/TabSelector.tsx Normal file
View File

@ -0,0 +1,102 @@
"use client"
import { useState, useRef, useEffect } from "react"
const tabs = ["Новинки", "Коллекции", "Популярное"]
interface TabSelectorProps {
onTabChange: (index: number) => void;
}
export default function TabSelector({ onTabChange }: TabSelectorProps) {
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null)
const [activeIndex, setActiveIndex] = useState(0)
const [hoverStyle, setHoverStyle] = useState({})
const [activeStyle, setActiveStyle] = useState({ left: "0px", width: "0px" })
const tabRefs = useRef<(HTMLDivElement | null)[]>([])
useEffect(() => {
if (hoveredIndex !== null) {
const hoveredElement = tabRefs.current[hoveredIndex]
if (hoveredElement) {
const { offsetLeft, offsetWidth } = hoveredElement
setHoverStyle({
left: `${offsetLeft}px`,
width: `${offsetWidth}px`,
})
}
}
}, [hoveredIndex])
useEffect(() => {
const activeElement = tabRefs.current[activeIndex]
if (activeElement) {
const { offsetLeft, offsetWidth } = activeElement
setActiveStyle({
left: `${offsetLeft}px`,
width: `${offsetWidth}px`,
})
}
// Вызываем функцию обратного вызова при изменении активного таба
onTabChange(activeIndex);
}, [activeIndex, onTabChange])
useEffect(() => {
requestAnimationFrame(() => {
const firstElement = tabRefs.current[0]
if (firstElement) {
const { offsetLeft, offsetWidth } = firstElement
setActiveStyle({
left: `${offsetLeft}px`,
width: `${offsetWidth}px`,
})
}
})
}, [])
return (
<div className="flex justify-center items-center w-full py-8 bg-white">
<div className="w-full max-w-[1200px] h-[60px] relative flex items-center justify-center">
<div className="p-0">
<div className="relative">
{/* Hover Highlight */}
<div
className="absolute h-[30px] transition-all duration-300 ease-out bg-[#0e0f1114] rounded-[6px] flex items-center"
style={{
...hoverStyle,
opacity: hoveredIndex !== null ? 1 : 0,
}}
/>
{/* Active Indicator */}
<div
className="absolute bottom-[-6px] h-[2px] bg-black transition-all duration-300 ease-out"
style={activeStyle}
/>
{/* Tabs */}
<div className="relative flex space-x-[24px] items-center">
{tabs.map((tab, index) => (
<div
key={index}
ref={(el) => (tabRefs.current[index] = el)}
className={`px-3 py-2 cursor-pointer transition-colors duration-300 h-[30px] ${
index === activeIndex ? "text-black font-medium" : "text-gray-500"
}`}
onMouseEnter={() => setHoveredIndex(index)}
onMouseLeave={() => setHoveredIndex(null)}
onClick={() => setActiveIndex(index)}
>
<div className="text-sm leading-5 whitespace-nowrap flex items-center justify-center h-full">
{tab}
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
)
}

View File

@ -5,6 +5,10 @@ import { useState, useEffect } from 'react';
import Header from '../components/Header';
import Hero from '../components/Hero';
import CookieNotification from '../components/CookieNotification';
import TabSelector from '../components/TabSelector';
import NewArrivals from '../components/NewArrivals';
import Collections from '../components/Collections';
import PopularCategories from '../components/PopularCategories';
import fs from 'fs';
import path from 'path';
import { Heart } from 'lucide-react';
@ -14,6 +18,8 @@ import Footer from '../components/Footer';
interface HomeProps {
heroImages: string[];
products: Product[];
collections: Collection[];
categories: Category[];
}
// Тип для товара
@ -26,11 +32,30 @@ interface Product {
isNew?: boolean;
}
export default function Home({ heroImages, products }: HomeProps) {
// Тип для коллекции
interface Collection {
id: number;
name: string;
image: string;
description: string;
url: string;
}
// Тип для категории
interface Category {
id: number;
name: string;
image: string;
url: string;
}
export default function Home({ heroImages, products, collections, categories }: HomeProps) {
// Состояние для отслеживания наведения на карточки товаров
const [hoveredProduct, setHoveredProduct] = useState<number | null>(null);
// Состояние для отслеживания избранных товаров
const [favorites, setFavorites] = useState<number[]>([]);
// Состояние для отслеживания активного таба
const [activeTab, setActiveTab] = useState(0);
// Состояние для отслеживания прокрутки страницы
const [scrolled, setScrolled] = useState(false);
@ -60,6 +85,25 @@ export default function Home({ heroImages, products }: HomeProps) {
);
};
// Функция для обработки изменения активного таба
const handleTabChange = (index: number) => {
setActiveTab(index);
};
// Рендерим контент в зависимости от активного таба
const renderTabContent = () => {
switch (activeTab) {
case 0:
return <NewArrivals products={products} />;
case 1:
return <Collections collections={collections} />;
case 2:
return <PopularCategories categories={categories} />;
default:
return <NewArrivals products={products} />;
}
};
return (
<div className="min-h-screen bg-white font-['Arimo']">
<Head>
@ -75,71 +119,11 @@ export default function Home({ heroImages, products }: HomeProps) {
{/* Секция с HERO элементом */}
<Hero images={heroImages} />
{/* Секция с товарами */}
<section className="py-16 bg-white">
<div className="container mx-auto px-6">
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
className="text-3xl text-center font-medium mb-12"
>
Коллекция
</motion.h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-x-6 gap-y-12">
{products.map((product, index) => (
<motion.div
key={product.id}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 }}
className="group relative"
onMouseEnter={() => setHoveredProduct(product.id)}
onMouseLeave={() => setHoveredProduct(null)}
>
{/* Метка "New" */}
{product.isNew && (
<div className="absolute top-4 left-4 z-10 bg-gray-100 text-gray-700 text-xs px-3 py-1 rounded-full">
New
</div>
)}
{/* Кнопка избранного */}
<button
onClick={(e) => toggleFavorite(product.id, e)}
className="absolute top-4 right-4 z-10 p-1"
>
<Heart
className={`w-6 h-6 transition-colors ${
favorites.includes(product.id)
? 'fill-black text-black'
: 'text-gray-400 hover:text-black'
}`}
/>
</button>
{/* Изображение товара */}
<div className="relative aspect-[3/4] w-full overflow-hidden mb-4">
<Image
src={hoveredProduct === product.id && product.images.length > 1 ? product.images[1] : product.images[0]}
alt={product.name}
layout="fill"
objectFit="cover"
className="transition-all duration-500 hover:scale-105"
/>
</div>
{/* Информация о товаре */}
<div className="px-1">
<h3 className="text-sm text-gray-700 mb-2 line-clamp-2">{product.name}</h3>
<p className="text-base font-medium">{product.price.toLocaleString()} </p>
</div>
</motion.div>
))}
</div>
</div>
</section>
{/* Табы для выбора категорий */}
<TabSelector onTabChange={handleTabChange} />
{/* Контент в зависимости от выбранного таба */}
{renderTabContent()}
</main>
<Footer />
@ -187,6 +171,83 @@ export async function getStaticProps() {
}
];
// Данные о коллекциях
const collections = [
{
id: 1,
name: 'Весна-Лето 2024',
image: '/photos/photo1.jpg',
description: 'Легкие ткани и яркие цвета для теплого сезона',
url: '/collections/spring-summer-2024'
},
{
id: 2,
name: 'Осень-Зима 2023',
image: '/photos/photo2.jpg',
description: 'Теплые и уютные модели для холодного времени года',
url: '/collections/autumn-winter-2023'
},
{
id: 3,
name: 'Базовый гардероб',
image: '/photos/head_photo.png',
description: 'Классические модели, которые никогда не выходят из моды',
url: '/collections/basic'
}
];
// Данные о категориях
const categories = [
{
id: 1,
name: 'Женская обувь',
image: '/category/shoes.jpg',
url: '/category/shoes'
},
{
id: 2,
name: 'Шляпы и перчатки',
image: '/category/hat.jpg',
url: '/category/hats-and-gloves'
},
{
id: 3,
name: 'Штаны и брюки',
image: '/category/pants.jpg',
url: '/category/pants'
},
{
id: 4,
name: 'Свитеры и кардиганы',
image: '/category/sweaters.jpg',
url: '/category/sweaters'
},
{
id: 5,
name: 'Платья и юбки',
image: '/category/dress.jpg',
url: '/category/dress'
},
{
id: 6,
name: 'Костюмы',
image: '/category/jacket.jpg',
url: '/category/jackets'
},
{
id: 7,
name: 'Женский шелк',
image: '/category/silk.jpg',
url: '/category/womens-silk'
},
{
id: 8,
name: 'Аксессуары',
image: '/category/scarf.jpg',
url: '/category/accessories'
}
];
// Получение изображений для слайдера из папки hero_photos
const heroImagesDirectory = path.join(process.cwd(), 'public/hero_photos');
let heroImages = [];
@ -215,6 +276,8 @@ export async function getStaticProps() {
props: {
heroImages,
products,
collections,
categories
},
// Перегенерация страницы каждые 10 минут
revalidate: 600,

BIN
public/category/dress.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
public/category/hat.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/category/jacket.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
public/category/pants.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/category/scarf.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
public/category/shoes.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
public/category/silk.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB