// Main app controller const App = { consentGiven: false, isMobileMode: false, _preReg: null, // callback после согласия async init() { const isTouch = ('ontouchstart' in window) || navigator.maxTouchPoints > 0; const smallScreen = window.innerWidth <= 768; const savedMode = localStorage.getItem('buhapp.mode'); if (savedMode === 'desktop') { this.isMobileMode = false; } else if (savedMode === 'mobile') { this.isMobileMode = true; } else { this.isMobileMode = !smallScreen; } this.applyMode(); document.getElementById('mode-toggle').onclick = () => { this.isMobileMode = !this.isMobileMode; localStorage.setItem('buhapp.mode', this.isMobileMode ? 'mobile' : 'desktop'); this.applyMode(); if (window.MapView?.map) { setTimeout(() => window.MapView.map.invalidateSize(), 100); } }; document.querySelectorAll('.tab').forEach(btn => { btn.onclick = () => this.showView(btn.dataset.view); }); if (TG_APP.isTelegram && TG_APP.user) { try { await API.Auth.loginWithTelegram(TG_APP.initData); await API.Auth.me(); await this.checkConsent(); await this.maybeOnboard(); this.showView('map'); } catch (e) { this.showError('Не удалось войти через Telegram: ' + e.message); } } else if (API.Store.access) { try { await API.Auth.me(); await this.checkConsent(); this.showView('map'); } catch (e) { API.Store.clear(); this.showError('Сессия истекла: ' + e.message); } } else { // Вне Telegram — показать email login (но сначала согласия) this.showWelcome(); } document.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey && document.activeElement?.id === 'msg-input') return; if (e.key === 'Escape') { if (window.ChatView?.cleanup) window.ChatView.cleanup(); if (API.Store.access) this.showView('map'); } }); }, applyMode() { if (this.isMobileMode) { document.body.classList.add('mobile-mode'); document.getElementById('mode-toggle').textContent = '🖥️ На весь экран'; } else { document.body.classList.remove('mobile-mode'); document.getElementById('mode-toggle').textContent = '📱 Мобильный'; } }, async checkConsent() { try { const s = await API.ConsentApi.status(); if (s.all_accepted) return true; } catch (e) { console.warn('consent status check failed', e); } // не показываем если нет авторизации — это для авторизованных if (!API.Store.user) return true; this.showConsent(); return false; }, showConsent() { const ov = document.createElement('div'); ov.id = 'consent-overlay'; ov.innerHTML = `

🍻 Добро пожаловать в BuhApp

Приложение для поиска компании. Только для лиц 18+.

Перед тем как продолжить, прочти и прими:
Без принятия согласий пользование приложением невозможно.
`; // цепляемся к #mobile-frame если он есть const target = document.querySelector('body.mobile-mode #mobile-frame') || document.body; target.appendChild(ov); const check = () => { const ok = document.getElementById('c-adult').checked && document.getElementById('c-terms').checked && document.getElementById('c-privacy').checked && document.getElementById('c-disclaimer').checked; document.getElementById('c-submit').disabled = !ok; }; ['c-adult','c-terms','c-privacy','c-disclaimer'].forEach(id => document.getElementById(id).addEventListener('change', check) ); document.getElementById('c-submit').onclick = async () => { try { await API.ConsentApi.accept({ adult: true, terms: true, privacy: true, disclaimer: true }); ov.remove(); this.consentGiven = true; if (this._preReg) { const fn = this._preReg; this._preReg = null; fn(); } else { await this.maybeOnboard(); this.showView('map'); } } catch (e) { TG_APP.showAlert('Ошибка: ' + e.message); } }; }, showError(msg) { document.getElementById('app').innerHTML = `
⚠️
${msg}
`; }, showWelcome() { // Приветственный экран вне Telegram — выбор: email login или открыть в Telegram document.getElementById('app').innerHTML = `

🍻 BuhApp

Приложение для поиска компании (18+)

BuhApp работает внутри Telegram — это безопаснее, проще и быстрее.

🚀 Открыть в Telegram
🍻 Только для лиц 18+. Использование = принятие соглашения и отказа от ответственности.
`; document.getElementById('use-email').onclick = () => this.showEmailLogin(); }, showEmailLogin() { document.getElementById('app').innerHTML = `

Вход

