krona-front/src/features/cms/CmsModule.tsx

705 lines
34 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useMemo } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { Badge, Button, MaturityBadge, useToast } from '../../shared/ui';
import {
Globe,
Smartphone,
Tablet,
Monitor,
Plus,
Edit3,
Image,
Trash2,
ArrowUp,
ArrowDown,
Settings2,
Download,
Layers,
} from 'lucide-react';
import { ulid } from '../../shared/lib/ulid';
interface CmsPage {
id: string;
title: string;
slug: string;
viewsToday: number;
status: 'published' | 'draft';
lastEdited: string;
}
export type WidgetType = 'hero' | 'features' | 'pricing' | 'testimonials' | 'form' | 'cta';
export interface CmsWidget {
id: string;
type: WidgetType;
title: string;
subtitle: string;
ctaText?: string;
badge?: string;
accentColor?: string;
}
const INITIAL_WIDGETS: CmsWidget[] = [
{
id: 'w-hero',
type: 'hero',
title: 'Свежие роллы и паназиатская кухня в Новосибирске',
subtitle: 'Доставка за 35 минут или ролл в подарок при следующем заказе',
ctaText: 'Заказать онлайн со скидкой 20%',
badge: 'Спецпредложение осени',
},
{
id: 'w-features',
type: 'features',
title: 'Почему выбирают Суши & Гёдза',
subtitle: 'Только охлажденный лосось с Фарерских островов и авторские рецепты шефа',
badge: 'Качество',
},
{
id: 'w-pricing',
type: 'pricing',
title: 'Популярные сеты для компаний',
subtitle: 'Выгодные комбо-наборы со скидкой до 30%',
ctaText: 'Выбрать сет',
badge: 'Хиты продаж',
},
{
id: 'w-testimonials',
type: 'testimonials',
title: 'Отзывы наших постоянных гостей',
subtitle: 'Более 4 800 довольных клиентов на Flamp и 2ГИС',
badge: 'Рейтинг 4.9',
},
{
id: 'w-form',
type: 'form',
title: 'Забронируйте столик на вечер',
subtitle: 'Мы подготовим стол и подарим фирменный десерт Моти',
ctaText: 'Подтвердить бронь',
badge: 'Быстрая бронь',
},
{
id: 'w-cta',
type: 'cta',
title: 'Установите приложение или оформите в Telegram',
subtitle: 'Получите 500 приветственных бонусов на первый заказ',
ctaText: 'Открыть Telegram-бот',
badge: 'Мобильный бонус',
},
];
const PAGES_LIST: CmsPage[] = [
{ id: 'p1', title: 'Главная страница доставки', slug: '/', viewsToday: 1420, status: 'published', lastEdited: 'Вчера, 18:40' },
{ id: 'p2', title: 'Меню & Корзина онлайн', slug: '/menu', viewsToday: 3890, status: 'published', lastEdited: '08.09.2026' },
{ id: 'p3', title: 'Акции и Сеты сентября', slug: '/promo-autumn', viewsToday: 840, status: 'published', lastEdited: '09.09.2026' },
{ id: 'p4', title: 'Бронирование столов / запись', slug: '/booking', viewsToday: 410, status: 'published', lastEdited: '05.09.2026' },
{ id: 'p5', title: 'Контакты и точки в городе', slug: '/contacts', viewsToday: 230, status: 'published', lastEdited: '01.09.2026' },
];
export const CmsModule: React.FC = () => {
const location = useLocation();
const navigate = useNavigate();
const { showToast } = useToast();
const [pages] = useState<CmsPage[]>(PAGES_LIST);
const [selectedTab, setSelectedTab] = useState<'pages' | 'builder' | 'forms' | 'media' | 'seo' | 'domains'>('pages');
// Page Builder States
const [widgets, setWidgets] = useState<CmsWidget[]>(INITIAL_WIDGETS);
const [selectedWidgetId, setSelectedWidgetId] = useState<string>(INITIAL_WIDGETS[0].id);
const [viewport, setViewport] = useState<'desktop' | 'tablet' | 'mobile'>('desktop');
// SEO Form Mock State
const [seoTitle, setSeoTitle] = useState('Суши & Гёдза — Доставка роллов и паназиатской кухни в Новосибирске');
const [seoDescription, setSeoDescription] = useState('Свежие суши, роллы, воки и супы с быстрой доставкой за 35 минут. Заказывайте любимые сеты онлайн со скидкой 20%!');
const activeTab = useMemo(() => {
const p = location.pathname;
if (p.includes('/cms/forms')) return 'forms';
if (p.includes('/cms/media')) return 'media';
if (p.includes('/cms/seo')) return 'seo';
if (p.includes('/cms/domains')) return 'domains';
if (p.includes('/cms/builder')) return 'builder';
if (p.includes('/cms/pages')) return 'pages';
return selectedTab;
}, [location.pathname, selectedTab]);
const setActiveTab = setSelectedTab;
const selectedWidget = widgets.find((w) => w.id === selectedWidgetId) || widgets[0];
const handleAddWidget = (type: WidgetType) => {
const typeTitles: Record<WidgetType, { title: string; subtitle: string; cta: string; badge: string }> = {
hero: { title: 'Новый промо-баннер', subtitle: 'Описание специального предложения', cta: 'Подробнее', badge: 'Промо' },
features: { title: 'Наши преимущества', subtitle: 'Ключевые особенности нашего сервиса', cta: '', badge: 'Сервис' },
pricing: { title: 'Тарифы и меню', subtitle: 'Выберите подходящую опцию', cta: 'Заказать', badge: 'Цены' },
testimonials: { title: 'Отзывы гостей', subtitle: 'Что говорят наши посетители', cta: '', badge: 'Отзывы' },
form: { title: 'Форма обратной связи', subtitle: 'Оставьте заявку и мы свяжемся с вами', cta: 'Отправить заявку', badge: 'Заявка' },
cta: { title: 'Присоединяйтесь к нам!', subtitle: 'Специальный бонус при первом визите', cta: 'Получить бонус', badge: 'Бонус' },
};
const info = typeTitles[type];
const newWidget: CmsWidget = {
id: `w-${type}-${ulid()}`,
type,
title: info.title,
subtitle: info.subtitle,
ctaText: info.cta || undefined,
badge: info.badge,
};
setWidgets((prev) => [...prev, newWidget]);
setSelectedWidgetId(newWidget.id);
showToast({
title: 'Виджет добавлен',
message: `Блок «${info.title}» добавлен на холст`,
type: 'success',
});
};
const handleMoveWidget = (index: number, direction: 'up' | 'down') => {
const newIndex = direction === 'up' ? index - 1 : index + 1;
if (newIndex < 0 || newIndex >= widgets.length) return;
const newWidgets = [...widgets];
const temp = newWidgets[index];
newWidgets[index] = newWidgets[newIndex];
newWidgets[newIndex] = temp;
setWidgets(newWidgets);
};
const handleDeleteWidget = (id: string) => {
if (widgets.length <= 1) {
showToast({
title: 'Невозможно удалить',
message: 'На странице должен оставаться минимум один блок',
type: 'warning',
});
return;
}
const filtered = widgets.filter((w) => w.id !== id);
setWidgets(filtered);
if (selectedWidgetId === id) {
setSelectedWidgetId(filtered[0].id);
}
showToast({
title: 'Блок удален',
message: 'Виджет удален со страницы',
type: 'info',
});
};
const handleUpdateWidgetProperty = (field: keyof CmsWidget, val: string) => {
setWidgets((prev) =>
prev.map((w) => (w.id === selectedWidgetId ? { ...w, [field]: val } : w))
);
};
const handleSaveJson = () => {
showToast({
title: 'Макет сохранен',
message: `JSON-конфигурация (${widgets.length} виджетов) сохранена в локальное хранилище`,
type: 'success',
});
};
return (
<div className="space-y-4 animate-fade-in">
{/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 bg-krona-surface p-3.5 rounded-xl border border-krona-border shadow-krona-soft">
<div>
<div className="flex items-center gap-2">
<h2 className="text-sm font-bold text-krona-primary">Сайты & Krona CMS</h2>
<MaturityBadge level="target" size="sm" />
<Badge variant="gold" size="sm">krona.site/sushi-nsk</Badge>
</div>
<p className="text-xs text-krona-secondary mt-0.5">
Визуальный блочный конструктор посадочных страниц, формы захвата лидов, медиатека и SEO
</p>
</div>
<div className="flex items-center gap-1.5 overflow-x-auto text-xs">
{[
{ id: 'pages', label: 'Страницы', route: '/cms/pages' },
{ id: 'builder', label: 'Конструктор' },
{ id: 'forms', label: 'Формы', route: '/cms/forms' },
{ id: 'media', label: 'Медиа', route: '/cms/media' },
{ id: 'seo', label: 'SEO', route: '/cms/seo' },
{ id: 'domains', label: 'Домены', route: '/cms/domains' },
].map((tab) => (
<button
key={tab.id}
onClick={() => {
setActiveTab(tab.id as any);
if (tab.route) navigate(tab.route);
}}
className={`px-3 py-1.5 rounded-md font-medium transition-colors whitespace-nowrap ${
activeTab === tab.id
? 'bg-[#2F684C] text-white shadow-sm'
: 'bg-krona-workspace text-krona-secondary hover:text-krona-primary border border-krona-border'
}`}
>
{tab.label}
</button>
))}
</div>
</div>
{/* 1. PAGES TAB */}
{activeTab === 'pages' && (
<div className="bg-krona-surface border border-krona-border rounded-xl shadow-krona-soft overflow-hidden">
<table className="w-full text-left text-xs border-collapse">
<thead className="bg-krona-workspace/80 text-krona-secondary border-b border-krona-border">
<tr>
<th className="p-3 font-semibold">Название страницы</th>
<th className="p-3 font-semibold">URL / Slug</th>
<th className="p-3 font-semibold">Просмотров сегодня</th>
<th className="p-3 font-semibold">Дата редакции</th>
<th className="p-3 font-semibold text-center">Статус</th>
<th className="p-3 font-semibold text-right">Действие</th>
</tr>
</thead>
<tbody className="divide-y divide-krona-border/50">
{pages.map((p) => (
<tr key={p.id} className="hover:bg-krona-workspace/50 transition-colors">
<td className="p-3 font-medium text-krona-primary flex items-center gap-2">
<Globe className="w-4 h-4 text-krona-secondary" />
<span>{p.title}</span>
</td>
<td className="p-3 font-mono text-krona-secondary">{p.slug}</td>
<td className="p-3 font-bold text-krona-primary">{p.viewsToday}</td>
<td className="p-3 text-krona-secondary">{p.lastEdited}</td>
<td className="p-3 text-center">
<Badge variant="green" size="sm">Опубликована</Badge>
</td>
<td className="p-3 text-right">
<button
onClick={() => setActiveTab('builder')}
className="text-krona-green font-medium hover:underline inline-flex items-center gap-1"
>
<Edit3 className="w-3.5 h-3.5" />
Редактор
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
{/* 2. VISUAL BUILDER TAB */}
{activeTab === 'builder' && (
<div className="space-y-3">
{/* Builder Top Toolbar */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 bg-krona-surface p-3 rounded-xl border border-krona-border shadow-krona-soft">
<div className="flex items-center gap-2">
<span className="font-bold text-xs text-krona-primary">
Редактирование: «Главная страница доставки»
</span>
<Badge variant="green" size="sm">Auto-save</Badge>
</div>
<div className="flex items-center gap-2">
{/* Responsive Viewport Switcher */}
<div className="flex items-center bg-krona-workspace p-1 rounded-lg border border-krona-border text-xs">
<button
onClick={() => setViewport('desktop')}
className={`p-1.5 rounded transition-colors ${
viewport === 'desktop' ? 'bg-[#2F684C] text-white shadow-sm' : 'text-krona-secondary'
}`}
title="Desktop (100%)"
>
<Monitor className="w-3.5 h-3.5" />
</button>
<button
onClick={() => setViewport('tablet')}
className={`p-1.5 rounded transition-colors ${
viewport === 'tablet' ? 'bg-[#2F684C] text-white shadow-sm' : 'text-krona-secondary'
}`}
title="Tablet (768px)"
>
<Tablet className="w-3.5 h-3.5" />
</button>
<button
onClick={() => setViewport('mobile')}
className={`p-1.5 rounded transition-colors ${
viewport === 'mobile' ? 'bg-[#2F684C] text-white shadow-sm' : 'text-krona-secondary'
}`}
title="Mobile (375px)"
>
<Smartphone className="w-3.5 h-3.5" />
</button>
</div>
<Button
variant="secondary"
size="sm"
onClick={handleSaveJson}
className="text-xs"
>
<Download className="w-3.5 h-3.5 mr-1" />
Сохранить JSON
</Button>
<Button
variant="primary"
size="sm"
onClick={() => {
showToast({
title: 'Страница опубликована',
message: 'Новая версия сайта успешно развернута на krona.site/sushi-nsk',
type: 'success',
});
}}
className="text-xs"
>
Опубликовать
</Button>
</div>
</div>
{/* Builder Workspace 3 Columns */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-3.5">
{/* Left: Widget Palette */}
<div className="lg:col-span-3 bg-krona-surface border border-krona-border rounded-xl p-3 space-y-3 shadow-krona-soft">
<div className="flex items-center justify-between pb-2 border-b border-krona-border">
<span className="text-xs font-bold text-krona-primary flex items-center gap-1.5">
<Layers className="w-3.5 h-3.5 text-krona-gold" /> Палитра виджетов
</span>
</div>
<div className="space-y-2 text-xs">
{[
{ type: 'hero' as const, label: 'Hero баннер', desc: 'Главный заголовок с призывом' },
{ type: 'features' as const, label: 'Feature Grid', desc: 'Сетка преимуществ с иконками' },
{ type: 'pricing' as const, label: 'Цены & Меню', desc: 'Карточки сетов и комбо' },
{ type: 'testimonials' as const, label: 'Отзывы гостей', desc: 'Карусель цитат с рейтингом' },
{ type: 'form' as const, label: 'Лид-форма', desc: 'Форма бронирования стола' },
{ type: 'cta' as const, label: 'CTA блок', desc: 'Финальный призыв к заказу' },
].map((item) => (
<button
key={item.type}
onClick={() => handleAddWidget(item.type)}
className="w-full text-left p-2.5 rounded-lg border border-krona-border hover:border-krona-green/70 bg-krona-workspace hover:bg-krona-surface transition-all flex items-center justify-between group"
>
<div>
<div className="font-semibold text-krona-primary group-hover:text-krona-green transition-colors">
+ {item.label}
</div>
<div className="text-[10px] text-krona-secondary">{item.desc}</div>
</div>
<Plus className="w-3.5 h-3.5 text-krona-secondary group-hover:text-krona-green" />
</button>
))}
</div>
</div>
{/* Center: Canvas Preview */}
<div className="lg:col-span-6 bg-krona-workspace/60 border border-krona-border rounded-xl p-4 flex flex-col items-center overflow-y-auto min-h-[560px] max-h-[750px]">
<div
className={`w-full transition-all duration-300 space-y-3 ${
viewport === 'mobile'
? 'max-w-[375px] border-x border-krona-border shadow-md bg-krona-surface p-3 rounded-2xl'
: viewport === 'tablet'
? 'max-w-[640px] border border-krona-border shadow-md bg-krona-surface p-4 rounded-xl'
: 'max-w-full'
}`}
>
{widgets.map((w, idx) => {
const isSelected = w.id === selectedWidgetId;
return (
<div
key={w.id}
onClick={() => setSelectedWidgetId(w.id)}
className={`relative p-4 rounded-xl border transition-all cursor-pointer select-none bg-krona-surface shadow-sm ${
isSelected
? 'border-krona-green ring-2 ring-krona-green/30'
: 'border-krona-border hover:border-krona-border-strong'
}`}
>
{/* Widget Controls bar */}
<div className="flex items-center justify-between pb-2 mb-2 border-b border-krona-border/60">
<div className="flex items-center gap-1.5">
<span className="font-bold text-[10px] uppercase text-krona-secondary">
#{idx + 1} {w.type}
</span>
{w.badge && <Badge variant="gold" size="sm">{w.badge}</Badge>}
</div>
<div className="flex items-center gap-1" onClick={(e) => e.stopPropagation()}>
<button
onClick={() => handleMoveWidget(idx, 'up')}
disabled={idx === 0}
className="p-1 rounded hover:bg-krona-workspace text-krona-secondary disabled:opacity-30"
title="Поднять выше"
>
<ArrowUp className="w-3 h-3" />
</button>
<button
onClick={() => handleMoveWidget(idx, 'down')}
disabled={idx === widgets.length - 1}
className="p-1 rounded hover:bg-krona-workspace text-krona-secondary disabled:opacity-30"
title="Опустить ниже"
>
<ArrowDown className="w-3 h-3" />
</button>
<button
onClick={() => handleDeleteWidget(w.id)}
className="p-1 rounded hover:bg-rose-500/10 text-krona-secondary hover:text-rose-600"
title="Удалить блок"
>
<Trash2 className="w-3 h-3" />
</button>
</div>
</div>
{/* Visual Widget Preview Rendering */}
<div className="space-y-1.5">
<h4 className="font-bold text-xs text-krona-primary leading-snug">
{w.title}
</h4>
<p className="text-[11px] text-krona-secondary leading-relaxed">
{w.subtitle}
</p>
{w.ctaText && (
<div className="pt-2">
<span className="inline-flex items-center px-3 py-1 rounded bg-[#2F684C] text-white text-[10px] font-semibold">
{w.ctaText} →
</span>
</div>
)}
</div>
</div>
);
})}
</div>
</div>
{/* Right: Selected Widget Properties Editor */}
<div className="lg:col-span-3 bg-krona-surface border border-krona-border rounded-xl p-3.5 space-y-3.5 shadow-krona-soft text-xs">
<div className="flex items-center justify-between pb-2 border-b border-krona-border">
<span className="font-bold text-krona-primary flex items-center gap-1.5">
<Settings2 className="w-3.5 h-3.5 text-krona-green" /> Свойства виджета
</span>
<span className="text-[10px] uppercase font-mono text-krona-secondary">
{selectedWidget.type}
</span>
</div>
<div className="space-y-2.5">
<div className="space-y-1">
<label className="text-[10px] font-semibold text-krona-secondary block">Заголовок блока:</label>
<input
type="text"
value={selectedWidget.title}
onChange={(e) => handleUpdateWidgetProperty('title', e.target.value)}
className="w-full p-2 bg-krona-workspace border border-krona-border rounded-lg text-xs text-krona-primary focus:outline-none focus:border-krona-green"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-semibold text-krona-secondary block">Подзаголовок / Текст:</label>
<textarea
rows={3}
value={selectedWidget.subtitle}
onChange={(e) => handleUpdateWidgetProperty('subtitle', e.target.value)}
className="w-full p-2 bg-krona-workspace border border-krona-border rounded-lg text-xs text-krona-primary focus:outline-none focus:border-krona-green resize-none"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-semibold text-krona-secondary block">Текст кнопки действия (CTA):</label>
<input
type="text"
value={selectedWidget.ctaText || ''}
onChange={(e) => handleUpdateWidgetProperty('ctaText', e.target.value)}
placeholder="Например: Заказать онлайн"
className="w-full p-2 bg-krona-workspace border border-krona-border rounded-lg text-xs text-krona-primary focus:outline-none focus:border-krona-green"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-semibold text-krona-secondary block">Бейдж / Тег:</label>
<input
type="text"
value={selectedWidget.badge || ''}
onChange={(e) => handleUpdateWidgetProperty('badge', e.target.value)}
placeholder="Например: Акция недели"
className="w-full p-2 bg-krona-workspace border border-krona-border rounded-lg text-xs text-krona-primary focus:outline-none focus:border-krona-green"
/>
</div>
</div>
</div>
</div>
</div>
)}
{/* 3. FORMS TAB */}
{activeTab === 'forms' && (
<div className="bg-krona-surface border border-krona-border rounded-xl p-4 shadow-krona-soft space-y-4">
<div className="flex items-center justify-between pb-2 border-b border-krona-border">
<div>
<h4 className="text-xs font-bold text-krona-primary">Лид-формы и виджеты захвата заявок</h4>
<p className="text-[11px] text-krona-secondary">Формы автоматически создают лиды и сделки в CRM</p>
</div>
<Button variant="primary" size="sm">
<Plus className="w-3.5 h-3.5 mr-1" />
Новая форма
</Button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3.5 text-xs">
<div className="p-3.5 rounded-xl border border-krona-border bg-krona-workspace space-y-2">
<div className="flex items-center justify-between">
<span className="font-bold text-krona-primary text-sm">Форма быстрой брони столика</span>
<Badge variant="green" size="sm">Активна</Badge>
</div>
<p className="text-krona-secondary text-[11px]">Поля: Имя, Телефон, Дата/Время, Персоны, Пожелания</p>
<div className="flex justify-between pt-2 border-t border-krona-border/60 text-[11px]">
<span>Конверсия: 24.8%</span>
<span className="font-semibold text-krona-green">148 заявок</span>
</div>
</div>
<div className="p-3.5 rounded-xl border border-krona-border bg-krona-workspace space-y-2">
<div className="flex items-center justify-between">
<span className="font-bold text-krona-primary text-sm">Виджет подписки на скидку 20%</span>
<Badge variant="green" size="sm">Всплывающее окно</Badge>
</div>
<p className="text-krona-secondary text-[11px]">Поля: Номер телефона (с отправкой промокода в SMS/Telegram)</p>
<div className="flex justify-between pt-2 border-t border-krona-border/60 text-[11px]">
<span>Конверсия: 14.2%</span>
<span className="font-semibold text-krona-green">312 лидов</span>
</div>
</div>
</div>
</div>
)}
{/* 4. MEDIA LIBRARY TAB */}
{activeTab === 'media' && (
<div className="bg-krona-surface border border-krona-border rounded-xl p-4 shadow-krona-soft space-y-4">
<div className="flex items-center justify-between pb-2 border-b border-krona-border">
<div>
<h4 className="text-xs font-bold text-krona-primary">Медиабиблиотека сайта</h4>
<p className="text-[11px] text-krona-secondary">Фотографии блюд, баннеры акций, логотипы и видеоматериалы</p>
</div>
<Button variant="primary" size="sm">
<Plus className="w-3.5 h-3.5 mr-1" />
Загрузить файл
</Button>
</div>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
{[
{ name: 'philadelphia_banner.jpg', size: '1.2 MB', category: 'Баннеры' },
{ name: 'tom_yam_dish.png', size: '840 KB', category: 'Каталог' },
{ name: 'autumn_set_promo.jpg', size: '2.1 MB', category: 'Акции' },
{ name: 'krona_hall_interior.jpg', size: '3.4 MB', category: 'Интерьер' },
].map((media, idx) => (
<div key={idx} className="p-3 rounded-xl border border-krona-border bg-krona-workspace space-y-2 text-xs">
<div className="h-28 rounded-lg bg-krona-surface border border-krona-border flex items-center justify-center text-krona-secondary">
<Image className="w-8 h-8 opacity-40" />
</div>
<div className="font-semibold text-krona-primary truncate">{media.name}</div>
<div className="flex justify-between text-[10px] text-krona-secondary">
<span>{media.category}</span>
<span>{media.size}</span>
</div>
</div>
))}
</div>
</div>
)}
{/* 5. SEO TAB */}
{activeTab === 'seo' && (
<div className="bg-krona-surface border border-krona-border rounded-xl p-4 shadow-krona-soft space-y-4">
<div className="pb-2 border-b border-krona-border">
<h4 className="text-xs font-bold text-krona-primary">Поисковая оптимизация (SEO)</h4>
<p className="text-[11px] text-krona-secondary">Мета-теги, OpenGraph и сниппет поисковой выдачи Яндекс и Google</p>
</div>
<div className="space-y-3 max-w-xl text-xs">
<div className="space-y-1">
<label className="font-semibold text-krona-primary">Meta Title (Заголовок в поисковике):</label>
<input
type="text"
value={seoTitle}
onChange={(e) => setSeoTitle(e.target.value)}
className="w-full p-2 bg-krona-workspace border border-krona-border rounded-lg text-xs text-krona-primary focus:outline-none focus:border-krona-green"
/>
</div>
<div className="space-y-1">
<label className="font-semibold text-krona-primary">Meta Description (Описание):</label>
<textarea
rows={3}
value={seoDescription}
onChange={(e) => setSeoDescription(e.target.value)}
className="w-full p-2 bg-krona-workspace border border-krona-border rounded-lg text-xs text-krona-primary focus:outline-none focus:border-krona-green resize-none"
/>
</div>
<div className="p-3 bg-krona-workspace rounded-xl border border-krona-border space-y-1">
<span className="text-[10px] uppercase text-krona-secondary font-bold">Превью в Яндекс:</span>
<div className="text-blue-600 text-sm font-semibold hover:underline cursor-pointer">
{seoTitle}
</div>
<div className="text-emerald-700 text-xs">krona.site/sushi-nsk</div>
<p className="text-xs text-krona-secondary">{seoDescription}</p>
</div>
<Button
variant="primary"
size="sm"
onClick={() => {
showToast({
title: 'SEO мета-теги сохранены',
message: 'Настройки поисковой оптимизации применены к сайту',
type: 'success',
});
}}
>
Сохранить SEO-настройки
</Button>
</div>
</div>
)}
{/* 6. DOMAINS TAB */}
{activeTab === 'domains' && (
<div className="bg-krona-surface border border-krona-border rounded-xl p-4 shadow-krona-soft space-y-4">
<div className="pb-2 border-b border-krona-border">
<h4 className="text-xs font-bold text-krona-primary">Подключение доменов & SSL-сертификаты</h4>
<p className="text-[11px] text-krona-secondary">Привязка собственного домена второго уровня к CMS</p>
</div>
<div className="space-y-3 max-w-xl text-xs">
<div className="p-3.5 bg-krona-workspace rounded-xl border border-krona-border flex items-center justify-between">
<div className="flex items-center gap-2">
<Globe className="w-5 h-5 text-krona-green" />
<div>
<span className="font-bold text-krona-primary block">sushi-nsk.ru</span>
<span className="text-[10px] text-krona-secondary">SSL активен • Let's Encrypt Wildcard</span>
</div>
</div>
<Badge variant="green" size="sm">Подключен</Badge>
</div>
<div className="p-3.5 bg-krona-workspace rounded-xl border border-krona-border flex items-center justify-between">
<div className="flex items-center gap-2">
<Globe className="w-5 h-5 text-krona-secondary" />
<div>
<span className="font-bold text-krona-primary block">krona.site/sushi-nsk</span>
<span className="text-[10px] text-krona-secondary">Системный поддомен платформы</span>
</div>
</div>
<Badge variant="neutral" size="sm">Основной</Badge>
</div>
</div>
</div>
)}
</div>
);
};