/* 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();