// Utility: Save and load credentials function saveCredentials(username, password) { localStorage.setItem('username', username); localStorage.setItem('password', password); } function getCredentials() { return { username: localStorage.getItem('username'), password: localStorage.getItem('password') }; } function clearCredentials() { localStorage.removeItem('username'); localStorage.removeItem('password'); } function logout() { clearCredentials(); window.location.href = 'login.html'; } // Registration if (document.getElementById('registerForm')) { document.getElementById('registerForm').onsubmit = async function(e) { e.preventDefault(); const username = document.getElementById('registerUsername').value; const password = document.getElementById('registerPassword').value; const res = await fetch('http://localhost:8080/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); if (res.ok) { saveCredentials(username, password); window.location.href = 'inbox.html'; } else { const err = await res.text(); document.getElementById('registerError').innerText = err; } }; } // Login if (document.getElementById('loginForm')) { document.getElementById('loginForm').onsubmit = async function(e) { e.preventDefault(); const username = document.getElementById('loginUsername').value; const password = document.getElementById('loginPassword').value; // Try to fetch mailbox to verify credentials const res = await fetch(`http://localhost:8080/mailbox?user=${encodeURIComponent(username)}`, { headers: { 'Authorization': 'Basic ' + btoa(username + ':' + password) } }); if (res.ok) { saveCredentials(username, password); window.location.href = 'inbox.html'; } else { document.getElementById('loginError').innerText = 'Invalid username or password.'; } }; } // Inbox if (document.getElementById('email-list')) { const { username, password } = getCredentials(); if (!username || !password) { window.location.href = 'login.html'; } else { fetch(`http://localhost:8080/mailbox?user=${encodeURIComponent(username)}`, { headers: { 'Authorization': 'Basic ' + btoa(username + ':' + password) } }) .then(res => res.json()) .then(emails => { if (!Array.isArray(emails)) throw new Error('Invalid mailbox response'); let userDomain = null; for (const email of emails) { if (email.to === username && email.domain) { userDomain = email.domain; break; } if (email.from === username && email.domain) { userDomain = email.domain; break; } } const userFull = userDomain ? `${username}!${userDomain}` : username; const inboxEmails = emails.filter(email => { if (!email.to) return false; const toFull = email.domain ? `${email.to}!${email.domain}` : email.to; return toFull === userFull; }); const sentEmails = emails.filter(email => { if (!email.from) return false; const fromFull = email.domain ? `${email.from}!${email.domain}` : email.from; return fromFull === userFull; }); // Sort emails by timestamp descending (newest first) inboxEmails.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)); sentEmails.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)); let currentFolder = 'inbox'; let selectedEmail = null; function formatDate(iso) { if (!iso) return ''; const date = new Date(iso); if (isNaN(date)) return ''; const now = new Date(); const isToday = date.toDateString() === now.toDateString(); const diffMs = now - date; const diffDays = diffMs / (1000 * 60 * 60 * 24); if (isToday) { // Show time (e.g. 2:30pm or 4pm) let hour = date.getHours(); let minute = date.getMinutes(); let ampm = hour >= 12 ? 'pm' : 'am'; hour = hour % 12; if (hour === 0) hour = 12; if (minute === 0) { return hour + ampm; } else { return hour + ':' + String(minute).padStart(2, '0') + ampm; } } else if (diffDays < 7) { // Show day of week (e.g. Mon) return date.toLocaleDateString(undefined, { weekday: 'short' }); } else { // Show month and day (e.g. Jun 7) return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }); } } function renderList() { const listDiv = document.getElementById('email-list'); if (!listDiv) return; let emailsToShow = currentFolder === 'inbox' ? inboxEmails : sentEmails; emailsToShow = filterEmails(emailsToShow); let html = ''; emailsToShow.forEach((email, idx) => { const isSelected = selectedEmail === idx; const subject = email.subject || '(No Subject)'; const fromFull = email.from && email.domain ? `${email.from}!${email.domain}` : email.from || ''; const toFull = email.to && email.domain ? `${email.to}!${email.domain}` : email.to || ''; const dateStr = formatDate(email.timestamp); html += `
${subject}
${currentFolder === 'inbox' ? 'From: ' + fromFull : 'To: ' + toFull}
${dateStr}
`; }); listDiv.innerHTML = html; // Add click listeners document.querySelectorAll('.email-list-item').forEach(item => { item.onclick = function() { selectedEmail = parseInt(this.getAttribute('data-idx')); renderList(); renderDetail(); }; }); } function renderDetail() { const detailDiv = document.getElementById('email-detail'); if (!detailDiv) return; let emailsToShow = currentFolder === 'inbox' ? inboxEmails : sentEmails; if (selectedEmail == null || !emailsToShow[selectedEmail]) { detailDiv.innerHTML = '
Select an email to view details.
'; return; } const email = emailsToShow[selectedEmail]; const fromFull = email.from && email.domain ? `${email.from}!${email.domain}` : email.from || ''; const toFull = email.to && email.domain ? `${email.to}!${email.domain}` : email.to || ''; const dateStr = formatDate(email.timestamp); function formatFullDate(iso) { if (!iso) return ''; const date = new Date(iso); if (isNaN(date)) return ''; let month = String(date.getMonth() + 1).padStart(2, '0'); let day = String(date.getDate()).padStart(2, '0'); let year = date.getFullYear(); let hour = date.getHours(); let minute = date.getMinutes(); let ampm = hour >= 12 ? 'pm' : 'am'; hour = hour % 12; if (hour === 0) hour = 12; let timeStr = minute === 0 ? `${hour}${ampm}` : `${hour}:${String(minute).padStart(2, '0')}${ampm}`; return `${month}/${day}/${year} @ ${timeStr}`; } const fullDateStr = formatFullDate(email.timestamp); // Send POST to /mailbox/open to indicate message was opened const { username, password } = getCredentials(); if (username && password && email.id) { fetch('http://localhost:8080/mailbox/open', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Basic ' + btoa(username + ':' + password) }, body: JSON.stringify({ user: username, id: email.id }) }); } // Show 'Read' if in sent folder and email.read is true let readHtml = ''; if (currentFolder === 'sent' && email.read === true) { readHtml = 'Read'; } detailDiv.innerHTML = `
${email.subject || '(No Subject)'}
From: ${fromFull}
To: ${toFull}
${readHtml}${dateStr}
${email.body || ''}
`; } // Helper to filter emails (no-op for now, can add search/filter later) function filterEmails(emails) { return emails; } // Sidebar folder switching document.getElementById('sidebar-inbox').onclick = function() { currentFolder = 'inbox'; selectedEmail = null; document.getElementById('sidebar-inbox').classList.add('active'); document.getElementById('sidebar-sent').classList.remove('active'); renderList(); renderDetail(); }; document.getElementById('sidebar-sent').onclick = function() { currentFolder = 'sent'; selectedEmail = null; document.getElementById('sidebar-inbox').classList.remove('active'); document.getElementById('sidebar-sent').classList.add('active'); renderList(); renderDetail(); }; renderList(); renderDetail(); }) .catch(err => { document.getElementById('inboxError').innerText = 'Failed to load inbox.'; console.error('Inbox load error:', err); }); } } // Send Email if (document.getElementById('sendForm')) { document.getElementById('sendForm').onsubmit = async function(e) { e.preventDefault(); const { username, password } = getCredentials(); if (!username || !password) { window.location.href = 'login.html'; return; } const toField = document.getElementById('to').value.trim(); // Require ! format if (!/^\w+!.+/.test(toField)) { document.getElementById('sendError').innerText = 'Recipient must be in the format username!domain'; return; } let to = toField; let domain = ''; if (toField.includes('!')) { [to, domain] = toField.split('!'); } const subject = document.getElementById('subject').value; const body = document.getElementById('body').value; const email = domain ? { from: username, to, domain, subject, body } : { from: username, to, subject, body }; const res = await fetch('http://localhost:8080/email', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Basic ' + btoa(username + ':' + password) }, body: JSON.stringify(email) }); if (res.ok) { window.location.href = 'inbox.html'; } else { const err = await res.text(); document.getElementById('sendError').innerText = err; } }; } // Compose Panel logic (bottom right, non-blocking) if (document.getElementById('openComposeBtn')) { const panel = document.getElementById('composePanel'); const openBtn = document.getElementById('openComposeBtn'); const closeXBtn = document.getElementById('composeCloseXBtn'); const form = document.getElementById('composeForm'); const errorP = document.getElementById('composeError'); openBtn.onclick = function() { panel.style.display = 'flex'; form.reset(); errorP.innerText = ''; }; if (closeXBtn) { closeXBtn.onclick = function() { panel.style.display = 'none'; }; } form.onsubmit = async function(e) { e.preventDefault(); const { username, password } = getCredentials(); if (!username || !password) { window.location.href = 'login.html'; return; } const toField = document.getElementById('composeTo').value.trim(); if (!/^\w+!.+/.test(toField)) { errorP.innerText = 'Recipient must be in the format username!domain'; return; } let to = toField; let domain = ''; if (toField.includes('!')) { [to, domain] = toField.split('!'); } const subject = document.getElementById('composeSubject').value; const body = document.getElementById('composeBody').value; const email = domain ? { from: username, to, domain, subject, body } : { from: username, to, subject, body }; const res = await fetch('http://localhost:8080/email', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Basic ' + btoa(username + ':' + password) }, body: JSON.stringify(email) }); if (res.ok) { panel.style.display = 'none'; location.reload(); } else { const err = await res.text(); errorP.innerText = err; } }; // Ensure dark mode is applied on load for inbox.html applyDarkMode(getDarkModePref()); } // Dark mode toggle logic function applyDarkMode(isDark) { if (isDark) { document.body.classList.add('dark-mode'); } else { document.body.classList.remove('dark-mode'); } } function getDarkModePref() { return localStorage.getItem('darkMode') === 'true'; } function setDarkModePref(val) { localStorage.setItem('darkMode', val ? 'true' : 'false'); } if (document.getElementById('toggleDarkModeBtn')) { const btn = document.getElementById('toggleDarkModeBtn'); btn.onclick = function() { const isDark = !getDarkModePref(); setDarkModePref(isDark); applyDarkMode(isDark); }; // Apply on load applyDarkMode(getDarkModePref()); } else { // Apply dark mode on other pages too applyDarkMode(getDarkModePref()); }