Remove legacy configuration files and scripts, transitioning to Vite for build and development processes. Introduce new HTML files for the landing and terms pages, along with stubs for API responses. Update package.json and package-lock.json to include Vite and related dependencies, enhancing the project structure and build efficiency.

This commit is contained in:
Primakov Alexandr Alexandrovich 2025-10-24 14:35:30 +03:00
parent ebf0daacce
commit 7907238c1a
27 changed files with 1461 additions and 621 deletions

1
.npmrc
View File

@ -1 +0,0 @@
package-lock=true

View File

@ -1,7 +0,0 @@
# Ignore artifacts:
build
dist
coverage
stubs
logs
d-scripts

View File

@ -1,7 +0,0 @@
{
"tabWidth": 2,
"semi": true,
"jsxBracketSameLine": true,
"arrowParens": "avoid",
"singleQuote": true
}

10
@types/emotion.d.ts vendored
View File

@ -1,10 +0,0 @@
import '@emotion/react';
import { lightTheme } from '../src/app.theme';
declare module '@emotion/react' {
export interface Theme {
colors: typeof lightTheme.colors,
shadows: typeof lightTheme.shadows
}
}

19
@types/index.d.ts vendored
View File

@ -1,19 +0,0 @@
declare const IS_PROD: string;
declare module '*.svg' {
const value: string;
export default value;
}
declare module '*.jpg' {
const value: string;
export default value;
}
declare module '*.png' {
const value: string;
export default value;
}

31
Jenkinsfile vendored
View File

@ -1,31 +0,0 @@
pipeline {
agent {
docker {
image 'node:20'
}
}
stages {
stage('install') {
steps {
sh 'node -v'
sh 'npm -v'
sh 'npm ci'
}
}
stage('eslint') {
steps {
sh 'npm run eslint'
}
}
stage('clean-all') {
steps {
sh 'rm -rf .[!.]*'
sh 'rm -rf ./*'
sh 'ls -a'
}
}
}
}

82
VITE_BUILD_GUIDE.md Normal file
View File

@ -0,0 +1,82 @@
# 🚀 Production сборка с Vite
## ✅ Как работает
### Встроенные переменные Vite
Vite автоматически определяет режим сборки:
- **Dev**: `mode = 'development'`
- **Production**: `mode = 'production'`
В `vite.config.ts`:
```typescript
export default defineConfig(({ mode }) => {
const isProd = mode === 'production';
return {
base: isProd
? 'https://static.brojs.ru/landing/main/' // Production CDN
: '/', // Dev локально
// ...
}
});
```
## 🌐 Команды
### Dev режим
```bash
npm start # mode = 'development'
npm run dev # mode = 'development'
```
- Base: `/`
- URL: `http://localhost:8099/`
- Ассеты: локальные пути
### Production сборка
```bash
npm run build # mode = 'production'
```
- Base: `https://static.brojs.ru/landing/main/`
- Все пути автоматически заменяются на CDN!
## 📦 Результат production сборки
### index.html (главная)
```html
<script src="https://static.brojs.ru/landing/main/main.[hash].js"></script>
```
### terms.html (Terms)
```html
<link rel="stylesheet" href="https://static.brojs.ru/landing/main/terms.[hash].css">
```
## 🎯 Кроссплатформенность
**Windows**: работает
**Linux**: работает
**macOS**: работает
Vite использует встроенный параметр `mode`, который работает **везде без дополнительных пакетов**!
## 🔧 Альтернативный способ (если нужен кастомный env)
Если понадобится установить свои переменные:
### Установи cross-env
```bash
npm install --save-dev cross-env
```
### Обнови package.json
```json
{
"scripts": {
"build": "cross-env NODE_ENV=production vite build"
}
}
```
Но это **не нужно** для нашего случая! Vite сам всё делает правильно! ✨

View File

@ -1,6 +0,0 @@
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }],
'@babel/preset-typescript',
],
};

View File

@ -1,2 +0,0 @@
sh stop.sh;
sh up-nginx.sh;

View File

@ -1 +0,0 @@
docker stop adminka_nginx2;

View File

@ -1 +0,0 @@
docker run --name adminka_nginx2 -v $PWD/nginx.conf:/etc/nginx/nginx.conf:ro -v $PWD/dist:/usr/share/nginx/html --rm -d -p 3072:80 nginx;

View File

