// VibeChat Messenger App const API = { me: '/api/auth/me', logout: '/api/auth/logout', users: '/api/users', chats: '/api/chats', updateProfile: '/api/users/me' }; let currentUser = null; let currentChat = null; let allChats = []; let allUsers = []; // Initialize app window.addEventListener('DOMContentLoaded', async () => { const token = localStorage.getItem('vibechat_token'); if (!token) { window.location.href = 'index.html'; return; } await loadCurrentUser(); await loadChats(); await loadUsers(); // Inject emoji picker HTML const emojis = ['😀','😂','😍','🥰','😎','🤔','👍','❤️','🔥','🎉','👋','🙏','💯','✨','🚀','💪','🎯','✅','📌','🔔']; document.getElementById('emojiPicker').innerHTML = `
${emojis.map(e => `${e}`).join('')}
`; }); // Load current user async function loadCurrentUser() { try { const token = localStorage.getItem('vibechat_token'); const res = await fetch(API.me, { headers: { 'Authorization': `Bearer ${token}` } }); const json = await res.json(); if (!json.ok) { localStorage.removeItem('vibechat_token'); window.location.href = 'index.html'; return; } currentUser = json.user; localStorage.setItem('vibechat_user', JSON.stringify(currentUser)); // Update UI document.getElementById('currentUserName').textContent = currentUser.username; document.getElementById('currentUserAvatar').src = currentUser.avatar; } catch (e) { console.error('Failed to load user:', e); } } // Load chats async function loadChats() { try { const token = localStorage.getItem('vibechat_token'); const res = await fetch(API.chats, { headers: { 'Authorization': `Bearer ${token}` } }); const json = await res.json(); if (json.ok) { allChats = json.chats; renderChatList(); } } catch (e) { console.error('Failed to load chats:', e); } } // Render chat list function renderChatList() { const chatList = document.getElementById('chatList'); if (allChats.length === 0) { chatList.innerHTML = `

Нет чатов

Нажмите + чтобы создать новый

