// Carousel preview + edit. Phone frame on left, slide list on right, edit mode. const { useState: useStateP, useRef: useRefP } = React; const SLIDE_KINDS = [ { id: 'cover', name: 'Обложка', icon: 'Cover' }, { id: 'header', name: 'Заголовок', icon: 'Type' }, { id: 'number', name: 'Цифра', icon: 'Number' }, { id: 'quote', name: 'Цитата', icon: 'Quote' }, { id: 'body', name: 'Текст', icon: 'Stack' }, ]; function Preview({ org, slides, setSlides, onBack, onSaved }) { const { Button, IconButton, Card, Pill, Modal } = window.UI; const I = window.Icons; const { SlideFrame } = window.Carousel; const [active, setActive] = useStateP(0); const [editing, setEditing] = useStateP(false); const [tgOpen, setTgOpen] = useStateP(false); const [downloadOpen, setDownloadOpen] = useStateP(false); const [savedTip, setSavedTip] = useStateP(false); const slide = slides[active]; const updateSlide = (patch) => { setSlides(slides.map((s, i) => i === active ? { ...s, ...patch } : s)); }; const regenerateOne = () => { // Mock: shuffle small details to feel "regenerated" setSlides(slides.map((s, i) => i === active ? { ...s, body: s.body ? s.body + ' ' : s.body, label: s.label } : s)); }; return (
{/* Top actions */}
превью
{slides[0].title}
{savedTip && (
Черновик сохранён
)}
{/* Phone preview */}
{slides.map((s, i) => (
))} {/* nav arrows */} {/* dots */}
{slides.map((_, i) => ( ))}
{String(active + 1).padStart(2, '0')} / {String(slides.length).padStart(2, '0')}
{/* Slide list + editor */}
{/* Slide thumbnails */}
Слайды · {slides.length}
{slides.map((s, i) => ( ))}
{/* Edit panel */} {editing ? (
Редактировать слайд {active + 1}
Тип
{SLIDE_KINDS.map((k) => { const Icon = I[k.icon]; const active2 = slide.kind === k.id; return ( ); })}
{(slide.kind === 'cover' || slide.kind === 'header' || slide.kind === 'body') && ( updateSlide({ title: v })}/> )} {slide.kind === 'cover' && ( updateSlide({ subtitle: v })} multi/> )} {slide.kind === 'number' && ( <> updateSlide({ number: v })}/> updateSlide({ body: v })} multi/> )} {slide.kind === 'quote' && ( <> updateSlide({ body: v })} multi/> updateSlide({ author: v })}/> )} {slide.kind === 'header' && ( updateSlide({ body: v })} multi/> )} {slide.kind === 'body' && (
Пункты
{(slide.items || []).map((it, i) => (
updateSlide({ items: slide.items.map((x, idx) => idx === i ? e.target.value : x) })} className="flex-1 h-9 px-3 rounded-lg border border-ink-200 text-[13px] focus:outline-none focus:border-ink-900"/> updateSlide({ items: slide.items.filter((_, idx) => idx !== i) })}>
))}
)} updateSlide({ label: v })}/>
) : (
Сводка
  • Слайдов{slides.length}
  • Формат1080×1350 · 4:5
  • Шрифты{org.brand.fonts.join(' · ')}
  • Палитра
    {org.brand.colors.map((c, i) => )}
)}
{/* Download modal */} setDownloadOpen(false)} title="ZIP с PNG-слайдами" sub="Можно загружать прямо в Instagram-карусель.">
{slides[0].title?.toLowerCase().replace(/[^a-zа-я0-9]+/gi, '-') || 'carousel'}.zip
{slides.length} файлов · ~{(slides.length * 0.4).toFixed(1)} МБ
{/* Telegram modal */} setTgOpen(false)} title="Отправить в Telegram" sub="Бот пришлёт все слайды отдельным альбомом в @username">
@omneee_bot
Подключён к этой организации
); } function FieldText({ label, value, onChange, multi = false }) { return (
{label}
{multi ? (