buhapp-telegram/public/js/app.js
root 897fc4cd31 Add email login fallback (for testing outside Telegram)
WebApp detects if Telegram SDK is missing, shows email login form
so we can test without the bot running.
2026-08-20 17:52:38 +00:00

246 lines
9.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Main app controller
const App = {
consentGiven: false,
async init() {
// bind tabbar
document.querySelectorAll('.tab').forEach(btn => {
btn.onclick = () => this.showView(btn.dataset.view);
});
// restore tokens from localStorage
API.Store.load();
if (TG_APP.isAvailable && TG_APP.initData) {
// авторизация через Telegram
try {
await API.Auth.loginWithTelegram(TG_APP.initData);
await this.checkConsent();
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 {
// WebApp открыт вне Telegram — предложим email login
this.showEmailLogin();
}
// bind input events for chat
document.body.addEventListener('click', (e) => {
const sendBtn = e.target.closest('#msg-send');
if (sendBtn) { window.ChatView.send(); return; }
const cancel = e.target.closest('#edit-cancel');
if (cancel) { document.getElementById('msg-input').value = ''; document.getElementById('edit-bar').style.display='none'; return; }
});
document.body.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && e.target.id === 'msg-input') {
if (!e.shiftKey) { e.preventDefault(); window.ChatView.send(); }
}
});
},
async checkConsent() {
// показываем согласия при первом входе
if (API.Store.user && !API.Store.user.consents_given) {
this.showConsent();
return false;
}
return true;
},
showConsent() {
const ov = document.createElement('div');
ov.id = 'consent-overlay';
ov.innerHTML = `
<h1>🍻 Добро пожаловать в BuhApp</h1>
<p class="sub">Приложение для поиска компании. Только для лиц 18+.</p>
<div class="card" style="background:var(--bg-elev);">
<p style="margin-bottom:12px;font-size:13px;">
⚠️ <strong>Внимание</strong>: BuhApp — это платформа для знакомств и встреч с другими пользователями.
Включает в себя риски, связанные с употреблением алкоголя и личными встречами.
</p>
<p style="font-size:13px;">
Пожалуйста, ознакомьтесь и примите следующие документы:
</p>
<div class="consent-line"><input type="checkbox" id="c-adult"><label for="c-adult">Мне исполнилось 18 лет (обязательно)</label></div>
<div class="consent-line"><input type="checkbox" id="c-terms"><label for="c-terms">Пользовательское соглашение (v1.0)</label></div>
<div class="consent-line"><input type="checkbox" id="c-privacy"><label for="c-privacy">Политика конфиденциальности (152-ФЗ, v1.0)</label></div>
<div class="consent-line"><input type="checkbox" id="c-disclaimer"><label for="c-disclaimer">Отказ от ответственности (v1.0)</label></div>
<button class="btn" id="c-accept" style="margin-top:16px;">Принять и продолжить</button>
</div>
`;
document.body.appendChild(ov);
document.getElementById('c-accept').onclick = async () => {
const a = document.getElementById('c-adult').checked;
const t = document.getElementById('c-terms').checked;
const p = document.getElementById('c-privacy').checked;
const d = document.getElementById('c-disclaimer').checked;
if (!a || !t || !p || !d) {
TG_APP.showAlert('Нужно принять все 4 пункта');
return;
}
try {
await API.api('/api/v1/auth/consents', {
method: 'POST',
body: {
adult: a, terms: t, privacy: p, disclaimer: d,
},
});
TG_APP.haptic('notification');
ov.remove();
this.consentGiven = true;
this.showView('map');
} catch (e) {
TG_APP.showAlert('Ошибка: ' + e.message);
}
};
},
showError(msg) {
document.getElementById('app').innerHTML = `
<div class="view">
<div class="card" style="text-align:center;padding:32px 16px;">
<div style="font-size:48px;margin-bottom:16px;">⚠️</div>
<div>${msg}</div>
</div>
</div>
`;
},
showEmailLogin() {
document.getElementById('app').innerHTML = `
<div class="view">
<h1>🍻 BuhApp</h1>
<div class="sub">Приложение для поиска компании (Telegram Mini App)</div>
<div class="card">
<div style="background:var(--warning);color:#000;padding:8px 12px;border-radius:8px;margin-bottom:12px;font-size:13px;">
⚠️ WebApp лучше открывать через Telegram — там авторизация автоматическая.
Здесь можно зайти по email для тестирования.
</div>
<label>Email</label>
<input class="input" id="el-email" type="email" placeholder="you@example.com">
<label>Пароль</label>
<input class="input" id="el-pass" type="password" placeholder="минимум 8 символов">
<button class="btn" id="el-login">Войти</button>
<button class="btn ghost" id="el-register">Регистрация</button>
</div>
<div class="card" style="font-size:12px;color:var(--text-dim);">
🍻 BuhApp — приложение для лиц 18+. Встречи и употребление алкоголя — на свой риск.
Принимая соглашение, вы подтверждаете совершеннолетие.
</div>
</div>
`;
document.getElementById('el-login').onclick = () => this.emailLogin();
document.getElementById('el-register').onclick = () => this.emailRegister();
},
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();
await this.checkConsent();
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;
if (!email || pass.length < 8) {
TG_APP.showAlert('Email и пароль (≥8 символов) обязательны');
return;
}
try {
await API.Auth.registerEmail({
email,
password: pass,
name: email.split('@')[0],
consents: { adult: true, terms: true, privacy: true, disclaimer: true },
});
await API.Auth.me();
this.consentGiven = true;
this.showView('map');
} catch (e) {
TG_APP.showAlert('Ошибка: ' + e.message);
}
},
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 = '<div class="view">Неизвестный экран</div>';
}
},
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 => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
}
function formatTime(iso) {
if (!iso) return '';
const d = new Date(iso);
const today = new Date();
if (d.toDateString() === today.toDateString()) {
return d.getHours().toString().padStart(2, '0') + ':' + d.getMinutes().toString().padStart(2, '0');
}
return d.getDate().toString().padStart(2, '0') + '.' + (d.getMonth() + 1).toString().padStart(2, '0');
}
function formatDate(iso) {
if (!iso) return '';
const d = new Date(iso);
return d.getDate().toString().padStart(2, '0') + '.' + (d.getMonth() + 1).toString().padStart(2, '0') + '.' + d.getFullYear();
}
window.App = App;
document.addEventListener('DOMContentLoaded', () => App.init());