// Version: 2.02.00 // --- GLOBAL SHARED COMPONENTS --- const { useState, useEffect, useMemo, useRef } = React; const ADMIN_VERSION = "2.02.00"; const API_BASE = (window.location.protocol === 'blob:' || window.location.protocol === 'file:') ? 'https://kp-generator-b4fb.onrender.com' : ''; window.TelegramLogin = ({ onAuth, botUsername }) => { const ref = useRef(null); const onAuthRef = useRef(onAuth); useEffect(() => { onAuthRef.current = onAuth; }, [onAuth]); useEffect(() => { window.onTelegramAuth = (user) => { if (onAuthRef.current) onAuthRef.current(user); }; if (ref.current) { ref.current.innerHTML = ""; const script = document.createElement("script"); script.src = "https://telegram.org/js/telegram-widget.js?22"; script.setAttribute("data-telegram-login", botUsername); script.setAttribute("data-size", "large"); script.setAttribute("data-radius", "10"); script.setAttribute("data-request-access", "write"); script.setAttribute("data-userpic", "false"); script.setAttribute("data-onauth", "onTelegramAuth(user)"); script.async = true; ref.current.appendChild(script); } }, [botUsername]); return
; }; window.ApiTestButton = () => { const [status, setStatus] = useState('idle'); const test = async () => { setStatus('loading'); try { const res = await fetch(`/api/proxy/search?article=102529M`); if (!res.ok) throw new Error(res.statusText); const json = await res.json(); if (json.found !== undefined) { setStatus('success'); setTimeout(() => setStatus('idle'), 3000); } else { throw new Error("Некорректный ответ"); } } catch (e) { console.error(e); setStatus('error'); setTimeout(() => setStatus('idle'), 3000); } }; if (status === 'loading') return Проверка...; if (status === 'success') return ✅ OK; if (status === 'error') return ❌ Ошибка; return ; }; window.BonusBanner = ({ refLink, isVip }) => { const [expanded, setExpanded] = useState(false); const copyRef = (e) => { e.stopPropagation(); navigator.clipboard.writeText(refLink).then(() => alert("Ссылка скопирована!")); }; const shareTelegram = (e) => { e.stopPropagation(); const text = encodeURIComponent("Привет! Рекомендую этот конструктор КП. Регистрируйся по ссылке и получи бонусы:"); window.open(`https://t.me/share/url?url=${refLink}&text=${text}`, '_blank'); }; return (
setExpanded(!expanded)} className={`bg-gradient-to-r from-blue-600 to-indigo-600 rounded-xl p-4 shadow-lg text-white mb-8 relative overflow-hidden cursor-pointer transition-all ${expanded ? 'h-auto' : 'h-16 flex items-center'}`}>

{isVip ? "Пригласить коллегу" : "Бонусная программа"}

{expanded && (

{isVip ? "У вас уже максимальный статус VIP. Поделитесь ссылкой с коллегами, чтобы они получили 2 месяца VIP при регистрации!" : Пригласите коллегу или друга! Вы получите 3 месяца VIP (Speed), а он — 2 месяца VIP сразу после регистрации. }

{refLink || "Loading..."}
)}
{expanded &&
}
); }; window.TariffModal = ({ isOpen, onClose, botLink, prices }) => { if (!isOpen) return null; return (
e.stopPropagation()}>

Выберите тариф

Manager

{prices.manager} ⭐️
  • 5 документов
  • Без водяного знака
  • Сохранение в облако
  • Без интеграции 1С
Выбрать Manager

Speed

HIT
{prices.speed} ⭐️
  • Безлимит документов
  • Без водяного знака
  • Интеграция 1С
  • Приоритетная поддержка
Выбрать Speed
); }; window.AdminPanel = ({ onBack, adminVersion = ADMIN_VERSION }) => { const [users, setUsers] = useState([]); const [tab, setTab] = useState('users'); const [search, setSearch] = useState(''); const [sort, setSort] = useState({ key: 'id', dir: 'desc' }); const [config, setConfig] = useState({ trial_days: 0, price_2_weeks: 0, price_1_month: 0, price_manager_month: 0 }); const [loading, setLoading] = useState(false); const [userFiles, setUserFiles] = useState(null); useEffect(() => { loadUsers(); }, []); const loadUsers = async () => { setLoading(true); try { const res = await fetch('/api/admin/users'); const data = await res.json(); if (!res.ok) { alert(`Error: ${data.message}`); setLoading(false); return; } if (Array.isArray(data)) setUsers(data); } catch (e) { console.error(e); } setLoading(false); }; const loadConfig = async () => { try { const res = await fetch('/api/admin/config'); if (res.ok) setConfig(await res.json()); } catch (e) { } }; const saveConfig = async () => { try { await fetch('/api/admin/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config) }); alert("Настройки сохранены"); } catch (e) { alert("Ошибка сохранения"); } }; const toggleBlock = async (id) => { if (!confirm("Изменить блокировку?")) return; await fetch(`/api/admin/users/${id}/toggle_block`, { method: 'POST' }); loadUsers(); }; const toggleVip = async (id) => { if (!confirm("Переключить VIP?")) return; await fetch(`/api/admin/users/${id}/toggle_vip`, { method: 'POST' }); loadUsers(); }; const cycleTier = async (id) => { if (!confirm("Изменить тариф (Free -> Manager -> Speed)?")) return; await fetch(`/api/admin/users/${id}/cycle_tier`, { method: 'POST' }); loadUsers(); }; const setSubDays = async (id) => { const daysStr = prompt("Введите количество дней Speed (0 - чтобы отключить):", "30"); if (daysStr === null) return; const days = parseInt(daysStr); if (isNaN(days) || days < 0) { alert("Введите корректное число"); return; } try { await fetch(`/api/admin/users/${id}/set_sub`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ days }) }); loadUsers(); } catch (e) { alert("Ошибка обновления"); } }; const openFiles = async (u) => { setUserFiles({ userId: u.id, name: u.name, files: [] }); try { const res = await fetch(`/api/projects/${u.id}`); if (res.ok) setUserFiles({ userId: u.id, name: u.name, files: await res.json() }); } catch (e) { } }; const downloadFile = async (fid, fname) => { const res = await fetch(`/api/project/${fid}`); const data = await res.json(); const blob = new Blob([data.data], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = (fname || "project") + ".json"; a.click(); }; const deleteFile = async (fid) => { if (!confirm("Удалить файл?")) return; await fetch(`/api/projects/${fid}`, { method: 'DELETE' }); openFiles({ id: userFiles.userId, name: userFiles.name }); loadUsers(); }; const uploadFile = async (e) => { const file = e.target.files[0]; if (!file || !userFiles) return; const reader = new FileReader(); reader.onload = async (ev) => { await fetch(`/api/projects`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: userFiles.userId, id: 0, name: file.name.replace('.json', '') + " (Upload)", data: e.target.result }) }); alert("Загружено"); openFiles({ id: userFiles.userId, name: userFiles.name }); loadUsers(); }; reader.readAsText(file); e.target.value = ''; }; const sortedUsers = useMemo(() => { let res = users.filter(u => (u.name && u.name.toLowerCase().includes(search.toLowerCase())) || (u.username && u.username.toLowerCase().includes(search.toLowerCase())) || String(u.telegram_id).includes(search)); return res.sort((a, b) => { let valA = a[sort.key], valB = b[sort.key]; if (sort.key === 'created_at' || sort.key === 'last_active') { const parse = (s) => { if (!s) return 0; const [d, t] = s.split(' '); const [dd, mm, yy] = d.split('.'); return new Date(yy, mm - 1, dd).getTime(); }; valA = parse(valA); valB = parse(valB); } if (valA < valB) return sort.dir === 'asc' ? -1 : 1; if (valA > valB) return sort.dir === 'asc' ? 1 : -1; return 0; }); }, [users, search, sort]); const handleSort = (key) => setSort({ key, dir: sort.key === key && sort.dir === 'asc' ? 'desc' : 'asc' }); return (

Админ-панель

Frontend: v{adminVersion}
{tab === 'users' && (
setSearch(e.target.value)} className="pl-9 pr-4 py-2 rounded-lg border focus:outline-none focus:ring-2 focus:ring-blue-400" />
)}
{tab === 'users' && (
{loading ? : sortedUsers.map(u => { const tierStyle = u.is_vip ? 'bg-yellow-100 text-yellow-700 border-yellow-200' : u.tier === 'speed' ? 'bg-purple-100 text-purple-700 border-purple-200' : u.tier === 'manager' ? 'bg-blue-50 text-blue-600 border-blue-200' : 'bg-gray-100 text-gray-500'; return ( ) })}
handleSort('name')}>Пользователь {sort.key === 'name' && (sort.dir === 'asc' ? '↑' : '↓')} Тариф handleSort('projects_count')}>Проекты {sort.key === 'projects_count' && (sort.dir === 'asc' ? '↑' : '↓')} handleSort('storage_kb')}>Объем {sort.key === 'storage_kb' && (sort.dir === 'asc' ? '↑' : '↓')} handleSort('last_active')}>Активность {sort.key === 'last_active' && (sort.dir === 'asc' ? '↑' : '↓')} handleSort('created_at')}>Регистрация {sort.key === 'created_at' && (sort.dir === 'asc' ? '↑' : '↓')} Файлы Действия
Загрузка...
{u.photo ? :
{u.name?.[0]}
}
{u.name}
@{u.username}
{u.projects_count} {u.storage_kb} KB {u.last_active} {u.created_at}
)} {tab === 'settings' && (

Настройки тарифов

setConfig({ ...config, trial_days: +e.target.value })} className="w-full border rounded p-2" />
setConfig({ ...config, price_manager_month: +e.target.value })} className="w-full border rounded p-2" />
setConfig({ ...config, price_1_month: +e.target.value })} className="w-full border rounded p-2" />
)} {userFiles && (
setUserFiles(null)}>
e.stopPropagation()}>

Файлы: {userFiles.name}

{userFiles.files.length === 0 ? (
Нет файлов
) : ( userFiles.files.map(f => (
{f.name}
{new Date(f.updated_at).toLocaleString()}
)) )}
Загрузить файл пользователю:
)}
); };