Комплексные улучшения: FAQ, форма, карта, WhatsApp/Telegram, SEO
- FloatingContacts: кнопки WhatsApp + Telegram (правый нижний угол) - ScrollToTop: кнопка "наверх" (появляется после 400px скролла) - FAQ секция: 6 вопросов с аккордеоном, id=faq в навбаре - Hero: телефон под CTA кнопками - Форма: поле телефона, реальная отправка (fetch FormSpree или mailto fallback) - Яндекс.Карты embed в блоке контактов - Navigation: убран дубль "Контакты" из links, добавлен FAQ - img width/height на логотипах (антиCLS) - JSON-LD: og-image.svg → .png, добавлен postalCode, уточнен streetAddress - prerender.mjs: динамически обновляет хеш preload шрифта - src/config.js: централизованный конфиг (WhatsApp, Telegram, form endpoint) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -56,14 +56,15 @@
|
|||||||
"description": "IT-аутсорсинг, кибербезопасность и техническая поддержка для бизнеса в Пушкино и Московской области.",
|
"description": "IT-аутсорсинг, кибербезопасность и техническая поддержка для бизнеса в Пушкино и Московской области.",
|
||||||
"url": "https://sag24.ru",
|
"url": "https://sag24.ru",
|
||||||
"logo": "https://sag24.ru/logo.png",
|
"logo": "https://sag24.ru/logo.png",
|
||||||
"image": "https://sag24.ru/og-image.svg",
|
"image": "https://sag24.ru/og-image.png",
|
||||||
"telephone": ["+74953637476", "+74953637335", "+74959454456"],
|
"telephone": ["+74953637476", "+74953637335", "+74959454456"],
|
||||||
"email": "info@sag24.ru",
|
"email": "info@sag24.ru",
|
||||||
"address": {
|
"address": {
|
||||||
"@type": "PostalAddress",
|
"@type": "PostalAddress",
|
||||||
"streetAddress": "д. 38/14",
|
"streetAddress": "пр-кт Московский, д. 38/14",
|
||||||
"addressLocality": "Пушкино",
|
"addressLocality": "Пушкино",
|
||||||
"addressRegion": "Московская область",
|
"addressRegion": "Московская область",
|
||||||
|
"postalCode": "141207",
|
||||||
"addressCountry": "RU"
|
"addressCountry": "RU"
|
||||||
},
|
},
|
||||||
"geo": {
|
"geo": {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { readFileSync, writeFileSync, rmSync } from 'fs'
|
import { readFileSync, writeFileSync, rmSync, readdirSync } from 'fs'
|
||||||
import { resolve, dirname } from 'path'
|
import { resolve, dirname } from 'path'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
@@ -8,9 +8,23 @@ const root = resolve(__dirname, '..')
|
|||||||
// Import SSR bundle built by vite build --ssr
|
// Import SSR bundle built by vite build --ssr
|
||||||
const { render } = await import('../dist/server/entry-server.js')
|
const { render } = await import('../dist/server/entry-server.js')
|
||||||
|
|
||||||
const template = readFileSync(resolve(root, 'dist/index.html'), 'utf-8')
|
let html = readFileSync(resolve(root, 'dist/index.html'), 'utf-8')
|
||||||
|
|
||||||
|
// Inject prerendered HTML
|
||||||
const appHtml = render()
|
const appHtml = render()
|
||||||
const html = template.replace('<div id="root"></div>', `<div id="root">${appHtml}</div>`)
|
html = html.replace('<div id="root"></div>', `<div id="root">${appHtml}</div>`)
|
||||||
|
|
||||||
|
// Fix preload font: find actual cyrillic-700 woff2 hash in dist/assets
|
||||||
|
const assetsDir = resolve(root, 'dist/assets')
|
||||||
|
const fontFile = readdirSync(assetsDir).find(f => f.match(/manrope-cyrillic-700-normal-.+\.woff2/))
|
||||||
|
if (fontFile) {
|
||||||
|
html = html.replace(
|
||||||
|
/href="\/assets\/manrope-cyrillic-700-normal-[^"]+\.woff2"/,
|
||||||
|
`href="/assets/${fontFile}"`
|
||||||
|
)
|
||||||
|
console.log(`✓ Preload font updated: ${fontFile}`)
|
||||||
|
}
|
||||||
|
|
||||||
writeFileSync(resolve(root, 'dist/index.html'), html)
|
writeFileSync(resolve(root, 'dist/index.html'), html)
|
||||||
|
|
||||||
// Cleanup SSR bundle
|
// Cleanup SSR bundle
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import Navigation from './components/Navigation.jsx'
|
|||||||
import Footer from './components/Footer.jsx'
|
import Footer from './components/Footer.jsx'
|
||||||
import Home from './pages/Home.jsx'
|
import Home from './pages/Home.jsx'
|
||||||
import NotFound from './pages/NotFound.jsx'
|
import NotFound from './pages/NotFound.jsx'
|
||||||
|
import FloatingContacts from './components/FloatingContacts.jsx'
|
||||||
|
import ScrollToTop from './components/ScrollToTop.jsx'
|
||||||
|
|
||||||
function LangSync() {
|
function LangSync() {
|
||||||
const { lang } = useLanguage()
|
const { lang } = useLanguage()
|
||||||
@@ -34,6 +36,8 @@ export default function App() {
|
|||||||
<Router />
|
<Router />
|
||||||
</main>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
|
<FloatingContacts />
|
||||||
|
<ScrollToTop />
|
||||||
</LanguageProvider>
|
</LanguageProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
33
src/components/FloatingContacts.jsx
Normal file
33
src/components/FloatingContacts.jsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { WHATSAPP_PHONE, TELEGRAM_USERNAME } from '../config.js'
|
||||||
|
|
||||||
|
export default function FloatingContacts() {
|
||||||
|
return (
|
||||||
|
<div className="fixed bottom-6 right-6 z-50 flex flex-col gap-3">
|
||||||
|
<a
|
||||||
|
href={`https://t.me/${TELEGRAM_USERNAME}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label="Написать в Telegram"
|
||||||
|
className="w-12 h-12 rounded-full flex items-center justify-center shadow-lg transition-transform hover:scale-110"
|
||||||
|
style={{ background: '#2AABEE' }}
|
||||||
|
>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="white">
|
||||||
|
<path d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2zm4.93 6.77-1.9 8.96c-.14.62-.5.77-.99.48l-2.76-2.03-1.33 1.28c-.15.15-.27.27-.55.27l.2-2.82 5.1-4.61c.22-.2-.05-.31-.34-.11l-6.31 3.97-2.72-.85c-.59-.18-.6-.59.12-.87l10.63-4.1c.49-.18.93.12.75.83z"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={`https://wa.me/${WHATSAPP_PHONE}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label="Написать в WhatsApp"
|
||||||
|
className="w-12 h-12 rounded-full flex items-center justify-center shadow-lg transition-transform hover:scale-110"
|
||||||
|
style={{ background: '#25D366' }}
|
||||||
|
>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="white">
|
||||||
|
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ export default function Footer() {
|
|||||||
<footer className="bg-slate-900 text-slate-400">
|
<footer className="bg-slate-900 text-slate-400">
|
||||||
{/* Main footer row */}
|
{/* Main footer row */}
|
||||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 py-10 flex flex-col md:flex-row items-center justify-between gap-4">
|
<div className="max-w-6xl mx-auto px-4 sm:px-6 py-10 flex flex-col md:flex-row items-center justify-between gap-4">
|
||||||
<img src="/logo.png" alt="Сисадмингрупп" className="h-10 w-auto brightness-0 invert" />
|
<img src="/logo.png" alt="Сисадмингрупп" className="h-10 w-auto brightness-0 invert" width="40" height="40" />
|
||||||
<span className="text-sm">{year} © {t('footer.rights')}</span>
|
<span className="text-sm">{year} © {t('footer.rights')}</span>
|
||||||
<div className="flex flex-wrap justify-center gap-x-6 gap-y-1 text-sm">
|
<div className="flex flex-wrap justify-center gap-x-6 gap-y-1 text-sm">
|
||||||
{phones.map((p, i) => (
|
{phones.map((p, i) => (
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ export default function Navigation() {
|
|||||||
const links = [
|
const links = [
|
||||||
{ label: t('nav.services'), href: '#services' },
|
{ label: t('nav.services'), href: '#services' },
|
||||||
{ label: t('nav.about'), href: '#about' },
|
{ label: t('nav.about'), href: '#about' },
|
||||||
{ label: t('nav.contact'), href: '#contact' },
|
{ label: t('nav.faq'), href: '#faq' },
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="fixed top-0 left-0 right-0 z-50 bg-white shadow-sm">
|
<header className="fixed top-0 left-0 right-0 z-50 bg-white shadow-sm">
|
||||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 h-16 flex items-center justify-between">
|
<div className="max-w-6xl mx-auto px-4 sm:px-6 h-16 flex items-center justify-between">
|
||||||
<a href="#" className="flex items-center">
|
<a href="#" className="flex items-center">
|
||||||
<img src="/logo.png" alt="Сисадмингрупп" className="h-12 w-auto" />
|
<img src="/logo.png" alt="Сисадмингрупп" className="h-12 w-auto" width="48" height="48" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{/* Desktop nav */}
|
{/* Desktop nav */}
|
||||||
|
|||||||
24
src/components/ScrollToTop.jsx
Normal file
24
src/components/ScrollToTop.jsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { ChevronUp } from 'lucide-react'
|
||||||
|
|
||||||
|
export default function ScrollToTop() {
|
||||||
|
const [visible, setVisible] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onScroll = () => setVisible(window.scrollY > 400)
|
||||||
|
window.addEventListener('scroll', onScroll, { passive: true })
|
||||||
|
return () => window.removeEventListener('scroll', onScroll)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!visible) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
|
||||||
|
aria-label="Наверх"
|
||||||
|
className="fixed bottom-6 left-6 z-50 w-10 h-10 rounded-full bg-slate-800/80 hover:bg-slate-700 text-white flex items-center justify-center shadow-lg transition-all hover:scale-110"
|
||||||
|
>
|
||||||
|
<ChevronUp size={20} />
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
5
src/config.js
Normal file
5
src/config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// Настройки для связи и формы
|
||||||
|
export const WHATSAPP_PHONE = '74953637476' // без +
|
||||||
|
export const TELEGRAM_USERNAME = 'sag24ru' // @username без @
|
||||||
|
export const FORM_ENDPOINT = '' // https://formspree.io/f/XXXXXXX — оставить пустым, будет mailto:
|
||||||
|
export const EMAIL = 'info@sag24.ru'
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { ChevronRight, Server, Shield, Headphones, Phone, Mail, MapPin, CheckCircle2 } from 'lucide-react'
|
import { ChevronRight, Server, Shield, Headphones, Phone, Mail, MapPin, CheckCircle2, ChevronDown } from 'lucide-react'
|
||||||
import { useLanguage } from '../contexts/LanguageContext.jsx'
|
import { useLanguage } from '../contexts/LanguageContext.jsx'
|
||||||
import { useReveal } from '../components/useReveal.js'
|
import { useReveal } from '../components/useReveal.js'
|
||||||
|
import { FORM_ENDPOINT, EMAIL } from '../config.js'
|
||||||
|
|
||||||
const serviceIcons = [Server, Shield, Headphones]
|
const serviceIcons = [Server, Shield, Headphones]
|
||||||
|
|
||||||
@@ -26,20 +27,69 @@ function ServiceCard({ icon: Icon, title, description, points }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function FaqItem({ q, a }) {
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
return (
|
||||||
|
<div className="border border-slate-200 rounded-xl overflow-hidden">
|
||||||
|
<button
|
||||||
|
onClick={() => setOpen(!open)}
|
||||||
|
className="w-full flex items-center justify-between px-6 py-4 text-left bg-white hover:bg-slate-50 transition-colors"
|
||||||
|
>
|
||||||
|
<span className="font-semibold text-slate-800 pr-4">{q}</span>
|
||||||
|
<ChevronDown size={18} className={`text-slate-400 flex-shrink-0 transition-transform duration-200 ${open ? 'rotate-180' : ''}`} />
|
||||||
|
</button>
|
||||||
|
{open && (
|
||||||
|
<div className="px-6 py-4 bg-slate-50 border-t border-slate-200 text-slate-600 text-sm leading-relaxed">
|
||||||
|
{a}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const { t } = useLanguage()
|
const { t } = useLanguage()
|
||||||
const [formState, setFormState] = useState({ name: '', company: '', message: '' })
|
const [formState, setFormState] = useState({ name: '', company: '', phone: '', message: '' })
|
||||||
const [submitted, setSubmitted] = useState(false)
|
const [submitted, setSubmitted] = useState(false)
|
||||||
|
const [sending, setSending] = useState(false)
|
||||||
|
const [error, setError] = useState('')
|
||||||
|
|
||||||
const aboutRef = useReveal()
|
const aboutRef = useReveal()
|
||||||
const contactRef = useReveal()
|
const contactRef = useReveal()
|
||||||
|
const faqRef = useReveal()
|
||||||
|
|
||||||
const services = t('services.items')
|
const services = t('services.items')
|
||||||
const stats = [t('about.stat1'), t('about.stat2'), t('about.stat3')]
|
const stats = [t('about.stat1'), t('about.stat2'), t('about.stat3')]
|
||||||
|
const faqItems = t('faq.items')
|
||||||
|
const firstPhone = t('contact.phones')[0]
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
setSending(true)
|
||||||
|
setError('')
|
||||||
|
|
||||||
|
if (FORM_ENDPOINT) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(FORM_ENDPOINT, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
||||||
|
body: JSON.stringify(formState),
|
||||||
|
})
|
||||||
|
if (res.ok) {
|
||||||
setSubmitted(true)
|
setSubmitted(true)
|
||||||
|
} else {
|
||||||
|
setError(t('contact.formError'))
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setError(t('contact.formError'))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback: mailto
|
||||||
|
const body = `Имя: ${formState.name}\nКомпания: ${formState.company}\nТелефон: ${formState.phone}\n\n${formState.message}`
|
||||||
|
window.location.href = `mailto:${EMAIL}?subject=Заявка с сайта sag24.ru&body=${encodeURIComponent(body)}`
|
||||||
|
setSubmitted(true)
|
||||||
|
}
|
||||||
|
setSending(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -61,7 +111,7 @@ export default function Home() {
|
|||||||
<p className="text-lg sm:text-xl text-slate-300 max-w-2xl mx-auto mb-10 leading-relaxed">
|
<p className="text-lg sm:text-xl text-slate-300 max-w-2xl mx-auto mb-10 leading-relaxed">
|
||||||
{t('hero.subtitle')}
|
{t('hero.subtitle')}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
<div className="flex flex-col sm:flex-row gap-4 justify-center mb-10">
|
||||||
<a href="#contact" className="px-8 py-4 bg-blue-600 hover:bg-blue-500 text-white font-semibold rounded-lg transition-all duration-300 flex items-center justify-center gap-2 group">
|
<a href="#contact" className="px-8 py-4 bg-blue-600 hover:bg-blue-500 text-white font-semibold rounded-lg transition-all duration-300 flex items-center justify-center gap-2 group">
|
||||||
{t('hero.cta')}
|
{t('hero.cta')}
|
||||||
<ChevronRight size={20} className="group-hover:translate-x-1 transition-transform" />
|
<ChevronRight size={20} className="group-hover:translate-x-1 transition-transform" />
|
||||||
@@ -70,6 +120,10 @@ export default function Home() {
|
|||||||
{t('hero.ctaSecondary')}
|
{t('hero.ctaSecondary')}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<a href={`tel:${firstPhone.replace(/\D/g,'')}`} className="inline-flex items-center gap-2 text-blue-300 hover:text-white transition-colors text-lg font-medium">
|
||||||
|
<Phone size={18} />
|
||||||
|
{firstPhone}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -165,6 +219,23 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* FAQ */}
|
||||||
|
<section id="faq" className="py-24 bg-white border-t border-slate-100">
|
||||||
|
<div className="max-w-3xl mx-auto px-4 sm:px-6">
|
||||||
|
<div ref={faqRef} className="section-reveal">
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<h2 className="text-3xl sm:text-4xl font-bold text-slate-900 mb-4">{t('faq.title')}</h2>
|
||||||
|
<p className="text-slate-500 text-lg">{t('faq.subtitle')}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
{faqItems.map((item, i) => (
|
||||||
|
<FaqItem key={i} q={item.q} a={item.a} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
{/* Contact */}
|
{/* Contact */}
|
||||||
<section id="contact" className="py-24 bg-slate-900">
|
<section id="contact" className="py-24 bg-slate-900">
|
||||||
<div className="max-w-6xl mx-auto px-4 sm:px-6">
|
<div className="max-w-6xl mx-auto px-4 sm:px-6">
|
||||||
@@ -197,8 +268,8 @@ export default function Home() {
|
|||||||
<a href="mailto:info@sag24.ru" className="text-white font-medium hover:text-blue-400 transition-colors">info@sag24.ru</a>
|
<a href="mailto:info@sag24.ru" className="text-white font-medium hover:text-blue-400 transition-colors">info@sag24.ru</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="w-10 h-10 bg-blue-500/10 rounded-lg flex items-center justify-center flex-shrink-0">
|
<div className="w-10 h-10 bg-blue-500/10 rounded-lg flex items-center justify-center flex-shrink-0 mt-0.5">
|
||||||
<MapPin size={20} className="text-blue-400" />
|
<MapPin size={20} className="text-blue-400" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -207,6 +278,19 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Yandex Map */}
|
||||||
|
<div className="mt-8 rounded-xl overflow-hidden border border-white/10">
|
||||||
|
<iframe
|
||||||
|
src="https://yandex.ru/map-widget/v1/?ll=37.857200%2C56.009400&z=16&pt=37.857200%2C56.009400%2Cpm2rdm&l=map"
|
||||||
|
width="100%"
|
||||||
|
height="220"
|
||||||
|
frameBorder="0"
|
||||||
|
title="Офис Сисадмингрупп на карте"
|
||||||
|
loading="lazy"
|
||||||
|
allowFullScreen
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white/5 border border-white/10 rounded-xl p-8">
|
<div className="bg-white/5 border border-white/10 rounded-xl p-8">
|
||||||
@@ -232,19 +316,28 @@ export default function Home() {
|
|||||||
onChange={e => setFormState({ ...formState, company: e.target.value })}
|
onChange={e => setFormState({ ...formState, company: e.target.value })}
|
||||||
className="w-full bg-white/10 border border-white/10 rounded-lg px-4 py-3 text-white placeholder-slate-400 focus:outline-none focus:border-blue-500 transition-colors text-sm"
|
className="w-full bg-white/10 border border-white/10 rounded-lg px-4 py-3 text-white placeholder-slate-400 focus:outline-none focus:border-blue-500 transition-colors text-sm"
|
||||||
/>
|
/>
|
||||||
|
<input
|
||||||
|
type="tel"
|
||||||
|
placeholder={t('contact.formPhone')}
|
||||||
|
value={formState.phone}
|
||||||
|
onChange={e => setFormState({ ...formState, phone: e.target.value })}
|
||||||
|
className="w-full bg-white/10 border border-white/10 rounded-lg px-4 py-3 text-white placeholder-slate-400 focus:outline-none focus:border-blue-500 transition-colors text-sm"
|
||||||
|
/>
|
||||||
<textarea
|
<textarea
|
||||||
required
|
required
|
||||||
rows={5}
|
rows={4}
|
||||||
placeholder={t('contact.formMessage')}
|
placeholder={t('contact.formMessage')}
|
||||||
value={formState.message}
|
value={formState.message}
|
||||||
onChange={e => setFormState({ ...formState, message: e.target.value })}
|
onChange={e => setFormState({ ...formState, message: e.target.value })}
|
||||||
className="w-full bg-white/10 border border-white/10 rounded-lg px-4 py-3 text-white placeholder-slate-400 focus:outline-none focus:border-blue-500 transition-colors text-sm resize-none"
|
className="w-full bg-white/10 border border-white/10 rounded-lg px-4 py-3 text-white placeholder-slate-400 focus:outline-none focus:border-blue-500 transition-colors text-sm resize-none"
|
||||||
/>
|
/>
|
||||||
|
{error && <p className="text-red-400 text-sm">{error}</p>}
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="w-full py-3 bg-blue-600 hover:bg-blue-500 text-white font-semibold rounded-lg transition-colors"
|
disabled={sending}
|
||||||
|
className="w-full py-3 bg-blue-600 hover:bg-blue-500 disabled:opacity-60 text-white font-semibold rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
{t('contact.formSubmit')}
|
{sending ? t('contact.formSending') : t('contact.formSubmit')}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export const en = {
|
|||||||
nav: {
|
nav: {
|
||||||
services: 'Services',
|
services: 'Services',
|
||||||
about: 'About',
|
about: 'About',
|
||||||
|
faq: 'FAQ',
|
||||||
contact: 'Contact',
|
contact: 'Contact',
|
||||||
},
|
},
|
||||||
hero: {
|
hero: {
|
||||||
@@ -50,8 +51,41 @@ export const en = {
|
|||||||
formName: 'Your name',
|
formName: 'Your name',
|
||||||
formCompany: 'Company',
|
formCompany: 'Company',
|
||||||
formMessage: 'Describe your task',
|
formMessage: 'Describe your task',
|
||||||
|
formPhone: 'Your phone',
|
||||||
formSubmit: 'Send Request',
|
formSubmit: 'Send Request',
|
||||||
|
formSending: 'Sending...',
|
||||||
formSuccess: 'Request sent! We will contact you shortly.',
|
formSuccess: 'Request sent! We will contact you shortly.',
|
||||||
|
formError: 'Send error. Please call us directly.',
|
||||||
|
},
|
||||||
|
faq: {
|
||||||
|
title: 'FAQ',
|
||||||
|
subtitle: 'Common questions about IT outsourcing',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
q: 'How much does IT outsourcing cost?',
|
||||||
|
a: 'Cost depends on the number of workstations and service scope. For 5–10 workstations — from 15,000 RUB/month. Contact us for an exact quote.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'How fast do you respond to issues?',
|
||||||
|
a: 'SLA from 15 minutes for remote tasks. On-site visit within 2–4 hours in Pushkino and nearby cities.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'Do you work outside Pushkino?',
|
||||||
|
a: 'Yes, we serve companies across the Moscow Region. Remote support has no geographic limits.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'What is included in the service contract?',
|
||||||
|
a: 'Services are defined in the SLA: 24/7 support, infrastructure monitoring, remote and on-site service, antivirus protection.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'Do we need to stop operations during onboarding?',
|
||||||
|
a: 'No. Onboarding takes 1–2 days and does not require stopping any business processes.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'Do you only work with small businesses?',
|
||||||
|
a: 'We serve companies from 3 to 200+ workstations, including government institutions and manufacturing enterprises.',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
partners: {
|
partners: {
|
||||||
title: 'Partners',
|
title: 'Partners',
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export const ru = {
|
|||||||
nav: {
|
nav: {
|
||||||
services: 'Услуги',
|
services: 'Услуги',
|
||||||
about: 'О нас',
|
about: 'О нас',
|
||||||
|
faq: 'FAQ',
|
||||||
contact: 'Контакты',
|
contact: 'Контакты',
|
||||||
},
|
},
|
||||||
hero: {
|
hero: {
|
||||||
@@ -50,8 +51,41 @@ export const ru = {
|
|||||||
formName: 'Ваше имя',
|
formName: 'Ваше имя',
|
||||||
formCompany: 'Компания',
|
formCompany: 'Компания',
|
||||||
formMessage: 'Опишите задачу',
|
formMessage: 'Опишите задачу',
|
||||||
|
formPhone: 'Ваш телефон',
|
||||||
formSubmit: 'Отправить заявку',
|
formSubmit: 'Отправить заявку',
|
||||||
|
formSending: 'Отправка...',
|
||||||
formSuccess: 'Заявка отправлена! Мы свяжемся с вами в ближайшее время.',
|
formSuccess: 'Заявка отправлена! Мы свяжемся с вами в ближайшее время.',
|
||||||
|
formError: 'Ошибка отправки. Позвоните нам напрямую.',
|
||||||
|
},
|
||||||
|
faq: {
|
||||||
|
title: 'Частые вопросы',
|
||||||
|
subtitle: 'Ответы на типовые вопросы об IT-аутсорсинге',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
q: 'Сколько стоит IT-аутсорсинг?',
|
||||||
|
a: 'Стоимость зависит от количества рабочих мест и состава услуг. Для 5–10 рабочих мест — от 15 000 руб./мес. Свяжитесь с нами для точного расчёта.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'Как быстро вы реагируете на проблемы?',
|
||||||
|
a: 'SLA от 15 минут для удалённых задач. Выезд специалиста в течение 2–4 часов по Пушкино и ближайшим городам.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'Работаете ли вы за пределами Пушкино?',
|
||||||
|
a: 'Да, обслуживаем компании по всей Московской области. Удалённая поддержка — без ограничений по географии.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'Что входит в договор на обслуживание?',
|
||||||
|
a: 'Перечень услуг фиксируется в SLA: техподдержка 24/7, мониторинг инфраструктуры, удалённое и выездное обслуживание, антивирусная защита.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'Нужно ли прерывать работу при подключении?',
|
||||||
|
a: 'Нет. Подключение к обслуживанию занимает 1–2 дня и не требует остановки бизнес-процессов.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'Вы работаете только с малым бизнесом?',
|
||||||
|
a: 'Обслуживаем компании от 3 до 200+ рабочих мест, включая государственные учреждения и производственные предприятия.',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
partners: {
|
partners: {
|
||||||
title: 'Партнёры',
|
title: 'Партнёры',
|
||||||
|
|||||||
Reference in New Issue
Block a user