buhapp-telegram/public/js/views/review.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

116 lines
4.3 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.

// 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;