// Version: 2.02.01 // --- GLOBAL APP COMPONENT --- const { useState, useEffect } = React; const { createRoot } = ReactDOM; // Using global components const Editor = window.Editor; const AdminPanel = window.AdminPanel; const BonusBanner = window.BonusBanner; const TariffModal = window.TariffModal; const ApiTestButton = window.ApiTestButton; const TelegramLogin = window.TelegramLogin; const LandingPage = window.LandingPage; // New Landing Component // Using global Icons const { FileText, Star, Settings, Shield, LogOut, Plus, Trash2, AlertTriangle } = window; const { formatDate } = window; const App = () => { const [view, setView] = useState('loading'); const [user, setUser] = useState(null); const [botLink, setBotLink] = useState(''); const [projects, setProjects] = useState([]); const [currentProject, setCurrentProject] = useState(null); const [showIntegrations, setShowIntegrations] = useState(false); const [showTariffModal, setShowTariffModal] = useState(false); const [useAquaPlaza, setUseAquaPlaza] = useState(true); const [prices, setPrices] = useState({ manager: 0, speed: 0 }); const BOT_USERNAME = window.BOT_USERNAME || "REPLACE_ME_ON_SERVER"; useEffect(() => { const savedUser = localStorage.getItem('kp_user'); const savedAuthDate = localStorage.getItem('kp_auth_date'); const savedSetting = localStorage.getItem('use_aquaplaza'); if (savedSetting === 'false') setUseAquaPlaza(false); const now = Date.now(); const oneDay = 24 * 60 * 60 * 1000; // Check URL parameters for direct access const urlParams = new URLSearchParams(window.location.search); const mode = urlParams.get('mode'); // e.g. ?mode=login const startParam = urlParams.get('start'); // Telegram deep linking param if (savedUser && savedAuthDate && (now - parseInt(savedAuthDate) < oneDay)) { try { const u = JSON.parse(savedUser); setUser(u); setView('dashboard'); fetchProjects(u.id); fetch('/api/config/public').then(r => r.json()).then(d => { if(d) setPrices({ manager: d.price_manager, speed: d.price_speed }); }).catch(e => console.error("Price fetch error", e)); } catch (e) { localStorage.removeItem('kp_user'); // If token invalid, decide where to go based on URL if (mode === 'login' || startParam) setView('auth'); else setView('landing'); } } else { localStorage.removeItem('kp_user'); localStorage.removeItem('kp_auth_date'); // If not logged in, check URL to decide between Landing and Auth if (mode === 'login' || startParam) { setView('auth'); } else { setView('landing'); } } }, []); const toggleAquaPlaza = () => { const newValue = !useAquaPlaza; setUseAquaPlaza(newValue); localStorage.setItem('use_aquaplaza', newValue.toString()); }; const handleAuth = async (tgUser) => { try { const res = await fetch('/api/auth/telegram', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(tgUser) }); const data = await res.json(); if (!res.ok) throw new Error(data.message || "Error"); if (data.status === 'ok') { const userWithUsername = { ...data.user, username: tgUser.username }; setUser(userWithUsername); if (data.bot_link) setBotLink(data.bot_link); if (data.config) setPrices({ manager: data.config.price_manager, speed: data.config.price_speed }); localStorage.setItem('kp_user', JSON.stringify(userWithUsername)); localStorage.setItem('kp_auth_date', Date.now().toString()); await fetchProjects(data.user.id); setView('dashboard'); } else { alert("Ошибка: " + data.message); } } catch (e) { alert("Ошибка входа: " + e.message); } }; const fetchProjects = async (uid) => { try { const res = await fetch(`/api/projects/${uid}?t=${Date.now()}`); const data = await res.json(); setProjects(Array.isArray(data) ? data : []); } catch (e) { setProjects([]); } }; const handleCreateNew = () => { setCurrentProject({ id: null, name: "Новое КП", data: null }); setView('editor'); }; const handleOpenProject = async (pid) => { try { const res = await fetch(`/api/project/${pid}`); const data = await res.json(); setCurrentProject({ id: data.id, name: data.name, data: JSON.parse(data.data) }); setView('editor'); } catch (e) { alert("Ошибка открытия"); } }; const handleDeleteProject = async (pid, e) => { e.stopPropagation(); if (!confirm("Удалить?")) return; await fetch(`/api/projects/${pid}`, { method: 'DELETE' }); fetchProjects(user.id); }; const handleLogout = () => { localStorage.removeItem('kp_user'); localStorage.removeItem('kp_auth_date'); setUser(null); setView('landing'); }; // Determine privileges const getTierLabel = () => { if (user?.is_vip) return "VIP (Безлимит)"; if (user?.tier === 'speed') return `Speed (до ${user.sub_end})`; if (user?.tier === 'manager') return `Manager (до ${user.sub_end})`; return "Free (1 док)"; } const isSpeedOrVip = user?.tier === 'speed' || user?.is_vip; const daysLeft = user?.days_left || 0; const showWarning = (user?.tier === 'speed' || user?.tier === 'manager') && daysLeft <= 2 && daysLeft >= 0; if (view === 'landing') { return setView('auth')} />; } if (view === 'auth') { return (

Конструктор КП

Авторизуйтесь через Telegram, чтобы создавать, сохранять и управлять коммерческими предложениями.

); } if (view === 'admin') { return setView('dashboard')} />; } if (view === 'dashboard') { return (
setShowTariffModal(false)} botLink={botLink} prices={prices} /> {showIntegrations && (
setShowIntegrations(false)}>
e.stopPropagation()}>

Интеграции

Aquaplaza-online.ru
Поиск товаров по артикулу
)}
{showWarning && (
Внимание! Ваша подписка истекает через {daysLeft} дн. Скоро вы будете переведены на Free тариф.
)}

Мои Проекты

{getTierLabel()}
{!user.is_vip && ( )} {(isSpeedOrVip || user?.role === 'admin') && ()} {user?.role === "admin" && ()} {user?.name}
Создать новое КП
{Array.isArray(projects) && projects.map(p => (
handleOpenProject(p.id)} className="bg-white rounded-xl shadow-sm hover:shadow-md border border-gray-100 p-6 cursor-pointer transition relative group flex flex-col justify-between min-h-[200px]">

{p.name || "Без названия"}

Обновлено: {formatDate(p.updated_at)}

))}
); } return { setView('dashboard'); fetchProjects(user.id); }} />; }; const root = createRoot(document.getElementById('kp-app-root')); root.render();