Initial commit: TopSysOps site + admin panel
Multi-page public site (Next.js), Node/Express backend with Postgres, and Vite admin/account app, restructured from a single-page layout into dedicated service, promotions, cases, about, FAQ, and blog pages for SEO. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013otXCiTZsxdZ4SJKZ9wUky
This commit is contained in:
commit
130c5382cd
108 files changed
+7146
No files matched your search
@@ -0,0 +1,88 @@
|
||||
import Link from 'next/link';
|
||||
import Reveal from '../Reveal.jsx';
|
||||
import SectionHeading from '../SectionHeading.jsx';
|
||||
import { getIcon } from '../../lib/icons.js';
|
||||
|
||||
export function ServicePriceList({ category }) {
|
||||
return (
|
||||
<ul className="space-y-3">
|
||||
{category.items.map((it) => (
|
||||
<li key={it.id} className="pt-3 border-t border-slate-100 dark:border-slate-800">
|
||||
<div className="flex items-baseline justify-between gap-2">
|
||||
<span className="text-sm">{it.name}</span>
|
||||
<span className="text-sm text-emerald-500 whitespace-nowrap font-mono">
|
||||
{it.price_text}
|
||||
{it.period}
|
||||
</span>
|
||||
</div>
|
||||
{it.note && <p className="text-xs mt-1 text-slate-400 dark:text-slate-500">{it.note}</p>}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Services({
|
||||
categories,
|
||||
compact = false,
|
||||
limit,
|
||||
moreHref,
|
||||
eyebrow = 'services --price-list',
|
||||
title = 'Услуги IT-аутсорсинга и техподдержки: прайс-лист',
|
||||
desc = 'Гибкие тарифы для бизнеса любого масштаба. Возможна индивидуальная настройка под ваши задачи.',
|
||||
}) {
|
||||
const list = limit ? categories.slice(0, limit) : categories;
|
||||
|
||||
return (
|
||||
<section id="services" className="max-w-6xl mx-auto px-5 sm:px-8 py-20">
|
||||
<SectionHeading eyebrow={eyebrow} title={title} desc={desc} />
|
||||
|
||||
{list.length === 0 && <p className="text-sm text-slate-400">Прайс-лист пока пуст.</p>}
|
||||
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||
{list.map((cat, i) => {
|
||||
const Icon = getIcon(cat.icon);
|
||||
return (
|
||||
<Reveal key={cat.id} delay={i * 60}>
|
||||
<div className="h-full rounded-lg border p-5 transition-transform hover:-translate-y-1 bg-white border-slate-200 dark:bg-slate-900 dark:border-slate-800">
|
||||
<Icon className="h-5 w-5 text-emerald-500 mb-3" />
|
||||
<Link href={`/uslugi/${cat.slug}`} className="hover:text-emerald-500 transition-colors">
|
||||
<h3 className="font-bold mb-1 font-display text-[15px]">{cat.title}</h3>
|
||||
</Link>
|
||||
<p className="text-xs mb-4 text-slate-500 dark:text-slate-400">{cat.description}</p>
|
||||
{compact ? (
|
||||
<Link
|
||||
href={`/uslugi/${cat.slug}`}
|
||||
className="text-sm text-emerald-500 font-medium hover:underline"
|
||||
>
|
||||
Подробнее и цены →
|
||||
</Link>
|
||||
) : (
|
||||
<ServicePriceList category={cat} />
|
||||
)}
|
||||
</div>
|
||||
</Reveal>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{moreHref && (
|
||||
<Reveal className="mt-8">
|
||||
<Link href={moreHref} className="text-sm text-emerald-500 font-medium hover:underline">
|
||||
Все услуги и полный прайс-лист →
|
||||
</Link>
|
||||
</Reveal>
|
||||
)}
|
||||
|
||||
{!compact && !moreHref && (
|
||||
<Reveal className="mt-8">
|
||||
<div className="rounded-lg border p-5 bg-white border-slate-200 dark:bg-slate-900 dark:border-slate-800">
|
||||
<p className="text-xs text-slate-400 dark:text-slate-500">
|
||||
Цены указаны в рублях (RUB). Точная стоимость рассчитывается индивидуально после анализа задач.
|
||||
</p>
|
||||
</div>
|
||||
</Reveal>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user