- 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)
116 lines
4.3 KiB
JavaScript
116 lines
4.3 KiB
JavaScript
// Review screens — create + user-reviews list
|
||
const ReviewView = {
|
||
rating: 5,
|
||
chatId: null,
|
||
otherId: null,
|
||
otherName: null,
|
||
|
||
async render(root, params) {
|
||
this.chatId = params.chatId;
|
||
this.otherId = params.otherId;
|
||
this.otherName = params.otherName;
|
||
this.rating = 5;
|
||
root.innerHTML = `
|
||
<div class="view">
|
||
<h1>⭐ Отзыв</h1>
|
||
<div class="sub">О ${escapeHtml(this.otherName || 'пользователе')}</div>
|
||
<div class="card">
|
||
<label>Ваша оценка</label>
|
||
<div class="stars" id="stars">
|
||
${[1,2,3,4,5].map(n => `<span data-n="${n}" class="${n<=5?'on':''}">★</span>`).join('')}
|
||
</div>
|
||
<label style="margin-top:12px;">Комментарий (необязательно)</label>
|
||
<textarea class="input" id="r-body" rows="4" placeholder="Как прошла встреча?" maxlength="2000" style="resize:none;"></textarea>
|
||
<div class="row">
|
||
<label>Анонимно</label>
|
||
<input type="checkbox" id="r-anon" style="width:24px;height:24px;">
|
||
</div>
|
||
<button class="btn" id="r-submit">Отправить</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
document.querySelectorAll('#stars span').forEach(s => {
|
||
s.onclick = () => {
|
||
this.rating = parseInt(s.dataset.n, 10);
|
||
document.querySelectorAll('#stars span').forEach((x, i) => {
|
||
x.classList.toggle('on', i < this.rating);
|
||
});
|
||
TG_APP.haptic('selection');
|
||
};
|
||
});
|
||
document.getElementById('r-submit').onclick = () => this.submit();
|
||
},
|
||
|
||
async submit() {
|
||
try {
|
||
await API.ReviewApi.create(
|
||
this.chatId,
|
||
this.rating,
|
||
document.getElementById('r-body').value.trim(),
|
||
document.getElementById('r-anon').checked
|
||
);
|
||
TG_APP.haptic('notification');
|
||
App.toast('Отзыв отправлен ✓');
|
||
setTimeout(() => App.showView('chats'), 800);
|
||
} catch (e) {
|
||
TG_APP.showAlert('Ошибка: ' + e.message);
|
||
}
|
||
},
|
||
};
|
||
|
||
const UserReviewsView = {
|
||
async render(root, params) {
|
||
const userId = params.userId;
|
||
const userName = params.userName || 'Пользователь';
|
||
root.innerHTML = `
|
||
<div class="view">
|
||
<h1>⭐ Отзывы</h1>
|
||
<div id="ur-header" class="loader">Загрузка...</div>
|
||
</div>
|
||
`;
|
||
try {
|
||
const [stats, list] = await Promise.all([
|
||
API.ReviewApi.stats(userId),
|
||
API.ReviewApi.listForUser(userId),
|
||
]);
|
||
const header = document.getElementById('ur-header');
|
||
header.className = 'card';
|
||
header.innerHTML = `
|
||
<div style="display:flex;align-items:center;gap:12px;">
|
||
<div class="rating-big">${stats.rating_avg ? stats.rating_avg.toFixed(1) : '—'}</div>
|
||
<div>
|
||
<div class="stars" style="font-size:18px;">
|
||
${[1,2,3,4,5].map(n => `<span class="${n<=Math.round(stats.rating_avg||0)?'on':''}">★</span>`).join('')}
|
||
</div>
|
||
<div class="sub">${stats.rating_count} отзывов</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
const v = document.createElement('div');
|
||
v.innerHTML = '<h2>' + escapeHtml(userName) + '</h2>';
|
||
if (!list.reviews || list.reviews.length === 0) {
|
||
v.innerHTML += '<div class="empty">Пока нет отзывов</div>';
|
||
} else {
|
||
list.reviews.forEach((r) => {
|
||
const card = document.createElement('div');
|
||
card.className = 'card';
|
||
card.innerHTML = `
|
||
<div style="display:flex;justify-content:space-between;">
|
||
<div class="stars" style="font-size:14px;">${[1,2,3,4,5].map(n=>`<span class="${n<=r.rating?'on':''}">★</span>`).join('')}</div>
|
||
<div class="sub" style="margin:0;">${formatDate(r.created_at)}</div>
|
||
</div>
|
||
<div class="sub">${r.anonymous ? 'Аноним' : 'ID: ' + r.reviewer_id.slice(0,8)}</div>
|
||
${r.body ? '<div style="margin-top:6px;">' + escapeHtml(r.body) + '</div>' : ''}
|
||
`;
|
||
v.appendChild(card);
|
||
});
|
||
}
|
||
header.parentElement.appendChild(v);
|
||
} catch (e) {
|
||
document.getElementById('ur-header').innerHTML = '<div class="empty">Ошибка: ' + e.message + '</div>';
|
||
}
|
||
},
|
||
};
|
||
|
||
window.ReviewView = ReviewView;
|
||
window.UserReviewsView = UserReviewsView; |