Initial commit: TopSysOps site + admin panel

Multi-page public site (Next.js), Node/Express backend with Postgres,
and Vite admin/account app, restructured from a single-page layout
into dedicated service, promotions, cases, about, FAQ, and blog pages
for SEO.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013otXCiTZsxdZ4SJKZ9wUky
This commit is contained in:
ogrechkoandClaude Sonnet 5 committed 2026-08-25 23:12:16 +03:00
commit 130c5382cd
108 files changed
+7146

No files matched your search

+35
View File
@@ -0,0 +1,35 @@
'use client';
import { createContext, useContext, useEffect, useState } from 'react';
const ThemeContext = createContext({ dark: false, toggle: () => {} });
export const useTheme = () => useContext(ThemeContext);
export function ThemeProvider({ children }) {
// Инлайн-скрипт в layout.jsx уже выставляет класс "dark" на <html> ДО гидратации
// (чтобы не было мигания темы). Читаем этот класс напрямую при монтировании.
const [dark, setDark] = useState(() => {
if (typeof document === 'undefined') return false;
return document.documentElement.classList.contains('dark');
});
// Побочные эффекты (DOM-класс, localStorage) вынесены в отдельный useEffect,
// а не в апдейтер состояния - React может вызвать функцию апдейтера больше
// одного раза за один рендер, и тогда classList.toggle сработал бы дважды,
// фактически отменяя переключение (баг "не срабатывает" / "снова светлая").
useEffect(() => {
document.documentElement.classList.toggle('dark', dark);
try {
localStorage.setItem('ts-theme', dark ? 'dark' : 'light');
} catch (e) {
// localStorage unavailable - theme just won't persist
}
}, [dark]);
return (
<ThemeContext.Provider value={{ dark, toggle: () => setDark((d) => !d) }}>
{children}
</ThemeContext.Provider>
);
}