@ -1,13 +0,0 @@
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import pluginReact from "eslint-plugin-react";
export default [
{files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"]},
{languageOptions: { globals: globals.browser }},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
pluginReact.configs.flat.recommended,
];

View File

@ -1,88 +0,0 @@
/* eslint-disable @typescript-eslint/no-require-imports */
/* eslint-disable no-undef */
const path = require('path');
const pkg = require('./package');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const webpack = require('webpack');
const isProd = process.env.NODE_ENV === 'production';
module.exports = {
apiPath: 'stubs/api',
apps: {
main: {
version: 'master',
name: 'cleanName',
},
},
webpackConfig: {
entry: {
index: './src/index.tsx',
terms: './src/terms.js', // Entry для стилей terms
},
output: {
publicPath: isProd
? 'https://static.brojs.ru/landing/main/'
: `/static/${pkg.name}/${process.env.VERSION || pkg.version}/`,
filename: '[name].js?[contenthash]',
},
module: {
rules: [
{
test: /\.module\.scss$/,
use: [
isProd ? MiniCssExtractPlugin.loader : 'style-loader',
{
loader: 'css-loader',
options: {
modules: {
localIdentName: isProd ? '[hash:base64:8]' : '[name]__[local]--[hash:base64:5]',
},
},
},
'sass-loader'
],
},
{
test: /\.scss$/,
exclude: /\.module\.scss$/,
use: [
isProd ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
'sass-loader'
],
},
],
},
plugins: [
// Главная страница (с React)
new HtmlWebpackPlugin({
template: './src/index.ejs',
filename: 'index.html',
chunks: ['index'],
}),
// Terms страница (статика + SCSS)
new HtmlWebpackPlugin({
template: './src/terms.html',
filename: 'terms.html',
chunks: isProd ? [] : ['terms'], // В production не нужен JS
cssPath: isProd
? 'https://static.brojs.ru/landing/main/terms.css'
: `/static/${pkg.name}/${process.env.VERSION || pkg.version}/terms.css`,
}),
// Извлечение CSS в отдельные файлы для production
...(isProd ? [
new MiniCssExtractPlugin({
filename: '[name].css',
})
] : []),
new webpack.DefinePlugin({
IS_PROD: process.env.NODE_ENV === 'production',
}),
],
},
navigations: {},
features: {},
config: {},
};

View File

@ -3,17 +3,23 @@
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,600,700,900&amp;subset=cyrillic,cyrillic-ext" rel="stylesheet" />
<title>bro-js admin</title>
<style>body {margin: 0; padding: 0;}</style>
<title>BROJS.RU - Платформа обучения фронтенд-разработке</title>
<meta name="yandex-verification" content="98f7e15d1ad66018" />
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<noscript><div><img src="https://mc.yandex.ru/watch/87860751" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
<noscript>
<div><img src="https://mc.yandex.ru/watch/87860751" style="position:absolute; left:-9999px;" alt="" /></div>
</noscript>
<div id="app"></div>
<script src="https://static.brojs.ru/fire.app/1.8.4/index.js"></script>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>
</html>

View File

@ -1,9 +0,0 @@
module.exports = {
clearMocks: true,
collectCoverage: true,
collectCoverageFrom: ['./src/**/*.ts?(x)'],
coverageDirectory: 'coverage',
coverageProvider: 'v8',
preset: 'ts-jest',
testEnvironment: 'jsdom',
};

1122
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,18 +3,20 @@
"version": "2.0.1",
"description": "",
"main": "./src/index.tsx",
"scripts": {
"docker:rerun": "docker stop adminka_nginx2 && sh d-scripts/up-nginx.sh",
"predeploy": "npm i && npm run build:prod",
"redeploy": "npm run predeploy && npm run docker:rerun || sh d-scripts/up-nginx.sh",
"clean": "rimraf dist",
"eslint": "npx eslint src",
"prettier": "prettier --write .",
"test": "jest --coverage",
"start": "brojs server --port=8099 --with-open-browser",
"build": "npm run clean && brojs build --dev",
"build:prod": "npm run clean && brojs build"
},
"scripts": {
"docker:rerun": "docker stop adminka_nginx2 && sh d-scripts/up-nginx.sh",
"predeploy": "npm i && npm run build:prod",
"redeploy": "npm run predeploy && npm run docker:rerun || sh d-scripts/up-nginx.sh",
"clean": "rimraf dist",
"eslint": "npx eslint src",
"prettier": "prettier --write .",
"test": "jest --coverage",
"dev": "vite",
"start": "vite",
"build": "vite build",
"build:prod": "vite build --mode production",
"preview": "vite preview"
},
"keywords": [],
"author": "",
"license": "MIT",
@ -41,6 +43,7 @@
"devDependencies": {
"@eslint/js": "^9.9.0",
"@types/jest": "^29.5.12",
"@vitejs/plugin-react": "^5.1.0",
"babel-jest": "^29.7.0",
"css-loader": "^7.1.2",
"eslint": "^8.57.0",
@ -55,6 +58,8 @@
"sass-loader": "^16.0.6",
"style-loader": "^4.0.0",
"ts-jest": "^29.2.3",
"typescript-eslint": "^8.1.0"
"typescript-eslint": "^8.1.0",
"vite": "^7.1.12",
"vite-plugin-sass": "^0.1.0"
}
}

122
readme.md
View File

