// Onboarding wizard: org → brandbook → sources. Final → main app.
const { useState: useStateO, useMemo: useMemoO } = React;
const FONT_LIST = [
{ name: 'Manrope', vibe: 'Универсальный sans · UI-friendly' },
{ name: 'Inter', vibe: 'Чистый sans · нейтральный' },
{ name: 'Space Grotesk', vibe: 'Геометричный · технологичный' },
{ name: 'Playfair Display', vibe: 'Серифный · редакционный' },
{ name: 'DM Serif Display', vibe: 'Высокий контраст · финансы / люкс' },
{ name: 'Instrument Serif', vibe: 'Тёплый serif · мягкий и литературный' },
{ name: 'JetBrains Mono', vibe: 'Моно · техно' },
];
const TONE_PRESETS = ['Дружелюбный', 'Экспертный', 'Игривый', 'Формальный'];
function Onboarding({ email, onDone }) {
const { Button, IconButton, TextInput, Textarea, ColorSwatch, Pill } = window.UI;
const I = window.Icons;
const { SlideThumb } = window.Carousel;
const [step, setStep] = useStateO(0);
const [orgName, setOrgName] = useStateO('');
const [slug, setSlug] = useStateO('');
const [touchedSlug, setTouchedSlug] = useStateO(false);
const [colors, setColors] = useStateO(['#0F0F0F', '#FFFFFF', '#7C5CFF']);
const [fonts, setFonts] = useStateO(['Manrope', 'Instrument Serif']);
const [fontMenu, setFontMenu] = useStateO(null); // 0 | 1 | null
const [tones, setTones] = useStateO(['Дружелюбный']);
const [tone, setTone] = useStateO('');
const [logo, setLogo] = useStateO(null);
const [rss, setRss] = useStateO('');
const [tg, setTg] = useStateO('');
const [site, setSite] = useStateO('');
useMemoO(() => {
if (!touchedSlug) {
const s = orgName.toLowerCase().replace(/[^a-zа-я0-9]+/gi, '-').replace(/^-+|-+$/g, '').slice(0, 30);
setSlug(s);
}
}, [orgName, touchedSlug]);
const finish = () => {
onDone({
org: {
name: orgName || 'Моя организация',
slug: slug || 'my-org',
initial: (orgName || 'M')[0].toUpperCase(),
accent: colors[2] || '#7C5CFF',
role: 'Новая организация',
brand: { colors, fonts, tone: tone || tones.join(', '), tonePresets: tones, logo },
},
sources: { rss, tg, site },
});
};
const STEPS = ['Организация', 'Брендбук', 'Источники'];
return (
{STEPS.map((label, i) => (
{i < step ? : i + 1}
{label}
{i < STEPS.length - 1 &&
}
))}
{step === 0 && (
Шаг 01
Создайте организацию
Это рабочее пространство для одного бренда. Внутри можно создать сколько угодно — личных, клиентских, командных.
Название
Slug
omneee.app/
{ setSlug(v); setTouchedSlug(true); }} placeholder="my-studio" size="lg" className="flex-1"/>
Так будет выглядеть адрес панели.
setStep(1)} disabled={!orgName.trim()} iconRight={}>Дальше
предпросмотр
{(orgName || '·')[0].toUpperCase()}
{orgName || 'Название организации'}
{slug ? `omneee.app/${slug}` : 'omneee.app/slug'}
{['Дашборд', 'Источники', 'Идеи', 'Генерация'].map((x) => (
{x}
))}
)}
{step === 1 && (
Шаг 02
Брендбук
Это превратится в дизайн каждой карусели. Всё можно менять позже.
{/* colors */}
Цвета бренда
1–4 цвета. Первый — основной фон.
{colors.length < 4 && (
} onClick={() => setColors([...colors, '#000000'])}>Добавить
)}
{colors.map((c, i) => (
setColors(colors.map((x, idx) => idx === i ? v : x))}
onRemove={colors.length > 1 ? () => setColors(colors.filter((_, idx) => idx !== i)) : null}
removable={colors.length > 1}
size={56}
/>
{c.toUpperCase()}
))}
{/* fonts */}
Шрифты
Первый — для заголовков, второй — для текста.
{[0, 1].map((idx) => (
setFontMenu(fontMenu === idx ? null : idx)}
className="w-full text-left bg-white border border-ink-200 rounded-2xl p-4 hover:border-ink-300 transition-colors"
>
{idx === 0 ? 'Заголовки' : 'Текст'}
{fonts[idx]}
{(FONT_LIST.find(f => f.name === fonts[idx]) || {}).vibe || ''}
{fontMenu === idx && (
{FONT_LIST.map((f) => (
{ setFonts(fonts.map((x, i) => i === idx ? f.name : x)); setFontMenu(null); }}
className={'w-full text-left rounded-xl px-3 py-2 flex items-center justify-between hover:bg-ink-50 ' + (fonts[idx] === f.name ? 'bg-ink-50' : '')}
>
{fonts[idx] === f.name && }
))}
)}
))}
{/* tone */}
Тон голоса
Выберите ярлыки и опишите голос своими словами.
{TONE_PRESETS.map((t) => (
setTones(tones.includes(t) ? tones.filter(x => x !== t) : [...tones, t])}>{t}
))}
{/* logo */}
Логотип (необязательно)
{
const f = e.target.files && e.target.files[0];
if (f) setLogo({ name: f.name });
}}/>
{logo ? logo.name : 'Загрузите SVG, PNG или JPG'}
До 2 МБ. Подходит и тёмный, и светлый.
setStep(0)} icon={}>Назад
setStep(2)} iconRight={}>Дальше
{/* preview cards */}
)}
{step === 2 && (
Шаг 03
Подключите источники
Мы будем читать их в фоне и предлагать темы. Можно пропустить — добавите позже.
} label="RSS-фиды" placeholder="https://example.com/feed.xml" value={rss} onChange={setRss} hint="По одному в строке"/>
} label="Telegram-каналы" placeholder="@channel_name" value={tg} onChange={setTg} hint="Только публичные каналы"/>
} label="Свой сайт" placeholder="https://mybrand.com" value={site} onChange={setSite} hint="Чтобы Omneee знал ваш контекст"/>
setStep(1)} icon={}>Назад
Пропустить
}>Готово, начнём
что произойдёт дальше
{[
['Сканируем источники', 'В фоне за 1–3 минуты'],
['Предлагаем темы', 'Те, что подходят вашему бренду'],
['Генерируем карусели', 'На основе ваших цветов, шрифтов и тона'],
['Кидаем в Telegram', 'Когда пост готов'],
].map(([t, s], i) => (
{i + 1}
))}
)}
);
}
function SourceField({ icon, label, placeholder, hint, value, onChange }) {
return (
);
}
function BrandPreview({ colors, fonts }) {
const [c1, c2, c3] = [colors[0] || '#000', colors[1] || '#fff', colors[2] || '#7C5CFF'];
return (
<>
01 · COVER
OMNEEE
Ваш бренд в каждой карусели
Цвета, шрифты и тон Omneee помнит везде.
@brand
{colors.map((c, i) => (
))}
>
);
}
window.Screens = window.Screens || {};
window.Screens.Onboarding = Onboarding;