diff --git a/frontend/src/App.js b/frontend/src/App.js index 4443f9a..2b07db6 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -12,6 +12,7 @@ import CampaignPage from './pages/CampaignPage'; import MainLayout from './components/MainLayout'; import './App.css'; import { useUser } from './context/UserContext'; +import { ThemeProvider } from './context/ThemeContext'; function App() { const { user, token, isCheckingAuth } = useUser(); @@ -21,108 +22,110 @@ function App() { } return ( - - - } /> - - - - ) : ( - - ) - } - /> - - - - ) : ( - - ) - } - /> - - - - ) : ( - - ) - } - /> - - - - ) : ( - - ) - } - /> - - - - ) : ( - - ) - } - /> - - - - ) : ( - - ) - } - /> - - - - ) : ( - - ) - } - /> - - - - ) : ( - - ) - } - /> - } /> - - + + + + } /> + + + + ) : ( + + ) + } + /> + + + + ) : ( + + ) + } + /> + + + + ) : ( + + ) + } + /> + + + + ) : ( + + ) + } + /> + + + + ) : ( + + ) + } + /> + + + + ) : ( + + ) + } + /> + + + + ) : ( + + ) + } + /> + + + + ) : ( + + ) + } + /> + } /> + + + ); } diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js index 1d66f4d..f37abdb 100644 --- a/frontend/src/components/Header.js +++ b/frontend/src/components/Header.js @@ -1,10 +1,12 @@ import React from 'react'; import styles from './Header.module.css'; +import ThemeToggle from './ThemeToggle'; const Header = ({ user, onLogout }) => { return (
+ {user?.email || 'Аккаунт'}
diff --git a/frontend/src/components/Header.module.css b/frontend/src/components/Header.module.css index d621679..baa4bbd 100644 --- a/frontend/src/components/Header.module.css +++ b/frontend/src/components/Header.module.css @@ -1,8 +1,8 @@ .header { width: 100%; height: 64px; - background: #fff; - border-bottom: 1.5px solid #e5e7eb; + background: var(--card-background); + border-bottom: 1.5px solid var(--border-color); display: flex; align-items: center; justify-content: flex-end; @@ -38,7 +38,7 @@ } .logout:hover { - background: linear-gradient(90deg, #3730a3 0%, #06b6d4 100%); + background: linear-gradient(90deg, #4f46e5 0%, #06b6d4 100%); } /* Адаптивные стили для мобильных устройств */ diff --git a/frontend/src/components/SideMenu.module.css b/frontend/src/components/SideMenu.module.css index 82eda35..912df49 100644 --- a/frontend/src/components/SideMenu.module.css +++ b/frontend/src/components/SideMenu.module.css @@ -1,8 +1,8 @@ .menu { width: 250px; min-height: 100vh; - background: #f3f4f6; - border-right: 1.5px solid #e5e7eb; + background: var(--card-background); + border-right: 1.5px solid var(--border-color); display: flex; flex-direction: column; padding: 0 0 24px 0; @@ -12,10 +12,10 @@ .project { font-size: 22px; font-weight: 700; - color: #3730a3; + color: #6366f1; padding: 32px 24px 16px 24px; letter-spacing: 1px; - border-bottom: 1px solid #e5e7eb; + border-bottom: 1px solid var(--border-color); margin-bottom: 12px; } @@ -42,20 +42,20 @@ ul { li { padding: 12px 24px; font-size: 16px; - color: #374151; + color: var(--text-color); cursor: pointer; border-left: 3px solid transparent; transition: background 0.15s, border 0.15s, color 0.15s; } li:hover { - background: #e0e7ff; - color: #3730a3; + background: var(--hover-background); + color: #6366f1; } .active { - background: #e0e7ff; - color: #3730a3; + background: var(--hover-background); + color: #6366f1; border-left: 3px solid #6366f1; font-weight: 600; } @@ -120,7 +120,7 @@ li:hover { top: 0; z-index: 1000; width: 250px; - box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1); + box-shadow: 2px 0 10px var(--shadow-color); } .menu.open { diff --git a/frontend/src/components/ThemeToggle.js b/frontend/src/components/ThemeToggle.js new file mode 100644 index 0000000..64211bc --- /dev/null +++ b/frontend/src/components/ThemeToggle.js @@ -0,0 +1,36 @@ +import React from 'react'; +import { useTheme } from '../context/ThemeContext'; +import styles from './ThemeToggle.module.css'; + +const ThemeToggle = () => { + const { theme, toggleTheme, isDark } = useTheme(); + + return ( + + ); +}; + +export default ThemeToggle; \ No newline at end of file diff --git a/frontend/src/components/ThemeToggle.module.css b/frontend/src/components/ThemeToggle.module.css new file mode 100644 index 0000000..4d6211b --- /dev/null +++ b/frontend/src/components/ThemeToggle.module.css @@ -0,0 +1,50 @@ +.themeToggle { + background: none; + border: none; + cursor: pointer; + padding: 8px; + border-radius: 8px; + color: var(--text-color); + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + background-color: var(--card-background); + border: 1px solid var(--border-color); +} + +.themeToggle:hover { + background-color: var(--hover-background); + transform: scale(1.05); +} + +.themeToggle:active { + transform: scale(0.95); +} + +.themeToggle svg { + transition: transform 0.3s ease; +} + +.themeToggle:hover svg { + transform: rotate(15deg); +} + +/* Адаптивные стили */ +@media (max-width: 768px) { + .themeToggle { + width: 36px; + height: 36px; + padding: 6px; + } +} + +@media (max-width: 480px) { + .themeToggle { + width: 32px; + height: 32px; + padding: 4px; + } +} \ No newline at end of file diff --git a/frontend/src/context/ThemeContext.js b/frontend/src/context/ThemeContext.js new file mode 100644 index 0000000..b4c953c --- /dev/null +++ b/frontend/src/context/ThemeContext.js @@ -0,0 +1,67 @@ +import React, { createContext, useContext, useState, useEffect } from 'react'; + +const ThemeContext = createContext(); + +export const useTheme = () => { + const context = useContext(ThemeContext); + if (!context) { + throw new Error('useTheme must be used within a ThemeProvider'); + } + return context; +}; + +export const ThemeProvider = ({ children }) => { + const [theme, setTheme] = useState(() => { + // Проверяем сохраненную тему в localStorage + const savedTheme = localStorage.getItem('theme'); + if (savedTheme) { + return savedTheme; + } + // Если нет сохраненной темы, используем системную + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; + }); + + useEffect(() => { + // Сохраняем тему в localStorage + localStorage.setItem('theme', theme); + + // Применяем тему к документу + document.documentElement.setAttribute('data-theme', theme); + + // Обновляем CSS переменные + const root = document.documentElement; + if (theme === 'dark') { + root.style.setProperty('--background-color', '#1a1a1a'); + root.style.setProperty('--text-color', '#ffffff'); + root.style.setProperty('--border-color', '#333333'); + root.style.setProperty('--card-background', '#2d2d2d'); + root.style.setProperty('--hover-background', '#3a3a3a'); + root.style.setProperty('--secondary-text', '#a0a0a0'); + root.style.setProperty('--shadow-color', 'rgba(0, 0, 0, 0.3)'); + } else { + root.style.setProperty('--background-color', '#ffffff'); + root.style.setProperty('--text-color', '#000000'); + root.style.setProperty('--border-color', '#e5e7eb'); + root.style.setProperty('--card-background', '#ffffff'); + root.style.setProperty('--hover-background', '#f9fafb'); + root.style.setProperty('--secondary-text', '#6b7280'); + root.style.setProperty('--shadow-color', 'rgba(0, 0, 0, 0.1)'); + } + }, [theme]); + + const toggleTheme = () => { + setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light'); + }; + + const value = { + theme, + toggleTheme, + isDark: theme === 'dark' + }; + + return ( + + {children} + + ); +}; \ No newline at end of file diff --git a/frontend/src/index.css b/frontend/src/index.css index b1cd28c..ae1f6a2 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -6,6 +6,9 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; overflow-x: hidden; + background-color: var(--background-color); + color: var(--text-color); + transition: background-color 0.3s ease, color 0.3s ease; } code { @@ -18,6 +21,27 @@ code { box-sizing: border-box; } +/* CSS переменные для тем */ +:root { + --background-color: #ffffff; + --text-color: #000000; + --border-color: #e5e7eb; + --card-background: #ffffff; + --hover-background: #f9fafb; + --secondary-text: #6b7280; + --shadow-color: rgba(0, 0, 0, 0.1); +} + +[data-theme="dark"] { + --background-color: #1a1a1a; + --text-color: #ffffff; + --border-color: #333333; + --card-background: #2d2d2d; + --hover-background: #3a3a3a; + --secondary-text: #a0a0a0; + --shadow-color: rgba(0, 0, 0, 0.3); +} + /* Медиа-запросы для разных устройств */ @media (max-width: 768px) { html { @@ -39,19 +63,7 @@ code { } } -/* Поддержка темной темы */ -@media (prefers-color-scheme: dark) { - :root { - --background-color: #1a1a1a; - --text-color: #ffffff; - --border-color: #333333; - } -} - -@media (prefers-color-scheme: light) { - :root { - --background-color: #ffffff; - --text-color: #000000; - --border-color: #e5e7eb; - } +/* Плавные переходы для всех элементов */ +* { + transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease; } diff --git a/frontend/src/pages/Login.js b/frontend/src/pages/Login.js index 5d91429..97769f3 100644 --- a/frontend/src/pages/Login.js +++ b/frontend/src/pages/Login.js @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import styles from './Login.module.css'; import { useUser } from '../context/UserContext'; +import ThemeToggle from '../components/ThemeToggle'; const Login = () => { const [email, setEmail] = useState(''); @@ -37,6 +38,9 @@ const Login = () => { return (
+
+ +

Вход в систему