refactor: extract sections
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
38
src/components/VendorCard.tsx
Normal file
38
src/components/VendorCard.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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'
|
||||
---
|
||||
|
||||
|
||||
@@ -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">
|
||||
@@ -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}`}
|
||||
33
src/sections/CategoryPageHero.tsx
Normal file
33
src/sections/CategoryPageHero.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
@@ -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>
|
||||
@@ -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}`}
|
||||
@@ -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
25
src/sections/Vendors.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user