buhapp-telegram/public/js/views/profile.js
root 21d281a1c6 Telegram WebApp: Mini App with map, chats, profile, reviews
- index.html with bottom tabbar (Map/Chats/Profile/Settings)
- Telegram WebApp SDK auth via /api/v1/auth/telegram
- Consent gate on first login (4 checkboxes)
- Map view with Leaflet (OSM) + nearby users + radius selector
- Chat list + single chat with WebSocket realtime
- Profile editing (drinks, activities, purposes)
- Review screen with stars + anonymous toggle
- User reviews list with stats
- User profile (public) with rating, write/block/report
- Legal pages (TERMS / PRIVACY / DISCLAIMER)
2026-08-20 17:43:16 +00:00

115 lines
4.6 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.

// Profile view (свой профиль)
const ProfileView = {
drinks: new Set(),
activities: new Set(),
purposes: new Set(),
anonymous: false,
DRINKS: [
['beer', '🍺 Пиво'], ['wine', '🍷 Вино'], ['whiskey', '🥃 Виски'], ['vodka', '🍸 Водка'],
['cocktail', '🍹 Коктейль'], ['rum', '🥃 Ром'], ['gin', '🍸 Джин'], ['tequila', '🌵 Текила'],
['champagne', '🍾 Шампанское'], ['non_alcoholic', '🥤 Безалкогольное'],
],
ACTIVITIES: [
['walk', '🚶 Прогулка'], ['dinner', '🍽️ Ужин'], ['bar', '🍻 Бар'], ['cafe', '☕ Кафе'],
['travel', '✈️ Путешествие'], ['movie', '🎬 Кино'], ['concert', '🎵 Концерт'],
['sport', '⚽ Спорт'], ['gaming', '🎮 Игры'], ['reading', '📚 Чтение'],
],
PURPOSES: [
['chat', '💬 Пообщаться'], ['drink', '🍻 Выпить'], ['walk', '🌳 Погулять'],
['dinner', '🍽️ Поужинать'], ['travel', '✈️ Поехать'], ['friendship', '🤝 Дружба'],
['relationship', '❤️ Отношения'],
],
async render(root) {
try {
const me = await API.Auth.me();
API.Store.user = me.user;
const prefs = me.prefs || {};
this.drinks = new Set(prefs.drinks || []);
this.activities = new Set(prefs.activities || []);
this.purposes = new Set(prefs.purposes || []);
this.renderForm(root, me.user);
} catch (e) {
root.innerHTML = '<div class="view"><div class="empty">Не удалось загрузить: ' + e.message + '</div></div>';
}
},
renderForm(root, u) {
root.innerHTML = `
<div class="view">
<h1>👤 Профиль</h1>
<div class="card">
<div style="font-size:22px;font-weight:700;">${escapeHtml(u.name || '')}</div>
${u.telegram_id ? `<div class="sub">@${escapeHtml(u.username || '')}</div>` : ''}
${u.telegram_id ? '' : `<div class="sub">${escapeHtml(u.email || '')}</div>`}
</div>
<label>Имя</label>
<input class="input" id="p-name" value="${escapeHtml(u.name || '')}">
<label>Город</label>
<input class="input" id="p-city" placeholder="Москва" value="${escapeHtml(u.city || '')}">
<label>О себе</label>
<textarea class="input" id="p-bio" rows="3" placeholder="Расскажи о себе" style="resize:none;">${escapeHtml(u.bio || '')}</textarea>
<label>Пол</label>
<select class="input" id="p-gender">
<option value="">Не указан</option>
<option value="m" ${u.gender === 'm' ? 'selected' : ''}>Мужской</option>
<option value="f" ${u.gender === 'f' ? 'selected' : ''}>Женский</option>
<option value="o" ${u.gender === 'o' ? 'selected' : ''}>Другое</option>
</select>
<h2>🍻 Что пью</h2>
<div id="p-drinks"></div>
<h2>🎯 Чем занимаюсь</h2>
<div id="p-activities"></div>
<h2>💡 Цели</h2>
<div id="p-purposes"></div>
<button class="btn" id="p-save">Сохранить</button>
</div>
`;
this.renderChips('p-drinks', this.DRINKS, this.drinks);
this.renderChips('p-activities', this.ACTIVITIES, this.activities);
this.renderChips('p-purposes', this.PURPOSES, this.purposes);
document.getElementById('p-save').onclick = () => this.save();
},
renderChips(elId, items, selected) {
const el = document.getElementById(elId);
items.forEach(([key, label]) => {
const chip = document.createElement('span');
chip.className = 'chip' + (selected.has(key) ? ' active' : '');
chip.textContent = label;
chip.onclick = () => {
if (selected.has(key)) selected.delete(key); else selected.add(key);
chip.classList.toggle('active');
TG_APP.haptic('selection');
};
el.appendChild(chip);
});
},
async save() {
try {
await API.Auth.updateMe({
name: document.getElementById('p-name').value.trim(),
city: document.getElementById('p-city').value.trim(),
bio: document.getElementById('p-bio').value.trim(),
gender: document.getElementById('p-gender').value,
});
await API.Auth.updatePrefs({
drinks: [...this.drinks],
activities: [...this.activities],
purposes: [...this.purposes],
});
TG_APP.haptic('notification');
App.toast('Сохранено ✓');
} catch (e) {
TG_APP.showAlert('Ошибка: ' + e.message);
}
},
};
window.ProfileView = ProfileView;