refactor: extract sections

This commit is contained in:
Zotov Ivan Yuryevich
2026-04-04 19:28:07 +03:00
parent 18001a918f
commit 56f6ce41c1
17 changed files with 139 additions and 116 deletions

View File

@@ -1,82 +0,0 @@
import { ArrowLeft } from 'lucide-react'
interface Vendor {
name: string
description: string
logo?: string
products?: string[]
}
interface CategoryPageProps {
title: string
description: string
vendors: Vendor[]
}
export function CategoryPage({ title, description, vendors }: CategoryPageProps) {
return (
<div className="min-h-screen bg-gray-50">
{/* Hero секция */}
<div className="bg-white border-b border-gray-100 pt-32 pb-16">
<div className="max-w-[1600px] mx-auto px-6 xl:px-12 2xl:px-16">
<a
href="/"
className="inline-flex items-center gap-2 text-gray-600 hover:text-blue-600 mb-8 transition-colors group"
>
<ArrowLeft className="w-5 h-5 group-hover:-translate-x-1 transition-transform" />
Назад к главной
</a>
<div className="flex items-start gap-8">
<div className="flex-1">
<div className="inline-block px-4 py-1.5 bg-blue-50 text-blue-600 rounded-full text-sm mb-6">
Каталог решений
</div>
<h1 className="text-5xl lg:text-6xl mb-6 leading-tight text-gray-900">{title}</h1>
<p className="text-xl text-gray-600 max-w-3xl leading-relaxed">{description}</p>
</div>
<div className="hidden xl:block w-32 h-32 bg-gradient-to-br from-blue-500/10 to-blue-600/10 rounded-3xl flex-shrink-0"></div>
</div>
</div>
</div>
{/* Список вендоров */}
<div className="max-w-[1600px] mx-auto px-6 xl:px-12 2xl:px-16 py-20">
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{vendors.map((vendor, index) => (
<div
key={index}
className="bg-white rounded-2xl p-8 hover:shadow-lg transition-all border border-gray-100 group flex flex-col"
>
<div className="space-y-4 flex-1 flex flex-col">
<div className="flex items-start justify-between">
<h3 className="text-2xl text-gray-900 group-hover:text-blue-600 transition-colors">{vendor.name}</h3>
</div>
<p className="text-gray-600 leading-relaxed">{vendor.description}</p>
{vendor.products && vendor.products.length > 0 && (
<div className="pt-4 border-t border-gray-100">
<div className="text-sm text-gray-500 mb-3 uppercase tracking-wider">Решения</div>
<div className="flex flex-wrap gap-2">
{vendor.products.map((product, idx) => (
<span key={idx} className="px-3 py-1.5 bg-blue-50 text-blue-700 rounded-lg text-sm">
{product}
</span>
))}
</div>
</div>
)}
<button className="mt-auto pt-6 w-full py-3 bg-blue-50 text-blue-600 rounded-xl hover:bg-blue-600 hover:text-white transition-all">
Узнать больше
</button>
</div>
</div>
))}
</div>
</div>
</div>
)
}

View File