Войди или зарегистрируйся по email
Введите email и пароль
Регистрируясь, вы подтверждаете совершеннолетие (18+) и принимаете соглашение.
`; document.getElementById('el-login').onclick = () => this.emailLogin(); document.getElementById('el-register').onclick = () => this.emailRegisterPre(); const update = () => { const email = document.getElementById('el-email').value.trim(); const pass = document.getElementById('el-pass').value; const ok = email.length > 0 && pass.length >= 8; document.getElementById('el-login').disabled = !ok; document.getElementById('el-register').disabled = !ok; const hint = document.getElementById('el-hint'); if (pass.length > 0 && pass.length < 8) { hint.textContent = 'Пароль слишком короткий (' + pass.length + '/8)'; hint.style.color = 'var(--danger)'; } else if (ok) { hint.textContent = '✓ Готово'; hint.style.color = 'var(--success)'; } else { hint.textContent = 'Введите email и пароль'; hint.style.color = 'var(--text-dim)'; } }; document.getElementById('el-email').addEventListener('input', update); document.getElementById('el-pass').addEventListener('input', update); }, // Сначала consent overlay, потом реальная регистрация emailRegisterPre() { const email = document.getElementById('el-email').value.trim(); const pass = document.getElementById('el-pass').value; if (!email || pass.length < 8) { TG_APP.showAlert('Email и пароль (≥8 символов) обязательны'); return; } // проверим дубликат email this._preReg = () => this.emailRegister(); this.showConsent(); }, async emailLogin() { const email = document.getElementById('el-email').value.trim(); const pass = document.getElementById('el-pass').value; try { await API.Auth.loginEmail(email, pass); await API.Auth.me(); // для email-логина согласия можно проверить, но юзер их уже дал await this.maybeOnboard(); this.showView('map'); } catch (e) { TG_APP.showAlert('Ошибка: ' + e.message); } }, async emailRegister() { const email = document.getElementById('el-email').value.trim(); const pass = document.getElementById('el-pass').value; // показываем сообщение о процессе const ov = document.createElement('div'); ov.id = 'consent-overlay'; ov.innerHTML = `
Регистрация...
`; const target = document.querySelector('body.mobile-mode #mobile-frame') || document.body; target.appendChild(ov); try { const r = await API.Auth.registerEmail({ email, password: pass, name: email.split('@')[0], consents: { adult: true, terms: true, privacy: true, disclaimer: true }, }); await API.Auth.me(); ov.remove(); await this.maybeOnboard(); this.showView('map'); } catch (e) { ov.remove(); TG_APP.showAlert('Ошибка регистрации: ' + (e?.message || 'unknown')); // вернёмся к форме this.showEmailLogin(); } }, async maybeOnboard() { if (this._onboardRunning) return; this._onboardRunning = true; try { if (!window.OnboardingView) return; if (OnboardingView.isDone()) return; await OnboardingView.render(document.body); } finally { this._onboardRunning = false; } }, showView(name, params = {}) { document.querySelectorAll('.tab').forEach(t => t.classList.toggle('active', t.dataset.view === name)); const root = document.getElementById('app'); if (window.ChatView?.cleanup) window.ChatView.cleanup(); switch (name) { case 'map': MapView.render(root); break; case 'chats': ChatsView.render(root); break; case 'chat': ChatView.render(root, params); break; case 'profile': ProfileView.render(root); break; case 'settings': SettingsView.render(root); break; case 'review': ReviewView.render(root, params); break; case 'user-reviews': UserReviewsView.render(root, params); break; case 'user-profile': UserProfileView.render(root, params); break; default: root.innerHTML = '
Неизвестный экран
'; } }, openChat(chatId, otherId, otherName) { this.showView('chat', { chatId, otherId, otherName }); }, openReview(chatId, otherId, otherName) { this.showView('review', { chatId, otherId, otherName }); }, openUserProfile(userId, name) { this.showView('user-profile', { userId, name }); }, toast(msg) { const t = document.createElement('div'); t.className = 'toast'; t.textContent = msg; document.body.appendChild(t); setTimeout(() => t.remove(), 2000); }, }; // helpers function escapeHtml(s) { if (!s) return ''; return String(s).replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); } // Debug-оверлей: показывает ошибки JS прямо на экране (для отладки в обычном браузере) window.addEventListener('error', (e) => { console.error('Global error:', e); showDebugError(e.message + ' @ ' + (e.filename || '?') + ':' + (e.lineno || '?')); }); window.addEventListener('unhandledrejection', (e) => { console.error('Unhandled rejection:', e); showDebugError('Promise: ' + (e.reason && e.reason.message ? e.reason.message : (e.reason || 'unknown'))); }); function showDebugError(msg) { try { let d = document.getElementById('debug-errors'); if (!d) { d = document.createElement('div'); d.id = 'debug-errors'; d.style.cssText = 'position:fixed;top:0;left:0;right:0;max-height:50vh;overflow:auto;background:#900;color:#fff;padding:12px;font:12px monospace;z-index:99999;white-space:pre-wrap;'; document.body.appendChild(d); } const line = document.createElement('div'); line.textContent = msg; d.appendChild(line); } catch {} } document.addEventListener('DOMContentLoaded', () => { App.init().catch((e) => { console.error('App.init failed:', e); showDebugError('App.init: ' + (e && e.message ? e.message : String(e))); }); });