feat: fetch categories in menu
This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
import { Send, Video } from 'lucide-react'
|
||||
|
||||
import { SiteContactItems } from '../components/SiteContactItems'
|
||||
import type { CategoriesListQuery } from '../graphql/graphql'
|
||||
import type { SiteConfigContact } from '../lib/cms/contact/types'
|
||||
|
||||
export interface FooterProps {
|
||||
contacts: readonly SiteConfigContact[]
|
||||
solutionCategories: ReadonlyArray<NonNullable<CategoriesListQuery['categories'][number]>>
|
||||
}
|
||||
|
||||
export function Footer({ contacts }: FooterProps) {
|
||||
export function Footer({ contacts, solutionCategories }: FooterProps) {
|
||||
return (
|
||||
<footer className="bg-gray-900 text-gray-400 pt-20 pb-10 border-t border-gray-800">
|
||||
<div className="max-w-400 mx-auto px-6 xl:px-12 2xl:px-16">
|
||||
@@ -59,26 +61,13 @@ export function Footer({ contacts }: FooterProps) {
|
||||
<div>
|
||||
<h4 className="text-white mb-6">Решения</h4>
|
||||
<ul className="space-y-3">
|
||||
<li>
|
||||
<a href="#" className="hover:text-blue-400 transition-colors">
|
||||
Облачные решения
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" className="hover:text-blue-400 transition-colors">
|
||||
Кибербезопасность
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" className="hover:text-blue-400 transition-colors">
|
||||
Цифровая трансформация
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" className="hover:text-blue-400 transition-colors">
|
||||
Управление данными
|
||||
</a>
|
||||
</li>
|
||||
{solutionCategories.map(category => (
|
||||
<li key={category.documentId}>
|
||||
<a href={`/${category.slug}`} className="hover:text-blue-400 transition-colors">
|
||||
{category.title ?? category.slug}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,41 +1,26 @@
|
||||
import { Menu, X, ChevronDown } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { ContactModal } from '../components/ContactModal'
|
||||
import type { CategoriesListQuery } from '../graphql/graphql'
|
||||
import type { SiteConfigContact } from '../lib/cms/contact/types'
|
||||
import { navigateHomeSectionAnchor } from '../lib/scrollToHash'
|
||||
|
||||
type CategoryNavItem = NonNullable<CategoriesListQuery['categories'][number]>
|
||||
|
||||
export interface HeaderProps {
|
||||
contacts: readonly SiteConfigContact[]
|
||||
solutionCategories: readonly CategoryNavItem[]
|
||||
serviceCategories: readonly CategoryNavItem[]
|
||||
}
|
||||
|
||||
export function Header({ contacts }: HeaderProps) {
|
||||
export function Header({ contacts, solutionCategories, serviceCategories }: HeaderProps) {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false)
|
||||
const [isSolutionsOpen, setIsSolutionsOpen] = useState(false)
|
||||
const [isServicesOpen, setIsServicesOpen] = useState(false)
|
||||
const [isContactModalOpen, setIsContactModalOpen] = useState(false)
|
||||
|
||||
const solutions = [
|
||||
{ name: 'Облако', path: 'cloud' },
|
||||
{ name: 'Российское ПО', path: 'russian-software' },
|
||||
{ name: 'Кибербезопасность', path: 'cybersecurity' },
|
||||
{ name: 'Решения на базе ИИ', path: 'ai-solutions' },
|
||||
{ name: 'Российские ноутбуки, ПК и серверы', path: 'russian-hardware' },
|
||||
{ name: 'Аппаратные решения', path: 'hardware-solutions' },
|
||||
{ name: 'САПР и ГИС', path: 'cad-gis' },
|
||||
{ name: 'Индустриальные решения', path: 'industrial-solutions' },
|
||||
{ name: 'Для госсектора', path: 'government' },
|
||||
{ name: 'Решения для бизнеса', path: 'business' },
|
||||
]
|
||||
|
||||
const services = [
|
||||
{ name: 'Консалтинг и проектирование', path: 'consulting' },
|
||||
{ name: 'Поставка и внедрение решений', path: 'implementation' },
|
||||
{ name: 'Управляемые сервисы', path: 'managed-services' },
|
||||
{ name: 'Интеграция и разработка', path: 'integration' },
|
||||
{ name: 'Специализированные услуги по отраслям', path: 'specialized-services' },
|
||||
{ name: 'Обслуживание и сопровождение', path: 'support' },
|
||||
{ name: 'Услуги по подписке', path: 'subscription' },
|
||||
]
|
||||
const solutions = [...solutionCategories].sort((a, b) => a.sortOrder - b.sortOrder)
|
||||
const services = [...serviceCategories].sort((a, b) => a.sortOrder - b.sortOrder)
|
||||
|
||||
return (
|
||||
<header className="fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-md border-b border-gray-100">
|
||||
@@ -47,58 +32,62 @@ export function Header({ contacts }: HeaderProps) {
|
||||
</a>
|
||||
|
||||
<div className="hidden lg:flex items-center gap-8">
|
||||
<div
|
||||
className="relative"
|
||||
onMouseEnter={() => setIsSolutionsOpen(true)}
|
||||
onMouseLeave={() => setIsSolutionsOpen(false)}
|
||||
>
|
||||
<button className="flex items-center gap-1 text-gray-700 hover:text-blue-600 transition-colors py-2 whitespace-nowrap">
|
||||
Решения
|
||||
<ChevronDown className={`w-4 h-4 transition-transform ${isSolutionsOpen ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
{solutions.length > 0 && (
|
||||
<div
|
||||
className="relative"
|
||||
onMouseEnter={() => setIsSolutionsOpen(true)}
|
||||
onMouseLeave={() => setIsSolutionsOpen(false)}
|
||||
>
|
||||
<button className="flex items-center gap-1 text-gray-700 hover:text-blue-600 transition-colors py-2 whitespace-nowrap">
|
||||
Решения
|
||||
<ChevronDown className={`w-4 h-4 transition-transform ${isSolutionsOpen ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
|
||||
{isSolutionsOpen && (
|
||||
<div className="absolute top-full left-0 pt-2">
|
||||
<div className="w-80 bg-white rounded-xl shadow-xl border border-gray-100 py-3 animate-in fade-in slide-in-from-top-2 duration-200">
|
||||
{solutions.map(solution => (
|
||||
<a
|
||||
key={solution.path}
|
||||
href={`/${solution.path}`}
|
||||
className="block w-full text-left px-5 py-3 text-gray-700 hover:bg-blue-50 hover:text-blue-600 transition-colors"
|
||||
>
|
||||
{solution.name}
|
||||
</a>
|
||||
))}
|
||||
{isSolutionsOpen && (
|
||||
<div className="absolute top-full left-0 pt-2">
|
||||
<div className="w-80 bg-white rounded-xl shadow-xl border border-gray-100 py-3 animate-in fade-in slide-in-from-top-2 duration-200">
|
||||
{solutions.map(solution => (
|
||||
<a
|
||||
key={solution.documentId}
|
||||
href={`/${solution.slug}`}
|
||||
className="block w-full text-left px-5 py-3 text-gray-700 hover:bg-blue-50 hover:text-blue-600 transition-colors"
|
||||
>
|
||||
{solution.title ?? solution.slug}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="relative"
|
||||
onMouseEnter={() => setIsServicesOpen(true)}
|
||||
onMouseLeave={() => setIsServicesOpen(false)}
|
||||
>
|
||||
<button className="flex items-center gap-1 text-gray-700 hover:text-blue-600 transition-colors py-2 whitespace-nowrap">
|
||||
Услуги
|
||||
<ChevronDown className={`w-4 h-4 transition-transform ${isServicesOpen ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{services.length > 0 && (
|
||||
<div
|
||||
className="relative"
|
||||
onMouseEnter={() => setIsServicesOpen(true)}
|
||||
onMouseLeave={() => setIsServicesOpen(false)}
|
||||
>
|
||||
<button className="flex items-center gap-1 text-gray-700 hover:text-blue-600 transition-colors py-2 whitespace-nowrap">
|
||||
Услуги
|
||||
<ChevronDown className={`w-4 h-4 transition-transform ${isServicesOpen ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
|
||||
{isServicesOpen && (
|
||||
<div className="absolute top-full left-0 pt-2">
|
||||
<div className="w-80 bg-white rounded-xl shadow-xl border border-gray-100 py-3 animate-in fade-in slide-in-from-top-2 duration-200">
|
||||
{services.map(service => (
|
||||
<a
|
||||
key={service.path}
|
||||
href={`/${service.path}`}
|
||||
className="block w-full text-left px-5 py-3 text-gray-700 hover:bg-blue-50 hover:text-blue-600 transition-colors"
|
||||
>
|
||||
{service.name}
|
||||
</a>
|
||||
))}
|
||||
{isServicesOpen && (
|
||||
<div className="absolute top-full left-0 pt-2">
|
||||
<div className="w-80 bg-white rounded-xl shadow-xl border border-gray-100 py-3 animate-in fade-in slide-in-from-top-2 duration-200">
|
||||
{services.map(service => (
|
||||
<a
|
||||
key={service.documentId}
|
||||
href={`/${service.slug}`}
|
||||
className="block w-full text-left px-5 py-3 text-gray-700 hover:bg-blue-50 hover:text-blue-600 transition-colors"
|
||||
>
|
||||
{service.title ?? service.slug}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<a
|
||||
href="/#cases"
|
||||
className="text-gray-700 hover:text-blue-600 transition-colors whitespace-nowrap"
|
||||
@@ -140,60 +129,64 @@ export function Header({ contacts }: HeaderProps) {
|
||||
{isMenuOpen && (
|
||||
<div className="lg:hidden pt-4 pb-2">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsSolutionsOpen(!isSolutionsOpen)}
|
||||
className="text-gray-700 hover:text-blue-600 w-full text-left flex items-center justify-between"
|
||||
>
|
||||
Решения
|
||||
<ChevronDown className={`w-4 h-4 transition-transform ${isSolutionsOpen ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
{isSolutionsOpen && (
|
||||
<div className="pl-4 mt-2 space-y-2">
|
||||
{solutions.map(solution => (
|
||||
<a
|
||||
key={solution.path}
|
||||
href={`/${solution.path}`}
|
||||
onClick={() => {
|
||||
setIsMenuOpen(false)
|
||||
setIsSolutionsOpen(false)
|
||||
}}
|
||||
className="block w-full text-left text-sm text-gray-600 hover:text-blue-600 py-2"
|
||||
>
|
||||
{solution.name}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsServicesOpen(!isServicesOpen)}
|
||||
className="text-gray-700 hover:text-blue-600 w-full text-left flex items-center justify-between"
|
||||
>
|
||||
Услуги
|
||||
<ChevronDown className={`w-4 h-4 transition-transform ${isServicesOpen ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
{isServicesOpen && (
|
||||
<div className="pl-4 mt-2 space-y-2">
|
||||
{services.map(service => (
|
||||
<a
|
||||
key={service.path}
|
||||
href={`/${service.path}`}
|
||||
onClick={() => {
|
||||
setIsMenuOpen(false)
|
||||
setIsServicesOpen(false)
|
||||
}}
|
||||
className="block w-full text-left text-sm text-gray-600 hover:text-blue-600 py-2"
|
||||
>
|
||||
{service.name}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{solutions.length > 0 && (
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsSolutionsOpen(!isSolutionsOpen)}
|
||||
className="text-gray-700 hover:text-blue-600 w-full text-left flex items-center justify-between"
|
||||
>
|
||||
Решения
|
||||
<ChevronDown className={`w-4 h-4 transition-transform ${isSolutionsOpen ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
{isSolutionsOpen && (
|
||||
<div className="pl-4 mt-2 space-y-2">
|
||||
{solutions.map(solution => (
|
||||
<a
|
||||
key={solution.documentId}
|
||||
href={`/${solution.slug}`}
|
||||
onClick={() => {
|
||||
setIsMenuOpen(false)
|
||||
setIsSolutionsOpen(false)
|
||||
}}
|
||||
className="block w-full text-left text-sm text-gray-600 hover:text-blue-600 py-2"
|
||||
>
|
||||
{solution.title ?? solution.slug}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{services.length > 0 && (
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsServicesOpen(!isServicesOpen)}
|
||||
className="text-gray-700 hover:text-blue-600 w-full text-left flex items-center justify-between"
|
||||
>
|
||||
Услуги
|
||||
<ChevronDown className={`w-4 h-4 transition-transform ${isServicesOpen ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
{isServicesOpen && (
|
||||
<div className="pl-4 mt-2 space-y-2">
|
||||
{services.map(service => (
|
||||
<a
|
||||
key={service.documentId}
|
||||
href={`/${service.slug}`}
|
||||
onClick={() => {
|
||||
setIsMenuOpen(false)
|
||||
setIsServicesOpen(false)
|
||||
}}
|
||||
className="block w-full text-left text-sm text-gray-600 hover:text-blue-600 py-2"
|
||||
>
|
||||
{service.title ?? service.slug}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<a
|
||||
href="/#cases"
|
||||
onClick={e => {
|
||||
|
||||
Reference in New Issue
Block a user