From 9bbf88a8f743f8b1be49f368192a2c44b44ac860 Mon Sep 17 00:00:00 2001 From: ospab Date: Mon, 5 Jan 2026 20:11:22 +0300 Subject: [PATCH] feat: Implement dark mode support across the application - Added ThemeContext to manage theme state and toggle functionality. - Updated components to support dark mode styles, including header, dashboard, and home page. - Enhanced CSS for smooth transitions between light and dark themes. - Modified authentication context to handle async login operations. - Improved user experience by preserving theme preference in local storage. - Refactored login and register pages to handle OAuth tokens and errors more gracefully. --- .github/copilot-instructions.md | 98 ++++++---------- ospabhost/backend/.env | 4 +- .../backend/src/modules/auth/oauth.routes.ts | 13 --- .../src/modules/auth/passport.config.ts | 38 ------- .../src/modules/blog/upload.controller.ts | 2 +- ospabhost/frontend/.env | 6 +- ospabhost/frontend/src/components/footer.tsx | 56 +++++---- ospabhost/frontend/src/components/header.tsx | 26 ++--- .../frontend/src/context/ThemeContext.tsx | 106 ++++++++++++++++++ .../frontend/src/context/authcontext.tsx | 25 +++-- ospabhost/frontend/src/index.css | 23 ++++ ospabhost/frontend/src/main.tsx | 5 +- .../frontend/src/pages/dashboard/mainpage.tsx | 100 +++++++---------- ospabhost/frontend/src/pages/index.tsx | 52 ++++----- ospabhost/frontend/src/pages/login.tsx | 90 +++++++-------- ospabhost/frontend/src/pages/register.tsx | 39 +++---- ospabhost/frontend/tailwind.config.js | 1 + 17 files changed, 366 insertions(+), 318 deletions(-) create mode 100644 ospabhost/frontend/src/context/ThemeContext.tsx diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 85133d0..2d6d49b 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,72 +1,38 @@ -# Copilot Instructions for Ospabhost 8.1 +# Copilot Instructions for Ospabhost +## Architecture Overview +- **Monorepo**: Backend (Express + TypeScript + Prisma) and Frontend (React + Vite + TypeScript). +- **Backend Entry**: `ospabhost/backend/src/index.ts` - Express server with CORS, rate limiting, WebSocket, and module routes (`/api/*`). +- **Modules**: Organized in `ospabhost/backend/src/modules/*` (auth, storage, ticket, blog, etc.), each exporting routes and services. +- **Database**: MySQL via Prisma (`ospabhost/backend/prisma/schema.prisma`), models like User, StorageBucket, Ticket with string statuses (`pending`, `approved`, `active`). +- **Frontend Dashboard**: `ospabhost/frontend/src/pages/dashboard/mainpage.tsx` - dynamic sidebar tabs based on user roles (`operator`, `isAdmin`). -## Архитектура и основные компоненты -- **Монорепозиторий**: две части — `backend` (Express, TypeScript, Prisma) и `frontend` (React, Vite, TypeScript). -- **Backend**: - - Точка входа: `backend/src/index.ts` (Express, маршруты `/api/*`, CORS, логирование, WebSocket). - - Модули: `backend/src/modules/*` — домены (auth, admin, ticket, check, blog, notification, user, session, qr-auth, storage, payment, account, sitemap), каждый экспортирует маршруты и сервисы. - - Интеграция с MinIO: для S3-совместимого хранилища, параметры из `.env`. - - ORM: Prisma, схема — `backend/prisma/schema.prisma`, миграции и seed-скрипты — в `backend/prisma/`. - - Статические файлы чеков: `backend/uploads/checks` (доступны по `/uploads/checks`). -- **Frontend**: - - SPA на React + Vite, точка входа: `frontend/src/main.tsx`. - - Страницы: `frontend/src/pages/*`, компоненты: `frontend/src/components/*`. - - Авторизация: `frontend/src/context/authcontext.tsx` (контекст, хуки). - - Дашборд: `frontend/src/pages/dashboard/mainpage.tsx` — реализует сайдбар, вкладки, загрузку данных пользователя, обработку токена, обновление данных через кастомное событие `userDataUpdate`. - - Локализация: поддержка ru/en через `useTranslation` и `LocaleProvider`. +## Key Patterns & Conventions +- **API Routes**: All backend routes prefixed with `/api/`, authenticated via JWT in localStorage. +- **Status Fields**: Use string enums (e.g., Check: `pending`/`approved`/`rejected`; Ticket: `open`/`in_progress`/`resolved`). +- **MinIO Integration**: S3-compatible storage via MinIO SDK, configs from `.env`, buckets per user with quotas. +- **Localization**: ru/en support via `useTranslation` hook, URLs like `/en/dashboard`. +- **WebSocket**: Real-time updates via `/ws`, initialized in `ospabhost/backend/src/websocket/server.ts`. +- **Security**: Rate limiting (1000 req/15min global, 10 req/15min auth), CORS from `PUBLIC_APP_ORIGIN`, helmet middleware. +- **Static Files**: Check uploads in `ospabhost/backend/uploads/checks`, served at `/uploads/checks`. +- **Notifications**: Email/push via `web-push`, templates in `ospabhost/backend/src/modules/notification/email.service.ts`. -## Ключевые паттерны и конвенции -- **API**: все маршруты backend — с префиксом `/api/`. -- **Модули backend**: каждый домен — отдельная папка, экспортирует маршруты и сервисы. -- **Работа с MinIO**: все операции S3 через MinIO SDK, параметры берутся из `.env`. -- **Статусные поля**: для Check, Ticket, Post, StorageBucket — строковые статусы (`pending`, `approved`, `open`, `published`, `active` и др.). -- **Пароли**: генерируются через `generateSecurePassword` (для консольных учётных данных S3). -- **Описание тарифа**: для StoragePlan — цена за GB, трафик, операции. -- **Frontend**: авторизация через контекст, проверка токена, автоматический logout при ошибке 401. -- **Дашборд**: вкладки и права оператора/админа определяются по полям `operator` и `isAdmin` в userData, обновление данных через событие `userDataUpdate`. -- **Уведомления**: email и push через web-push, шаблоны в `notification/email.service.ts`. -- **QR-аутентификация**: временные коды с TTL 60 сек, статусы `pending`, `confirmed`, `expired`. +## Development Workflows +- **Backend**: `npm run dev` (ts-node-dev hot-reload), `npm run build` (TypeScript), PM2 for production (`npm run pm2:start`). +- **Frontend**: `npm run dev` (Vite), `npm run build`, `npm run preview`. +- **Database**: Prisma migrations in `ospabhost/backend/prisma/migrations/`, seed scripts for plans/promocodes. +- **OAuth**: Google/GitHub/VK/Yandex via Passport.js, configs in `ospabhost/backend/src/modules/auth/passport.config.ts`. +- **Blog**: Rich text via Quill, statuses `draft`/`published`/`archived`. -## Сборка, запуск и workflow -- **Backend**: - - `npm run dev` — запуск с hot-reload (ts-node-dev). - - `npm run build` — компиляция TypeScript. - - `npm start` — запуск собранного кода. - - PM2: `npm run pm2:start`, `pm2:restart`, etc. для production. -- **Frontend**: - - `npm run dev` — запуск Vite dev server. - - `npm run build` — сборка TypeScript + Vite. - - `npm run preview` — предпросмотр production-сборки. - - `npm run lint` — проверка ESLint. +## Integration Points +- **Frontend ↔ Backend**: Axios API client, JWT auth, WebSocket for real-time. +- **MinIO**: All S3 ops through `storage.service.ts`, console credentials generated weekly. +- **Push Notifications**: Web Push API, subscriptions in PushSubscription model. +- **QR Auth**: Temporary codes (60s TTL), statuses `pending`/`confirmed`/`expired`. -## Интеграции и взаимодействие -- **Frontend ↔ Backend**: через REST API (`/api/*`), авторизация через JWT-токен в localStorage, WebSocket для real-time обновлений. -- **Backend ↔ MinIO**: для S3 хранилища, параметры из `.env`. -- **OAuth**: поддержка Google, GitHub, VK, Yandex через Passport.js. -- **Push-уведомления**: через web-push API, подписки в PushSubscription. -- **Prisma**: миграции и seed-скрипты — в `backend/prisma/`. +## Examples +- Add new module: Create `ospabhost/backend/src/modules/newmodule/` with `routes.ts` and `service.ts`, import in `index.ts`. +- Update user data: Dispatch `userDataUpdate` event to refresh dashboard. +- Storage bucket: Create via `StorageBucket` model, link to `StoragePlan` for pricing. -## Внешние зависимости -- **Backend**: express, prisma, axios, bcrypt, jsonwebtoken, multer, dotenv, minio, web-push, passport, ws. -- **Frontend**: react, react-dom, react-router-dom, tailwindcss, axios, quill, recharts, xterm. - -## Примеры ключевых файлов -- `backend/src/index.ts` — точка входа, маршрутизация, WebSocket. -- `backend/src/modules/storage/storage.service.ts` — интеграция с MinIO. -- `backend/prisma/schema.prisma` — схема данных (User, StorageBucket, Ticket, etc.). -- `frontend/src/pages/dashboard/mainpage.tsx` — дашборд, обработка токена, сайдбар, вкладки. -- `frontend/src/context/authcontext.tsx` — авторизация, JWT, logout. - -## Особенности и conventions -- **CORS**: разрешены origins из `.env` (PUBLIC_APP_ORIGIN, etc.). -- **Логирование**: каждый запрос логируется с датой и методом через `logger`. -- **Статические файлы**: чеки доступны по `/uploads/checks`. -- **Пароли для S3 консоли**: генерируются еженедельно, хэшируются. -- **Frontend**: сайдбар и вкладки строятся динамически, права по userData, локализация через `useTranslation`. -- **Блог**: Rich Text через Quill, статусы `draft`, `published`, `archived`. -- **Тикеты**: автоназначение операторам, внутренние комментарии. - ---- - -_Обновляйте этот файл при изменении архитектуры, workflow или паттернов. Для уточнения разделов — дайте обратную связь!_ \ No newline at end of file +Reference: `ospabhost/backend/src/index.ts`, `ospabhost/backend/prisma/schema.prisma`, `ospabhost/frontend/src/pages/dashboard/mainpage.tsx`. \ No newline at end of file diff --git a/ospabhost/backend/.env b/ospabhost/backend/.env index 067460e..1eec2c4 100644 --- a/ospabhost/backend/.env +++ b/ospabhost/backend/.env @@ -45,8 +45,10 @@ GITHUB_CLIENT_SECRET=623db1b4285637d328689857f3fc8ae19d84b7f1 YANDEX_CLIENT_ID=d8a889ea467f4d699d1854ac7a4f9b48 YANDEX_CLIENT_SECRET=e599f43f50274344b3bd9a007692c36b -VK_CLIENT_ID=your_vk_client_id_here + +VK_CLIENT_ID=54255963 VK_CLIENT_SECRET=your_vk_client_secret_here + # OAuth Callback URL OAUTH_CALLBACK_URL=https://api.ospab.host/api/auth diff --git a/ospabhost/backend/src/modules/auth/oauth.routes.ts b/ospabhost/backend/src/modules/auth/oauth.routes.ts index fafb6d8..5f00ccb 100644 --- a/ospabhost/backend/src/modules/auth/oauth.routes.ts +++ b/ospabhost/backend/src/modules/auth/oauth.routes.ts @@ -51,17 +51,4 @@ router.get( } ); -// VKontakte OAuth -router.get('/vkontakte', passport.authenticate('vkontakte', { scope: ['email'] })); - -router.get( - '/vkontakte/callback', - passport.authenticate('vkontakte', { session: false, failureRedirect: `${FRONTEND_URL}/login?error=auth_failed` }), - (req: Request, res: Response) => { - const user = req.user as AuthenticatedUser; - const token = jwt.sign({ id: user.id }, JWT_SECRET, { expiresIn: '24h' }); - res.redirect(`${FRONTEND_URL}/login?token=${token}`); - } -); - export default router; diff --git a/ospabhost/backend/src/modules/auth/passport.config.ts b/ospabhost/backend/src/modules/auth/passport.config.ts index e697fd4..efced5b 100644 --- a/ospabhost/backend/src/modules/auth/passport.config.ts +++ b/ospabhost/backend/src/modules/auth/passport.config.ts @@ -5,7 +5,6 @@ import passport from 'passport'; import { Strategy as GoogleStrategy } from 'passport-google-oauth20'; import { Strategy as GitHubStrategy } from 'passport-github'; import { Strategy as YandexStrategy } from 'passport-yandex'; -import { Strategy as VKontakteStrategy } from 'passport-vkontakte'; import { prisma } from '../../prisma/client'; const JWT_SECRET = process.env.JWT_SECRET || 'your_super_secret_key'; const OAUTH_CALLBACK_URL = process.env.OAUTH_CALLBACK_URL || 'https://api.ospab.host/api/auth'; @@ -123,43 +122,6 @@ if (process.env.YANDEX_CLIENT_ID && process.env.YANDEX_CLIENT_SECRET) { ); } -// VKontakte OAuth -if (process.env.VK_CLIENT_ID && process.env.VK_CLIENT_SECRET) { - passport.use( - new VKontakteStrategy( - { - clientID: process.env.VK_CLIENT_ID, - clientSecret: process.env.VK_CLIENT_SECRET, - callbackURL: `${OAUTH_CALLBACK_URL}/vkontakte/callback`, - profileFields: ['email', 'city', 'bdate'] - }, - async (accessToken: string, refreshToken: string, params: any, profile: any, done: any) => { - try { - // VK возвращает email в params, а не в profile - const email = params.email || profile.emails?.[0]?.value; - - if (!email) { - throw new Error('Email не предоставлен ВКонтакте. Убедитесь, что вы разрешили доступ к email.'); - } - - const oauthProfile: OAuthProfile = { - id: profile.id, - displayName: profile.displayName || `${profile.name?.givenName || ''} ${profile.name?.familyName || ''}`.trim(), - emails: [{ value: email }], - provider: 'vkontakte' - }; - - const user = await findOrCreateUser(oauthProfile); - return done(null, user); - } catch (error) { - return done(error as Error); - } - } - ) - ); -} - - passport.serializeUser((user: any, done) => { done(null, user.id); }); diff --git a/ospabhost/backend/src/modules/blog/upload.controller.ts b/ospabhost/backend/src/modules/blog/upload.controller.ts index d3d3dac..f753c26 100644 --- a/ospabhost/backend/src/modules/blog/upload.controller.ts +++ b/ospabhost/backend/src/modules/blog/upload.controller.ts @@ -19,7 +19,7 @@ export const uploadImage = async (req: Request, res: Response) => { return res.status(200).json({ success: true, data: { - url: `https://ospab.host:5000${imageUrl}`, + url: `https://api.ospab.host${imageUrl}`, filename: req.file.filename } }); diff --git a/ospabhost/frontend/.env b/ospabhost/frontend/.env index b3f533c..2b15f09 100644 --- a/ospabhost/frontend/.env +++ b/ospabhost/frontend/.env @@ -3,6 +3,6 @@ VITE_CARD_NUMBER="2204 2402 3323 3354" VITE_TURNSTILE_SITE_KEY=0x4AAAAAAB7306voAK0Pjx8O -# API URLs (с портом 5000) -VITE_API_URL=https://ospab.host:5000 -VITE_SOCKET_URL=wss://ospab.host:5000 +# API URLs +VITE_API_URL=https://api.ospab.host +VITE_SOCKET_URL=wss://api.ospab.host diff --git a/ospabhost/frontend/src/components/footer.tsx b/ospabhost/frontend/src/components/footer.tsx index 50b98e5..99af835 100644 --- a/ospabhost/frontend/src/components/footer.tsx +++ b/ospabhost/frontend/src/components/footer.tsx @@ -1,16 +1,19 @@ import { Link } from 'react-router-dom'; import { FaGithub } from 'react-icons/fa'; +import { FaSun, FaMoon } from 'react-icons/fa'; import logo from '../assets/logo.svg'; import { useTranslation } from '../i18n'; import { useLocalePath } from '../middleware'; +import { useTheme } from '../context/ThemeContext'; const Footer = () => { const currentYear = new Date().getFullYear(); const { t, locale, setLocale } = useTranslation(); const localePath = useLocalePath(); + const { theme, toggleTheme } = useTheme(); return ( -