@@ -23,11 +23,11 @@ export function ContactModal({ isOpen, onClose }: ContactModalProps) {
const modalContent = (
<div
className="fixed inset-0 z-[9999] flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm"
className="fixed inset-0 z-9999 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm"
onClick={onClose}
>
<div
className="relative bg-white rounded-2xl w-full max-w-[700px] max-h-[90vh] overflow-y-auto shadow-2xl"
className="relative bg-white rounded-2xl w-full max-w-175 max-h-[90vh] overflow-y-auto shadow-2xl"
onClick={e => e.stopPropagation()}
>
{/* Заголовок */}
@@ -38,7 +38,7 @@ export function ContactModal({ isOpen, onClose }: ContactModalProps) {
</div>
<button
onClick={onClose}
className="w-9 h-9 flex items-center justify-center rounded-lg hover:bg-gray-100 transition-colors flex-shrink-0 ml-4"
className="w-9 h-9 flex items-center justify-center rounded-lg hover:bg-gray-100 transition-colors shrink-0 ml-4"
>
<X className="w-5 h-5 text-gray-500" />
</button>
@@ -156,7 +156,7 @@ export function ContactModal({ isOpen, onClose }: ContactModalProps) {
{/* Слоты для встречи в ВКС */}
<div>
<label className="block text-sm text-gray-700 mb-2 flex items-center gap-2">
<label className=" text-sm text-gray-700 mb-2 flex items-center gap-2">
<Calendar className="w-4 h-4" />
Предпочитаемое время для встречи в ВКС (выберите 2 варианта)
</label>

View File

@@ -0,0 +1,38 @@
interface VendorCardProps {
name?: string
description?: string
products?: string[]
}
export function VendorCard({ name, description, products }: VendorCardProps) {
return (
<div className="bg-white rounded-2xl p-8 hover:shadow-lg transition-all border border-gray-100 group flex flex-col">
<div className="space-y-4 flex-1 flex flex-col">
{name && (
<div className="flex items-start justify-between">
<h3 className="text-2xl text-gray-900 group-hover:text-blue-600 transition-colors">{name}</h3>
</div>
)}
{description && <p className="text-gray-600 leading-relaxed">{description}</p>}
{products && products.length > 0 && (
<div className="pt-4 border-t border-gray-100">
<div className="text-sm text-gray-500 mb-3 uppercase tracking-wider">Решения</div>
<div className="flex flex-wrap gap-2">
{products.map((product, idx) => (
<span key={idx} className="px-3 py-1.5 bg-blue-50 text-blue-700 rounded-lg text-sm">
{product}
</span>
))}
</div>
</div>
)}
<button className="mt-auto w-full py-3 bg-blue-50 text-blue-600 rounded-xl hover:bg-blue-600 hover:text-white transition-all">
Узнать больше
</button>
</div>
</div>
)
}

View File

@@ -2,8 +2,8 @@
import Layout from '../layouts/Layout.astro'
import '../styles/globals.css'
import { getCollection, type CollectionEntry } from 'astro:content'
import { Header } from '../components/Header'
import { Footer } from '../components/Footer'
import { Header } from '../sections/Header'
import { Footer } from '../sections/Footer'
import { CategoryView } from '../views/CategoryView'
export async function getStaticPaths() {

View File

@@ -1,8 +1,8 @@
---
import Layout from '../layouts/Layout.astro'
import '../styles/globals.css'
import { Header } from '../components/Header'
import { Footer } from '../components/Footer'
import { Header } from '../sections/Header'
import { Footer } from '../sections/Footer'
import { HomePage } from '../views/HomePage'
---

View File

@@ -94,7 +94,7 @@ export function About() {
return (
<section className="py-4 bg-gray-50">
<div className="max-w-[1600px] mx-auto px-6 xl:px-12 2xl:px-16">
<div className="max-w-400 mx-auto px-6 xl:px-12 2xl:px-16">
<div className="max-w-3xl mb-16">
<p className="text-sm uppercase tracking-wider text-gray-500 mb-4">О компании</p>
<h2 className="text-4xl lg:text-5xl text-gray-900 mb-6 flex flex-wrap items-baseline gap-2">

View File

@@ -39,7 +39,7 @@ const cases = [
export function Cases() {
return (
<section className="py-4 bg-white">
<div className="max-w-[1600px] mx-auto px-6 xl:px-12 2xl:px-16">
<div className="max-w-400 mx-auto px-6 xl:px-12 2xl:px-16">
<div className="max-w-3xl mb-16">
<p className="text-sm uppercase tracking-wider text-gray-500 mb-4">Кейсы</p>
<h2 className="text-4xl lg:text-5xl text-gray-900 mb-6">Реализованные проекты</h2>
@@ -57,7 +57,7 @@ export function Cases() {
className="group bg-white rounded-2xl overflow-hidden border border-gray-100 hover:border-blue-200 hover:shadow-xl transition-all duration-300 cursor-pointer flex flex-col"
>
<div
className={`relative h-64 overflow-hidden bg-gradient-to-br ${caseItem.gradient} flex items-center justify-center`}
className={`relative h-64 overflow-hidden bg-linear-to-br ${caseItem.gradient} flex items-center justify-center`}
>
<div
className={`w-24 h-24 ${caseItem.iconBg} rounded-2xl flex items-center justify-center group-hover:scale-110 transition-transform duration-300 shadow-md ${floatClass}`}

View File

@@ -0,0 +1,33 @@
import { ArrowLeft } from 'lucide-react'
interface CategoryPageHeroProps {
title?: string
description?: string
badge?: string
}
export function CategoryPageHero({ title, description, badge }: CategoryPageHeroProps) {
return (
<section className="bg-white border-b border-gray-100 pt-32 pb-16">
<div className="max-w-400 mx-auto px-6 xl:px-12 2xl:px-16">
<a
href="/"
className="inline-flex items-center gap-2 text-gray-600 hover:text-blue-600 mb-8 transition-colors group"
>
<ArrowLeft className="w-5 h-5 group-hover:-translate-x-1 transition-transform" />
Назад к главной
</a>
<div className="flex items-start gap-8">
<div className="flex-1">
{badge && (
<div className="inline-block px-4 py-1.5 bg-blue-50 text-blue-600 rounded-full text-sm mb-6">{badge}</div>
)}
{title && <h1 className="text-5xl lg:text-6xl mb-6 leading-tight text-gray-900">{title}</h1>}
{description && <p className="text-xl text-gray-600 max-w-3xl leading-relaxed">{description}</p>}
</div>
</div>
</div>
</section>
)
}

View File

@@ -3,7 +3,7 @@ import { Phone, Mail, MapPin, Clock } from 'lucide-react'
export function Contact() {
return (
<section className="py-4 bg-gray-50">
<div className="max-w-[900px] mx-auto px-6">
<div className="max-w-225 mx-auto px-6">
{/* Заголовок */}
<div className="text-center mb-12">
<h2 className="text-3xl lg:text-4xl text-gray-900 mb-3">Обсудим ваш проект</h2>

View File

@@ -3,11 +3,11 @@ import { Mail, Phone, MapPin, Send, Hash, Code, Video } from 'lucide-react'
export function Footer() {
return (
<footer className="bg-gray-900 text-gray-400 pt-20 pb-10 border-t border-gray-800">
<div className="max-w-[1600px] mx-auto px-6 xl:px-12 2xl:px-16">
<div className="max-w-400 mx-auto px-6 xl:px-12 2xl:px-16">
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-12 mb-12">
<div>
<div className="flex items-center gap-3 mb-6">
<div className="w-9 h-9 bg-gradient-to-br from-blue-600 to-blue-700 rounded-lg flex items-center justify-center shadow-md">
<div className="w-9 h-9 bg-linear-to-br from-blue-600 to-blue-700 rounded-lg flex items-center justify-center shadow-md">
<div className="w-5 h-5 border-2 border-white rounded"></div>
</div>
<span className="text-2xl text-white tracking-tight">SOFTCLICK</span>
@@ -105,20 +105,20 @@ export function Footer() {
<h4 className="text-white mb-6">Контакты</h4>
<ul className="space-y-4">
<li className="flex items-start gap-3">
<Phone className="w-5 h-5 mt-1 flex-shrink-0 text-blue-400" />
<Phone className="w-5 h-5 mt-1 shrink-0 text-blue-400" />
<div>
<div className="text-white">+7 (495) 123-45-67</div>
<div>Пн-Пт 9:00-18:00</div>
</div>
</li>
<li className="flex items-start gap-3">
<Mail className="w-5 h-5 mt-1 flex-shrink-0 text-blue-400" />
<Mail className="w-5 h-5 mt-1 shrink-0 text-blue-400" />
<a href="mailto:info@softclick.ru" className="hover:text-blue-400 transition-colors">
info@softclick.ru
</a>
</li>
<li className="flex items-start gap-3">
<MapPin className="w-5 h-5 mt-1 flex-shrink-0 text-blue-400" />
<MapPin className="w-5 h-5 mt-1 shrink-0 text-blue-400" />
<div>Москва, Пресненская наб., 12</div>
</li>
</ul>

View File

@@ -1,6 +1,6 @@
import { Menu, X, ChevronDown } from 'lucide-react'
import { useState } from 'react'
import { ContactModal } from './ContactModal'
import { ContactModal } from '../components/ContactModal'
import { navigateHomeSectionAnchor } from '../lib/scrollToHash'
export function Header() {
@@ -34,7 +34,7 @@ export function Header() {
return (
<header className="fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-md border-b border-gray-100">
<nav className="max-w-[1600px] mx-auto px-6 xl:px-12 2xl:px-16 py-4">
<nav className="max-w-400 mx-auto px-6 xl:px-12 2xl:px-16 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-12">
<a href="/" className="flex items-center gap-3 group">

View File

@@ -7,7 +7,7 @@ interface HeroProps {
export function Hero({ onOpenContactModal }: HeroProps) {
return (
<section className="relative pt-32 pb-20 overflow-hidden bg-white">
<div className="max-w-[1600px] mx-auto px-6 xl:px-12 2xl:px-16">
<div className="max-w-400 mx-auto px-6 xl:px-12 2xl:px-16">
<div className="grid lg:grid-cols-2 gap-16 items-center">
<div className="space-y-8">
<div className="inline-flex items-center gap-2 px-4 py-2 bg-blue-50 text-blue-700 rounded-full border border-blue-100">
@@ -74,7 +74,7 @@ export function Hero({ onOpenContactModal }: HeroProps) {
<div className="float-item aspect-square bg-blue-50 rounded-3xl flex items-center justify-center group hover:bg-blue-100 transition-colors">
<Cloud className="w-12 h-12 text-blue-600" />
</div>
<div className="float-item aspect-square bg-gradient-to-br from-blue-500 to-blue-600 rounded-3xl flex items-center justify-center shadow-lg">
<div className="float-item aspect-square bg-linear-to-br from-blue-500 to-blue-600 rounded-3xl flex items-center justify-center shadow-lg">
<Shield className="w-12 h-12 text-white" />
</div>
</div>

View File

@@ -54,7 +54,7 @@ const services = [
export function Services() {
return (
<section id="services" className="py-24 bg-white">
<div className="max-w-[1600px] mx-auto px-6 xl:px-12 2xl:px-16">
<div className="max-w-400 mx-auto px-6 xl:px-12 2xl:px-16">
<div className="max-w-3xl mb-16">
<p className="text-sm uppercase tracking-wider text-gray-500 mb-4">Услуги</p>
<h2 className="text-4xl lg:text-5xl text-gray-900 mb-6">Комплексные IT-решения для вашего бизнеса</h2>
@@ -80,7 +80,7 @@ export function Services() {
className="group bg-white rounded-2xl border border-gray-100 hover:border-blue-200 hover:shadow-xl transition-all duration-300 overflow-hidden"
>
<div
className={`relative h-48 overflow-hidden bg-gradient-to-br ${service.gradient} flex items-center justify-center`}
className={`relative h-48 overflow-hidden bg-linear-to-br ${service.gradient} flex items-center justify-center`}
>
<div
className={`w-20 h-20 ${service.iconBg} rounded-2xl flex items-center justify-center group-hover:scale-110 transition-transform duration-300 shadow-md ${floatClass}`}

View File

@@ -42,7 +42,7 @@ const solutions = [
export function Solutions() {
return (
<section id="solutions" className="py-24 bg-gray-50">
<div className="max-w-[1600px] mx-auto px-6 xl:px-12 2xl:px-16">
<div className="max-w-400 mx-auto px-6 xl:px-12 2xl:px-16">
<div className="max-w-3xl mb-16">
<p className="text-sm uppercase tracking-wider text-gray-500 mb-4">Отраслевые решения</p>
<h2 className="text-4xl lg:text-5xl text-gray-900 mb-6">Решения под ключ для разных отраслей</h2>
@@ -58,7 +58,7 @@ export function Solutions() {
className="group p-8 bg-white rounded-2xl hover:shadow-xl border border-gray-100 hover:border-blue-100 transition-all duration-300 cursor-pointer"
>
<div className="flex items-start gap-4 mb-4">
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center group-hover:scale-110 transition-transform shadow-sm">
<div className="w-12 h-12 bg-linear-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center group-hover:scale-110 transition-transform shadow-sm">
<Icon className="w-6 h-6 text-white" />
</div>
<div className="flex-1">

25
src/sections/Vendors.tsx Normal file
View File

@@ -0,0 +1,25 @@
import { VendorCard } from '../components/VendorCard'
interface Vendor {
name?: string
description?: string
products?: string[]
}
interface VendorsProps {
vendors?: Vendor[]
}
export function Vendors({ vendors }: VendorsProps) {
if (vendors && vendors.length > 0) {
return (
<div className="max-w-400 mx-auto px-6 xl:px-12 2xl:px-16 py-20">
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{vendors.map(({ name, description, products }, index) => (
<VendorCard key={index} name={name} description={description} products={products} />
))}
</div>
</div>
)
}
}

View File

@@ -1,8 +1,17 @@
import type { ComponentProps } from 'react'
import { CategoryPage } from '../components/CategoryPage'
import { CategoryPageHero } from '../sections/CategoryPageHero'
import { Vendors } from '../sections/Vendors'
export type CategoryViewProps = Pick<ComponentProps<typeof CategoryPage>, 'title' | 'description' | 'vendors'>
export type CategoryViewProps = {
title?: string
description?: string
} & ComponentProps<typeof Vendors>
export function CategoryView({ title, description, vendors }: CategoryViewProps) {
return <CategoryPage title={title} description={description} vendors={vendors} />
return (
<div className="min-h-screen bg-gray-50">
<CategoryPageHero badge="Каталог решений" title={title} description={description} />
<Vendors vendors={vendors} />
</div>
)
}

View File

@@ -1,10 +1,10 @@
import { useEffect, useState } from 'react'
import { Hero } from '../components/Hero'
import { Services } from '../components/Services'
import { Solutions } from '../components/Solutions'
import { Cases } from '../components/Cases'
import { About } from '../components/About'
import { Contact } from '../components/Contact'
import { Hero } from '../sections/Hero'
import { Services } from '../sections/Services'
import { Solutions } from '../sections/Solutions'
import { Cases } from '../sections/Cases'
import { About } from '../sections/About'
import { Contact } from '../sections/Contact'
import { ContactModal } from '../components/ContactModal'
import { scrollToHashTarget } from '../lib/scrollToHash'