// App entry — auth/onboarding/shell + state-driven routing + org-scoped data. const { useState: useStateApp, useMemo: useMemoApp, useEffect: useEffectApp } = React; function App() { const { ORGS, SOURCES, IDEAS, POSTS, SAMPLE_CAROUSEL, FILES, BRAND_ASSETS, ANALYTICS } = window.OmneeeData; // === auth === const [authStage, setAuthStage] = useStateApp('landing'); // landing | onboarding | app const [user, setUser] = useStateApp({ email: '', name: '' }); // === org-scoped data === const [orgs, setOrgs] = useStateApp(() => ORGS.map((o) => ({ ...o, brandPdf: (BRAND_ASSETS[o.id] || {}).pdf || null, brandImages: (BRAND_ASSETS[o.id] || {}).images || [], }))); const [orgId, setOrgId] = useStateApp(ORGS[0].id); const [allData, setAllData] = useStateApp(() => { const out = {}; ORGS.forEach((o) => { out[o.id] = { sources: SOURCES[o.id] || [], ideas: IDEAS[o.id] || [], posts: POSTS[o.id] || [], files: FILES[o.id] || [], }; }); return out; }); // === routing === const [route, setRoute] = useStateApp('dashboard'); // dashboard | sources | ideas | generate | posts | brand | settings | preview const [routeParams, setRouteParams] = useStateApp({}); const [genPrefill, setGenPrefill] = useStateApp(null); const [carouselSlides, setCarouselSlides] = useStateApp(null); // current preview's slides (mutable) const [previewPostId, setPreviewPostId] = useStateApp(null); // === onboarding result === const handleAuthed = ({ email, firstTime }) => { setUser({ email, name: email.split('@')[0] }); if (firstTime) { setAuthStage('onboarding'); } else { setAuthStage('app'); setRoute('dashboard'); } }; const handleOnboardingDone = ({ org, sources }) => { const id = (org.slug || 'org') + '-' + Date.now(); const newOrg = { id, ...org }; const newSources = []; const parseLines = (text, type, mk) => text.split(/\n+/).map(s => s.trim()).filter(Boolean).forEach((u, i) => { newSources.push({ id: id + '-s-' + type + i, type, url: u, last: 'только что', count: 0, enabled: true }); }); if (sources.rss) parseLines(sources.rss, 'rss'); if (sources.tg) parseLines(sources.tg, 'tg'); if (sources.site) parseLines(sources.site, 'site'); setOrgs([newOrg, ...orgs]); setAllData({ ...allData, [id]: { sources: newSources, ideas: [], posts: [] } }); setOrgId(id); setAuthStage('app'); setRoute('dashboard'); }; const handleLogout = () => { setAuthStage('landing'); setUser({ email: '', name: '' }); setRoute('dashboard'); }; const currentOrg = orgs.find(o => o.id === orgId) || orgs[0]; const data = allData[orgId] || { sources: [], ideas: [], posts: [], files: [] }; const setData = (next) => setAllData({ ...allData, [orgId]: next }); const approvePost = (postId) => { setData({ ...data, posts: data.posts.map(p => p.id === postId ? { ...p, status: 'ready' } : p) }); }; const goRoute = (id, params = {}) => { setRouteParams(params); if (id === 'settings' && params.tab) { setRoute('settings'); } else if (id === 'preview') { // Open carousel preview for a post id, or last generated if (params.postId) { setPreviewPostId(params.postId); const post = data.posts.find(p => p.id === params.postId); // Use sample carousel for this org but customize title for the post const base = SAMPLE_CAROUSEL(currentOrg).map(s => ({ ...s })); base[0].title = post ? post.title : base[0].title; setCarouselSlides(base); } setRoute('preview'); } else { setRoute(id); } }; const onSwitchOrg = (id) => { setOrgId(id); setRoute('dashboard'); }; const onCreateOrg = () => { setAuthStage('onboarding'); }; const onDeleteOrg = () => { const next = orgs.filter(o => o.id !== orgId); setOrgs(next); if (next.length === 0) { // back to onboarding setAuthStage('onboarding'); return; } setOrgId(next[0].id); setRoute('dashboard'); }; const onChangeOrg = (newOrg) => { setOrgs(orgs.map(o => o.id === orgId ? newOrg : o)); }; const onUseIdeaForGen = (idea) => { setGenPrefill({ source: 'ideas', topic: { title: idea.preview, src: 'из бэклога · ' + idea.when } }); setRoute('generate'); }; const onGenerated = ({ topic, style }) => { // Insert mock post and jump to preview const slides = SAMPLE_CAROUSEL(currentOrg).map(s => ({ ...s })); slides[0].title = topic.title; if (topic.title.length > 24) slides[0].subtitle = 'Свежая карусель по выбранной теме'; const newPost = { id: 'p' + Date.now(), title: topic.title, status: 'draft', slides: slides.length, date: 'только что', cover: { kind: slides[0].kind, fg: slides[0].fg, bg: slides[0].bg, accent: slides[0].accent, label: slides[0].label }, }; setData({ ...data, posts: [newPost, ...data.posts] }); setPreviewPostId(newPost.id); setCarouselSlides(slides); setGenPrefill(null); setRoute('preview'); }; const onSavedDraft = () => { if (!previewPostId) return; setData({ ...data, posts: data.posts.map(p => p.id === previewPostId ? { ...p, status: p.status === 'sent' ? 'sent' : 'draft' } : p), }); }; // === render === if (authStage === 'landing') { return ; } if (authStage === 'onboarding') { return ; } // App shell const ROUTE_TITLES = { dashboard: 'Дашборд', sources: 'Источники', ideas: 'Идеи', generate: 'Генерация', posts: 'Посты', brand: 'Брендбук', settings: 'Настройки', preview: 'Превью', }; let body = null; switch (route) { case 'dashboard': body = goRoute(r, p)} onApprove={approvePost}/>; break; case 'sources': body = ; break; case 'ideas': body = ; break; case 'generate': body = ; break; case 'analytics': body = goRoute('preview', { postId: id })}/>; break; case 'preview': body = ( setCarouselSlides(s)} onBack={() => setRoute('posts')} onSaved={onSavedDraft} /> ); break; case 'posts': body = goRoute('preview', { postId: id }), navigate: (r) => goRoute(r) }}/>; break; case 'brand': body = ; break; case 'settings': body = ; break; default: body = null; } return ( goRoute(r)} onSwitchOrg={onSwitchOrg} onCreateOrg={onCreateOrg} onLogout={handleLogout} > {body} ); } ReactDOM.createRoot(document.getElementById('root')).render();