Initial commit: TopSysOps site + admin panel

Multi-page public site (Next.js), Node/Express backend with Postgres,
and Vite admin/account app, restructured from a single-page layout
into dedicated service, promotions, cases, about, FAQ, and blog pages
for SEO.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013otXCiTZsxdZ4SJKZ9wUky
This commit is contained in:
ogrechkoandClaude Sonnet 5 committed 2026-08-25 23:12:16 +03:00
commit 130c5382cd
108 files changed
+7146

No files matched your search

+53
View File
@@ -0,0 +1,53 @@
'use client';
import { useEffect } from 'react';
// Провайдер и адреса задаются на этапе сборки через переменные NEXT_PUBLIC_*
// (см. корневой .env и docker-compose.yml -> nextjs -> build.args). Смена
// провайдера требует пересборки: docker compose up -d --build nextjs.
const PROVIDER = process.env.NEXT_PUBLIC_CHAT_PROVIDER || 'none';
function loadTopTicket() {
// TopTicket - собственная разработка компании. Embed-сниппет вида:
// <script src="https://help.top-sysops.ru/widget.js" data-key="..." async></script>
const widgetUrl = process.env.NEXT_PUBLIC_TOPTICKET_WIDGET_URL;
const widgetKey = process.env.NEXT_PUBLIC_TOPTICKET_WIDGET_KEY;
if (!widgetUrl || !widgetKey) return undefined;
const script = document.createElement('script');
script.src = widgetUrl;
script.setAttribute('data-key', widgetKey);
script.async = true;
document.body.appendChild(script);
return () => script.remove();
}
function loadChatwoot() {
const baseUrl = process.env.NEXT_PUBLIC_CHATWOOT_BASE_URL;
const token = process.env.NEXT_PUBLIC_CHATWOOT_WEBSITE_TOKEN;
if (!baseUrl || !token) return undefined;
const script = document.createElement('script');
script.src = `${baseUrl.replace(/\/$/, '')}/packs/js/sdk.js`;
script.defer = true;
script.async = true;
script.onload = () => {
if (window.chatwootSDK) {
window.chatwootSDK.run({ websiteToken: token, baseUrl });
}
};
document.body.appendChild(script);
return () => script.remove();
}
export default function SupportChat() {
useEffect(() => {
if (PROVIDER === 'topticket') return loadTopTicket();
if (PROVIDER === 'chatwoot') return loadChatwoot();
return undefined;
}, []);
return null;
}