feat: migrate to Next.js 15 App Router with i18n and multi-page SEO
- Replace Vite+React SPA with Next.js 15, TypeScript, App Router - Static export (output: 'export'), deploy via npm run deploy - i18n routing: /ru/... and /en/... with generateStaticParams - New pages: /uslugi/ (catalog), /uslugi/[slug]/ (8 service pages), /faq/, /kontakty/ - SEO: generateMetadata on all pages, JSON-LD Service schema, hreflang alternates - ContactForm: 'use client', Turnstile + POST to /api/contact.php - Fonts: Manrope via next/font/google (CSS variable) - Remove: vite.config.js, entry-server.jsx, prerender scripts, LanguageContext Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
83
src/app/[lang]/kontakty/page.tsx
Normal file
83
src/app/[lang]/kontakty/page.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import type { Metadata } from 'next'
|
||||
import { getDictionary, LOCALES, type Locale } from '@/lib/i18n'
|
||||
import ContactForm from '@/components/ui/ContactForm'
|
||||
import { Phone, Mail, MapPin } from 'lucide-react'
|
||||
|
||||
export function generateStaticParams() {
|
||||
return LOCALES.map(lang => ({ lang }))
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }): Promise<Metadata> {
|
||||
const { lang } = await params
|
||||
const d = getDictionary(lang)
|
||||
return {
|
||||
title: d.contact.title,
|
||||
description: d.contact.subtitle,
|
||||
alternates: { canonical: `https://sag24.ru/${lang}/kontakty/` },
|
||||
}
|
||||
}
|
||||
|
||||
export default async function KontaktyPage({ params }: { params: Promise<{ lang: string }> }) {
|
||||
const { lang: langStr } = await params
|
||||
const lang = langStr as Locale
|
||||
const d = getDictionary(lang)
|
||||
return (
|
||||
<div className="pt-16">
|
||||
<section className="py-24 bg-slate-900">
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6">
|
||||
<div className="grid md:grid-cols-2 gap-16">
|
||||
<div>
|
||||
<h1 className="text-3xl sm:text-4xl font-bold text-white mb-4">{d.contact.title}</h1>
|
||||
<p className="text-slate-400 text-lg mb-10">{d.contact.subtitle}</p>
|
||||
<div className="flex flex-col gap-6">
|
||||
<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">
|
||||
<Phone size={20} className="text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-slate-400 text-xs uppercase tracking-wider mb-1">{d.contact.phone}</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
{d.contact.phones.map((phone, i) => (
|
||||
<a key={i} href={`tel:${phone.replace(/\D/g,'')}`} className="text-white font-medium hover:text-blue-400 transition-colors">{phone}</a>
|
||||
))}
|
||||
</div>
|
||||
</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">
|
||||
<Mail size={20} className="text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-slate-400 text-xs uppercase tracking-wider mb-0.5">{d.contact.email}</div>
|
||||
<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-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>
|
||||
<div className="text-slate-400 text-xs uppercase tracking-wider mb-0.5">{d.contact.address}</div>
|
||||
<span className="text-white font-medium">{d.contact.addressValue}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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"
|
||||
style={{ border: 0 }}
|
||||
title="Офис Сисадмингрупп"
|
||||
loading="lazy"
|
||||
allowFullScreen
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ContactForm d={d} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user