/* app.jsx — оболочка: сайдбар, шапка, нижняя навигация, темы, роутинг, монтаж. */ const NAV = [ { key: "home", label: "Главная", icon: "grid" }, { key: "notes", label: "Записи", icon: "notes" }, { key: "search", label: "Поиск", icon: "search" }, { key: "reminders", label: "Напомн.", icon: "bell" }, { key: "passwords", label: "Пароли", icon: "lock" }, ]; const ALL_ROUTES = [...NAV, { key: "settings", label: "Настройки", icon: "settings" }]; const URLP = new URLSearchParams(location.search); function useTheme() { const [theme, setTheme] = React.useState(() => { const forced = URLP.get("theme"); if (forced === "light" || forced === "dark") return forced; const saved = localStorage.getItem("nb-theme"); if (saved) return saved; return window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; }); React.useEffect(() => { document.documentElement.classList.toggle("dark", theme === "dark"); localStorage.setItem("nb-theme", theme); }, [theme]); return [theme, () => setTheme((t) => (t === "dark" ? "light" : "dark"))]; } // применяем плотность ленты из настроек к function useDensity() { const settings = Store.useSettings(); React.useEffect(() => { document.documentElement.dataset.density = settings.density || "cozy"; }, [settings.density]); } function Brand({ compact }) { return (
Умный блокнот {!compact && (
Умный блокнот
личный второй экран
)}
); } function Sidebar({ active, onNav, user, onLogout }) { return ( ); } function TopBar({ theme, onToggleTheme, user, onLogout, title, active, onNav }) { return (
{title}
{user.name}
); } function BottomNav({ active, onNav }) { return ( ); } function Fab({ onClick, label = "Новая запись" }) { return ( ); } function App() { const [theme, toggleTheme] = useTheme(); useDensity(); const [authed, setAuthed] = React.useState(() => URLP.get("auto") === "1"); const [user, setUser] = React.useState({ name: "", timezone: "" }); const [route, setRoute] = React.useState("home"); const [openNote, setOpenNote] = React.useState(null); const [creating, setCreating] = React.useState(false); const [pwSignal, setPwSignal] = React.useState(0); const [reloadKey, setReloadKey] = React.useState(0); const bump = () => setReloadKey((k) => k + 1); // авто-вход внутри Telegram (Mini App) + тихое восстановление сессии по cookie React.useEffect(() => { const tg = window.Telegram && window.Telegram.WebApp; if (tg) { try { tg.ready(); tg.expand(); } catch (e) {} try { if (tg.disableVerticalSwipes) tg.disableVerticalSwipes(); } catch (e) {} try { if (tg.setHeaderColor) tg.setHeaderColor("bg_color"); } catch (e) {} try { if (tg.setBackgroundColor) tg.setBackgroundColor("bg_color"); } catch (e) {} } if (tg && tg.initData) { api.authWebapp(tg.initData).then((r) => { setUser(r.user); setAuthed(true); }).catch(() => {}); return; } if (URLP.get("auto") === "1") return; api.getMe().then((u) => { setUser(u); setAuthed(true); }).catch(() => {}); }, []); React.useEffect(() => { if (authed) { api.getMe().then(setUser).catch(() => {}); Store.load(); } }, [authed]); const logout = () => { try { window.vault && window.vault.lock(); api.logout(); } catch (e) {} setAuthed(false); }; if (!authed) return setAuthed(true)} />; const title = (ALL_ROUTES.find((n) => n.key === route) || ALL_ROUTES[0]).label; const showFab = route === "notes" || route === "passwords" || route === "home"; const onFab = () => { if (route === "passwords") setPwSignal((s) => s + 1); else setCreating(true); }; return (
{route === "home" && setCreating(true)} onOpenNote={setOpenNote} />} {route === "notes" && setCreating(true)} />} {route === "search" && } {route === "reminders" && } {route === "passwords" && } {route === "settings" && }
{showFab && } setOpenNote(null)} onSaved={bump} onDeleted={bump} onOpenRelated={(r) => setOpenNote(r)} /> setCreating(false)} onCreated={() => { setRoute("notes"); bump(); }} />
); } ReactDOM.createRoot(document.getElementById("root")).render();