// detect if we need to point to a production backend (Render) const API_BASE = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' ? '' : 'https://manit-chat-app.onrender.com'; // Production URL fallback const socket = io(API_BASE); // State let currentUser = null; let activeChatId = null; let currentGroups = []; let currentUsers = []; let communityPosts = []; let userFollowing = []; let activeCommunityTab = 'global'; let unreadCounts = {}; // --- Utility Helpers --- function escapeHTML(str) { const p = document.createElement('p'); p.textContent = str; return p.innerHTML; } function parseMessageContent(content) { // Escape HTML first let escaped = escapeHTML(content); // Regex for code blocks: ```code``` const codeBlockRegex = /```([\s\S]*?)```/g; escaped = escaped.replace(codeBlockRegex, (match, code) => { return `
${code.trim()}`;
});
// Regex for inline code: `code`
const inlineCodeRegex = /`([^`]+)`/g;
escaped = escaped.replace(inlineCodeRegex, (match, code) => {
return `${code}`;
});
return escaped;
}
function copyMessage(text, btn) {
navigator.clipboard.writeText(text).then(() => {
const icon = btn.querySelector('i');
const oldClass = icon.className;
icon.className = 'fas fa-check';
btn.classList.add('copied');
setTimeout(() => {
icon.className = oldClass;
btn.classList.remove('copied');
}, 2000);
}).catch(err => {
console.error('Copy failed:', err);
});
}
// DOM Elements
const loginPage = document.getElementById('login-page');
const chatPage = document.getElementById('chat-page');
const chatBackBtn = document.getElementById('chat-back-btn');
const loginBtn = document.getElementById('login-btn');
const loginError = document.getElementById('login-error');
const chatList = document.getElementById('chat-list');
const messagesContainer = document.getElementById('messages');
const messageInput = document.getElementById('message-input');
const sendMsgBtn = document.getElementById('send-msg-btn');
const newGroupBtn = document.getElementById('new-group-btn');
const groupModal = document.getElementById('group-modal');
const confirmGroupBtn = document.getElementById('confirm-group');
const cancelGroupBtn = document.getElementById('cancel-group');
const groupNameInput = document.getElementById('group-name-input');
const userAvatar = document.getElementById('user-avatar');
const avatarPlaceholder = document.getElementById('avatar-placeholder');
const activeChatName = document.getElementById('active-chat-name');
const chatHeader = document.getElementById('chat-header');
const inputArea = document.getElementById('input-area');
const memberSelector = document.getElementById('member-selector');
const mobileBackBtn = document.getElementById('mobile-back-btn');
const messageError = document.createElement('div');
messageError.style.color = 'var(--whatsapp-green)';
messageError.style.fontSize = '0.8em';
messageError.style.padding = '10px';
messageError.style.textAlign = 'center';
messageError.style.display = 'none';
inputArea.parentNode.insertBefore(messageError, inputArea);
const landingPage = document.getElementById('landing-page');
const getStartedBtn = document.getElementById('get-started-btn');
const backToLandingBtn = document.getElementById('back-to-landing-btn');
const navLoginBtn = document.getElementById('nav-login-btn');
const navUserContainer = document.getElementById('nav-user-container');
const navUserBtn = document.getElementById('nav-user-btn');
const navLogoutMenu = document.getElementById('nav-logout-menu');
const navLogoutExecBtn = document.getElementById('nav-logout-exec-btn');
const chatSearchInput = document.getElementById('chat-search-input');
// Community Elements
const communityPage = document.getElementById('community-page');
const communityFeed = document.getElementById('community-feed');
const communityBackBtn = document.getElementById('community-back-btn');
const mobileCommunityBackBtn = document.getElementById('mobile-community-back-btn');
const commSidebarToggle = document.getElementById('comm-sidebar-toggle');
const commSidebarClose = document.getElementById('comm-sidebar-close');
const communitySidebar = document.getElementById('community-sidebar');
const joinCommunityBtn = document.getElementById('join-community-btn');
const submitPostBtn = document.getElementById('submit-post-btn');
const postContent = document.getElementById('post-content');
const postCode = document.getElementById('post-code');
const postGithub = document.getElementById('post-github');
const postDeploy = document.getElementById('post-deploy');
const codeInputArea = document.getElementById('code-input-area');
const toggleCodeBtn = document.getElementById('toggle-code-btn');
const suggestedUsers = document.getElementById('suggested-users');
const commNavItems = document.querySelectorAll('.comm-nav-item');
// Initialization
document.addEventListener('DOMContentLoaded', () => {
const savedUser = localStorage.getItem('user');
if (savedUser) {
currentUser = JSON.parse(savedUser);
}
updateTopNavUI();
initLandingAnimations();
handleHashChange();
// Toggle mobile view when back button is clicked
if (mobileBackBtn) {
mobileBackBtn.addEventListener('click', () => {
document.body.classList.remove('mobile-chat-active');
});
}
});
function updateTopNavUI() {
if (currentUser) {
navLoginBtn.style.display = 'none';
navUserContainer.style.display = 'block';
navUserBtn.innerHTML = `${currentUser.fullName.split(' ')[0]}`;
} else {
navLoginBtn.style.display = 'block';
navUserContainer.style.display = 'none';
}
}
if (navUserBtn) {
navUserBtn.addEventListener('click', (e) => {
e.stopPropagation();
const isOpen = navLogoutMenu.style.display === 'block';
navLogoutMenu.style.display = isOpen ? 'none' : 'block';
});
}
if (navLogoutExecBtn) {
navLogoutExecBtn.addEventListener('click', () => {
localStorage.removeItem('user');
localStorage.removeItem('token');
window.location.hash = '';
location.reload();
});
}
// Close menu on click outside
document.addEventListener('click', () => {
if (navLogoutMenu) navLogoutMenu.style.display = 'none';
});
// History API Routing
window.addEventListener('hashchange', handleHashChange);
function handleHashChange() {
const isUserLoggedIn = !!localStorage.getItem('user');
// Hide all main views initially
landingPage.style.display = 'none';
loginPage.style.display = 'none';
chatPage.style.display = 'none';
communityPage.style.display = 'none';
if (window.location.hash === '#login') {
if (isUserLoggedIn) { window.location.hash = ''; return; }
loginPage.style.display = 'flex';
gsap.fromTo(".login-card", {opacity: 0, y: 30, scale: 0.9}, {opacity: 1, y: 0, scale: 1, duration: 0.6, ease: "back.out(1.5)"});
}
else if (window.location.hash === '#chat') {
if (!isUserLoggedIn) { window.location.hash = '#login'; return; }
showChatPage();
}
else if (window.location.hash === '#community') {
if (!isUserLoggedIn) { window.location.hash = '#login'; return; }
showCommunityPage();
}
else {
// Landing View (Home)
landingPage.style.display = 'flex';
// (Animations already triggered by initLandingAnimations if it's the first visit)
}
}
function initLandingAnimations() {
gsap.registerPlugin(ScrollTrigger);
gsap.to(".landing-text", { y: 0, opacity: 1, duration: 1, ease: "power3.out", delay: 0.2 });
gsap.to(".landing-animation", { opacity: 1, duration: 1, ease: "power3.out", delay: 0.5 });
gsap.to(".blob-1", {
x: "random(-50, 50)",
y: "random(-50, 50)",
duration: 5,
repeat: -1,
yoyo: true,
ease: "sine.inOut"
});
gsap.to(".blob-2", {
x: "random(-50, 50)",
y: "random(-50, 50)",
duration: 6,
repeat: -1,
yoyo: true,
ease: "sine.inOut"
});
// Animate feature cards on scroll
gsap.to(".feature-card", {
scrollTrigger: {
trigger: ".features-section",
scroller: "#landing-page", // The scrollable container
start: "top 80%",
},
y: 0,
opacity: 1,
duration: 0.8,
stagger: 0.2,
ease: "power2.out"
});
lottie.loadAnimation({
container: document.getElementById('lottie-container'),
renderer: 'svg',
loop: true,
autoplay: true,
path: 'https://assets2.lottiefiles.com/packages/lf20_puciaact.json'
});
}
if (getStartedBtn) {
getStartedBtn.addEventListener('click', () => {
if (localStorage.getItem('user')) {
window.location.hash = 'chat';
} else {
window.location.hash = 'login';
}
});
}
if (navLoginBtn) {
navLoginBtn.addEventListener('click', () => {
if (!localStorage.getItem('user')) {
window.location.hash = 'login';
}
});
}
if (backToLandingBtn) {
backToLandingBtn.addEventListener('click', () => {
window.location.hash = ''; // Clear hash triggers handleHashChange
});
}
const logoutBtn = document.getElementById('logout-btn');
// Login Logic
// Logic for Chat "Back" button
if (chatBackBtn) {
chatBackBtn.addEventListener('click', () => {
window.location.hash = ''; // Return to landing
});
}
loginBtn.addEventListener('click', async () => {
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
if (!username || !password) return;
loginBtn.innerText = 'Logging in...';
loginBtn.disabled = true;
try {
const response = await fetch(`${API_BASE}/api/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (data.success) {
currentUser = data.user;
localStorage.setItem('user', JSON.stringify(currentUser));
localStorage.setItem('token', data.token);
updateTopNavUI();
// Redirect based on previous intent or to chat by default
window.location.hash = 'chat';
} else {
loginError.innerText = data.message || 'Invalid credentials';
loginError.style.display = 'block';
}
} catch (err) {
loginError.innerText = 'Connection error. Try again.';
loginError.style.display = 'block';
} finally {
loginBtn.innerText = 'Log In';
loginBtn.disabled = false;
}
});
function showChatPage() {
landingPage.style.display = 'none';
loginPage.style.display = 'none';
communityPage.style.display = 'none';
chatPage.style.display = 'flex';
gsap.fromTo("#chat-page", {opacity: 0}, {opacity: 1, duration: 0.5, ease: "power2.out"});
// Set User Profile
if (currentUser.photoUrl) {
userAvatar.src = currentUser.photoUrl;
userAvatar.style.display = 'block';
avatarPlaceholder.style.display = 'none';
} else {
userAvatar.style.display = 'none';
avatarPlaceholder.style.display = 'flex';
}
// Register user with server memory (for discovery)
socket.emit('register-user', currentUser);
// In case of server restart, re-register on reconnect
socket.on('connect', () => {
socket.emit('register-user', currentUser);
});
loadChats();
}
// Group Logic
newGroupBtn.addEventListener('click', () => {
memberSelector.innerHTML = '';
currentUsers.filter(u => u.studentId !== currentUser.studentId).forEach(user => {
const div = document.createElement('div');
div.style.padding = '5px 0';
div.innerHTML = `
`;
memberSelector.appendChild(div);
});
groupModal.style.display = 'flex';
});
cancelGroupBtn.addEventListener('click', () => {
groupModal.style.display = 'none';
});
confirmGroupBtn.addEventListener('click', async () => {
const name = groupNameInput.value;
if (!name) return;
const selectedMembers = Array.from(memberSelector.querySelectorAll('input:checked')).map(cb => cb.value);
try {
const response = await fetch(`${API_BASE}/api/groups`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name,
createdBy: currentUser.studentId,
members: selectedMembers
})
});
const group = await response.json();
groupModal.style.display = 'none';
groupNameInput.value = '';
loadChats(); // Refresh list
} catch (err) {
console.error('Group creation error:', err);
}
});
// Fetching Chats (Users + Groups)
async function loadChats() {
if (!currentUser) return; // Exit if not logged in
try {
const [groupsRes, usersRes] = await Promise.all([
fetch(`${API_BASE}/api/groups`, { headers: { 'x-user-id': currentUser.studentId } }),
fetch(`${API_BASE}/api/users`)
]);
currentGroups = await groupsRes.json();
currentUsers = await usersRes.json();
// Update current user info if needed (for DP sync)
const updatedSelf = currentUsers.find(u => u.studentId === currentUser.studentId);
if (updatedSelf && updatedSelf.photoUrl && currentUser.photoUrl !== updatedSelf.photoUrl) {
currentUser.photoUrl = updatedSelf.photoUrl;
localStorage.setItem('user', JSON.stringify(currentUser));
userAvatar.src = currentUser.photoUrl;
userAvatar.style.display = 'block';
avatarPlaceholder.style.display = 'none';
}
renderChatList(currentGroups, currentUsers);
// Auto-join all chat rooms to receive background messages
currentGroups.forEach(g => socket.emit('join-chat', g._id));
currentUsers.forEach(u => {
if (u.studentId !== currentUser.studentId) {
const chatId = [currentUser.studentId, u.studentId].sort().join('--');
socket.emit('join-chat', chatId);
}
});
} catch (err) {
console.error('Error fetching chats:', err);
}
}
function renderChatList(groups, users) {
chatList.innerHTML = '';
// Render Groups
groups.forEach(chat => {
const div = document.createElement('div');
div.className = `chat-item ${activeChatId === chat._id ? 'active' : ''}`;
const unread = unreadCounts[chat._id] || 0;
div.innerHTML = `
Error loading posts: ${e.message}
Be the first to share your project or follow more peers!
${post.codeSnippet.replace(//g, ">")}