Files
topsysops-app/public-site/app/uslugi/[slug]/page.jsx
T
ogrechkoandClaude Sonnet 5 1e4f97030a Consolidate Organization schema, add ItemList/Offer markup, expand FAQ
- Merge the duplicate Organization/LocalBusiness JSON-LD blocks (home's
  LocalBusiness + o-kompanii's Organization) into one shared
  getOrganizationSchema() call rendered on every page. Deliberately NOT
  wired through the root layout: fetches there get frozen into the
  static shell at build time (backend unreachable during image build)
  and never revalidate, silently serving stale data to crawlers - each
  page calls it directly instead, where revalidation is proven to work.
- Add optional ItemList schema on /uslugi and Offer/OfferCatalog schema
  on /aktsii; entity pages (/uslugi/[slug] Service, /blog/[slug]
  Article) reference the organization via @id instead of duplicating it.
- Add 8 more real FAQ entries (pricing model, contract terms,
  onboarding time, hybrid IT staff setup, supported stacks, backups,
  152-FZ compliance, escalation) and give every FAQ its own slug and
  dedicated /faq/[slug] page with its own title/H1/FAQPage schema,
  modeled on the one-topic-per-page pattern (h/t remontka.pro).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013otXCiTZsxdZ4SJKZ9wUky
2026-08-26 00:57:07 +03:00

97 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';
import { ORGANIZATION_ID } from '../../../lib/schema.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': ORGANIZATION_ID },
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>
);
}