Files
topsysops-app/public-site/app/uslugi/[slug]/page.jsx
T
ogrechkoandClaude Sonnet 5 30e1aea20b Fix SEO/UX audit gaps: canonical tags, org schema, nav, header claims
Add canonical URLs and a sitewide Organization JSON-LD (linked via @id
to page-specific LocalBusiness/Service/Article schemas), move FAQ and
Reviews into the top nav, attach the 24/7 support claim to a tooltip
on the header badge instead of leaving it unexplained, drop the
unverifiable incidents_resolved stat from the hero terminal animation,
and clean up the empty-reviews state instead of showing a thin
placeholder.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013otXCiTZsxdZ4SJKZ9wUky
2026-08-25 23:43:17 +03:00

96 lines
3.7 KiB
React

import Link from 'next/link';
import { notFound } from 'next/navigation';
import { ArrowLeft, ArrowRight, CheckCircle2 } from 'lucide-react';
import Header from '../../../components/Header.jsx';
import Footer from '../../../components/Footer.jsx';
import Reveal from '../../../components/Reveal.jsx';
import { ServicePriceList } from '../../../components/sections/Services.jsx';
import { getIcon } from '../../../lib/icons.js';
import { getServiceLandingCopy } from '../../../lib/serviceLandingCopy.js';
import { serverApi } from '../../../lib/serverApi.js';
async function findCategory(slug) {
const services = await serverApi.getServices();
return services.find((c) => c.slug === slug) || null;
}
export async function generateMetadata({ params }) {
const category = await findCategory(params.slug);
if (!category) return { title: 'Услуга не найдена' };
const copy = getServiceLandingCopy(category);
return {
title: copy.metaTitle,
description: copy.metaDescription,
alternates: { canonical: `/uslugi/${category.slug}` },
};
}
export default async function ServiceLandingPage({ params }) {
const category = await findCategory(params.slug);
if (!category) notFound();
const copy = getServiceLandingCopy(category);
const Icon = getIcon(category.icon);
const serviceSchema = {
'@context': 'https://schema.org',
'@type': 'Service',
name: copy.h1,
description: copy.metaDescription,
provider: { '@id': `${process.env.NEXT_PUBLIC_SITE_URL || 'https://top-sysops.ru'}/#organization` },
areaServed: 'RU',
};
return (
<div>
{/* eslint-disable-next-line react/no-danger */}
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(serviceSchema) }} />
<Header />
<section className="max-w-4xl mx-auto px-5 sm:px-8 pt-16 pb-4">
<Link href="/uslugi" className="text-sm text-emerald-500 font-medium inline-flex items-center gap-1 mb-8">
<ArrowLeft className="h-3.5 w-3.5" /> Все услуги
</Link>
<Reveal>
<Icon className="h-8 w-8 text-emerald-500 mb-4" />
<h1 className="text-2xl sm:text-3xl font-bold mb-4 font-display">{copy.h1}</h1>
<p className="text-base text-slate-600 dark:text-slate-400 max-w-2xl mb-6">{copy.intro}</p>
</Reveal>
{copy.bullets.length > 0 && (
<Reveal delay={80}>
<ul className="space-y-2 mb-10">
{copy.bullets.map((b) => (
<li key={b} className="flex items-start gap-2 text-sm text-slate-700 dark:text-slate-300">
<CheckCircle2 className="h-4 w-4 text-emerald-500 mt-0.5 flex-shrink-0" />
{b}
</li>
))}
</ul>
</Reveal>
)}
</section>
<section className="max-w-4xl mx-auto px-5 sm:px-8 pb-20">
<Reveal delay={120}>
<div className="rounded-lg border p-5 bg-white border-slate-200 dark:bg-slate-900 dark:border-slate-800">
<h2 className="font-bold mb-1 font-display text-[15px]">{category.title}: цены</h2>
<p className="text-xs mb-4 text-slate-500 dark:text-slate-400">{category.description}</p>
<ServicePriceList category={category} />
</div>
</Reveal>
<Reveal delay={160} className="mt-8">
<Link
href="/kontakty"
className="inline-flex items-center gap-2 px-5 py-3 rounded-md bg-emerald-500 text-white font-medium text-sm hover:bg-emerald-600 transition-colors"
>
Обсудить задачу <ArrowRight className="h-4 w-4" />
</Link>
</Reveal>
</section>
<Footer />
</div>
);
}