877 lines
40 KiB
HTML
877 lines
40 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Secure Chat App</title>
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); height: 100vh; display: flex; justify-content: center; align-items: center; }
|
||
.container { width: 90%; max-width: 1200px; height: 90vh; background: white; border-radius: 15px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); display: flex; overflow: hidden; }
|
||
.auth-container { width: 400px; margin: 0 auto; background: white; padding: 40px; border-radius: 15px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); }
|
||
.auth-tabs { display: flex; margin-bottom: 30px; border-bottom: 1px solid #e9ecef; }
|
||
.auth-tab { flex: 1; padding: 15px; text-align: center; cursor: pointer; border-bottom: 3px solid transparent; transition: all 0.3s ease; }
|
||
.auth-tab.active { border-bottom-color: #3498db; color: #3498db; font-weight: 600; }
|
||
.auth-form { display: none; }
|
||
.auth-form.active { display: block; }
|
||
.form-group { margin-bottom: 20px; }
|
||
.form-group label { display: block; margin-bottom: 8px; font-weight: 500; color: #2c3e50; }
|
||
.form-control { width: 100%; padding: 15px 20px; border: 2px solid #e9ecef; border-radius: 10px; font-size: 16px; outline: none; transition: border-color 0.3s ease; }
|
||
.form-control:focus { border-color: #3498db; }
|
||
.btn { width: 100%; padding: 15px; background: linear-gradient(135deg, #3498db, #2980b9); color: white; border: none; border-radius: 10px; cursor: pointer; font-size: 16px; font-weight: 600; transition: all 0.3s ease; }
|
||
.btn:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(52, 152, 219, 0.4); }
|
||
.alert { padding: 12px 16px; border-radius: 8px; margin-bottom: 20px; display: none; }
|
||
.alert-success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
|
||
.alert-error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
|
||
.chat-container { width: 100%; height: 100%; display: none; }
|
||
.chats-panel { width: 350px; background: #2c3e50; color: white; display: flex; flex-direction: column; }
|
||
.chats-header { padding: 20px; border-bottom: 1px solid #34495e; }
|
||
.chats-tabs { display: flex; border-bottom: 1px solid #34495e; }
|
||
.chat-tab { flex: 1; padding: 15px; text-align: center; cursor: pointer; border-bottom: 3px solid transparent; transition: all 0.3s ease; }
|
||
.chat-tab.active { border-bottom-color: #3498db; color: #3498db; font-weight: 600; }
|
||
.search-container { padding: 15px; border-bottom: 1px solid #34495e; display: none; }
|
||
.search-input { width: 100%; padding: 12px 15px; border: 1px solid #34495e; border-radius: 20px; background: #34495e; color: white; outline: none; }
|
||
.search-input::placeholder { color: #95a5a6; }
|
||
.search-results { max-height: 300px; overflow-y: auto; }
|
||
.chat-list { list-style: none; flex: 1; overflow-y: auto; }
|
||
.chat-item { padding: 15px; border-bottom: 1px solid #34495e; cursor: pointer; transition: all 0.3s ease; display: flex; justify-content: space-between; align-items: center; }
|
||
.chat-item:hover { background: #34495e; }
|
||
.chat-item.active { background: #3498db; }
|
||
.chat-info { flex: 1; }
|
||
.chat-username { font-weight: 600; margin-bottom: 5px; }
|
||
.chat-phone { font-size: 12px; color: #bdc3c7; margin-bottom: 5px; }
|
||
.chat-last-message { font-size: 13px; color: #ecf0f1; opacity: 0.8; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 200px; }
|
||
.chat-time { font-size: 11px; color: #95a5a6; }
|
||
.unread-badge { background: #e74c3c; color: white; border-radius: 50%; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; }
|
||
.chat-panel { flex: 1; display: flex; flex-direction: column; background: #f8f9fa; }
|
||
.chat-header { padding: 20px; background: white; border-bottom: 1px solid #e9ecef; display: flex; justify-content: space-between; align-items: center; }
|
||
.logout-btn { padding: 8px 16px; background: #e74c3c; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px; }
|
||
.logout-btn:hover { background: #c0392b; }
|
||
.chat-messages { flex: 1; padding: 20px; overflow-y: auto; background: #f8f9fa; }
|
||
.message { max-width: 70%; margin: 15px 0; padding: 15px 20px; border-radius: 20px; position: relative; word-wrap: break-word; animation: fadeIn 0.3s ease; }
|
||
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
|
||
.incoming { background: white; border: 1px solid #e9ecef; align-self: flex-start; border-bottom-left-radius: 5px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); }
|
||
.outgoing { background: linear-gradient(135deg, #3498db, #2980b9); color: white; align-self: flex-end; margin-left: auto; border-bottom-right-radius: 5px; box-shadow: 0 2px 5px rgba(52, 152, 219, 0.3); }
|
||
.message-sender { font-size: 12px; font-weight: 600; margin-bottom: 5px; opacity: 0.8; }
|
||
.message-content { margin: 8px 0; line-height: 1.4; }
|
||
.message-time { font-size: 11px; text-align: right; opacity: 0.7; margin-top: 5px; }
|
||
.chat-input-area { padding: 20px; background: white; border-top: 1px solid #e9ecef; display: flex; gap: 12px; align-items: center; }
|
||
.message-input { flex: 1; padding: 15px 20px; border: 2px solid #e9ecef; border-radius: 25px; outline: none; font-size: 14px; transition: border-color 0.3s ease; }
|
||
.message-input:focus { border-color: #3498db; }
|
||
.send-button { padding: 15px 25px; background: linear-gradient(135deg, #3498db, #2980b9); color: white; border: none; border-radius: 25px; cursor: pointer; font-size: 14px; font-weight: 600; transition: all 0.3s ease; }
|
||
.send-button:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(52, 152, 219, 0.4); }
|
||
.send-button:disabled { background: #bdc3c7; cursor: not-allowed; transform: none; box-shadow: none; }
|
||
.system-message { text-align: center; color: #7f8c8d; font-style: italic; margin: 15px 0; font-size: 13px; padding: 10px; background: #f8f9fa; border-radius: 10px; }
|
||
.empty-state { text-align: center; color: #7f8c8d; padding: 40px 20px; }
|
||
.empty-state h3 { margin-bottom: 10px; font-weight: 300; }
|
||
|
||
/* Стили для кнопки удаления сообщения */
|
||
.message-actions { position: absolute; top: 5px; right: 10px; opacity: 0; transition: opacity 0.3s ease; }
|
||
.message:hover .message-actions { opacity: 1; }
|
||
.delete-btn { background: none; border: none; color: #95a5a6; cursor: pointer; font-size: 14px; padding: 5px; border-radius: 3px; }
|
||
.delete-btn:hover { background: rgba(0, 0, 0, 0.1); color: #e74c3c; }
|
||
|
||
/* Стили для модального окна удаления */
|
||
.modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 1000; }
|
||
.modal { background: white; border-radius: 10px; padding: 20px; max-width: 400px; width: 90%; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); }
|
||
.modal h3 { margin-bottom: 15px; color: #2c3e50; }
|
||
.modal p { margin-bottom: 20px; color: #7f8c8d; }
|
||
.modal-buttons { display: flex; gap: 10px; justify-content: flex-end; }
|
||
.modal-btn { padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; font-size: 14px; }
|
||
.modal-btn-cancel { background: #95a5a6; color: white; }
|
||
.modal-btn-cancel:hover { background: #7f8c8d; }
|
||
.modal-btn-delete { background: #e74c3c; color: white; }
|
||
.modal-btn-delete:hover { background: #c0392b; }
|
||
.modal-btn-delete-for-me { background: #3498db; color: white; }
|
||
.modal-btn-delete-for-me:hover { background: #2980b9; }
|
||
|
||
::-webkit-scrollbar { width: 6px; }
|
||
::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 3px; }
|
||
::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 3px; }
|
||
::-webkit-scrollbar-thumb:hover { background: #a8a8a8; }
|
||
.chats-panel ::-webkit-scrollbar-track { background: #34495e; }
|
||
.chats-panel ::-webkit-scrollbar-thumb { background: #2c3e50; }
|
||
.chats-panel ::-webkit-scrollbar-thumb:hover { background: #1a252f; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- Authentication Container -->
|
||
<div id="authContainer" class="auth-container">
|
||
<div class="auth-tabs">
|
||
<div class="auth-tab active" onclick="showTab('login')">Login</div>
|
||
<div class="auth-tab" onclick="showTab('register')">Register</div>
|
||
</div>
|
||
|
||
<div id="alert" class="alert" style="display: none;"></div>
|
||
|
||
<!-- Login Form -->
|
||
<div id="loginForm" class="auth-form active">
|
||
<div class="form-group">
|
||
<label for="loginPhone">Phone Number</label>
|
||
<input type="text" id="loginPhone" class="form-control" placeholder="+1234567890">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="loginPassword">Password</label>
|
||
<input type="password" id="loginPassword" class="form-control" placeholder="Enter your password">
|
||
</div>
|
||
<button class="btn" onclick="login()">Login</button>
|
||
</div>
|
||
|
||
<!-- Register Form -->
|
||
<div id="registerForm" class="auth-form">
|
||
<div class="form-group">
|
||
<label for="registerPhone">Phone Number</label>
|
||
<input type="text" id="registerPhone" class="form-control" placeholder="+1234567890">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="registerUsername">Username</label>
|
||
<input type="text" id="registerUsername" class="form-control" placeholder="Choose a username">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="registerPassword">Password</label>
|
||
<input type="password" id="registerPassword" class="form-control" placeholder="Choose a password (min 6 characters)">
|
||
</div>
|
||
<button class="btn" onclick="register()">Register</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Chat Container -->
|
||
<div class="container" style="display: none;" id="chatContainer">
|
||
<div class="chats-panel">
|
||
<div class="chats-header">
|
||
<h2>Chats</h2>
|
||
</div>
|
||
|
||
<div class="chats-tabs">
|
||
<div class="chat-tab active" onclick="showChatTab('existing')">My Chats</div>
|
||
<div class="chat-tab" onclick="showChatTab('search')">New Chat</div>
|
||
</div>
|
||
|
||
<!-- Search Container -->
|
||
<div class="search-container" id="searchContainer">
|
||
<input type="text" id="searchInput" class="search-input" placeholder="Search by phone or username...">
|
||
<div class="search-results" id="searchResults"></div>
|
||
</div>
|
||
|
||
<!-- Existing Chats List -->
|
||
<ul class="chat-list" id="chatsList">
|
||
<!-- Chats will be populated here -->
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="chat-panel">
|
||
<div class="chat-header">
|
||
<span id="chatHeader">Select a chat to start messaging</span>
|
||
<button class="logout-btn" onclick="logout()">Logout</button>
|
||
</div>
|
||
|
||
<div class="chat-messages" id="chatMessages"></div>
|
||
|
||
<div class="chat-input-area">
|
||
<input type="text" id="messageInput" class="message-input" placeholder="Type a message..." disabled>
|
||
<button onclick="sendMessage()" class="send-button" id="sendButton" disabled>Send</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal for delete confirmation -->
|
||
<div id="deleteModal" class="modal-overlay" style="display: none;">
|
||
<div class="modal">
|
||
<h3>Delete Message</h3>
|
||
<p>How would you like to delete this message?</p>
|
||
<div class="modal-buttons">
|
||
<button class="modal-btn modal-btn-cancel" onclick="closeDeleteModal()">Cancel</button>
|
||
<button class="modal-btn modal-btn-delete-for-me" onclick="deleteMessageForMe()">Delete for me</button>
|
||
<button class="modal-btn modal-btn-delete" onclick="deleteMessageForEveryone()">Delete for everyone</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
let ws = null;
|
||
let currentUser = '';
|
||
let currentToken = '';
|
||
let currentRecipient = '';
|
||
let chats = [];
|
||
let searchResults = [];
|
||
let messageHistory = {};
|
||
let currentChatTab = 'existing';
|
||
let messageToDelete = null;
|
||
|
||
// Check for saved session on page load
|
||
window.addEventListener('DOMContentLoaded', function() {
|
||
const savedToken = localStorage.getItem('chatToken');
|
||
const savedUsername = localStorage.getItem('chatUsername');
|
||
|
||
if (savedToken && savedUsername) {
|
||
validateToken(savedToken, savedUsername);
|
||
}
|
||
});
|
||
|
||
function showTab(tabName) {
|
||
document.querySelectorAll('.auth-tab').forEach(tab => tab.classList.remove('active'));
|
||
document.querySelectorAll('.auth-form').forEach(form => form.classList.remove('active'));
|
||
|
||
if (tabName === 'login') {
|
||
document.querySelector('.auth-tab:nth-child(1)').classList.add('active');
|
||
document.getElementById('loginForm').classList.add('active');
|
||
} else {
|
||
document.querySelector('.auth-tab:nth-child(2)').classList.add('active');
|
||
document.getElementById('registerForm').classList.add('active');
|
||
}
|
||
|
||
hideAlert();
|
||
}
|
||
|
||
function showAlert(message, type) {
|
||
const alert = document.getElementById('alert');
|
||
alert.textContent = message;
|
||
alert.className = `alert alert-${type}`;
|
||
alert.style.display = 'block';
|
||
}
|
||
|
||
function hideAlert() {
|
||
document.getElementById('alert').style.display = 'none';
|
||
}
|
||
|
||
async function validateToken(token, username) {
|
||
try {
|
||
const response = await fetch('/api/validate-token', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ token }),
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
currentUser = data.username;
|
||
currentToken = token;
|
||
|
||
localStorage.setItem('chatToken', token);
|
||
localStorage.setItem('chatUsername', data.username);
|
||
|
||
document.getElementById('authContainer').style.display = 'none';
|
||
document.getElementById('chatContainer').style.display = 'flex';
|
||
document.getElementById('chatHeader').textContent = `Welcome, ${currentUser}`;
|
||
|
||
loadChats();
|
||
connectWebSocket();
|
||
} else {
|
||
localStorage.removeItem('chatToken');
|
||
localStorage.removeItem('chatUsername');
|
||
showAlert('Session expired. Please login again.', 'error');
|
||
}
|
||
} catch (error) {
|
||
console.error('Token validation failed:', error);
|
||
localStorage.removeItem('chatToken');
|
||
localStorage.removeItem('chatUsername');
|
||
showAlert('Session validation failed. Please login again.', 'error');
|
||
}
|
||
}
|
||
|
||
async function loadChats() {
|
||
try {
|
||
console.log('Loading chats for user:', currentUser);
|
||
const response = await fetch('/api/chats', {
|
||
headers: { 'Authorization': `Bearer ${currentToken}` }
|
||
});
|
||
|
||
console.log('Chats response status:', response.status);
|
||
if (response.ok) {
|
||
chats = await response.json();
|
||
console.log('Loaded chats:', chats);
|
||
renderChatsList();
|
||
} else {
|
||
console.error('Failed to load chats, status:', response.status);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading chats:', error);
|
||
}
|
||
}
|
||
|
||
// Function to mark messages as read
|
||
async function markMessagesAsRead(fromUser) {
|
||
try {
|
||
const response = await fetch('/api/mark-read', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${currentToken}`
|
||
},
|
||
body: JSON.stringify({
|
||
from_user: fromUser,
|
||
to_user: currentUser
|
||
})
|
||
});
|
||
|
||
const data = await response.json();
|
||
if (data.success) {
|
||
console.log(`✅ Marked messages as read from ${fromUser}`);
|
||
// Обновляем список чатов чтобы убрать красный badge
|
||
loadChats();
|
||
} else {
|
||
console.error('❌ Failed to mark messages as read:', data.error);
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ Error marking messages as read:', error);
|
||
}
|
||
}
|
||
|
||
// Function to delete message
|
||
async function deleteMessage(messageId, forEveryone) {
|
||
try {
|
||
const response = await fetch('/api/delete-message', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${currentToken}`
|
||
},
|
||
body: JSON.stringify({
|
||
message_id: messageId,
|
||
for_everyone: forEveryone
|
||
})
|
||
});
|
||
|
||
const data = await response.json();
|
||
if (data.success) {
|
||
console.log(`✅ Message deleted: ${data.message}`);
|
||
showAlert(data.message, 'success');
|
||
|
||
// Remove message from UI
|
||
const messageElement = document.querySelector(`[data-message-id="${messageId}"]`);
|
||
if (messageElement) {
|
||
messageElement.remove();
|
||
}
|
||
|
||
// Update chat history
|
||
if (messageHistory[currentRecipient]) {
|
||
messageHistory[currentRecipient] = messageHistory[currentRecipient].filter(
|
||
msg => msg.id !== messageId
|
||
);
|
||
}
|
||
|
||
// Reload chats to update last message
|
||
loadChats();
|
||
} else {
|
||
console.error('❌ Failed to delete message:', data.message);
|
||
showAlert(data.message, 'error');
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ Error deleting message:', error);
|
||
showAlert('Error deleting message', 'error');
|
||
}
|
||
}
|
||
|
||
// Modal functions
|
||
function showDeleteModal(messageId) {
|
||
messageToDelete = messageId;
|
||
document.getElementById('deleteModal').style.display = 'flex';
|
||
}
|
||
|
||
function closeDeleteModal() {
|
||
messageToDelete = null;
|
||
document.getElementById('deleteModal').style.display = 'none';
|
||
}
|
||
|
||
function deleteMessageForMe() {
|
||
if (messageToDelete) {
|
||
deleteMessage(messageToDelete, false);
|
||
closeDeleteModal();
|
||
}
|
||
}
|
||
|
||
function deleteMessageForEveryone() {
|
||
if (messageToDelete) {
|
||
deleteMessage(messageToDelete, true);
|
||
closeDeleteModal();
|
||
}
|
||
}
|
||
|
||
function renderChatsList() {
|
||
const chatsList = document.getElementById('chatsList');
|
||
chatsList.innerHTML = '';
|
||
|
||
console.log('Rendering chats:', chats);
|
||
|
||
if (chats.length === 0) {
|
||
const emptyState = document.createElement('div');
|
||
emptyState.className = 'empty-state';
|
||
emptyState.innerHTML = `
|
||
<h3>No chats yet</h3>
|
||
<p>Start a new conversation by clicking "New Chat"</p>
|
||
`;
|
||
chatsList.appendChild(emptyState);
|
||
return;
|
||
}
|
||
|
||
chats.forEach(chat => {
|
||
const li = document.createElement('li');
|
||
li.className = 'chat-item';
|
||
if (chat.username === currentRecipient) {
|
||
li.classList.add('active');
|
||
}
|
||
|
||
const lastMessageTime = chat.last_message_time ?
|
||
formatTime(chat.last_message_time) : '';
|
||
const lastMessage = chat.last_message || 'No messages yet';
|
||
|
||
li.innerHTML = `
|
||
<div class="chat-info">
|
||
<div class="chat-username">${chat.username}</div>
|
||
<div class="chat-phone">${chat.phone}</div>
|
||
<div class="chat-last-message">${lastMessage}</div>
|
||
</div>
|
||
<div class="chat-meta">
|
||
<div class="chat-time">${lastMessageTime}</div>
|
||
${chat.unread_count > 0 ? `<div class="unread-badge">${chat.unread_count}</div>` : ''}
|
||
</div>
|
||
`;
|
||
|
||
li.onclick = () => selectChat(chat.username, chat.phone);
|
||
chatsList.appendChild(li);
|
||
});
|
||
}
|
||
|
||
function showChatTab(tab) {
|
||
currentChatTab = tab;
|
||
document.querySelectorAll('.chat-tab').forEach(tab => tab.classList.remove('active'));
|
||
|
||
if (tab === 'existing') {
|
||
document.querySelector('.chat-tab:nth-child(1)').classList.add('active');
|
||
document.getElementById('searchContainer').style.display = 'none';
|
||
renderChatsList();
|
||
} else {
|
||
document.querySelector('.chat-tab:nth-child(2)').classList.add('active');
|
||
document.getElementById('searchContainer').style.display = 'block';
|
||
document.getElementById('searchResults').innerHTML = '<div class="empty-state"><p>Search for users by phone or username</p></div>';
|
||
document.getElementById('searchInput').value = '';
|
||
}
|
||
}
|
||
|
||
let searchTimeout;
|
||
document.getElementById('searchInput').addEventListener('input', function(e) {
|
||
clearTimeout(searchTimeout);
|
||
const query = e.target.value.trim();
|
||
|
||
if (query.length < 2) {
|
||
document.getElementById('searchResults').innerHTML = '<div class="empty-state"><p>Enter at least 2 characters to search</p></div>';
|
||
return;
|
||
}
|
||
|
||
searchTimeout = setTimeout(() => searchUsers(query), 300);
|
||
});
|
||
|
||
async function searchUsers(query) {
|
||
try {
|
||
const response = await fetch(`/api/search-users?q=${encodeURIComponent(query)}`, {
|
||
headers: { 'Authorization': `Bearer ${currentToken}` }
|
||
});
|
||
|
||
if (response.ok) {
|
||
searchResults = await response.json();
|
||
renderSearchResults();
|
||
} else {
|
||
console.error('Search failed');
|
||
}
|
||
} catch (error) {
|
||
console.error('Error searching users:', error);
|
||
}
|
||
}
|
||
|
||
function renderSearchResults() {
|
||
const searchResultsContainer = document.getElementById('searchResults');
|
||
searchResultsContainer.innerHTML = '';
|
||
|
||
if (searchResults.length === 0) {
|
||
searchResultsContainer.innerHTML = '<div class="empty-state"><p>No users found</p></div>';
|
||
return;
|
||
}
|
||
|
||
searchResults.forEach(user => {
|
||
const div = document.createElement('div');
|
||
div.className = 'chat-item';
|
||
div.innerHTML = `
|
||
<div class="chat-info">
|
||
<div class="chat-username">${user.username}</div>
|
||
<div class="chat-phone">${user.phone}</div>
|
||
<div class="chat-last-message">Click to start chatting</div>
|
||
</div>
|
||
`;
|
||
|
||
div.onclick = () => {
|
||
selectChat(user.username, user.phone);
|
||
showChatTab('existing');
|
||
document.getElementById('searchInput').value = '';
|
||
};
|
||
|
||
searchResultsContainer.appendChild(div);
|
||
});
|
||
}
|
||
|
||
async function register() {
|
||
const phone = document.getElementById('registerPhone').value.trim();
|
||
const username = document.getElementById('registerUsername').value.trim();
|
||
const password = document.getElementById('registerPassword').value;
|
||
|
||
if (!phone || !username || !password) {
|
||
showAlert('Please fill all fields', 'error');
|
||
return;
|
||
}
|
||
|
||
if (password.length < 6) {
|
||
showAlert('Password must be at least 6 characters', 'error');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch('/api/register', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ phone, username, password }),
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
localStorage.setItem('chatToken', data.token);
|
||
localStorage.setItem('chatUsername', data.username);
|
||
|
||
currentUser = data.username;
|
||
currentToken = data.token;
|
||
|
||
document.getElementById('authContainer').style.display = 'none';
|
||
document.getElementById('chatContainer').style.display = 'flex';
|
||
document.getElementById('chatHeader').textContent = `Welcome, ${currentUser}`;
|
||
|
||
loadChats();
|
||
connectWebSocket();
|
||
|
||
showAlert('Registration successful!', 'success');
|
||
} else {
|
||
showAlert(data.message, 'error');
|
||
}
|
||
} catch (error) {
|
||
showAlert('Registration failed. Please try again.', 'error');
|
||
}
|
||
}
|
||
|
||
async function login() {
|
||
const phone = document.getElementById('loginPhone').value.trim();
|
||
const password = document.getElementById('loginPassword').value;
|
||
|
||
if (!phone || !password) {
|
||
showAlert('Please fill all fields', 'error');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch('/api/login', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ phone, password }),
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
localStorage.setItem('chatToken', data.token);
|
||
localStorage.setItem('chatUsername', data.username);
|
||
|
||
currentUser = data.username;
|
||
currentToken = data.token;
|
||
|
||
document.getElementById('authContainer').style.display = 'none';
|
||
document.getElementById('chatContainer').style.display = 'flex';
|
||
document.getElementById('chatHeader').textContent = `Welcome, ${currentUser}`;
|
||
|
||
loadChats();
|
||
connectWebSocket();
|
||
} else {
|
||
showAlert(data.message, 'error');
|
||
}
|
||
} catch (error) {
|
||
showAlert('Login failed. Please try again.', 'error');
|
||
}
|
||
}
|
||
|
||
function logout() {
|
||
localStorage.removeItem('chatToken');
|
||
localStorage.removeItem('chatUsername');
|
||
|
||
if (ws) ws.close();
|
||
|
||
currentUser = '';
|
||
currentToken = '';
|
||
currentRecipient = '';
|
||
chats = [];
|
||
searchResults = [];
|
||
messageHistory = {};
|
||
|
||
document.getElementById('authContainer').style.display = 'block';
|
||
document.getElementById('chatContainer').style.display = 'none';
|
||
|
||
document.getElementById('loginPhone').value = '';
|
||
document.getElementById('loginPassword').value = '';
|
||
document.getElementById('registerPhone').value = '';
|
||
document.getElementById('registerUsername').value = '';
|
||
document.getElementById('registerPassword').value = '';
|
||
|
||
showTab('login');
|
||
}
|
||
|
||
function connectWebSocket() {
|
||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||
const wsUrl = `${protocol}//${window.location.host}/ws`;
|
||
|
||
ws = new WebSocket(wsUrl);
|
||
|
||
ws.onopen = () => {
|
||
console.log('WebSocket connected');
|
||
ws.send(JSON.stringify({
|
||
type: 'auth',
|
||
token: currentToken
|
||
}));
|
||
addSystemMessage('Connected to chat server');
|
||
};
|
||
|
||
ws.onmessage = (event) => {
|
||
console.log('Received message:', event.data);
|
||
try {
|
||
const data = JSON.parse(event.data);
|
||
|
||
if (data.type === 'error') {
|
||
showAlert(data.content, 'error');
|
||
if (data.content.includes('Invalid') || data.content.includes('expired') || data.content.includes('auth')) {
|
||
logout();
|
||
}
|
||
} else if (data.type === 'system') {
|
||
addSystemMessage(data.content);
|
||
} else if (data.type === 'message_deleted') {
|
||
// Handle message deletion from server
|
||
const messageElement = document.querySelector(`[data-message-id="${data.message_id}"]`);
|
||
if (messageElement) {
|
||
messageElement.remove();
|
||
}
|
||
|
||
// Update chat history
|
||
if (messageHistory[currentRecipient]) {
|
||
messageHistory[currentRecipient] = messageHistory[currentRecipient].filter(
|
||
msg => msg.id !== data.message_id
|
||
);
|
||
}
|
||
|
||
// Reload chats to update last message
|
||
loadChats();
|
||
} else {
|
||
handleChatMessage(data);
|
||
}
|
||
} catch (e) {
|
||
console.error('Error parsing message:', e, 'Raw data:', event.data);
|
||
}
|
||
};
|
||
|
||
ws.onclose = () => {
|
||
console.log('WebSocket disconnected');
|
||
addSystemMessage('Disconnected from server');
|
||
document.getElementById('sendButton').disabled = true;
|
||
document.getElementById('messageInput').disabled = true;
|
||
};
|
||
|
||
ws.onerror = (error) => {
|
||
console.error('WebSocket error:', error);
|
||
addSystemMessage('Connection error');
|
||
};
|
||
}
|
||
|
||
async function selectChat(username, phone) {
|
||
currentRecipient = username;
|
||
|
||
// Помечаем сообщения как прочитанные при открытии чата
|
||
await markMessagesAsRead(username);
|
||
|
||
document.querySelectorAll('.chat-item').forEach(item => item.classList.remove('active'));
|
||
|
||
document.getElementById('chatHeader').textContent = `Chat with ${username} (${phone})`;
|
||
|
||
document.getElementById('sendButton').disabled = false;
|
||
document.getElementById('messageInput').disabled = false;
|
||
document.getElementById('messageInput').focus();
|
||
|
||
displayChatHistory(username);
|
||
}
|
||
|
||
function displayChatHistory(username) {
|
||
const chatMessages = document.getElementById('chatMessages');
|
||
chatMessages.innerHTML = '';
|
||
|
||
if (messageHistory[username]) {
|
||
messageHistory[username].forEach(msg => displayMessage(msg, false));
|
||
} else {
|
||
addSystemMessage('No message history found');
|
||
}
|
||
|
||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||
}
|
||
|
||
function handleChatMessage(msg) {
|
||
console.log('Handling chat message:', msg);
|
||
const otherUser = msg.from_user === currentUser ? msg.to_user : msg.from_user;
|
||
|
||
if (!messageHistory[otherUser]) {
|
||
messageHistory[otherUser] = [];
|
||
}
|
||
|
||
const messageExists = messageHistory[otherUser].some(
|
||
existingMsg => existingMsg.id === msg.id
|
||
);
|
||
|
||
if (!messageExists) {
|
||
messageHistory[otherUser].push(msg);
|
||
}
|
||
|
||
loadChats();
|
||
|
||
if (otherUser === currentRecipient) {
|
||
displayMessage(msg, true);
|
||
// Если сообщение пришло в открытый чат, сразу помечаем как прочитанное
|
||
if (msg.from_user === currentRecipient) {
|
||
markMessagesAsRead(currentRecipient);
|
||
}
|
||
}
|
||
}
|
||
|
||
function displayMessage(msg, scrollToBottom) {
|
||
const chatMessages = document.getElementById('chatMessages');
|
||
const messageDiv = document.createElement('div');
|
||
messageDiv.className = `message ${msg.from_user === currentUser ? 'outgoing' : 'incoming'}`;
|
||
messageDiv.setAttribute('data-message-id', msg.id);
|
||
|
||
if (msg.message_type === 'system') {
|
||
messageDiv.className = 'system-message';
|
||
messageDiv.textContent = msg.content;
|
||
} else {
|
||
const isOutgoing = msg.from_user === currentUser;
|
||
|
||
if (!isOutgoing) {
|
||
const senderDiv = document.createElement('div');
|
||
senderDiv.className = 'message-sender';
|
||
senderDiv.textContent = msg.from_user;
|
||
messageDiv.appendChild(senderDiv);
|
||
}
|
||
|
||
const contentDiv = document.createElement('div');
|
||
contentDiv.className = 'message-content';
|
||
contentDiv.textContent = msg.content;
|
||
messageDiv.appendChild(contentDiv);
|
||
|
||
const timeDiv = document.createElement('div');
|
||
timeDiv.className = 'message-time';
|
||
const date = new Date(msg.timestamp);
|
||
timeDiv.textContent = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||
messageDiv.appendChild(timeDiv);
|
||
|
||
// Add delete button for user's own messages or all messages
|
||
const actionsDiv = document.createElement('div');
|
||
actionsDiv.className = 'message-actions';
|
||
const deleteBtn = document.createElement('button');
|
||
deleteBtn.className = 'delete-btn';
|
||
deleteBtn.innerHTML = '×';
|
||
deleteBtn.title = 'Delete message';
|
||
deleteBtn.onclick = (e) => {
|
||
e.stopPropagation();
|
||
showDeleteModal(msg.id);
|
||
};
|
||
actionsDiv.appendChild(deleteBtn);
|
||
messageDiv.appendChild(actionsDiv);
|
||
}
|
||
|
||
chatMessages.appendChild(messageDiv);
|
||
|
||
if (scrollToBottom) {
|
||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||
}
|
||
}
|
||
|
||
function addSystemMessage(content) {
|
||
const chatMessages = document.getElementById('chatMessages');
|
||
const systemDiv = document.createElement('div');
|
||
systemDiv.className = 'system-message';
|
||
systemDiv.textContent = content;
|
||
chatMessages.appendChild(systemDiv);
|
||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||
}
|
||
|
||
function sendMessage() {
|
||
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
||
showAlert('Please connect to the server first', 'error');
|
||
return;
|
||
}
|
||
|
||
if (!currentRecipient) {
|
||
showAlert('Please select a user to chat with', 'error');
|
||
return;
|
||
}
|
||
|
||
const input = document.getElementById('messageInput');
|
||
const content = input.value.trim();
|
||
|
||
if (!content) return;
|
||
|
||
const message = {
|
||
id: generateId(),
|
||
from_user: currentUser,
|
||
to_user: currentRecipient,
|
||
content: content,
|
||
timestamp: Date.now(),
|
||
message_type: 'text'
|
||
};
|
||
|
||
console.log('Sending message:', message);
|
||
ws.send(JSON.stringify(message));
|
||
input.value = '';
|
||
|
||
setTimeout(() => loadChats(), 100);
|
||
}
|
||
|
||
function generateId() {
|
||
return Date.now().toString(36) + Math.random().toString(36).substr(2);
|
||
}
|
||
|
||
function formatTime(timestamp) {
|
||
if (!timestamp) return '';
|
||
|
||
const date = new Date(timestamp);
|
||
const now = new Date();
|
||
const diff = now - date;
|
||
|
||
if (diff < 24 * 60 * 60 * 1000) {
|
||
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||
} else {
|
||
return date.toLocaleDateString([], { month: 'short', day: 'numeric' });
|
||
}
|
||
}
|
||
|
||
document.getElementById('messageInput').addEventListener('keypress', function(e) {
|
||
if (e.key === 'Enter') sendMessage();
|
||
});
|
||
|
||
document.getElementById('loginPassword').addEventListener('keypress', function(e) {
|
||
if (e.key === 'Enter') login();
|
||
});
|
||
|
||
document.getElementById('registerPassword').addEventListener('keypress', function(e) {
|
||
if (e.key === 'Enter') register();
|
||
});
|
||
|
||
// Close modal when clicking outside
|
||
document.getElementById('deleteModal').addEventListener('click', function(e) {
|
||
if (e.target === this) {
|
||
closeDeleteModal();
|
||
}
|
||
});
|
||
|
||
document.getElementById('loginPhone').focus();
|
||
</script>
|
||
</body>
|
||
</html> |