@ -2,14 +2,15 @@
Лендинг платформы для обучения фронтенд-разработке
## 🚀 Особенности v3.0
## 🚀 Особенности v3.0 (Vite)
- ⚡ **Минимальные зависимости** - только необходимое
- ⚡ **Vite** - мгновенный запуск (vs 3+ сек у webpack)
- 📄 **Статический HTML** для идеального SEO
- 🎨 **SCSS + CSS Modules** для гибких стилей
- 📱 **Адаптивный дизайн** с responsive шрифтами
- 🎯 **React** только там, где нужна динамика
- 🔥 **Легкий bundle** terms.html (10 KB + 1.5 KB CSS, **без JS!**)
- 🎯 **React** только для главной страницы
- 🔌 **Express stubs** из `./stubs/api` работают как раньше!
- 🗜️ **Легкий bundle** terms.html (13 KB + 1.5 KB CSS, **без JS!**)
## 📦 Установка
@ -20,13 +21,14 @@ npm install
## 🛠️ Разработка
```bash
npm start # Dev сервер на http://localhost:8099
npm start # Dev сервер на http://localhost:8099
```
## 🏗️ Сборка
```bash
npm run build:prod # Production сборка в ./dist
npm run build # Production сборка в ./dist
npm run preview # Просмотр production сборки
```
## 📄 Страницы
@ -34,20 +36,46 @@ npm run build:prod # Production сборка в ./dist
### Главная `/`
- **React** приложение с анимацией Lottie
- Динамическая страница "В разработке"
- Файлы: `index.html` + `index.js` (916 KB) + `index.css` (190 B)
- Dev: `http://localhost:8099/`
- Prod: `brojs.ru/` → загружает JS с `https://static.brojs.ru/landing/main/`
### Terms `/terms`
- `/terms` → автоматический редирект на `/terms.html`
- **Чистый HTML** без JavaScript
- Полный текст пользовательского соглашения
- SEO-оптимизирован
- Адаптивный дизайн
- Файлы: `terms.html` (10.5 KB) + `terms.css` (1.5 KB)
- Dev: `http://localhost:8099/terms`
- Prod: `brojs.ru/terms` → загружает CSS с `https://static.brojs.ru/landing/main/`
## 🔌 API Stubs
Создавай заглушки API в `./stubs/api/`:
```javascript
// stubs/api/test.js → /api/test
module.exports = (req, res) => {
res.json({ message: 'Hello from stub!' });
};
```
```javascript
// stubs/api/user.js → /api/user
module.exports.default = {
id: 1,
name: 'John Doe'
};
```
Примеры:
- `http://localhost:8099/api/test`
- `http://localhost:8099/api/user`
## 🎨 Стилизация
### CSS Modules (для React компонентов)
```typescript
import * as styles from './styles/main.module.scss';
import styles from './styles/main.module.scss';
element.className = styles.app;
```
@ -71,7 +99,7 @@ h1 {
## 📱 Адаптивность
Все размеры шрифтов автоматически уменьшаются на мобильных устройствах (< 768px):
Все размеры шрифтов автоматически уменьшаются на мобильных (< 768px):
- H1: 2.5rem → 1.75rem
- H2: 1.75rem → 1.5rem
- H3: 1.25rem → 1.1rem
@ -80,50 +108,78 @@ h1 {
## 🗂️ Структура
```
src/
├── styles/
│ ├── main.module.scss # CSS Modules для React
│ ├── main.module.scss.d.ts # TypeScript типы
│ └── terms.scss # SCSS для статики
├── pages/
│ └── under-construction/ # Главная страница
├── index.tsx # Entry для React
├── terms.js # Entry для CSS
├── terms.html # Статический HTML
└── index.ejs # Шаблон для React
bro.landing/
├── index.html # Entry для главной
├── terms.html # Entry для Terms (статика)
├── vite.config.ts # Конфигурация Vite
├── src/
│ ├── index.tsx # React entry
│ ├── app.tsx # React App
│ ├── dashboard.tsx # Роутинг
│ ├── pages/
│ │ └── under-construction/
│ └── styles/
│ ├── main.module.scss # CSS Modules для React
│ └── terms.scss # SCSS для Terms
├── stubs/
│ └── api/
│ ├── test.js # Пример: GET /api/test
│ └── user.js # Пример: GET /api/user
└── dist/ # Build output
├── index.html # Главная
├── main.[hash].js # React bundle
├── terms.html # Terms статика
└── terms.[hash].css # Terms стили
```
## 🌐 Деплой
После `npm run build:prod`:
- `index.html``brojs.ru/`
- `terms.html``brojs.ru/terms`
- Статика → `static.brojs.ru/landing/main/`
### Production URLs
После `npm run build` и деплоя:
- **Главная**: `brojs.ru/``dist/index.html`
- JS: `https://static.brojs.ru/landing/main/main.[hash].js`
- **Terms**: `brojs.ru/terms``dist/terms.html`
- CSS: `https://static.brojs.ru/landing/main/terms.[hash].css`
### Vite автоматически подставляет CDN пути
В `vite.config.ts`:
```typescript
base: isProd
? 'https://static.brojs.ru/landing/main/' // Production
: '/' // Dev
```
Vite **автоматически** заменит все пути к ассетам на CDN URL в production!
## 🔧 Технологии
### Core
- **Vite 7** - молниеносная сборка
- **React 18** - только для динамических частей
- **TypeScript** - типизация
- **Webpack 5** - сборка с multiple entry points
- **SCSS** - препроцессор
- **CSS Modules** - изоляция стилей
### Библиотеки
- **React Router DOM** - клиентский роутинг
- **i18next** - интернационализация
- **Lottie React** - анимации
### Dev зависимости
- **sass** + **sass-loader** - компиляция SCSS
- **css-loader** - обработка CSS (с поддержкой CSS Modules)
- **style-loader** - инжект CSS в dev режиме
- **mini-css-extract-plugin** - извлечение CSS в production
- **@vitejs/plugin-react** - React Fast Refresh
- **sass** - компиляция SCSS
## ⚡ Почему Vite?
- 🚀 **Мгновенный запуск** (HMR из коробки)
- ⚡ **Быстрая сборка** (esbuild + Rollup)
- 🎯 **Простая конфигурация** (70 строк vs 500+)
- 📦 **Меньше зависимостей**
- 🔥 **Современный стандарт**
## 📚 Документация
Полная документация в [cloud.md](./cloud.md)
## 🤝 Участие
---
Проект использует минималистичный подход - добавляем зависимости только по мере необходимости!
**Простота + Скорость + Мощь!** 🎉

View File

@ -1,12 +1,22 @@
import React from 'react';
import React, { useEffect } from 'react';
import { Routes, Route } from 'react-router-dom';
import { UnderConstructionPage } from './pages';
// Компонент для редиректа на terms.html
const TermsRedirect = () => {
useEffect(() => {
window.location.href = '/terms.html';
}, []);
return <div>Загрузка Terms...</div>;
};
export const Dashboard = () => {
return (
<Routes>
<Route path="/" element={<UnderConstructionPage />} />
<Route path="/terms" element={<TermsRedirect />} />
<Route path="*" element={<UnderConstructionPage />} />
</Routes>
);

View File

@ -1,40 +1,11 @@
import React from 'react';
import i18next from 'i18next';
import { i18nextReactInitConfig } from '@brojs/cli';
import { createRoot, hydrateRoot } from 'react-dom/client'
import { createRoot } from 'react-dom/client';
import App from './app';
import * as styles from './styles/main.module.scss';
import './styles/main.module.scss';
i18next.t = i18next.t.bind(i18next);
const i18nextPromise = i18nextReactInitConfig(i18next);
const MOUNT_NODE = document.getElementById('app');
// Применяем класс к app контейнеру
if (MOUNT_NODE) {
MOUNT_NODE.className = styles.app || 'app';
const root = createRoot(MOUNT_NODE);
root.render(<App />);
}
(async () => {
await Promise.all([i18nextPromise]);
// Если страница была пре-рендерена, используем hydrate вместо render
const hasPrerenderedContent = MOUNT_NODE.hasChildNodes();
if (hasPrerenderedContent) {
hydrateRoot(MOUNT_NODE, <App />);
} else {
const rootElement = createRoot(MOUNT_NODE);
rootElement.render(<App />);
if (module.hot) {
module.hot.accept('./app', async () => {
await i18next.reloadResources();
rootElement.render(<App />);
});
}
}
})();
export const mount = () => console.log('mounted');
export const unmount = () => console.log('unmounted');

View File

@ -1,291 +0,0 @@
import React from 'react';
import { Box, Container, Heading, Text, VStack, Divider, Link } from '@chakra-ui/react';
import { Helmet } from 'react-helmet';
export const Terms = () => {
return (
<>
<Helmet>
<title>Пользовательское соглашение - BROJS.RU</title>
<meta name="description" content="Пользовательское соглашение для платформы обучения фронтенд-разработке BROJS.RU" />
</Helmet>
<Box bg="gray.50" minH="100vh" py={8}>
<Container maxW="4xl" bg="white" shadow="lg" borderRadius="md" p={{ base: 6, md: 10 }}>
<VStack spacing={6} align="stretch">
{/* Заголовок */}
<Box textAlign="center" mb={4}>
<Heading as="h1" size="2xl" mb={2} color="blue.600">
Пользовательское соглашение
</Heading>
<Text fontSize="sm" color="gray.600">
для BROJS.RU
</Text>
<Text fontSize="sm" color="gray.500" mt={2}>
Последнее обновление: 25 мая 2025 г.
</Text>
</Box>
<Divider />
{/* 1. Термины */}
<Box>
<Heading as="h2" size="lg" mb={3} color="gray.800">
1. Термины
</Heading>
<VStack spacing={2} align="stretch">
<Text><strong>Платформа</strong> сайт <Link href="https://brojs.ru" color="blue.500" isExternal>https://brojs.ru</Link>, предоставляющий услуги обучения фронтенд-разработке.</Text>
<Text><strong>Пользователь</strong> лицо, зарегистрированное на Платформе.</Text>
<Text><strong>Микрофронтенд-проект</strong> код, конфигурации и иные материалы, созданные Пользователем.</Text>
<Text><strong>Gravatar</strong> сторонний сервис (<Link href="https://gravatar.com" color="blue.500" isExternal>https://gravatar.com</Link>), предоставляющий аватары на основе email-адресов пользователей.</Text>
<Text><strong>Интеллектуальная собственность</strong> результаты интеллектуальной деятельности, включая, но не ограничиваясь, программные коды, дизайны, тексты, графику и другие объекты, защищенные законом.</Text>
</VStack>
</Box>
{/* 2. Условия использования */}
<Box>
<Heading as="h2" size="lg" mb={3} color="gray.800">
2. Условия использования
</Heading>
<Heading as="h3" size="md" mb={2} color="gray.700">
2.1. Регистрация
</Heading>
<Text mb={2}>Регистрация осуществляется через:</Text>
<Box as="ul" pl={6} mb={3}>
<li>Аккаунт Yandex;</li>
<li>Email (с подтверждением через ссылку).</li>
</Box>
<Heading as="h3" size="md" mb={2} color="gray.700">
2.2. Обязанности Пользователя
</Heading>
<Text mb={2}>Пользователь обязуется:</Text>
<Box as="ul" pl={6}>
<li>Не передавать учетные данные третьим лицам;</li>
<li>Не использовать Платформу для распространения незаконного контента или совершения мошеннических действий;</li>
<li>Соблюдать конфиденциальность личных данных других участников Платформы.</li>
</Box>
</Box>
{/* 3. Персональные данные */}
<Box>
<Heading as="h2" size="lg" mb={3} color="gray.800">
3. Персональные данные
</Heading>
<Heading as="h3" size="md" mb={2} color="gray.700">
3.1. Собираемые данные
</Heading>
<Text mb={2}>Платформа собирает:</Text>
<Box as="ul" pl={6} mb={3}>
<li>Никнейм;</li>
<li>Email;</li>
<li>ФИО (при наличии договора с учебным заведением);</li>
<li>Данные о посещении занятий (через QR-код).</li>
</Box>
<Heading as="h3" size="md" mb={2} color="gray.700">
3.2. Аватар через Gravatar
</Heading>
<Box as="ul" pl={6} mb={3}>
<li>Платформа не хранит аватары на своих серверах. Для отображения используется Gravatar.</li>
<li>Ссылка на аватар формируется на основе хэша email пользователя.</li>
<li>Пользователь может активировать/отозвать согласие на использование Gravatar в настройках профиля.</li>
<li>Отказ от Gravatar приведет к отображению стандартного изображения.</li>
</Box>
<Heading as="h3" size="md" mb={2} color="gray.700">
3.3. Цели обработки данных
</Heading>
<Box as="ul" pl={6} mb={3}>
<li>Предоставление доступа к Платформе;</li>
<li>Передача данных о посещении учебным заведениям (ФИО, email, дата и время) в формате Excel.</li>
</Box>
<Heading as="h3" size="md" mb={2} color="gray.700">
3.4. Хранение и передача данных
</Heading>
<Box as="ul" pl={6} mb={3}>
<li>Персональные данные хранятся в СУБД PostgreSQL через Keycloak.</li>
<li>Данные о посещении передаются учебным заведениям на основании договоров с преподавателями.</li>
<li>Передача данных осуществляется с применением шифрования и протоколов безопасности.</li>
</Box>
<Heading as="h3" size="md" mb={2} color="gray.700">
3.5. Срок хранения
</Heading>
<Box as="ul" pl={6} mb={3}>
<li>Персональные данные удаляются в течение 30 дней после удаления аккаунта.</li>
<li>Микрофронтенд-проекты хранятся 6 месяцев после завершения обучения.</li>
</Box>
<Heading as="h3" size="md" mb={2} color="gray.700">
3.6. Отзыв согласия
</Heading>
<Box as="ul" pl={6}>
<li>Для отзыва согласия на обработку персональных данных необходимо направить письмо на <Link href="mailto:primakov.pro@yandex.ru" color="blue.500">primakov.pro@yandex.ru</Link>.</li>
<li>Отзыв приведет к удалению всех данных пользователя вручную.</li>
<li>Частичное удаление отдельных категорий данных возможно по заявлению пользователя.</li>
</Box>
</Box>
{/* 4. Интеллектуальная собственность */}
<Box>
<Heading as="h2" size="lg" mb={3} color="gray.800">
4. Интеллектуальная собственность
</Heading>
<Heading as="h3" size="md" mb={2} color="gray.700">
4.1. Права Пользователя
</Heading>
<Box as="ul" pl={6} mb={3}>
<li>Пользователь сохраняет авторские права на созданные проекты.</li>
<li>Платформа не имеет прав на использование материалов Пользователя без явного согласия.</li>
</Box>
<Heading as="h3" size="md" mb={2} color="gray.700">
4.2. Права Администрации
</Heading>
<Box as="ul" pl={6}>
<li>Администрация вправе удалить контент при нарушении условий соглашения или через 6 месяцев после завершения обучения.</li>
<li>Проверка подлинности загружаемого материала осуществляется преподавателем, отвечающим за группу.</li>
</Box>
</Box>
{/* 5. Ответственность */}
<Box>
<Heading as="h2" size="lg" mb={3} color="gray.800">
5. Ответственность
</Heading>
<Heading as="h3" size="md" mb={2} color="gray.700">
5.1. Ограничение ответственности
</Heading>
<Text mb={2}>Администрация не несет ответственности за:</Text>
<Box as="ul" pl={6} mb={3}>
<li>Утрату данных из-за действий Пользователя;</li>
<li>Использование данных учебными заведениями после их передачи;</li>
<li>Некорректное отображение аватаров через Gravatar.</li>
</Box>
<Heading as="h3" size="md" mb={2} color="gray.700">
5.2. Основания для блокировки аккаунта
</Heading>
<Box as="ul" pl={6} mb={3}>
<li>Нарушение авторских прав;</li>
<li>Распространение спама/вирусов;</li>
<li>Предоставление недостоверных данных (включая ФИО).</li>
</Box>
<Heading as="h3" size="md" mb={2} color="gray.700">
5.3. Компенсация ущерба
</Heading>
<Box as="ul" pl={6}>
<li>В случае нарушения правил или утечки данных, Администрация обязана принять меры для минимизации последствий.</li>
</Box>
</Box>
{/* 6. Уведомления */}
<Box>
<Heading as="h2" size="lg" mb={3} color="gray.800">
6. Уведомления
</Heading>
<Heading as="h3" size="md" mb={2} color="gray.700">
6.1. Информационные сообщения
</Heading>
<Text mb={2}>Платформа вправе отправлять Пользователю:</Text>
<Box as="ul" pl={6} mb={3}>
<li>Уведомления о технических работах, изменениях функционала;</li>
<li>Сообщения о нарушениях или блокировке аккаунта;</li>
<li>Рекламу собственных услуг или услуг третьих лиц.</li>
</Box>
<Heading as="h3" size="md" mb={2} color="gray.700">
6.2. Отказ от уведомлений
</Heading>
<Box as="ul" pl={6}>
<li>Отказ от рекламных сообщений возможен через настройки Личного кабинета.</li>
<li>Отказ от информационных уведомлений может ограничить доступ к функциям Платформы.</li>
</Box>
</Box>
{/* 7. Безопасность данных */}
<Box>
<Heading as="h2" size="lg" mb={3} color="gray.800">
7. Безопасность данных
</Heading>
<Heading as="h3" size="md" mb={2} color="gray.700">
7.1. Технические меры
</Heading>
<Box as="ul" pl={6} mb={3}>
<li>Данные хранятся в СУБД PostgreSQL через Keycloak.</li>
<li>Шифрование данных при передаче (HTTPS).</li>
<li>Периодические тестирования системы на уязвимости.</li>
</Box>
<Heading as="h3" size="md" mb={2} color="gray.700">
7.2. Двухфакторная аутентификация
</Heading>
<Box as="ul" pl={6}>
<li>Пользователи могут добровольно активировать двухфакторную аутентификацию (OTP) через Личный кабинет.</li>
</Box>
</Box>
{/* 8. Применимое право и разрешение споров */}
<Box>
<Heading as="h2" size="lg" mb={3} color="gray.800">
8. Применимое право и разрешение споров
</Heading>
<Heading as="h3" size="md" mb={2} color="gray.700">
8.1. Применимое право
</Heading>
<Box as="ul" pl={6} mb={3}>
<li>Соглашение регулируется законодательством РФ. Для пользователей из стран СНГ применяются нормы ЕАЭС.</li>
<li>При расширении географии услуг Платформа будет соблюдать законодательство стран ЕСВР и ЕС.</li>
</Box>
<Heading as="h3" size="md" mb={2} color="gray.700">
8.2. Разрешение споров
</Heading>
<Box as="ul" pl={6}>
<li>Споры разрешаются в суде по месту нахождения администрации Платформы.</li>
</Box>
</Box>
{/* 9. Изменения соглашения */}
<Box>
<Heading as="h2" size="lg" mb={3} color="gray.800">
9. Изменения соглашения
</Heading>
<Text>Изменения вступают в силу после публикации на сайте.</Text>
</Box>
{/* 10. Контакты */}
<Box>
<Heading as="h2" size="lg" mb={3} color="gray.800">
10. Контакты
</Heading>
<Text>
Для обращений: <Link href="mailto:primakov.pro@yandex.ru" color="blue.500">primakov.pro@yandex.ru</Link>
</Text>
</Box>
<Divider mt={6} />
{/* Footer */}
<Box textAlign="center" pt={4}>
<Text fontSize="sm" color="gray.500">
© 2025 BROJS.RU. Все права защищены.
</Text>
</Box>
</VStack>
</Container>
</Box>
</>
);
};

View File

@ -1,2 +0,0 @@
export { Terms } from './Terms';

View File

@ -1,3 +0,0 @@
// Entry point for terms page styles
import './styles/terms.scss';

11
stubs/api/test.js Normal file
View File

@ -0,0 +1,11 @@
// Пример stub файла для /api/test
module.exports = (req, res) => {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({
message: 'Hello from stub!',
timestamp: new Date().toISOString(),
method: req.method,
url: req.url,
}));
};

8
stubs/api/user.js Normal file
View File

@ -0,0 +1,8 @@
// Пример stub для /api/user
module.exports.default = {
id: 1,
name: 'Test User',
email: 'user@brojs.ru',
role: 'student'
};

View File

@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8">
@ -8,7 +8,7 @@
<meta name="description" content="Пользовательское соглашение для платформы обучения фронтенд-разработке BROJS.RU. Условия использования, обработка персональных данных, права и обязанности сторон.">
<meta name="yandex-verification" content="98f7e15d1ad66018">
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,600,700,900&subset=cyrillic,cyrillic-ext" rel="stylesheet">
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cssPath %>">
<link rel="stylesheet" href="/src/styles/terms.scss">
</head>
<body>
<noscript><div><img src="https://mc.yandex.ru/watch/87860751" style="position:absolute; left:-9999px;" alt=""></div></noscript>
@ -18,24 +18,25 @@
<h1>Пользовательское соглашение</h1>
<p class="subtitle">для BROJS.RU</p>
<p class="date">Последнее обновление: 25 мая 2025 г.</p>
<hr>
<h2>1. Термины</h2>
<p><strong>Платформа</strong> — сайт <a href="https://brojs.ru" target="_blank">https://brojs.ru</a>, предоставляющий услуги обучения фронтенд-разработке.</p>
<p><strong>Пользователь</strong> — лицо, зарегистрированное на Платформе.</p>
<p><strong>Микрофронтенд-проект</strong> — код, конфигурации и иные материалы, созданные Пользователем.</p>
<p><strong>Gravatar</strong> — сторонний сервис (<a href="https://gravatar.com" target="_blank">https://gravatar.com</a>), предоставляющий аватары на основе email-адресов пользователей.</p>
<p><strong>Интеллектуальная собственность</strong> — результаты интеллектуальной деятельности, включая, но не ограничиваясь, программные коды, дизайны, тексты, графику и другие объекты, защищенные законом.</p>
<h2>2. Условия использования</h2>
<h3>2.1. Регистрация</h3>
<p>Регистрация осуществляется через:</p>
<ul>
<li>Аккаунт Yandex;</li>
<li>Email (с подтверждением через ссылку).</li>
</ul>
<h3>2.2. Обязанности Пользователя</h3>
<p>Пользователь обязуется:</p>
<ul>
@ -43,8 +44,9 @@
<li>Не использовать Платформу для распространения незаконного контента или совершения мошеннических действий;</li>
<li>Соблюдать конфиденциальность личных данных других участников Платформы.</li>
</ul>
<h2>3. Персональные данные</h2>
<h3>3.1. Собираемые данные</h3>
<p>Платформа собирает:</p>
<ul>
@ -53,7 +55,7 @@
<li>ФИО (при наличии договора с учебным заведением);</li>
<li>Данные о посещении занятий (через QR-код).</li>
</ul>
<h3>3.2. Аватар через Gravatar</h3>
<ul>
<li>Платформа не хранит аватары на своих серверах. Для отображения используется Gravatar.</li>
@ -61,47 +63,49 @@
<li>Пользователь может активировать/отозвать согласие на использование Gravatar в настройках профиля.</li>
<li>Отказ от Gravatar приведет к отображению стандартного изображения.</li>
</ul>
<h3>3.3. Цели обработки данных</h3>
<ul>
<li>Предоставление доступа к Платформе;</li>
<li>Передача данных о посещении учебным заведениям (ФИО, email, дата и время) в формате Excel.</li>
</ul>
<h3>3.4. Хранение и передача данных</h3>
<ul>
<li>Персональные данные хранятся в СУБД PostgreSQL через Keycloak.</li>
<li>Данные о посещении передаются учебным заведениям на основании договоров с преподавателями.</li>
<li>Передача данных осуществляется с применением шифрования и протоколов безопасности.</li>
</ul>
<h3>3.5. Срок хранения</h3>
<ul>
<li>Персональные данные удаляются в течение 30 дней после удаления аккаунта.</li>
<li>Микрофронтенд-проекты хранятся 6 месяцев после завершения обучения.</li>
</ul>
<h3>3.6. Отзыв согласия</h3>
<ul>
<li>Для отзыва согласия на обработку персональных данных необходимо направить письмо на <a href="mailto:primakov.pro@yandex.ru">primakov.pro@yandex.ru</a>.</li>
<li>Отзыв приведет к удалению всех данных пользователя вручную.</li>
<li>Частичное удаление отдельных категорий данных возможно по заявлению пользователя.</li>
</ul>
<h2>4. Интеллектуальная собственность</h2>
<h3>4.1. Права Пользователя</h3>
<ul>
<li>Пользователь сохраняет авторские права на созданные проекты.</li>
<li>Платформа не имеет прав на использование материалов Пользователя без явного согласия.</li>
</ul>
<h3>4.2. Права Администрации</h3>
<ul>
<li>Администрация вправе удалить контент при нарушении условий соглашения или через 6 месяцев после завершения обучения.</li>
<li>Проверка подлинности загружаемого материала осуществляется преподавателем, отвечающим за группу.</li>
</ul>
<h2>5. Ответственность</h2>
<h3>5.1. Ограничение ответственности</h3>
<p>Администрация не несет ответственности за:</p>
<ul>
@ -109,20 +113,21 @@
<li>Использование данных учебными заведениями после их передачи;</li>
<li>Некорректное отображение аватаров через Gravatar.</li>
</ul>
<h3>5.2. Основания для блокировки аккаунта</h3>
<ul>
<li>Нарушение авторских прав;</li>
<li>Распространение спама/вирусов;</li>
<li>Предоставление недостоверных данных (включая ФИО).</li>
</ul>
<h3>5.3. Компенсация ущерба</h3>
<ul>
<li>В случае нарушения правил или утечки данных, Администрация обязана принять меры для минимизации последствий.</li>
</ul>
<h2>6. Уведомления</h2>
<h3>6.1. Информационные сообщения</h3>
<p>Платформа вправе отправлять Пользователю:</p>
<ul>
@ -130,51 +135,51 @@
<li>Сообщения о нарушениях или блокировке аккаунта;</li>
<li>Рекламу собственных услуг или услуг третьих лиц.</li>
</ul>
<h3>6.2. Отказ от уведомлений</h3>
<ul>
<li>Отказ от рекламных сообщений возможен через настройки Личного кабинета.</li>
<li>Отказ от информационных уведомлений может ограничить доступ к функциям Платформы.</li>
</ul>
<h2>7. Безопасность данных</h2>
<h3>7.1. Технические меры</h3>
<ul>
<li>Данные хранятся в СУБД PostgreSQL через Keycloak.</li>
<li>Шифрование данных при передаче (HTTPS).</li>
<li>Периодические тестирования системы на уязвимости.</li>
</ul>
<h3>7.2. Двухфакторная аутентификация</h3>
<ul>
<li>Пользователи могут добровольно активировать двухфакторную аутентификацию (OTP) через Личный кабинет.</li>
</ul>
<h2>8. Применимое право и разрешение споров</h2>
<h3>8.1. Применимое право</h3>
<ul>
<li>Соглашение регулируется законодательством РФ. Для пользователей из стран СНГ применяются нормы ЕАЭС.</li>
<li>При расширении географии услуг Платформа будет соблюдать законодательство стран ЕСВР и ЕС.</li>
</ul>
<h3>8.2. Разрешение споров</h3>
<ul>
<li>Споры разрешаются в суде по месту нахождения администрации Платформы.</li>
</ul>
<h2>9. Изменения соглашения</h2>
<p>Изменения вступают в силу после публикации на сайте.</p>
<h2>10. Контакты</h2>
<p>Для обращений: <a href="mailto:primakov.pro@yandex.ru">primakov.pro@yandex.ru</a></p>
<div class="footer">
<p>© 2025 BROJS.RU. Все права защищены.</p>
</div>
</div>
</div>
<script src="https://static.brojs.ru/fire.app/1.8.4/index.js"></script>
</body>
</html>

85
vite.config.ts Normal file
View File

@ -0,0 +1,85 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
import fs from 'fs';
// Express middleware для stubs
const stubsMiddleware = () => ({
name: 'stubs-middleware',
configureServer(server: any) {
const stubsPath = path.resolve(__dirname, 'stubs/api');
server.middlewares.use('/api', (req: any, res: any, next: any) => {
// Получаем путь запроса без /api
const apiPath = req.url.replace(/\?.*$/, ''); // убираем query params
const stubFile = path.join(stubsPath, `${apiPath}.js`);
// Проверяем существует ли stub файл
if (fs.existsSync(stubFile)) {
try {
// Очищаем кеш модуля для hot reload
delete require.cache[require.resolve(stubFile)];
const stub = require(stubFile);
// Если это функция, вызываем её
if (typeof stub === 'function') {
stub(req, res, next);
} else if (stub.default && typeof stub.default === 'function') {
stub.default(req, res, next);
} else {
// Если это просто объект, отдаём как JSON
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(stub.default || stub));
}
} catch (error) {
console.error(`Error loading stub ${stubFile}:`, error);
res.statusCode = 500;
res.end(JSON.stringify({ error: 'Internal Server Error' }));
}
} else {
next();
}
});
}
});
export default defineConfig(({ mode }) => {
const isProd = mode === 'production';
return {
plugins: [
react(),
stubsMiddleware()
],
base: isProd ? 'https://static.brojs.ru/landing/main/' : '/',
server: {
port: 8099,
open: '/',
},
build: {
outDir: 'dist',
assetsDir: '.', // Все ассеты в корень dist
rollupOptions: {
input: {
main: path.resolve(__dirname, 'index.html'),
terms: path.resolve(__dirname, 'terms.html'),
},
output: {
entryFileNames: '[name].[hash].js',
chunkFileNames: '[name].[hash].js',
assetFileNames: '[name].[hash].[ext]'
}
},
},
css: {
modules: {
localsConvention: 'camelCase',
},
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
}}); // Двойная скобка для закрытия return и defineConfig