Комплексные улучшения: 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:
2026-03-14 05:54:19 +03:00
parent 27169b6a65
commit 851dd6173b
11 changed files with 260 additions and 18 deletions

View File

@@ -1,7 +1,8 @@
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 { useReveal } from '../components/useReveal.js'
import { FORM_ENDPOINT, EMAIL } from '../config.js'
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() {
const { t } = useLanguage()
const [formState, setFormState] = useState({ name: '', company: '', message: '' })
const [formState, setFormState] = useState({ name: '', company: '', phone: '', message: '' })
const [submitted, setSubmitted] = useState(false)
const [sending, setSending] = useState(false)
const [error, setError] = useState('')
const aboutRef = useReveal()
const contactRef = useReveal()
const faqRef = useReveal()
const services = t('services.items')
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()
setSubmitted(true)
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)
} 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 (
@@ -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">
{t('hero.subtitle')}
</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">
{t('hero.cta')}
<ChevronRight size={20} className="group-hover:translate-x-1 transition-transform" />
@@ -70,6 +120,10 @@ export default function Home() {
{t('hero.ctaSecondary')}
</a>
</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>
</section>
@@ -165,6 +219,23 @@ export default function Home() {
</div>
</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 */}
<section id="contact" className="py-24 bg-slate-900">
<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>
</div>
</div>
<div className="flex items-center gap-4">
<div className="w-10 h-10 bg-blue-500/10 rounded-lg flex items-center justify-center flex-shrink-0">
<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 mt-0.5">
<MapPin size={20} className="text-blue-400" />
</div>
<div>
@@ -207,6 +278,19 @@ export default function Home() {
</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 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 })}
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
required
rows={5}
rows={4}
placeholder={t('contact.formMessage')}
value={formState.message}
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"
/>
{error && <p className="text-red-400 text-sm">{error}</p>}
<button
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>
</form>
)}