Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d01a65859 | |||
| 0dbbc7487e | |||
| f8730d9d09 |
@@ -6,7 +6,7 @@ export const publicRouter = Router();
|
||||
publicRouter.get('/content', async (_req, res) => {
|
||||
try {
|
||||
const [sections, products, approach, settings] = await Promise.all([
|
||||
query<{ key: string; title: string; content_json: unknown; sort_order: number }>(
|
||||
query<{ key: string; title: string; content_json: unknown; content_json_en: unknown; sort_order: number }>(
|
||||
`SELECT key, title, content_json, content_json_en, sort_order
|
||||
FROM sections
|
||||
WHERE is_active = true
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
-- Английские колонки для переключателя языка.
|
||||
--
|
||||
-- Колонки уже существуют на боевой базе — их когда-то добавили вручную, минуя
|
||||
-- миграции. Найдено 25.08.2026 при слиянии веток: код их читает, а ни один
|
||||
-- файл в db/ не создаёт, то есть на любой ЧИСТОЙ базе (новый стенд, локальная
|
||||
-- разработка, восстановление из пустого) приложение падало бы на первом же
|
||||
-- запросе /api/content.
|
||||
--
|
||||
-- IF NOT EXISTS делает миграцию безопасной для боевой базы: там она ничего не
|
||||
-- изменит, только зафиксирует факт в _migrations.
|
||||
|
||||
ALTER TABLE sections ADD COLUMN IF NOT EXISTS content_json_en JSONB;
|
||||
|
||||
ALTER TABLE products ADD COLUMN IF NOT EXISTS title_en VARCHAR(256);
|
||||
ALTER TABLE products ADD COLUMN IF NOT EXISTS subtitle_en VARCHAR(512);
|
||||
ALTER TABLE products ADD COLUMN IF NOT EXISTS description_en TEXT;
|
||||
@@ -17,5 +17,6 @@
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
<script src="/cookies.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Уведомление о файлах cookie — Умный Байт</title>
|
||||
<meta name="robots" content="index, follow">
|
||||
<style>
|
||||
:root { --ink:#0f172a; --muted:#475569; --line:#e2e8f0; }
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;background:#f8fafc;color:var(--ink);
|
||||
font-family:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
|
||||
line-height:1.6;padding:32px 20px}
|
||||
.wrap{max-width:720px;margin:0 auto;background:#fff;border:1px solid var(--line);
|
||||
border-radius:16px;padding:36px}
|
||||
h1{font-size:1.6rem;margin:0 0 6px;letter-spacing:-.02em}
|
||||
.sub{color:var(--muted);font-size:14px;margin:0 0 28px}
|
||||
h2{font-size:1.05rem;margin:28px 0 8px}
|
||||
p,li{color:var(--muted);font-size:15px}
|
||||
table{width:100%;border-collapse:collapse;margin-top:10px;font-size:14px}
|
||||
th,td{text-align:left;padding:9px 10px;border-bottom:1px solid var(--line);vertical-align:top}
|
||||
th{color:var(--ink);font-weight:600}
|
||||
.back{display:inline-block;margin-top:28px;font-size:14px;color:var(--ink)}
|
||||
.req{margin-top:28px;padding-top:20px;border-top:1px solid var(--line);font-size:13px;color:var(--muted)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
|
||||
<h1>Уведомление о файлах cookie</h1>
|
||||
<p class="sub">Сайт umbyte.ru. Обновлено 25 августа 2026 года.</p>
|
||||
|
||||
<p>Куки — небольшие файлы, которые сайт сохраняет в браузере. Ниже честно
|
||||
описано, какие из них использует этот сайт и зачем.</p>
|
||||
|
||||
<h2>Что мы используем</h2>
|
||||
<p>Только технические куки. На сайте <strong>нет систем веб-аналитики</strong>
|
||||
(ни Яндекс Метрики, ни Google Analytics), <strong>нет рекламных пикселей</strong>
|
||||
и виджетов сторонних сервисов, которые следили бы за посетителями.</p>
|
||||
|
||||
<table>
|
||||
<tr><th>Назначение</th><th>Когда появляется</th><th>Срок</th></tr>
|
||||
<tr>
|
||||
<td>Сессия входа в панель управления сайтом</td>
|
||||
<td>Только у сотрудников после входа в закрытый раздел</td>
|
||||
<td>До выхода или окончания срока сессии</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Запоминание выбора в уведомлении о куки</td>
|
||||
<td>После нажатия кнопки в баннере</td>
|
||||
<td>180 дней</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Персональные данные</h2>
|
||||
<p>Через сайт мы не собираем персональные данные посетителей: форм обратной
|
||||
связи и подписки здесь нет. Кнопка связи открывает ваш почтовый клиент —
|
||||
письмо уходит напрямую, минуя наш сервер. Поэтому в отношении посетителей
|
||||
сайта мы не выступаем оператором персональных данных.</p>
|
||||
|
||||
<h2>Как отказаться</h2>
|
||||
<p>Нажмите «Отклонить» в баннере. Кроме того, куки можно удалить или запретить
|
||||
в настройках браузера — в разделе конфиденциальности. Учтите, что при полном
|
||||
запрете технических куки вход в закрытый раздел работать не будет.</p>
|
||||
|
||||
<h2>Изменения</h2>
|
||||
<p>Если на сайте появятся аналитика или формы сбора данных, это уведомление
|
||||
будет дополнено, а в баннере появится выбор категорий куки.</p>
|
||||
|
||||
<div class="req">
|
||||
ООО «Умный Байт», ИНН 3528347856, КПП 352801001, ОГРН 1263500003667.<br>
|
||||
162620, Вологодская область, город Череповец, улица Центральная, дом 12.<br>
|
||||
Регистрационный номер в реестре операторов персональных данных: 35-26-021324
|
||||
(приказ Роскомнадзора № 127 от 07.08.2026).<br>
|
||||
Вопросы по обработке данных: <a href="mailto:privacy@umbyte.ru">privacy@umbyte.ru</a>
|
||||
</div>
|
||||
|
||||
<a class="back" href="/">← На главную</a>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,111 @@
|
||||
/* Куки-баннер сайта «Умный Байт».
|
||||
*
|
||||
* Сделан по образцу баннера АгроТО (там он живёт в public/landing/cookies.js),
|
||||
* но без выбора категорий: на umbyte.ru нет ни аналитики, ни рекламных
|
||||
* пикселей, ни форм — только техническая сессия админки. Городить окно
|
||||
* настроек с переключателями, которые ничего не переключают, честнее не
|
||||
* делать: пользователю показывают выбор, которого нет.
|
||||
*
|
||||
* Если на сайте появится Метрика или форма обратной связи — берём полный
|
||||
* вариант с категориями из АгроТО, механика хранения совпадает.
|
||||
*
|
||||
* Хранение: localStorage["umbyte_cookie_consent_v1"] = {accepted, ts}
|
||||
* Программно: window.umbyteCookies.show() / .get() / .reset()
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var STORAGE_KEY = 'umbyte_cookie_consent_v1';
|
||||
var TTL_DAYS = 180;
|
||||
|
||||
function read() {
|
||||
try {
|
||||
var raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (!raw) return null;
|
||||
var p = JSON.parse(raw);
|
||||
if (!p || !p.ts) return null;
|
||||
if ((Date.now() - new Date(p.ts).getTime()) / 86400000 > TTL_DAYS) return null;
|
||||
return p;
|
||||
} catch (e) { return null; }
|
||||
}
|
||||
|
||||
function write(accepted) {
|
||||
var data = { accepted: !!accepted, ts: new Date().toISOString() };
|
||||
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); } catch (e) {}
|
||||
return data;
|
||||
}
|
||||
|
||||
var css = [
|
||||
'.umb-cc{position:fixed;inset:auto 0 0 0;z-index:9999;display:flex;justify-content:center;padding:16px;pointer-events:none;font-family:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}',
|
||||
'.umb-cc-bar{pointer-events:auto;background:#fff;border:1px solid #e2e8f0;border-radius:14px;box-shadow:0 12px 40px rgba(15,23,42,.12);padding:16px 20px;max-width:860px;width:100%;display:flex;gap:16px;align-items:center;animation:umbUp .25s ease-out}',
|
||||
'@keyframes umbUp{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}',
|
||||
'.umb-cc-text{flex:1;margin:0;font-size:13.5px;line-height:1.5;color:#475569}',
|
||||
'.umb-cc-text a{color:#0f172a;text-decoration:underline;text-underline-offset:2px}',
|
||||
'.umb-cc-actions{display:flex;gap:8px;flex-shrink:0}',
|
||||
'.umb-cc-btn{font-family:inherit;font-size:13px;font-weight:500;padding:9px 18px;border-radius:9px;border:1.5px solid #e2e8f0;background:#fff;color:#475569;cursor:pointer;transition:all .15s;white-space:nowrap}',
|
||||
'.umb-cc-btn:hover{border-color:#94a3b8;color:#0f172a}',
|
||||
'.umb-cc-btn-primary{background:#0f172a;color:#fff;border-color:#0f172a}',
|
||||
'.umb-cc-btn-primary:hover{background:#1e293b;border-color:#1e293b;color:#fff}',
|
||||
/* На телефоне: узкие отступы, мелкий шрифт, кнопки в один ряд с переносом
|
||||
текста внутри — иначе длинная подпись обрезается многоточием. */
|
||||
'@media(max-width:640px){',
|
||||
'.umb-cc{padding:8px}',
|
||||
'.umb-cc-bar{flex-direction:column;align-items:stretch;gap:10px;padding:12px 14px;border-radius:12px}',
|
||||
'.umb-cc-text{font-size:12.5px;line-height:1.4}',
|
||||
'.umb-cc-actions{width:100%;gap:6px}',
|
||||
'.umb-cc-btn{flex:1;padding:8px 6px;font-size:11.5px;white-space:normal;line-height:1.2;display:flex;align-items:center;justify-content:center}',
|
||||
'}'
|
||||
].join('');
|
||||
|
||||
function injectCss() {
|
||||
if (document.getElementById('umb-cc-style')) return;
|
||||
var s = document.createElement('style');
|
||||
s.id = 'umb-cc-style';
|
||||
s.textContent = css;
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
|
||||
function show() {
|
||||
if (document.getElementById('umb-cc-bar')) return;
|
||||
injectCss();
|
||||
var el = document.createElement('div');
|
||||
el.id = 'umb-cc-bar';
|
||||
el.className = 'umb-cc';
|
||||
el.innerHTML = ''
|
||||
+ '<div class="umb-cc-bar" role="dialog" aria-label="Файлы cookie">'
|
||||
+ '<p class="umb-cc-text">Мы используем только технические куки — они нужны, чтобы сайт работал. '
|
||||
+ 'Аналитики и рекламных сервисов на сайте нет. Подробнее в <a href="/cookies.html">уведомлении о куки</a>.</p>'
|
||||
+ '<div class="umb-cc-actions">'
|
||||
+ '<button type="button" class="umb-cc-btn" data-act="no">Отклонить</button>'
|
||||
+ '<button type="button" class="umb-cc-btn umb-cc-btn-primary" data-act="yes">Хорошо</button>'
|
||||
+ '</div>'
|
||||
+ '</div>';
|
||||
el.addEventListener('click', function (e) {
|
||||
var act = e.target && e.target.getAttribute && e.target.getAttribute('data-act');
|
||||
if (!act) return;
|
||||
write(act === 'yes');
|
||||
var b = document.getElementById('umb-cc-bar');
|
||||
if (b) b.remove();
|
||||
});
|
||||
document.body.appendChild(el);
|
||||
}
|
||||
|
||||
window.umbyteCookies = {
|
||||
show: show,
|
||||
get: read,
|
||||
reset: function () {
|
||||
try { localStorage.removeItem(STORAGE_KEY); } catch (e) {}
|
||||
var b = document.getElementById('umb-cc-bar');
|
||||
if (b) b.remove();
|
||||
show();
|
||||
}
|
||||
};
|
||||
|
||||
function init() { if (!read()) show(); }
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user