From 6973e8baeaefd76ab52c8b1944adb734b072259f Mon Sep 17 00:00:00 2001 From: Zotov Ivan Yuryevich Date: Wed, 8 Apr 2026 23:19:36 +0300 Subject: [PATCH] fetch sections from cms --- .../home-page.gql.generated.ts | 8 +- src/graphql-documents/home-page.gql.ts | 3 + src/graphql/graphql.ts | 6 +- src/lib/cms/color/index.ts | 4 + src/lib/cms/color/toBackground.ts | 21 ++ src/lib/cms/color/toElevatedGradient.ts | 21 ++ src/lib/cms/color/toGradient.ts | 21 ++ src/lib/cms/color/toTextColor.ts | 21 ++ src/lib/cms/icon/index.ts | 1 + src/lib/cms/icon/toComponent.ts | 54 +++++ src/lib/types.ts | 5 + src/pages/index.astro | 10 +- src/sections/About.tsx | 220 ++++++------------ src/sections/Cases.tsx | 117 +++++----- src/sections/Hero.tsx | 66 +++--- src/sections/Services.tsx | 118 +++++----- src/sections/Solutions.tsx | 116 ++++----- src/views/HomePage.tsx | 62 ++++- 18 files changed, 518 insertions(+), 356 deletions(-) create mode 100644 src/lib/cms/color/index.ts create mode 100644 src/lib/cms/color/toBackground.ts create mode 100644 src/lib/cms/color/toElevatedGradient.ts create mode 100644 src/lib/cms/color/toGradient.ts create mode 100644 src/lib/cms/color/toTextColor.ts create mode 100644 src/lib/cms/icon/index.ts create mode 100644 src/lib/cms/icon/toComponent.ts create mode 100644 src/lib/types.ts diff --git a/src/graphql-documents/home-page.gql.generated.ts b/src/graphql-documents/home-page.gql.generated.ts index d2f4b9c..0a57cee 100644 --- a/src/graphql-documents/home-page.gql.generated.ts +++ b/src/graphql-documents/home-page.gql.generated.ts @@ -3,11 +3,11 @@ import type { ExecutionResult } from "graphql"; import { execute } from "../graphql/execute"; -import type { HomePageQuery, HomePageQueryVariables } from "../graphql/graphql"; +import type { HomePageQueryQuery, HomePageQueryQueryVariables } from "../graphql/graphql"; import { homePageDocument } from "./home-page.gql"; -export const homePageQuery = { - async execute(variables?: HomePageQueryVariables): Promise> { - return execute(homePageDocument, variables); +export const homePageQueryQuery = { + async execute(variables?: HomePageQueryQueryVariables): Promise> { + return execute(homePageDocument, variables); }, }; diff --git a/src/graphql-documents/home-page.gql.ts b/src/graphql-documents/home-page.gql.ts index a5ae256..53825a9 100644 --- a/src/graphql-documents/home-page.gql.ts +++ b/src/graphql-documents/home-page.gql.ts @@ -4,6 +4,7 @@ export const homePageDocument = gql` query HomePageQuery { homePage { sections { + __typename ... on ComponentSectionsHomeHero { id title @@ -42,6 +43,7 @@ export const homePageDocument = gql` id title description + footnote icon { value } @@ -60,6 +62,7 @@ export const homePageDocument = gql` description pretitle badge + footnote color { value } diff --git a/src/graphql/graphql.ts b/src/graphql/graphql.ts index b40b316..effd158 100644 --- a/src/graphql/graphql.ts +++ b/src/graphql/graphql.ts @@ -1539,10 +1539,10 @@ export type HomePageQueryQueryVariables = Exact<{ [key: string]: never; }>; export type HomePageQueryQuery = { __typename?: 'Query', homePage?: { __typename?: 'HomePage', sections?: Array< | { __typename?: 'ComponentSectionsAbout', id: string, title?: string | null, description?: string | null, partners?: Array<{ __typename?: 'ComponentSharedString', id: string, value: string } | null> | null, cards?: Array<{ __typename?: 'ComponentCardsAdvantage', id: string, title?: string | null, description?: string | null, color?: { __typename?: 'ComponentSharedColor', value?: Enum_Componentsharedcolor_Value | null } | null, icon?: { __typename?: 'ComponentSharedIcon', value?: Enum_Componentsharedicon_Value | null } | null } | null> | null } - | { __typename?: 'ComponentSectionsCases', id: string, title?: string | null, description?: string | null, cards?: Array<{ __typename?: 'ComponentCardsCase', id: string, title?: string | null, description?: string | null, pretitle?: string | null, badge?: string | null, color?: { __typename?: 'ComponentSharedColor', value?: Enum_Componentsharedcolor_Value | null } | null, icon?: { __typename?: 'ComponentSharedIcon', value?: Enum_Componentsharedicon_Value | null } | null } | null> | null } + | { __typename?: 'ComponentSectionsCases', id: string, title?: string | null, description?: string | null, cards?: Array<{ __typename?: 'ComponentCardsCase', id: string, title?: string | null, description?: string | null, pretitle?: string | null, badge?: string | null, footnote?: string | null, color?: { __typename?: 'ComponentSharedColor', value?: Enum_Componentsharedcolor_Value | null } | null, icon?: { __typename?: 'ComponentSharedIcon', value?: Enum_Componentsharedicon_Value | null } | null } | null> | null } | { __typename?: 'ComponentSectionsHomeHero', id: string, title?: string | null, description?: string | null, badge?: string | null, stats?: Array<{ __typename?: 'ComponentSharedAttribute', id: string, key?: string | null, value?: string | null } | null> | null } | { __typename?: 'ComponentSectionsServices', id: string, title?: string | null, description?: string | null, cards?: Array<{ __typename?: 'ComponentCardsService', id: string, title?: string | null, description?: string | null, icon?: { __typename?: 'ComponentSharedIcon', value?: Enum_Componentsharedicon_Value | null } | null, color?: { __typename?: 'ComponentSharedColor', value?: Enum_Componentsharedcolor_Value | null } | null, category?: { __typename?: 'Category', slug: string } | null } | null> | null } - | { __typename?: 'ComponentSectionsSolutions', id: string, title?: string | null, description?: string | null, cards?: Array<{ __typename?: 'ComponentCardsSolution', id: string, title?: string | null, description?: string | null, icon?: { __typename?: 'ComponentSharedIcon', value?: Enum_Componentsharedicon_Value | null } | null, category?: { __typename?: 'Category', slug: string } | null } | null> | null } + | { __typename?: 'ComponentSectionsSolutions', id: string, title?: string | null, description?: string | null, cards?: Array<{ __typename?: 'ComponentCardsSolution', id: string, title?: string | null, description?: string | null, footnote?: string | null, icon?: { __typename?: 'ComponentSharedIcon', value?: Enum_Componentsharedicon_Value | null } | null, category?: { __typename?: 'Category', slug: string } | null } | null> | null } | { __typename?: 'Error' } | null> | null } | null }; @@ -1619,6 +1619,7 @@ export const HomePageQueryDocument = new TypedDocumentString(` id title description + footnote icon { value } @@ -1637,6 +1638,7 @@ export const HomePageQueryDocument = new TypedDocumentString(` description pretitle badge + footnote color { value } diff --git a/src/lib/cms/color/index.ts b/src/lib/cms/color/index.ts new file mode 100644 index 0000000..a216fe3 --- /dev/null +++ b/src/lib/cms/color/index.ts @@ -0,0 +1,4 @@ +export { toBackground } from './toBackground' +export { toElevatedGradient } from './toElevatedGradient' +export { toGradient } from './toGradient' +export { toTextColor } from './toTextColor' diff --git a/src/lib/cms/color/toBackground.ts b/src/lib/cms/color/toBackground.ts new file mode 100644 index 0000000..8c4f51d --- /dev/null +++ b/src/lib/cms/color/toBackground.ts @@ -0,0 +1,21 @@ +import { Enum_Componentsharedcolor_Value } from '../../../graphql/graphql' +import type { Maybe } from '../../types' + +const DEFAULT_BACKGROUND = 'bg-blue-50' + +const BACKGROUND_MAP: Record = { + [Enum_Componentsharedcolor_Value.Blue]: 'bg-blue-50', + [Enum_Componentsharedcolor_Value.Cyan]: 'bg-cyan-50', + [Enum_Componentsharedcolor_Value.Emerald]: 'bg-emerald-50', + [Enum_Componentsharedcolor_Value.Indigo]: 'bg-indigo-50', + [Enum_Componentsharedcolor_Value.Orange]: 'bg-orange-50', + [Enum_Componentsharedcolor_Value.Pink]: 'bg-pink-50', + [Enum_Componentsharedcolor_Value.Violet]: 'bg-violet-50', +} + +export function toBackground(color: Maybe): string { + if (color != null && color in BACKGROUND_MAP) { + return BACKGROUND_MAP[color] + } + return DEFAULT_BACKGROUND +} diff --git a/src/lib/cms/color/toElevatedGradient.ts b/src/lib/cms/color/toElevatedGradient.ts new file mode 100644 index 0000000..4ecc0fa --- /dev/null +++ b/src/lib/cms/color/toElevatedGradient.ts @@ -0,0 +1,21 @@ +import { Enum_Componentsharedcolor_Value } from '../../../graphql/graphql' +import type { Maybe } from '../../types' + +const DEFAULT_ELEVATED_GRADIENT = 'from-blue-100 to-cyan-100' + +const ELEVATED_GRADIENT_MAP: Record = { + [Enum_Componentsharedcolor_Value.Blue]: 'from-blue-100 to-cyan-100', + [Enum_Componentsharedcolor_Value.Cyan]: 'from-cyan-100 to-blue-100', + [Enum_Componentsharedcolor_Value.Emerald]: 'from-emerald-100 to-teal-100', + [Enum_Componentsharedcolor_Value.Indigo]: 'from-indigo-100 to-blue-100', + [Enum_Componentsharedcolor_Value.Orange]: 'from-orange-100 to-amber-100', + [Enum_Componentsharedcolor_Value.Pink]: 'from-pink-100 to-rose-100', + [Enum_Componentsharedcolor_Value.Violet]: 'from-violet-100 to-purple-100', +} + +export function toElevatedGradient(color: Maybe): string { + if (color != null && color in ELEVATED_GRADIENT_MAP) { + return ELEVATED_GRADIENT_MAP[color] + } + return DEFAULT_ELEVATED_GRADIENT +} diff --git a/src/lib/cms/color/toGradient.ts b/src/lib/cms/color/toGradient.ts new file mode 100644 index 0000000..b88d767 --- /dev/null +++ b/src/lib/cms/color/toGradient.ts @@ -0,0 +1,21 @@ +import { Enum_Componentsharedcolor_Value } from '../../../graphql/graphql' +import type { Maybe } from '../../types' + +const DEFAULT_GRADIENT = 'from-blue-50 to-cyan-50' + +const GRADIENT_MAP: Record = { + [Enum_Componentsharedcolor_Value.Blue]: 'from-blue-50 to-cyan-50', + [Enum_Componentsharedcolor_Value.Cyan]: 'from-cyan-50 to-blue-50', + [Enum_Componentsharedcolor_Value.Emerald]: 'from-emerald-50 to-teal-50', + [Enum_Componentsharedcolor_Value.Indigo]: 'from-indigo-50 to-blue-50', + [Enum_Componentsharedcolor_Value.Orange]: 'from-orange-50 to-amber-50', + [Enum_Componentsharedcolor_Value.Pink]: 'from-pink-50 to-rose-50', + [Enum_Componentsharedcolor_Value.Violet]: 'from-violet-50 to-purple-50', +} + +export function toGradient(color: Maybe): string { + if (color != null && color in GRADIENT_MAP) { + return GRADIENT_MAP[color] + } + return DEFAULT_GRADIENT +} diff --git a/src/lib/cms/color/toTextColor.ts b/src/lib/cms/color/toTextColor.ts new file mode 100644 index 0000000..ff52e1a --- /dev/null +++ b/src/lib/cms/color/toTextColor.ts @@ -0,0 +1,21 @@ +import { Enum_Componentsharedcolor_Value } from '../../../graphql/graphql' +import type { Maybe } from '../../types' + +const DEFAULT_TEXT_COLOR = 'text-blue-600' + +const TEXT_COLOR_MAP: Record = { + [Enum_Componentsharedcolor_Value.Blue]: 'text-blue-600', + [Enum_Componentsharedcolor_Value.Cyan]: 'text-cyan-600', + [Enum_Componentsharedcolor_Value.Emerald]: 'text-emerald-600', + [Enum_Componentsharedcolor_Value.Indigo]: 'text-indigo-600', + [Enum_Componentsharedcolor_Value.Orange]: 'text-orange-600', + [Enum_Componentsharedcolor_Value.Pink]: 'text-pink-600', + [Enum_Componentsharedcolor_Value.Violet]: 'text-violet-600', +} + +export function toTextColor(color: Maybe): string { + if (color != null && color in TEXT_COLOR_MAP) { + return TEXT_COLOR_MAP[color] + } + return DEFAULT_TEXT_COLOR +} diff --git a/src/lib/cms/icon/index.ts b/src/lib/cms/icon/index.ts new file mode 100644 index 0000000..678cd74 --- /dev/null +++ b/src/lib/cms/icon/index.ts @@ -0,0 +1 @@ +export { toComponent } from './toComponent' diff --git a/src/lib/cms/icon/toComponent.ts b/src/lib/cms/icon/toComponent.ts new file mode 100644 index 0000000..6b8ca0f --- /dev/null +++ b/src/lib/cms/icon/toComponent.ts @@ -0,0 +1,54 @@ +import type { LucideIcon } from 'lucide-react' +import { + Award, + Building2, + CheckCircle2, + Cloud, + Cpu, + Database, + Factory, + GraduationCap, + Heart, + Landmark, + Package, + Rocket, + Server, + Settings, + Shield, + ShoppingCart, + Users, + Zap, +} from 'lucide-react' + +import { Enum_Componentsharedicon_Value } from '../../../graphql/graphql' +import type { Maybe } from '../../types' + +const DEFAULT_ICON = Cloud + +const ICON_MAP: Record = { + [Enum_Componentsharedicon_Value.Award]: Award, + [Enum_Componentsharedicon_Value.Building]: Building2, + [Enum_Componentsharedicon_Value.CheckCircle]: CheckCircle2, + [Enum_Componentsharedicon_Value.Cloud]: Cloud, + [Enum_Componentsharedicon_Value.Cpu]: Cpu, + [Enum_Componentsharedicon_Value.Database]: Database, + [Enum_Componentsharedicon_Value.Factory]: Factory, + [Enum_Componentsharedicon_Value.GraduationCap]: GraduationCap, + [Enum_Componentsharedicon_Value.Heart]: Heart, + [Enum_Componentsharedicon_Value.Landmark]: Landmark, + [Enum_Componentsharedicon_Value.Package]: Package, + [Enum_Componentsharedicon_Value.Rocket]: Rocket, + [Enum_Componentsharedicon_Value.Server]: Server, + [Enum_Componentsharedicon_Value.Settings]: Settings, + [Enum_Componentsharedicon_Value.Shield]: Shield, + [Enum_Componentsharedicon_Value.ShoppingCart]: ShoppingCart, + [Enum_Componentsharedicon_Value.Users]: Users, + [Enum_Componentsharedicon_Value.Zap]: Zap, +} + +export function toComponent(value?: Maybe): LucideIcon { + if (value != null && value in ICON_MAP) { + return ICON_MAP[value] + } + return DEFAULT_ICON +} diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..2aa648f --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,5 @@ +/** + * Value that may be absent as `null` (e.g. GraphQL) or `undefined` (optional fields). + * Note: generated `src/graphql/graphql.ts` defines codegen `Maybe` as `T | null` only. + */ +export type Maybe = T | null | undefined diff --git a/src/pages/index.astro b/src/pages/index.astro index b7804f5..6c91ab8 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -4,12 +4,20 @@ import '../styles/globals.css' import { Header } from '../sections/Header' import { Footer } from '../sections/Footer' import { HomePage } from '../views/HomePage' +import { homePageQueryQuery } from '../graphql-documents/home-page.gql.generated' + +const result = await homePageQueryQuery.execute() +if (result.errors?.length) { + throw new Error(result.errors.map(e => e.message).join(', ')) +} + +const homePage = result.data?.homePage ?? null ---
- +
diff --git a/src/sections/About.tsx b/src/sections/About.tsx index f6b8e3e..7a97859 100644 --- a/src/sections/About.tsx +++ b/src/sections/About.tsx @@ -1,177 +1,101 @@ -import { - CheckCircle, - Rocket, - Zap, - Target, - Award, - Shield, - Clock, - HeadphonesIcon, - TrendingUp, - Users, - CheckCircle2, - Cloud, - Lock, - Database, - Briefcase, - Server, - Cpu, - Package, -} from 'lucide-react' import { useState, useEffect } from 'react' +import { toComponent } from '../lib/cms/icon' +import { toBackground, toTextColor } from '../lib/cms/color' +import type { Enum_Componentsharedcolor_Value, Enum_Componentsharedicon_Value } from '../graphql/graphql' +import type { Maybe } from '../lib/types' -const allVendors = [ - 'Яндекс.Облако', - 'VK Cloud', - 'MTC Web Services', - 'Сбер Облако', - 'Kaspersky', - 'Positive Technologies', - 'UserGate', - 'Astra Linux', - 'РедСофт', - 'Альт Линукс', - 'Postgres Pro', - 'Tarantool', - 'Мой Офис', - 'Р7-Офис', - '1С', - 'Галактика', - 'Kraftway', - 'Aquarius', - 'Эльбрус', - 'Битрикс24', - 'Код Безопасности', - 'Zabbix', - 'Nginx', - 'Selectel', - 'Лаборатория Эльбрус', - 'Basealt', - 'МойОфис Почта', - 'Видеомост', - 'Jazz Telecom', - 'Р-Видео', - 'СКБ Контур', - 'ЕТИС', - 'Кодикс', - 'Тензор', - 'Infowatch', - 'SearchInform', - 'Гарда', - 'Аладдин Р.Д.', - 'С-Терра', - 'DeviceLock', - 'КриптоПро', - 'Базальт СПО', - 'Аскон', - 'НаноСофт', - 'Directum', - 'Open Yard', - 'ТруКонф', - 'Yadro', - 'Depo', - 'Eltex', - 'Qtech', - 'D-Link', -] +export interface AboutCardCms { + id: string + title?: Maybe + description?: Maybe + color?: Maybe<{ value?: Maybe }> + icon?: Maybe<{ value?: Maybe }> +} + +export interface AboutCmsData { + title?: Maybe + description?: Maybe + partners?: Maybe>> + cards?: Maybe>> +} + +interface AboutProps { + data?: Maybe +} + +export function About({ data }: AboutProps) { + const title = data?.title ?? '' + const description = data?.description ?? '' + const partners = (data?.partners ?? []).filter((p): p is { id: string; value: string } => Boolean(p?.value)) + const cards = (data?.cards ?? []).filter((c): c is AboutCardCms => Boolean(c)) -export function About() { const [currentVendorIndex, setCurrentVendorIndex] = useState(0) const [isVisible, setIsVisible] = useState(true) useEffect(() => { + if (partners.length <= 1) return + const interval = setInterval(() => { setIsVisible(false) setTimeout(() => { - setCurrentVendorIndex(prev => (prev + 1) % allVendors.length) + setCurrentVendorIndex(prev => (prev + 1) % partners.length) setIsVisible(true) }, 800) }, 4500) return () => clearInterval(interval) - }, []) + }, [partners.length]) + + if (!title && !description && partners.length === 0 && cards.length === 0) { + return null + } return (

О компании

-

- Сотрудничаем с - - {allVendors[currentVendorIndex]} - -

-

- Комплексные решения для цифровой трансформации бизнеса. Работаем с 2021 года. -

+ {title || partners.length > 0 ? ( +

+ {title ? ( + + {title} + {partners.length > 0 ? '\u00a0' : ''} + + ) : null} + {partners.length > 0 ? ( + + {partners[currentVendorIndex]?.value} + + ) : null} +

+ ) : null} + {description ?

{description}

: null}
- {/* Ключевые преимущества для клиента */}
-
-
- -
-

Поставка оборудования

-

- Комплексные поставки серверов, СХД и сетевого оборудования от российских производителей. -

-
+ {cards.map(card => { + const Icon = toComponent(card.icon?.value) + const wrap = toBackground(card.color?.value) + const iconText = toTextColor(card.color?.value) -
-
- -
-

Официальное партнерство

-

- Статус золотого партнера ведущих российских вендоров. Прямые контракты и поддержка. -

-
- -
-
- -
-

Сертифицированные специалисты

-

- Инженеры с подтвержденной квалификацией по всем внедряемым решениям и платформам. -

-
- -
-
- -
-

Оперативный отклик

-

- Быстрое реагирование на запросы клиента. Четкие сроки решения задач и прозрачная коммуникация. -

-
- -
-
- -
-

Выделенная команда

-

- Персональный менеджер проекта и команда сертифицированных специалистов. -

-
- -
-
- -
-

Быстрый старт

-

- Запуск типовых проектов за 2 недели. Пилотное внедрение перед полным развертываием. -

-
+ return ( +
+
+ +
+ {card.title ?

{card.title}

: null} + {card.description ?

{card.description}

: null} +
+ ) + })}
diff --git a/src/sections/Cases.tsx b/src/sections/Cases.tsx index 9bf173f..fa9891f 100644 --- a/src/sections/Cases.tsx +++ b/src/sections/Cases.tsx @@ -1,85 +1,89 @@ -import { ArrowRight, Cloud, Cpu, Shield } from 'lucide-react' +import { ArrowRight } from 'lucide-react' +import { toComponent } from '../lib/cms/icon' +import { toElevatedGradient, toTextColor } from '../lib/cms/color' +import type { Enum_Componentsharedcolor_Value, Enum_Componentsharedicon_Value } from '../graphql/graphql' +import type { Maybe } from '../lib/types' -const cases = [ - { - icon: Cloud, - category: 'Облако', - company: 'Торговая сеть', - title: 'Миграция в Яндекс.Облако', - description: 'Перенос корпоративной инфраструктуры 500+ серверов с нулевым простоем', - results: '−40% TCO', - gradient: 'from-blue-100 to-cyan-100', - iconBg: 'bg-white', - iconColor: 'text-blue-600', - }, - { - icon: Cpu, - category: 'Автоматизация', - company: 'Производственный холдинг', - title: 'Внедрение 1С:ERP', - description: 'Автоматизация производственного и финансового учета для 15 предприятий', - results: '+25% эффективность', - gradient: 'from-emerald-100 to-teal-100', - iconBg: 'bg-white', - iconColor: 'text-emerald-600', - }, - { - icon: Shield, - category: 'Безопасность', - company: 'Финансовая группа', - title: 'Защита периметра Kaspersky', - description: 'Построение многоуровневой системы защиты критичной инфраструктуры', - results: '99.9% uptime', - gradient: 'from-violet-100 to-purple-100', - iconBg: 'bg-white', - iconColor: 'text-violet-600', - }, -] +export interface CaseCardCms { + id: string + title?: Maybe + description?: Maybe + pretitle?: Maybe + badge?: Maybe + footnote?: Maybe + color?: Maybe<{ value?: Maybe }> + icon?: Maybe<{ value?: Maybe }> +} + +export interface CasesCmsData { + title?: Maybe + description?: Maybe + cards?: Maybe>> +} + +interface CasesProps { + data?: Maybe +} + +export function Cases({ data }: CasesProps) { + const title = data?.title ?? '' + const description = data?.description ?? '' + const cards = (data?.cards ?? []).filter((c): c is CaseCardCms => Boolean(c)) + + if (!title && !description && cards.length === 0) { + return null + } -export function Cases() { return (

Кейсы

-

Реализованные проекты

-

Примеры успешного внедрения с конкретными результатами

+ {title ?

{title}

: null} + {description ?

{description}

: null}
- {cases.map((caseItem, index) => { - const Icon = caseItem.icon + {cards.map((caseItem, index) => { + const Icon = toComponent(caseItem.icon?.value) + const gradient = toElevatedGradient(caseItem.color?.value) + const iconColorClass = toTextColor(caseItem.color?.value) const floatClass = index === 0 ? 'animate-float' : index === 1 ? 'animate-float-delay-1' : 'animate-float-delay-2' + return (
- -
-
- - {caseItem.category} - +
+ {caseItem.badge ? ( +
+ + {caseItem.badge} + +
+ ) : null}
-
{caseItem.company}
+ {caseItem.pretitle ?
{caseItem.pretitle}
: null} -

{caseItem.title}

+ {caseItem.title ?

{caseItem.title}

: null} -

{caseItem.description}

+ {caseItem.description ? ( +

{caseItem.description}

+ ) : null}
- {caseItem.results} + {caseItem.footnote ?? ''}
@@ -89,7 +93,10 @@ export function Cases() {
- diff --git a/src/sections/Hero.tsx b/src/sections/Hero.tsx index f9c4bbd..a3ea20a 100644 --- a/src/sections/Hero.tsx +++ b/src/sections/Hero.tsx @@ -1,58 +1,70 @@ -import { ArrowRight, Play, Cloud, Shield, Zap, Database, Code, Users } from 'lucide-react' +import { ArrowRight, Cloud, Shield, Zap, Database, Code, Users } from 'lucide-react' +import type { Maybe } from '../lib/types' + +export interface HeroCmsData { + badge?: Maybe + title?: Maybe + description?: Maybe + stats?: Maybe; value?: Maybe }>>> +} interface HeroProps { onOpenContactModal?: () => void + data?: Maybe } -export function Hero({ onOpenContactModal }: HeroProps) { +export function Hero({ onOpenContactModal, data }: HeroProps) { + const badge = data?.badge ?? '' + const title = data?.title ?? '' + const description = data?.description ?? '' + const stats = (data?.stats ?? []).filter(Boolean) as Array<{ id: string; key?: Maybe; value?: Maybe }> + return (
-
- - Надежный партнер с 2021 года -
+ {badge ? ( +
+ + {badge} +
+ ) : null} -

IT-решения для роста вашего бизнеса

+ {title ?

{title}

: null} -

- Проектируем, внедряем и сопровождаем корпоративные IT-системы. Облачная инфраструктура, безопасность, - автоматизация процессов. -

+ {description ?

{description}

: null}
- +
-
-
-
300+
-
клиентов
+ {stats.length > 0 ? ( +
+ {stats.map(stat => ( +
+
{stat.value ?? ''}
+
{stat.key ?? ''}
+
+ ))}
-
-
50+
-
партнерств
-
-
-
95%
-
повторных обращений
-
-
+ ) : null}
- {/* Простая сетка иконок в стиле VK/Яндекс */}