`; return; } chatList.innerHTML = allChats.map(chat => `
${chat.name} ${chat.type !== 'private' ? `${chat.type === 'group' ? 'Группа' : 'Канал'}` : ''}
${chat.lastMessage ? chat.lastMessage.text : 'Нет сообщений'}
${chat.lastMessage ? `${formatTime(chat.lastMessage.createdAt)}` : ''}
`).join(''); } // Load users for new chat async function loadUsers() { try { const token = localStorage.getItem('vibechat_token'); const res = await fetch(API.users, { headers: { 'Authorization': `Bearer ${token}` } }); const json = await res.json(); if (json.ok) { allUsers = json.users; renderUserList(); } } catch (e) { console.error('Failed to load users:', e); } } // Render user list function renderUserList() { const userList = document.getElementById('userList'); if (allUsers.length === 0) { userList.innerHTML = '

Нет пользователей

'; return; } userList.innerHTML = allUsers.map(user => `
${user.username}
`).join(''); } // Select chat async function selectChat(chatId) { currentChat = allChats.find(c => c.id === chatId); if (!currentChat) return; // Update UI document.getElementById('emptyChat').style.display = 'none'; document.getElementById('messagesList').style.display = 'flex'; document.getElementById('messageInputContainer').style.display = 'block'; // Update header document.getElementById('chatHeaderName').textContent = currentChat.name; document.getElementById('chatHeaderAvatar').src = currentChat.avatar || 'https://api.dicebear.com/7.x/identicon/svg?seed=' + currentChat.id; document.getElementById('chatHeaderStatus').textContent = currentChat.type === 'channel' ? 'Канал' : (currentChat.type === 'group' ? `${currentChat.participants?.length || 0} участников` : 'Онлайн'); // Render chat list again to update active state renderChatList(); // Load messages await loadMessages(chatId); } // Load messages async function loadMessages(chatId) { try { const token = localStorage.getItem('vibechat_token'); const res = await fetch(`${API.chats}/${chatId}/messages`, { headers: { 'Authorization': `Bearer ${token}` } }); const json = await res.json(); if (json.ok) { renderMessages(json.messages); } } catch (e) { console.error('Failed to load messages:', e); } } // Render messages function renderMessages(messages) { const messagesList = document.getElementById('messagesList'); if (messages.length === 0) { messagesList.innerHTML = '

Сообщений пока нет

'; return; } messagesList.innerHTML = messages.map(msg => `
${msg.senderId !== currentUser.id ? `
${msg.senderName}
` : ''}
${escapeHtml(msg.text)}
${formatTime(msg.createdAt)}
`).join(''); // Scroll to bottom const container = document.getElementById('messagesContainer'); container.scrollTop = container.scrollHeight; } // Send message async function sendMessage() { const input = document.getElementById('messageInput'); const text = input.value.trim(); if (!text || !currentChat) return; try { const token = localStorage.getItem('vibechat_token'); const res = await fetch(`${API.chats}/${currentChat.id}/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ text }) }); const json = await res.json(); if (json.ok) { input.value = ''; await loadMessages(currentChat.id); await loadChats(); // Update last message } else { alert('Ошибка отправки: ' + json.error); } } catch (e) { alert('Ошибка: ' + e.message); } } // Handle enter key function handleKeyPress(event) { if (event.key === 'Enter') { sendMessage(); } } // Emoji picker function toggleEmojiPicker() { const picker = document.getElementById('emojiPicker'); picker.classList.toggle('active'); } function insertEmoji(emoji) { const input = document.getElementById('messageInput'); input.value += emoji; input.focus(); document.getElementById('emojiPicker').classList.remove('active'); } // Close emoji picker on outside click document.addEventListener('click', (e) => { const picker = document.getElementById('emojiPicker'); const btn = document.querySelector('.btn-emoji'); if (!picker.contains(e.target) && !btn.contains(e.target)) { picker.classList.remove('active'); } }); // New chat modal function showNewChatModal() { document.getElementById('newChatModal').classList.add('active'); loadUsers(); } function closeNewChatModal() { document.getElementById('newChatModal').classList.remove('active'); } function switchTab(tab) { document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); event.target.classList.add('active'); document.getElementById('chatTab').style.display = tab === 'chat' ? 'block' : 'none'; document.getElementById('groupTab').style.display = tab === 'group' ? 'block' : 'none'; document.getElementById('channelTab').style.display = tab === 'channel' ? 'block' : 'none'; } // Create private chat async function createPrivateChat(userId) { try { const token = localStorage.getItem('vibechat_token'); const res = await fetch(API.chats, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ type: 'private', participantId: userId }) }); const json = await res.json(); if (json.ok) { closeNewChatModal(); await loadChats(); selectChat(json.chat.id); } } catch (e) { alert('Ошибка: ' + e.message); } } // Create group async function createGroup() { const name = document.getElementById('groupName').value.trim(); if (!name) return alert('Введите название группы'); try { const token = localStorage.getItem('vibechat_token'); const res = await fetch(API.chats, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ type: 'group', name }) }); const json = await res.json(); if (json.ok) { closeNewChatModal(); document.getElementById('groupName').value = ''; await loadChats(); selectChat(json.chat.id); } } catch (e) { alert('Ошибка: ' + e.message); } } // Create channel async function createChannel() { const name = document.getElementById('channelName').value.trim(); if (!name) return alert('Введите название канала'); try { const token = localStorage.getItem('vibechat_token'); const res = await fetch(API.chats, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ type: 'channel', name }) }); const json = await res.json(); if (json.ok) { closeNewChatModal(); document.getElementById('channelName').value = ''; await loadChats(); selectChat(json.chat.id); } } catch (e) { alert('Ошибка: ' + e.message); } } // Settings function openSettings() { const user = JSON.parse(localStorage.getItem('vibechat_user')); document.getElementById('settingsAvatar').src = user.avatar; document.getElementById('settingsUsername').value = user.username; document.getElementById('settingsBio').value = user.bio || ''; document.getElementById('settingsModal').classList.add('active'); } function closeSettings() { document.getElementById('settingsModal').classList.remove('active'); } async function updateProfile(event) { event.preventDefault(); const username = document.getElementById('settingsUsername').value; const bio = document.getElementById('settingsBio').value; try { const token = localStorage.getItem('vibechat_token'); const res = await fetch(API.updateProfile, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ username, bio }) }); const json = await res.json(); if (json.ok) { localStorage.setItem('vibechat_user', JSON.stringify(json.user)); currentUser = json.user; document.getElementById('currentUserName').textContent = currentUser.username; document.getElementById('currentUserAvatar').src = currentUser.avatar; closeSettings(); alert('Профиль обновлён! ✅'); } } catch (e) { alert('Ошибка: ' + e.message); } } function changeAvatar() { const url = prompt('Введите URL аватара:', currentUser.avatar); if (url) { updateProfile({ preventDefault: () => {} }); } } // Logout async function logout() { if (!confirm('Вы уверены, что хотите выйти?')) return; try { const token = localStorage.getItem('vibechat_token'); await fetch(API.logout, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` } }); } catch (e) {} localStorage.removeItem('vibechat_token'); localStorage.removeItem('vibechat_user'); window.location.href = 'index.html'; } // Filter chats function filterChats() { const query = document.getElementById('searchInput').value.toLowerCase(); const filtered = allChats.filter(c => c.name.toLowerCase().includes(query)); const chatList = document.getElementById('chatList'); chatList.innerHTML = filtered.map(chat => `
${chat.name}
${chat.lastMessage ? chat.lastMessage.text : 'Нет сообщений'}
`).join(''); } // Helper functions function formatTime(isoString) { const date = new Date(isoString); const now = new Date(); const diff = now - date; if (diff < 60000) return 'Только что'; if (diff < 3600000) return Math.floor(diff / 60000) + ' мин назад'; if (diff < 86400000) return Math.floor(diff / 3600000) + ' ч назад'; if (diff < 604800000) return Math.floor(diff / 86400000) + ' дн назад'; return date.toLocaleDateString('ru-RU', { day: 'numeric', month: 'short' }); } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }