import { initializeApp } from "https://www.gstatic.com/firebasejs/10.14.0/firebase-app.js"; import { getDatabase, ref, push, set, remove, onValue, update } from "https://www.gstatic.com/firebasejs/10.14.0/firebase-database.js"; import { getAuth, GoogleAuthProvider, signInWithPopup, signOut, onAuthStateChanged, createUserWithEmailAndPassword, signInWithEmailAndPassword, sendPasswordResetEmail, sendEmailVerification, sendSignInLinkToEmail, isSignInWithEmailLink, signInWithEmailLink } from "https://www.gstatic.com/firebasejs/10.14.0/firebase-auth.js"; const firebaseConfig = { apiKey: "AIzaSyBUNW64TvBVS-FCBzY0KAGcqdq0gOrQhQw", authDomain: "companytracker-c2ed8.firebaseapp.com", databaseURL: "https://companytracker-c2ed8-default-rtdb.firebaseio.com", projectId: "companytracker-c2ed8", storageBucket: "companytracker-c2ed8.appspot.com", messagingSenderId: "472397366408", appId: "1:472397366408:web:4396f672863e73c7cdcb3d" }; const app = initializeApp(firebaseConfig); const db = getDatabase(app); const auth = getAuth(app); const provider = new GoogleAuthProvider(); let companyData = []; let currentUser = null; let isJenelle = false; let chartInstance = null; /* ------- Admin & allowed emails ------- */ const adminEmails = [ "gl2993@wayne.edu", "larava@wayne.edu", "ho5502@wayne.edu", "anushkaeggadi2006@gmail.com" ].map(x => x.toLowerCase()); // Only these emails can use Email/Password (or magic link); others must use Google const allowedEmailPasswordLogins = [ "ho5502@wayne.edu", "larava@wayne.edu", "gl2993@wayne.edu", ].map(x => x.toLowerCase()); /* ------- Filters state ------- */ let currentFilter = "all"; // 'all' | 'overdue' | 'week' | 'mine' let textSearch = ""; let industrySel = ""; let regionSel = ""; let typeSel = ""; /* ---------- Auth state ---------- */ onAuthStateChanged(auth, (user) => { if (user) { currentUser = user; document.getElementById("greeting").textContent = `πŸ‘‹ Welcome back, ${user.displayName || user.email}!`; document.getElementById("loginBtn").textContent = "Logout"; const email = (user.email || "").toLowerCase(); isJenelle = adminEmails.includes(email); if (isJenelle) { document.getElementById("adminBadge").classList.remove("hidden"); document.getElementById("addCompanyBtn").classList.remove("hidden"); } else { document.getElementById("adminBadge").classList.add("hidden"); document.getElementById("addCompanyBtn").classList.add("hidden"); } // Auto-send verification if logged in via Email/Password and not verified yet const usedPasswordProvider = (user.providerData || []).some(p => p.providerId === "password"); if (usedPasswordProvider && !user.emailVerified) { (async () => { try { await sendEmailVerification(user); } catch {} })(); } render(); } else { currentUser = null; isJenelle = false; document.getElementById("greeting").textContent = "πŸ‘‹ Welcome! Please sign in to start tracking companies πŸš€"; document.getElementById("loginBtn").textContent = "πŸ” Login with Google"; document.getElementById("adminBadge").classList.add("hidden"); document.getElementById("addCompanyBtn").classList.add("hidden"); render(); } }); /* ---------- Google login/logout ---------- */ document.getElementById("loginBtn").addEventListener("click", async () => { if (currentUser) { await signOut(auth); return; } try { await signInWithPopup(auth, provider); } catch (error) { alert("Login failed: " + error.message); } }); /* ---------- Email Login modal controls ---------- */ const emailModal = document.getElementById("emailModal"); const emailLoginBtn = document.getElementById("emailLoginBtn"); const closeEmailModal = document.getElementById("closeEmailModal"); const emailLoginForm = document.getElementById("emailLoginForm"); const emailRegisterBtn = document.getElementById("emailRegisterBtn"); const forgotBtn = document.getElementById("forgotBtn"); const magicLinkBtn = document.getElementById("magicLinkBtn"); // Remove resend if present const maybeResend = document.getElementById("resendVerifyBtn"); if (maybeResend) maybeResend.remove(); emailLoginBtn.addEventListener("click", () => emailModal.style.display = "block"); closeEmailModal?.addEventListener("click", () => emailModal.style.display = "none"); window.addEventListener("click", (e) => { if (e.target === emailModal) emailModal.style.display = "none"; }); /* ---------- Email/Password: Login (whitelisted only) ---------- */ emailLoginForm.addEventListener("submit", async (e) => { e.preventDefault(); const email = document.getElementById("emailInput").value.trim().toLowerCase(); const password = document.getElementById("passwordInput").value; if (!allowedEmailPasswordLogins.includes(email)) { alert("Please use 'Login with Google' for this email."); return; } try { await signInWithEmailAndPassword(auth, email, password); emailModal.style.display = "none"; } catch (err) { alert("Login failed: " + err.message); } }); /* ---------- Email/Password: Register (whitelisted only) ---------- */ emailRegisterBtn.addEventListener("click", async () => { const email = document.getElementById("emailInput").value.trim().toLowerCase(); const password = document.getElementById("passwordInput").value; if (!allowedEmailPasswordLogins.includes(email)) { alert("Registration with password is restricted. Please use 'Login with Google'."); return; } if (!email || !password) { alert("Enter email & password first."); return; } try { const cred = await createUserWithEmailAndPassword(auth, email, password); await sendEmailVerification(cred.user); alert("Account created. We sent a verification email β€” check your inbox/spam."); emailModal.style.display = "none"; } catch (err) { alert("Registration failed: " + err.message); } }); /* ---------- Forgot password (whitelisted only) ---------- */ forgotBtn.addEventListener("click", async () => { const email = document.getElementById("emailInput").value.trim().toLowerCase(); if (!email) { alert("Enter your email above first."); return; } if (!allowedEmailPasswordLogins.includes(email)) { alert("Password reset is only available for approved emails."); return; } try { await sendPasswordResetEmail(auth, email); alert("Password reset link sent. Check your inbox/spam."); } catch (err) { alert("Could not send reset email: " + err.message); } }); /* ---------- Magic Link (passwordless) for ho5502@wayne.edu ---------- */ const actionCodeSettings = { // πŸ”΄ REPLACE with your **exact** deployed URL url: "https://YOUR-SITE.netlify.app", handleCodeInApp: true }; magicLinkBtn.addEventListener("click", async () => { try { const email = document.getElementById("emailInput").value.trim().toLowerCase(); if (!email) { alert("Enter your email first."); return; } if (email !== "ho5502@wayne.edu") { alert("Magic link is only available for the approved .edu account."); return; } await sendSignInLinkToEmail(auth, email, actionCodeSettings); window.localStorage.setItem("emailForSignIn", email); alert("Magic sign-in link sent. Open it on this device."); } catch (e) { alert("Could not send magic link: " + e.message); } }); // If the user opened the magic link if (isSignInWithEmailLink(auth, window.location.href)) { let email = window.localStorage.getItem("emailForSignIn"); if (!email) email = prompt("Confirm your email to finish sign-in:"); if (email) { signInWithEmailLink(auth, email, window.location.href) .then(() => { window.localStorage.removeItem("emailForSignIn"); alert("Signed in via magic link."); }) .catch(err => alert("Magic link sign-in failed: " + err.message)); } } /* ---------- Load companies ---------- */ function loadCompanies() { const dbRef = ref(db, "companies"); onValue(dbRef, (snapshot) => { const data = snapshot.val() || {}; companyData = Object.entries(data).map(([id, v]) => ({ id, ...v })); render(); }); } /* ---------- Helpers: dates & filters ---------- */ function toDateOnly(d) { const dt = new Date(d); dt.setHours(0,0,0,0); return dt; } function isOverdue(followUp) { if (!followUp) return false; const today = toDateOnly(new Date()); const f = toDateOnly(new Date(followUp)); return f < today; } function isWithinThisWeek(followUp) { if (!followUp) return false; const today = toDateOnly(new Date()); const f = toDateOnly(new Date(followUp)); const end = toDateOnly(new Date()); end.setDate(today.getDate() + 7); return f >= today && f <= end; } function applyAllFilters(arr) { let out = [...arr]; // text search if (textSearch.trim()) { const q = textSearch.toLowerCase(); out = out.filter(c => (c.company||"").toLowerCase().includes(q) || (c.industry||"").toLowerCase().includes(q) || (c.region||"").toLowerCase().includes(q) || (c.type||"").toLowerCase().includes(q) || (c.description||"").toLowerCase().includes(q) ); } // dropdowns if (industrySel) out = out.filter(c => (c.industry||"") === industrySel); if (regionSel) out = out.filter(c => (c.region||"") === regionSel); if (typeSel) out = out.filter(c => (c.type||"") === typeSel); // pill filter if (currentFilter === "overdue") { out = out.filter(c => isOverdue(c.followUp)); } else if (currentFilter === "week") { out = out.filter(c => isWithinThisWeek(c.followUp)); } else if (currentFilter === "mine") { const me = (currentUser?.email || "").toLowerCase(); out = out.filter(c => (c.createdByEmail || "").toLowerCase() === me); } return out; } /* ---------- UI wiring for filters ---------- */ document.getElementById("filterAll").addEventListener("click", () => setPill("all")); document.getElementById("filterOverdue").addEventListener("click", () => setPill("overdue")); document.getElementById("filterWeek").addEventListener("click", () => setPill("week")); document.getElementById("filterMine").addEventListener("click", () => setPill("mine")); function setPill(name) { currentFilter = name; document.querySelectorAll(".pill").forEach(p => p.classList.remove("active")); const btn = document.querySelector(`.pill[data-filter="${name}"]`); if (btn) btn.classList.add("active"); render(); } document.getElementById("searchInput").addEventListener("input", (e) => { textSearch = e.target.value; render(); }); document.getElementById("industryFilter").addEventListener("change", (e) => { industrySel = e.target.value === "Industry" ? "" : e.target.value; render(); }); document.getElementById("regionFilter").addEventListener("change", (e) => { regionSel = e.target.value === "Region" ? "" : e.target.value; render(); }); document.getElementById("typeFilter").addEventListener("change", (e) => { typeSel = e.target.value === "Type" ? "" : e.target.value; render(); }); /* ---------- Reminders (browser-only) ---------- */ document.getElementById("enableNotifsBtn").addEventListener("click", async () => { if (!("Notification" in window)) { alert("Notifications not supported in this browser."); return; } if (Notification.permission === "granted") { alert("Notifications already enabled."); return; } const perm = await Notification.requestPermission(); if (perm === "granted") alert("Browser alerts enabled!"); }); function maybeNotifyOverdue(list) { // soft notifications (no backend): only if user allowed notifications if (!("Notification" in window) || Notification.permission !== "granted") return; const overdue = list.filter(c => isOverdue(c.followUp)); if (overdue.length === 0) return; const names = overdue.slice(0, 3).map(c => c.company).join(", ") + (overdue.length > 3 ? "…" : ""); new Notification("Overdue follow-ups", { body: names || "Check your dashboard." }); } /* ---------- Populate dropdowns ---------- */ function populateFilters() { const industries = [...new Set(companyData.map(c => c.industry).filter(Boolean))]; const regions = [...new Set(companyData.map(c => c.region).filter(Boolean))]; const types = [...new Set(companyData.map(c => c.type).filter(Boolean))]; fillDropdown("industryFilter", "Industry", industries); fillDropdown("regionFilter", "Region", regions); fillDropdown("typeFilter", "Type", types); } function fillDropdown(id, label, items) { const select = document.getElementById(id); select.innerHTML = ``; items.forEach(item => { const opt = document.createElement("option"); opt.value = item; opt.textContent = item; select.appendChild(opt); }); } /* ---------- Tracking count & reminders summary ---------- */ function updateTrackingCount(filtered) { document.getElementById("trackCount").textContent = `Showing ${filtered.length} of ${companyData.length} companies πŸš€`; } function updateReminderSummary() { const overdue = companyData.filter(c => isOverdue(c.followUp)); if (overdue.length === 0) { document.getElementById("reminderSummary").textContent = "No overdue items."; return; } const top = overdue.slice(0, 3).map(c => `${c.company} (${c.followUp})`).join(" β€’ "); document.getElementById("reminderSummary").innerHTML = `Overdue: ${overdue.length}   ${top}`; } /* ---------- Chart ---------- */ function drawChart(data) { const ctx = document.getElementById("industryChart").getContext("2d"); const grouped = data.reduce((acc, c) => { const key = c.industry || "Unspecified"; acc[key] = (acc[key] || 0) + 1; return acc; }, {}); if (chartInstance) chartInstance.destroy(); chartInstance = new Chart(ctx, { type: "pie", data: { labels: Object.keys(grouped), datasets: [{ data: Object.values(grouped), backgroundColor: ["#c084fc", "#fbc8d4", "#b3e5fc", "#a78bfa", "#fde68a", "#bbf7d0", "#fecaca"] }] } }); } /*- hgftffffggf the and the ssanders fgfr the fun sat rug u egg abcde fghijk lmnopqrstuvwxyz ethtrhthrrreththerhtttttttttttttttttt th --*/ /* ---------- Display table ---------- */ function displayTable(data) { const tbody = document.querySelector("#companyTable tbody"); tbody.innerHTML = ""; if (data.length === 0) { tbody.innerHTML = "No companies found."; return; } data.forEach(c => { const tr = document.createElement("tr"); const overdue = isOverdue(c.followUp); if (overdue) tr.classList.add("overdue"); const adminActions = isJenelle ? `
` : `Read-only`; tr.innerHTML = ` ${c.company || ""} ${overdue ? "πŸ”΄" : ""} ${c.industry || ""} ${c.region || ""} ${c.type || ""} ${c.description || ""} ${c.website ? `Visit` : "β€”"} ${c.followUp || "β€”"} ${c.updated || "β€”"} ${adminActions} `; tbody.appendChild(tr); const detailsTr = document.createElement("tr"); detailsTr.id = `details-row-${c.id}`; detailsTr.className = "details-row hidden"; detailsTr.innerHTML = `

Conversation Log

Loading…

Quick Log

${isJenelle ? "" : "Login as an admin to add notes."}
`; tbody.appendChild(detailsTr); }); } /* ---------- Expand/Collapse + Notes render ---------- */ window.toggleDetails = (id) => { const row = document.getElementById(`details-row-${id}`); const btn = document.getElementById(`toggle-${id}`); const isHidden = row.classList.contains("hidden"); if (isHidden) { row.classList.remove("hidden"); btn.classList.add("open"); btn.textContent = "β–Ό"; loadNotesInto(id); } else { row.classList.add("hidden"); btn.classList.remove("open"); btn.textContent = "β–Έ"; } }; function loadNotesInto(id) { const notesEl = document.getElementById(`notes-${id}`); const notesRef = ref(db, `companies/${id}/notes`); onValue(notesRef, (snap) => { const val = snap.val() || {}; const list = Object.values(val).sort((a,b) => (a.timestamp||"").localeCompare(b.timestamp||"")); if (list.length === 0) { notesEl.innerHTML = "No notes yet."; return; } notesEl.innerHTML = list.map(n => `
${formatTS(n.timestamp)}
${(n.note || "").replace(//g,">")}
β€” ${n.author || "Unknown"}
`).join(""); }, { onlyOnce: true }); } /* ---------- Quick save from dropdown ---------- */ window.saveQuickLog = async (id) => { if (!isJenelle) { alert("Only admins can add notes."); return; } const note = document.getElementById(`note-${id}`).value.trim(); const followIn = document.getElementById(`follow-${id}`).value; const whenSel = document.getElementById(`when-${id}`).value; const customDT = document.getElementById(`dt-${id}`).value; const ts = computeTimestamp(whenSel, customDT); const notesRef = push(ref(db, `companies/${id}/notes`)); await set(notesRef, { note, timestamp: ts, author: currentUser ? (currentUser.displayName || currentUser.email) : "Unknown" }); const companyRef = ref(db, `companies/${id}`); if (followIn) { const next = addPeriod(new Date(ts), followIn); await update(companyRef, { followUp: next.toISOString().slice(0,10), updated: new Date().toLocaleDateString() }); } else { await update(companyRef, { updated: new Date().toLocaleDateString() }); } // Clear inputs and refresh notes document.getElementById(`note-${id}`).value = ""; document.getElementById(`follow-${id}`).value = ""; document.getElementById(`when-${id}`).value = "now"; document.getElementById(`dt-${id}`).value = ""; loadNotesInto(id); // If follow-up changed to earlier, the item might become overdue; refresh reminders updateReminderSummary(); render(); }; /* ---------- Add/Edit modal & CRUD ---------- */ const addModal = document.getElementById("addModal"); const closeAddModal = document.getElementById("closeAddModal"); const addForm = document.getElementById("addForm"); const addModalTitle = document.getElementById("addModalTitle"); document.getElementById("addCompanyBtn").onclick = () => { if (!isJenelle) { alert("Only admins can add companies."); return; } formMode = "add"; editingId = null; addModalTitle.textContent = "Add Company"; resetAddForm(); addModal.style.display = "block"; }; closeAddModal.onclick = () => { addModal.style.display = "none"; formMode = "add"; editingId = null; }; let formMode = "add"; let editingId = null; function resetAddForm() { document.getElementById("newCompany").value = ""; document.getElementById("newIndustry").value = ""; document.getElementById("newRegion").value = ""; document.getElementById("newType").value = ""; document.getElementById("newDescription").value = ""; document.getElementById("newWebsite").value = ""; document.getElementById("newFollowUp").value = ""; } addForm.addEventListener("submit", async (e) => { e.preventDefault(); if (!isJenelle) { alert("Only admins can save changes."); return; } const payload = { company: document.getElementById("newCompany").value, industry: document.getElementById("newIndustry").value, region: document.getElementById("newRegion").value, type: document.getElementById("newType").value, description: document.getElementById("newDescription").value, website: document.getElementById("newWebsite").value, followUp: document.getElementById("newFollowUp").value, updated: new Date().toLocaleDateString(), // NEW: track owner for "My companies" filter createdByEmail: (currentUser?.email || "") }; if (formMode === "add") { await set(push(ref(db, "companies")), payload); } else if (formMode === "edit" && editingId) { await update(ref(db, "companies/" + editingId), payload); } addModal.style.display = "none"; formMode = "add"; editingId = null; }); window.editCompany = async (id) => { if (!isJenelle) { alert("Only admins can edit."); return; } const c = companyData.find(x => x.id === id); if (!c) return; formMode = "edit"; editingId = id; addModalTitle.textContent = "Edit Company"; document.getElementById("newCompany").value = c.company || ""; document.getElementById("newIndustry").value = c.industry || ""; document.getElementById("newRegion").value = c.region || ""; document.getElementById("newType").value = c.type || ""; document.getElementById("newDescription").value = c.description || ""; document.getElementById("newWebsite").value = c.website || ""; document.getElementById("newFollowUp").value = c.followUp || ""; addModal.style.display = "block"; }; window.deleteCompany = async (id) => { if (!isJenelle) { alert("Only admins can delete."); return; } if (confirm("Delete this company?")) await remove(ref(db, "companies/" + id)); }; /* ---------- CSV export ---------- */ document.getElementById("exportCSV").addEventListener("click", () => { const rows = [ ["company","industry","region","type","description","website","followUp","updated","createdByEmail"] ]; companyData.forEach(c => { rows.push([ c.company || "", c.industry || "", c.region || "", c.type || "", (c.description || "").replace(/(\r\n|\n|\r)/g, " "), c.website || "", c.followUp || "", c.updated || "", c.createdByEmail || "" ]); }); const csv = rows.map(r => r.map(v => `"${(v+"").replace(/"/g,'""')}"`).join(",")).join("\n"); const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `companies_${new Date().toISOString().slice(0,10)}.csv`; a.click(); URL.revokeObjectURL(url); }); /* ---------- Main render ---------- */ function render() { populateFilters(); const filtered = applyAllFilters(companyData); updateTrackingCount(filtered); updateReminderSummary(); displayTable(filtered); drawChart(filtered); maybeNotifyOverdue(companyData); } /* ---------- Kick off ---------- */ loadCompanies(); /* ---------- Utilities ---------- */ function computeTimestamp(whenSel, customDT) { const now = new Date(); if (whenSel === "now") return now.toISOString(); const d = new Date(); if (whenSel === "morning") { d.setHours(9,0,0,0); return d.toISOString(); } if (whenSel === "afternoon") { d.setHours(14,0,0,0); return d.toISOString(); } if (whenSel === "evening") { d.setHours(18,0,0,0); return d.toISOString(); } if (whenSel === "custom" && customDT) return new Date(customDT).toISOString(); return now.toISOString(); } function addPeriod(baseDate, token) { const d = new Date(baseDate); if (token === "3d") d.setDate(d.getDate() + 3); if (token === "1w") d.setDate(d.getDate() + 7); if (token === "2w") d.setDate(d.getDate() + 14); if (token === "1m") d.setMonth(d.getMonth() + 1); return d; } function formatTS(iso) { try { return new Date(iso).toLocaleString(); } catch { return iso; } }