more refactor in frontend javascript
Continuous integration / Check (push) Successful in 35s
Continuous integration / Rustfmt (push) Successful in 31s
Continuous integration / build (push) Successful in 30s
Continuous integration / build-release (push) Successful in 51s

This commit is contained in:
wt committed 2025-11-28 02:28:52 +07:00
1 parent 7f7936d37f
commit b833266664
14 files changed
+2345 -2366

No files matched your search

+1256
View File
File diff suppressed because it is too large. Load diff
+18 -2366
View File
File diff suppressed because it is too large. Load diff
+72
View File
@@ -0,0 +1,72 @@
function updateAllAvatars() {
const avatarContent = currentUserAvatar
? `<img src="${currentUserAvatar}" alt="${currentUser}">`
: currentUser.charAt(0).toUpperCase();
// Update all avatar locations (убран profileHeaderAvatarMain)
const avatarLocations = [
"profileHeaderAvatar",
"profileHeaderAvatarDesktop",
"currentUserAvatar",
];
avatarLocations.forEach((id) => {
const element = document.getElementById(id);
if (element) {
element.innerHTML = avatarContent;
}
});
// Update profile modal avatar
updateProfileModal();
}
// Function to upload avatar
async function uploadAvatar(file) {
const formData = new FormData();
formData.append("avatar", file);
try {
const response = await fetch("/api/upload-avatar", {
method: "POST",
headers: {
Authorization: `Bearer ${currentToken}`,
},
body: formData,
});
const data = await response.json();
if (data.success) {
currentUserAvatar = data.avatar_url;
updateAllAvatars();
showAlert("Avatar uploaded successfully!", "success");
loadChats(); // Refresh chats to show updated avatar
} else {
showAlert(data.message, "error");
}
} catch (error) {
console.error("Error uploading avatar:", error);
showAlert("Error uploading avatar", "error");
}
}
// Avatar upload handler
document.getElementById("avatarInput").addEventListener("change", function (e) {
const file = e.target.files[0];
if (file) {
// Validate file type
if (!file.type.startsWith("image/")) {
showAlert("Please select an image file", "error");
return;
}
// Validate file size (5MB)
if (file.size > 5 * 1024 * 1024) {
showAlert("Image must be less than 5MB", "error");
return;
}
uploadAvatar(file);
e.target.value = ""; // Reset input
}
});
+184
View File
@@ -0,0 +1,184 @@
async function selectChat(username, phone, avatarUrl) {
if (!username) return;
currentRecipient = username;
// Update mobile header
document.getElementById("mobileChatTitle").textContent = username;
// Update chat header with recipient info
const chatHeaderInfo = document.querySelector(".chat-header-info");
const avatarContent = avatarUrl
? `<img src="${avatarUrl}" alt="${username}">`
: username.charAt(0).toUpperCase();
chatHeaderInfo.innerHTML = `
<button class="back-btn" onclick="showChatsPanel()">←</button>
<div class="chat-header-avatar">${avatarContent}</div>
<div class="chat-header-text">
<div class="chat-header-username">${username}</div>
<div class="chat-header-phone">${phone}</div>
</div>
`;
// Помечаем сообщения как прочитанные при открытии чата
await markMessagesAsRead(username);
// Update active chat in list
document.querySelectorAll(".chat-item").forEach((item) => {
item.classList.remove("active");
const usernameElement = item.querySelector(".chat-username");
if (usernameElement && usernameElement.textContent === username) {
item.classList.add("active");
}
});
// Enable message input
document.getElementById("sendButton").disabled = false;
document.getElementById("messageInput").disabled = false;
// Focus on input after a short delay to ensure UI is updated
setTimeout(() => {
document.getElementById("messageInput").focus();
}, 100);
displayChatHistory(username);
}
function displayChatHistory(username) {
const chatMessages = document.getElementById("chatMessages");
chatMessages.innerHTML = "";
if (messageHistory[username] && messageHistory[username].length > 0) {
messageHistory[username].forEach((msg) => displayMessage(msg, false));
// Scroll to bottom after rendering
setTimeout(() => {
chatMessages.scrollTop = chatMessages.scrollHeight;
}, 50);
} else {
chatMessages.innerHTML = `
<div class="empty-state">
<h3>Start a conversation</h3>
<p>Send a message to start chatting with ${username}</p>
</div>
`;
}
}
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";
const avatarContent = chat.avatar_url
? `<img src="${chat.avatar_url}" alt="${chat.username}">`
: chat.username.charAt(0).toUpperCase();
li.innerHTML = `
<div class="chat-avatar">${avatarContent}</div>
<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, chat.avatar_url);
if (isMobile) {
hideChatsPanel();
}
};
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 = "";
}
}
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);
}
}
// Mobile navigation functions
function toggleChatsPanel() {
const chatsPanel = document.getElementById("chatsPanel");
chatsPanel.classList.toggle("active");
}
function showChatsPanel() {
const chatsPanel = document.getElementById("chatsPanel");
chatsPanel.classList.add("active");
}
function hideChatsPanel() {
const chatsPanel = document.getElementById("chatsPanel");
chatsPanel.classList.remove("active");
}
// Close chats panel when clicking on overlay (mobile)
document.getElementById("chatsPanel").addEventListener("click", function (e) {
if (e.target === this && isMobile) {
hideChatsPanel();
}
});
+128
View File
@@ -0,0 +1,128 @@
function reconnectWebSocket() {
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
reconnectAttempts++;
updateConnectionStatus(
"connecting",
`Reconnecting... (${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`,
);
reconnectTimeout = setTimeout(() => {
console.log(`Attempting to reconnect... (${reconnectAttempts})`);
connectWebSocket();
}, RECONNECT_DELAY);
} else {
updateConnectionStatus(
"disconnected",
"Failed to connect. Please refresh the page.",
);
showAlert("Connection lost. Please refresh the page.", "error");
}
}
function clearReconnection() {
if (reconnectTimeout) {
clearTimeout(reconnectTimeout);
reconnectTimeout = null;
}
reconnectAttempts = 0;
}
function updateConnectionStatus(status, message = "") {
const statusElement = document.getElementById("connectionStatus");
statusElement.textContent = message || status;
statusElement.className = `connection-status ${status}`;
statusElement.style.display = "block";
if (status === "connected") {
setTimeout(() => {
statusElement.style.display = "none";
}, 3000);
}
}
function connectWebSocket() {
// Clear any existing reconnection attempts
clearReconnection();
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const wsUrl = `${protocol}//${window.location.host}/ws`;
updateConnectionStatus("connecting", "Connecting...");
ws = new WebSocket(wsUrl);
ws.onopen = () => {
console.log("WebSocket connected");
updateConnectionStatus("connected", "Connected");
clearReconnection();
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 = (event) => {
console.log("WebSocket disconnected:", event.code, event.reason);
updateConnectionStatus("disconnected", "Disconnected");
addSystemMessage("Disconnected from server");
document.getElementById("sendButton").disabled = true;
document.getElementById("messageInput").disabled = true;
// Attempt to reconnect if this wasn't a normal closure
if (event.code !== 1000 && event.code !== 1001) {
reconnectWebSocket();
}
};
ws.onerror = (error) => {
console.error("WebSocket error:", error);
updateConnectionStatus("disconnected", "Connection error");
addSystemMessage("Connection error");
};
}
+19
View File
@@ -0,0 +1,19 @@
document.getElementById("deleteModal").addEventListener("click", function (e) {
if (e.target === this) {
closeDeleteModal();
}
});
// Modal functions
function showDeleteModal(messageId) {
messageToDelete = messageId;
document.getElementById("deleteModal").style.display = "flex";
document.getElementById("deleteModal").style.zIndex = 10;
document.body.classList.add("modal-open");
}
function closeDeleteModal() {
messageToDelete = null;
document.getElementById("deleteModal").style.display = "none";
document.body.classList.remove("modal-open");
}
+126
View File
@@ -0,0 +1,126 @@
let ws = null;
let currentUser = "";
let currentToken = "";
let currentRecipient = "";
let currentUserAvatar = "";
let currentUserPhone = "";
let chats = [];
let searchResults = [];
let messageHistory = {};
let currentChatTab = "existing";
let messageToDelete = null;
let isMobile = window.innerWidth <= 768;
let reconnectTimeout = null;
let reconnectAttempts = 0;
const MAX_RECONNECT_ATTEMPTS = 5;
const RECONNECT_DELAY = 3000;
function changePhoneNumber() {
const newPhone = prompt("Enter your new phone number:", currentUserPhone);
if (newPhone && newPhone.trim() !== "") {
// In a real app, you would send this to the server
currentUserPhone = newPhone.trim();
localStorage.setItem("chatUserPhone", currentUserPhone);
updateProfileModal();
showAlert("Phone number updated successfully!", "success");
}
}
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";
// Auto hide after 5 seconds
setTimeout(hideAlert, 5000);
}
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;
currentUserAvatar = data.avatar_url;
localStorage.setItem("chatToken", token);
localStorage.setItem("chatUsername", data.username);
document.getElementById("authContainer").style.display = "none";
document.getElementById("chatContainer").style.display = "flex";
updateUserProfile();
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");
}
}
function updateUserProfile() {
document.getElementById("currentUsername").textContent = currentUser;
updateAllAvatars();
}
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",
});
}
}
+94
View File
@@ -0,0 +1,94 @@
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);
localStorage.setItem("chatUserPhone", phone);
currentUser = data.username;
currentToken = data.token;
currentUserPhone = phone;
// Get user profile to load avatar
const profileResponse = await fetch("/api/profile", {
headers: {
Authorization: `Bearer ${currentToken}`,
},
});
if (profileResponse.ok) {
const profile = await profileResponse.json();
currentUserAvatar = profile.avatar_url;
}
document.getElementById("authContainer").style.display = "none";
document.getElementById("chatContainer").style.display = "flex";
updateUserProfile();
loadChats();
connectWebSocket();
} else {
showAlert(data.message, "error");
}
} catch (error) {
showAlert("Login failed. Please try again.", "error");
}
}
function logout() {
localStorage.removeItem("chatToken");
localStorage.removeItem("chatUsername");
localStorage.removeItem("chatUserPhone");
closeProfileModal();
if (ws) {
ws.close();
ws = null;
}
clearReconnection();
currentUser = "";
currentToken = "";
currentRecipient = "";
currentUserAvatar = "";
currentUserPhone = "";
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");
}
document
.getElementById("loginPassword")
.addEventListener("keypress", function (e) {
if (e.key === "Enter") login();
});
document.getElementById("loginPhone").focus();
+245
View File
@@ -0,0 +1,245 @@
// 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");
}
}
// 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 deleteMessageForMe() {
if (messageToDelete) {
deleteMessage(messageToDelete, false);
closeDeleteModal();
}
}
function deleteMessageForEveryone() {
if (messageToDelete) {
deleteMessage(messageToDelete, true);
closeDeleteModal();
}
}
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");
// Remove empty state if it exists
const emptyState = chatMessages.querySelector(".empty-state");
if (emptyState) {
emptyState.remove();
}
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
if (msg.from_user === currentUser) {
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) => {
console.log("delete message button clicked");
e.stopPropagation();
showDeleteModal(msg.id);
};
actionsDiv.appendChild(deleteBtn);
messageDiv.appendChild(actionsDiv);
}
}
chatMessages.appendChild(messageDiv);
console.log("message added");
if (scrollToBottom) {
setTimeout(() => {
chatMessages.scrollTop = chatMessages.scrollHeight;
}, 100);
}
}
function addSystemMessage(content) {
const chatMessages = document.getElementById("chatMessages");
// Remove empty state if it exists
const emptyState = chatMessages.querySelector(".empty-state");
if (emptyState) {
emptyState.remove();
}
const systemDiv = document.createElement("div");
systemDiv.className = "system-message";
systemDiv.textContent = content;
chatMessages.appendChild(systemDiv);
setTimeout(() => {
chatMessages.scrollTop = chatMessages.scrollHeight;
}, 100);
}
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 = "";
input.style.height = "auto";
setTimeout(() => loadChats(), 100);
}
// Event listeners
document
.getElementById("messageInput")
.addEventListener("keydown", function (e) {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
+37
View File
@@ -0,0 +1,37 @@
function openProfileModal() {
updateProfileModal();
document.getElementById("profileModal").style.display = "flex";
document.body.classList.add("modal-open");
}
function closeProfileModal() {
document.getElementById("profileModal").style.display = "none";
document.body.classList.remove("modal-open");
}
function updateProfileModal() {
// Update profile modal content
document.getElementById("profileModalName").textContent = currentUser;
document.getElementById("profileModalUsername").textContent = currentUser;
document.getElementById("profileModalPhone").textContent = currentUserPhone;
document.getElementById("profileModalPhoneNumber").textContent =
currentUserPhone;
// Update large avatar in modal
const modalAvatar = document.getElementById("profileModalAvatar");
if (currentUserAvatar) {
modalAvatar.innerHTML = `<img src="${currentUserAvatar}" alt="${currentUser}">`;
} else {
modalAvatar.innerHTML = currentUser.charAt(0).toUpperCase();
}
// Add the upload overlay
modalAvatar.innerHTML +=
'<div class="avatar-upload-overlay">Change Photo</div>';
}
document.getElementById("profileModal").addEventListener("click", function (e) {
if (e.target === this) {
closeProfileModal();
}
});
+54
View File
@@ -0,0 +1,54 @@
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);
localStorage.setItem("chatUserPhone", phone);
currentUser = data.username;
currentToken = data.token;
currentUserPhone = phone;
document.getElementById("authContainer").style.display = "none";
document.getElementById("chatContainer").style.display = "flex";
updateUserProfile();
loadChats();
connectWebSocket();
showAlert("Registration successful!", "success");
} else {
showAlert(data.message, "error");
}
} catch (error) {
showAlert("Registration failed. Please try again.", "error");
}
}
document
.getElementById("registerPassword")
.addEventListener("keypress", function (e) {
if (e.key === "Enter") register();
});
+75
View File
@@ -0,0 +1,75 @@
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";
const avatarContent = user.avatar_url
? `<img src="${user.avatar_url}" alt="${user.username}">`
: user.username.charAt(0).toUpperCase();
div.innerHTML = `
<div class="chat-avatar">${avatarContent}</div>
<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, user.avatar_url);
showChatTab("existing");
document.getElementById("searchInput").value = "";
if (isMobile) {
hideChatsPanel();
}
};
searchResultsContainer.appendChild(div);
});
}
+10
View File
@@ -0,0 +1,10 @@
let currentTheme = localStorage.getItem("theme") || "light";
// Initialize theme
document.body.setAttribute("data-theme", currentTheme);
function toggleTheme() {
currentTheme = currentTheme === "light" ? "dark" : "light";
document.body.setAttribute("data-theme", currentTheme);
localStorage.setItem("theme", currentTheme);
}
+27
View File
@@ -0,0 +1,27 @@
// Check for saved session on page load
window.addEventListener("DOMContentLoaded", function () {
const savedToken = localStorage.getItem("chatToken");
const savedUsername = localStorage.getItem("chatUsername");
const savedPhone = localStorage.getItem("chatUserPhone");
if (savedToken && savedUsername) {
currentUserPhone = savedPhone;
validateToken(savedToken, savedUsername);
}
// Auto-resize textarea
const messageInput = document.getElementById("messageInput");
messageInput.addEventListener("input", function () {
this.style.height = "auto";
this.style.height = Math.min(this.scrollHeight, 100) + "px";
});
// Update mobile detection on resize
window.addEventListener("resize", function () {
isMobile = window.innerWidth <= 768;
if (!isMobile) {
// On desktop, ensure chats panel is visible
document.getElementById("chatsPanel").classList.remove("active");
}
});
});