Refactor webpack configuration to include multiple entry points for index and terms pages. Remove SSR script and update package dependencies for improved build process. Add terms.html as a static page with appropriate styles and metadata.
All checks were successful
platform/bro-js/bro.landing/pipeline/head This commit looks good

This commit is contained in:
Primakov Alexandr Alexandrovich 2025-10-24 13:23:31 +03:00
parent 3b997a18e2
commit 6e55a331cb
8 changed files with 324 additions and 2397 deletions

View File

@ -1,5 +1,6 @@
/* 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 webpack = require('webpack');
@ -13,15 +14,31 @@ module.exports = {
version: 'master',
name: 'cleanName',
},
},
},
webpackConfig: {
entry: {
index: './src/index.tsx',
// terms страница не нужен JS, только HTML
},
output: {
publicPath: isProd
? 'https://static.brojs.ru/landing/main/'
: `/static/${pkg.name}/${process.env.VERSION || pkg.version}/`,
filename: '[name].js?[contenthash]',
},
plugins: [
new HtmlWebpackPlugin({}),
// Главная страница (с React)
new HtmlWebpackPlugin({
template: './src/index.ejs',
filename: 'index.html',
chunks: ['index'],
}),
// Terms страница (чистый HTML без JS)
new HtmlWebpackPlugin({
template: './src/terms.html',
filename: 'terms.html',
inject: false, // Не инжектим JS
}),
new webpack.DefinePlugin({
IS_PROD: process.env.NODE_ENV === 'production',
}),

2260
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@
"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 && node scripts/ssr-render.js"
"build:prod": "npm run clean && brojs build"
},
"keywords": [],
"author": "",
@ -22,10 +22,7 @@
"@babel/preset-typescript": "7.24.7",
"@brojs/cli": "1.9.5",
"@chakra-ui/icons": "^2.1.1",
"@chakra-ui/react": "^2.8.2",
"@emotion/css": "^11.13.0",
"@emotion/react": "^11.13.0",
"@emotion/styled": "^11.13.0",
"@reduxjs/toolkit": "^2.2.6",
"dayjs": "^1.11.12",
"express": "^4.19.2",
@ -36,28 +33,19 @@
"prettier": "^3.3.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-helmet": "^6.1.0",
"react-i18next": "^15.0.0",
"react-redux": "^9.1.2",
"react-router-dom": "^6.25.1",
"redux": "^5.0.1"
},
"devDependencies": {
"@babel/core": "^7.28.5",
"@babel/preset-env": "^7.28.5",
"@babel/preset-react": "^7.28.5",
"@babel/register": "^7.28.3",
"@emotion/cache": "^11.14.0",
"@emotion/server": "^11.11.0",
"@eslint/js": "^9.9.0",
"@types/jest": "^29.5.12",
"babel-jest": "^29.7.0",
"canvas": "^3.2.0",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^4.6.2",
"globals": "^15.9.0",
"happy-dom": "^15.11.7",
"html-webpack-plugin": "^5.6.0",
"jest": "^29.7.0",
"puppeteer": "^24.26.1",

View File

@ -1,140 +0,0 @@
/* eslint-disable @typescript-eslint/no-require-imports */
/* eslint-disable no-undef */
// Настройка Babel для транспиляции TSX/JSX в Node.js
require('@babel/register')({
extensions: ['.ts', '.tsx', '.js', '.jsx'],
presets: [
'@babel/preset-env',
'@babel/preset-react',
'@babel/preset-typescript'
],
ignore: [/node_modules/],
cache: false
});
const fs = require('fs');
const path = require('path');
const React = require('react');
const { renderToString } = require('react-dom/server');
const { Window } = require('happy-dom');
const { createCanvas } = require('canvas');
// Читаем index.ejs как основу для SSR
const ejsTemplatePath = path.resolve(__dirname, '../src/index.ejs');
const ejsTemplate = fs.readFileSync(ejsTemplatePath, 'utf-8');
// Настройка полноценного DOM окружения через happy-dom на основе index.ejs
const window = new Window({
url: 'http://localhost',
width: 1024,
height: 768
});
const document = window.document;
document.write(ejsTemplate);
// Расширяем happy-dom canvas поддержкой
window.HTMLCanvasElement.prototype.getContext = function() {
return createCanvas(200, 200).getContext('2d');
};
global.window = window;
global.document = document;
global.navigator = window.navigator;
global.HTMLElement = window.HTMLElement;
global.SVGElement = window.SVGElement;
console.log('🚀 Запуск SSR с рендерингом React компонентов...');
try {
// Импортируем компоненты
const { UnderConstruction } = require('../src/pages/under-construction/underConstruction.tsx');
const { Terms } = require('../src/pages/terms/Terms.tsx');
const { ChakraProvider, extendTheme } = require('@chakra-ui/react');
console.log('✅ Компоненты загружены');
// Функция для рендера с Chakra UI + базовыми стилями
function renderWithStyles(Component) {
// Chakra theme с базовыми стилями
const theme = extendTheme({});
// Генерируем базовые CSS переменные и стили Chakra
const baseStyles = `
<style data-emotion="chakra-global">
:root,:host{--chakra-vh:100vh}@supports(height:100dvh){:root,:host{--chakra-vh:100dvh}}
:root{color-scheme:light;--chakra-colors-chakra-body-text:#1A202C;--chakra-colors-chakra-body-bg:#fff;
--chakra-colors-chakra-border-color:#E2E8F0;--chakra-colors-chakra-placeholder-color:#A0AEC0}
body{background:var(--chakra-colors-chakra-body-bg);color:var(--chakra-colors-chakra-body-text);
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;line-height:1.5}
.chakra-container{width:100%;max-width:1200px;margin-left:auto;margin-right:auto;padding-left:16px;padding-right:16px}
.chakra-stack>*:not(style)~*:not(style){margin-top:1.5rem;margin-inline-start:0}
.chakra-heading{font-weight:700;font-size:1.875rem;line-height:2.25rem;margin-bottom:0.5rem}
.chakra-text{margin:0;margin-bottom:0.5rem}
.chakra-link{color:#3182ce;text-decoration:none}
.chakra-link:hover{text-decoration:underline}
.chakra-divider{border-color:var(--chakra-colors-chakra-border-color);border-style:solid;
border-width:0;border-bottom-width:1px;margin-top:1rem;margin-bottom:1rem}
ul,ol{margin-left:1.5rem;margin-bottom:1rem}
li{margin-bottom:0.5rem}
hr{border-top-width:1px;border-color:#E2E8F0}
</style>`;
const html = renderToString(
React.createElement(
ChakraProvider,
{ theme, cssVarsRoot: 'body' },
React.createElement(Component)
)
);
return { html, styles: baseStyles };
}
// Рендерим компоненты с извлечением стилей
const { html: homeContent, styles: homeStyles } = renderWithStyles(UnderConstruction);
const { html: termsContent, styles: termsStyles } = renderWithStyles(Terms);
console.log('✅ Компоненты отрендерены с Chakra UI + Emotion стилями');
// Читаем dist/index.html
const distPath = path.resolve(__dirname, '../dist');
const indexPath = path.join(distPath, 'index.html');
let indexHtml = fs.readFileSync(indexPath, 'utf-8');
// 1. Главная страница
const searchString = '<div id="app"></div>';
if (indexHtml.includes(searchString)) {
indexHtml = indexHtml
.replace(searchString, `<div id="app">${homeContent}</div>`)
.replace('</head>', `${homeStyles}</head>`);
fs.writeFileSync(indexPath, indexHtml, 'utf-8');
console.log('✅ index.html обновлен с SSR контентом и стилями');
}
// 2. Страница terms
let termsHtml = indexHtml
.replace(homeContent, termsContent)
.replace(homeStyles, termsStyles)
.replace('<title>bro-js admin</title>', '<title>Пользовательское соглашение - BROJS.RU</title>')
.replace(
'</head>',
'<meta name="description" content="Пользовательское соглашение для платформы обучения фронтенд-разработке BROJS.RU. Условия использования, обработка персональных данных, права и обязанности сторон." /></head>'
);
const termsPath = path.join(distPath, 'terms.html');
fs.writeFileSync(termsPath, termsHtml, 'utf-8');
console.log('✅ terms.html создан с SSR контентом и стилями');
console.log('🎉 SSR завершен успешно!');
console.log('📄 Созданы: index.html, terms.html');
console.log('💡 Весь контент отрендерен через React SSR');
console.log('🎨 Критические стили Emotion встроены в HTML');
} catch (error) {
console.error('❌ Ошибка при SSR:', error.message);
console.error(error.stack);
process.exit(1);
}

