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(PAGES_LIST); const [selectedTab, setSelectedTab] = useState<'pages' | 'builder' | 'forms' | 'media' | 'seo' | 'domains'>('pages'); // Page Builder States const [widgets, setWidgets] = useState(INITIAL_WIDGETS); const [selectedWidgetId, setSelectedWidgetId] = useState(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 = { 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 (
{/* Header */}

Сайты & Krona CMS

krona.site/sushi-nsk

Визуальный блочный конструктор посадочных страниц, формы захвата лидов, медиатека и SEO

{[ { 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) => ( ))}
{/* 1. PAGES TAB */} {activeTab === 'pages' && (
{pages.map((p) => ( ))}
Название страницы URL / Slug Просмотров сегодня Дата редакции Статус Действие
{p.title} {p.slug} {p.viewsToday} {p.lastEdited} Опубликована
)} {/* 2. VISUAL BUILDER TAB */} {activeTab === 'builder' && (
{/* Builder Top Toolbar */}
Редактирование: «Главная страница доставки» Auto-save
{/* Responsive Viewport Switcher */}
{/* Builder Workspace 3 Columns */}
{/* Left: Widget Palette */}
Палитра виджетов
{[ { 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) => ( ))}
{/* Center: Canvas Preview */}
{widgets.map((w, idx) => { const isSelected = w.id === selectedWidgetId; return (
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 */}
#{idx + 1} {w.type} {w.badge && {w.badge}}
e.stopPropagation()}>
{/* Visual Widget Preview Rendering */}

{w.title}

{w.subtitle}

{w.ctaText && (
{w.ctaText} →
)}
); })}
{/* Right: Selected Widget Properties Editor */}
Свойства виджета {selectedWidget.type}
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" />