View File

@ -1,19 +1,13 @@
import React from "react";
import { BrowserRouter } from "react-router-dom";
import {Helmet} from 'react-helmet';
import { Dashboard } from './dashboard';
const App = () => {
return (
<>
<Helmet>
<title>bro js</title>
</Helmet>
<BrowserRouter>
<Dashboard />
</BrowserRouter>
</>
<BrowserRouter>
<Dashboard />
</BrowserRouter>
);
};

View File

@ -1,14 +1,13 @@
import React from 'react';
import { Routes, Route } from 'react-router-dom';
import { UnderConstructionPage, TermsPage } from './pages';
import { UnderConstructionPage } from './pages';
export const Dashboard = () => {
return (
<Routes>
<Route path="/terms" element={<TermsPage />} />
<Route path="/" element={<UnderConstructionPage />} />
<Route path="*" element={<h1>Страница не найдена</h1>} />
<Route path="*" element={<UnderConstructionPage />} />
</Routes>
);
};

View File

@ -1,2 +1 @@
export { default as UnderConstructionPage } from './under-construction';
export { Terms as TermsPage } from './terms';

268
src/terms.html Normal file
View File

@ -0,0 +1,268 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Пользовательское соглашение - BROJS.RU</title>
<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">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
line-height: 1.6;
color: #1a202c;
background: #f7fafc;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 40px 20px;
}
.terms-doc {
background: white;
padding: 60px 80px;
border-radius: 8px;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}
@media (max-width: 768px) {
.terms-doc { padding: 40px 24px; }
}
h1 {
font-size: 2.5rem;
font-weight: 700;
color: #2d3748;
text-align: center;
margin-bottom: 0.5rem;
}
.subtitle {
text-align: center;
color: #718096;
font-size: 1.1rem;
margin-bottom: 1rem;
}
.date {
text-align: center;
color: #a0aec0;
font-size: 0.9rem;
margin-bottom: 2rem;
}
hr {
border: none;
border-top: 1px solid #e2e8f0;
margin: 2rem 0;
}
h2 {
font-size: 1.75rem;
font-weight: 700;
color: #2d3748;
margin: 2rem 0 1rem;
}
h3 {
font-size: 1.25rem;
font-weight: 600;
color: #4a5568;
margin: 1.5rem 0 0.75rem;
}
p {
margin-bottom: 1rem;
color: #4a5568;
}
ul {
margin: 1rem 0 1rem 2rem;
color: #4a5568;
}
li {
margin-bottom: 0.5rem;
}
a {
color: #3182ce;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
strong {
font-weight: 600;
color: #2d3748;
}
.footer {
text-align: center;
margin-top: 3rem;
padding-top: 2rem;
border-top: 1px solid #e2e8f0;
color: #a0aec0;
font-size: 0.9rem;
}
</style>
</head>
<body>
<noscript><div><img src="https://mc.yandex.ru/watch/87860751" style="position:absolute; left:-9999px;" alt=""></div></noscript>
<div class="container">
<div class="terms-doc">
<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>
<li>Не передавать учетные данные третьим лицам;</li>
<li>Не использовать Платформу для распространения незаконного контента или совершения мошеннических действий;</li>
<li>Соблюдать конфиденциальность личных данных других участников Платформы.</li>
</ul>
<h2>3. Персональные данные</h2>
<h3>3.1. Собираемые данные</h3>
<p>Платформа собирает:</p>
<ul>
<li>Никнейм;</li>
<li>Email;</li>
<li>ФИО (при наличии договора с учебным заведением);</li>
<li>Данные о посещении занятий (через QR-код).</li>
</ul>
<h3>3.2. Аватар через Gravatar</h3>
<ul>
<li>Платформа не хранит аватары на своих серверах. Для отображения используется Gravatar.</li>
<li>Ссылка на аватар формируется на основе хэша email пользователя.</li>
<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>
<li>Утрату данных из-за действий Пользователя;</li>
<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>
<li>Уведомления о технических работах, изменениях функционала;</li>
<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>